|
1 | 1 | package io.swagger.v3.oas.integration;
|
2 | 2 |
|
| 3 | +import com.fasterxml.jackson.annotation.JsonAnyGetter; |
| 4 | +import com.fasterxml.jackson.annotation.JsonAnySetter; |
| 5 | +import com.fasterxml.jackson.annotation.JsonIgnore; |
| 6 | +import com.fasterxml.jackson.annotation.JsonInclude; |
| 7 | +import com.fasterxml.jackson.annotation.JsonPropertyOrder; |
| 8 | +import com.fasterxml.jackson.databind.MapperFeature; |
3 | 9 | import com.fasterxml.jackson.databind.ObjectMapper;
|
| 10 | +import com.fasterxml.jackson.databind.SerializationFeature; |
| 11 | +import com.fasterxml.jackson.databind.annotation.JsonSerialize; |
4 | 12 | import io.swagger.v3.core.converter.ModelConverter;
|
5 | 13 | import io.swagger.v3.core.converter.ModelConverters;
|
6 | 14 | import io.swagger.v3.core.jackson.ModelResolver;
|
| 15 | +import io.swagger.v3.core.jackson.PathsSerializer; |
| 16 | +import io.swagger.v3.core.util.Json; |
| 17 | +import io.swagger.v3.core.util.Yaml; |
7 | 18 | import io.swagger.v3.oas.integration.api.ObjectMapperProcessor;
|
8 | 19 | import io.swagger.v3.oas.integration.api.OpenAPIConfiguration;
|
9 | 20 | import io.swagger.v3.oas.integration.api.OpenApiConfigurationLoader;
|
10 | 21 | import io.swagger.v3.oas.integration.api.OpenApiContext;
|
11 | 22 | import io.swagger.v3.oas.integration.api.OpenApiReader;
|
12 | 23 | import io.swagger.v3.oas.integration.api.OpenApiScanner;
|
13 | 24 | import io.swagger.v3.oas.models.OpenAPI;
|
| 25 | +import io.swagger.v3.oas.models.Paths; |
| 26 | +import io.swagger.v3.oas.models.media.Schema; |
14 | 27 | import org.apache.commons.lang3.StringUtils;
|
15 | 28 | import org.apache.commons.lang3.tuple.ImmutablePair;
|
16 | 29 | import org.slf4j.Logger;
|
@@ -43,6 +56,9 @@ public class GenericOpenApiContext<T extends GenericOpenApiContext> implements O
|
43 | 56 | private ObjectMapperProcessor objectMapperProcessor;
|
44 | 57 | private Set<ModelConverter> modelConverters;
|
45 | 58 |
|
| 59 | + private ObjectMapper outputJsonMapper; |
| 60 | + private ObjectMapper outputYamlMapper; |
| 61 | + |
46 | 62 | private ConcurrentHashMap<String, Cache> cache = new ConcurrentHashMap<>();
|
47 | 63 |
|
48 | 64 | // 0 doesn't cache
|
@@ -210,6 +226,52 @@ public final T modelConverters(Set<ModelConverter> modelConverters) {
|
210 | 226 | return (T) this;
|
211 | 227 | }
|
212 | 228 |
|
| 229 | + /** |
| 230 | + * @since 2.1.6 |
| 231 | + */ |
| 232 | + public ObjectMapper getOutputJsonMapper() { |
| 233 | + return outputJsonMapper; |
| 234 | + } |
| 235 | + |
| 236 | + /** |
| 237 | + * @since 2.1.6 |
| 238 | + */ |
| 239 | + @Override |
| 240 | + public void setOutputJsonMapper(ObjectMapper outputJsonMapper) { |
| 241 | + this.outputJsonMapper = outputJsonMapper; |
| 242 | + } |
| 243 | + |
| 244 | + /** |
| 245 | + * @since 2.1.6 |
| 246 | + */ |
| 247 | + public final T outputJsonMapper(ObjectMapper outputJsonMapper) { |
| 248 | + this.outputJsonMapper = outputJsonMapper; |
| 249 | + return (T) this; |
| 250 | + } |
| 251 | + |
| 252 | + /** |
| 253 | + * @since 2.1.6 |
| 254 | + */ |
| 255 | + public ObjectMapper getOutputYamlMapper() { |
| 256 | + return outputYamlMapper; |
| 257 | + } |
| 258 | + |
| 259 | + /** |
| 260 | + * @since 2.1.6 |
| 261 | + */ |
| 262 | + @Override |
| 263 | + public void setOutputYamlMapper(ObjectMapper outputYamlMapper) { |
| 264 | + this.outputYamlMapper = outputYamlMapper; |
| 265 | + } |
| 266 | + |
| 267 | + /** |
| 268 | + * @since 2.1.6 |
| 269 | + */ |
| 270 | + public final T outputYamlMapper(ObjectMapper outputYamlMapper) { |
| 271 | + this.outputYamlMapper = outputYamlMapper; |
| 272 | + return (T) this; |
| 273 | + } |
| 274 | + |
213 | 275 |
|
214 | 276 | protected void register() {
|
215 | 277 | OpenApiContextLocator.getInstance().putOpenApiContext(id, this);
|
@@ -363,16 +425,36 @@ public T init() throws OpenApiConfigurationException {
|
363 | 425 | if (modelConverters == null || modelConverters.isEmpty()) {
|
364 | 426 | modelConverters = buildModelConverters(ContextUtils.deepCopy(openApiConfiguration));
|
365 | 427 | }
|
| 428 | + if (outputJsonMapper == null) { |
| 429 | + outputJsonMapper = Json.mapper().copy(); |
| 430 | + } |
| 431 | + if (outputYamlMapper == null) { |
| 432 | + outputYamlMapper = Yaml.mapper().copy(); |
| 433 | + } |
| 434 | + if (openApiConfiguration.isSortOutput() != null && openApiConfiguration.isSortOutput()) { |
| 435 | + outputJsonMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true); |
| 436 | + outputJsonMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true); |
| 437 | + outputYamlMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true); |
| 438 | + outputYamlMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true); |
| 439 | + outputJsonMapper.addMixIn(OpenAPI.class, SortedOpenAPIMixin.class); |
| 440 | + outputJsonMapper.addMixIn(Schema.class, SortedSchemaMixin.class); |
| 441 | + outputYamlMapper.addMixIn(OpenAPI.class, SortedOpenAPIMixin.class); |
| 442 | + outputYamlMapper.addMixIn(Schema.class, SortedSchemaMixin.class); |
| 443 | + } |
366 | 444 | } catch (Exception e) {
|
367 | 445 | LOGGER.error("error initializing context: " + e.getMessage(), e);
|
368 | 446 | throw new OpenApiConfigurationException("error initializing context: " + e.getMessage(), e);
|
369 | 447 | }
|
370 | 448 |
|
| 449 | + |
371 | 450 | try {
|
372 | 451 | if (objectMapperProcessor != null) {
|
373 | 452 | ObjectMapper mapper = IntegrationObjectMapperFactory.createJson();
|
374 | 453 | objectMapperProcessor.processJsonObjectMapper(mapper);
|
375 | 454 | ModelConverters.getInstance().addConverter(new ModelResolver(mapper));
|
| 455 | + |
| 456 | + objectMapperProcessor.processOutputJsonObjectMapper(outputJsonMapper); |
| 457 | + objectMapperProcessor.processOutputYamlObjectMapper(outputYamlMapper); |
376 | 458 | }
|
377 | 459 | } catch (Exception e) {
|
378 | 460 | LOGGER.error("error configuring objectMapper: " + e.getMessage(), e);
|
@@ -442,6 +524,9 @@ private OpenAPIConfiguration mergeParentConfiguration(OpenAPIConfiguration confi
|
442 | 524 | if (merged.isPrettyPrint() == null) {
|
443 | 525 | merged.setPrettyPrint(parentConfig.isPrettyPrint());
|
444 | 526 | }
|
| 527 | + if (merged.isSortOutput() == null) { |
| 528 | + merged.setSortOutput(parentConfig.isSortOutput()); |
| 529 | + } |
445 | 530 | if (merged.isReadAllResources() == null) {
|
446 | 531 | merged.setReadAllResources(parentConfig.isReadAllResources());
|
447 | 532 | }
|
@@ -493,4 +578,36 @@ boolean isStale(long cacheTTL) {
|
493 | 578 | }
|
494 | 579 | }
|
495 | 580 |
|
| 581 | + @JsonPropertyOrder(value = {"openapi", "info", "externalDocs", "servers", "security", "tags", "paths", "components"}, alphabetic = true) |
| 582 | + static abstract class SortedOpenAPIMixin { |
| 583 | + |
| 584 | + @JsonAnyGetter |
| 585 | + @JsonPropertyOrder(alphabetic = true) |
| 586 | + public abstract Map<String, Object> getExtensions(); |
| 587 | + |
| 588 | + @JsonAnySetter |
| 589 | + public abstract void addExtension(String name, Object value); |
| 590 | + |
| 591 | + @JsonSerialize(using = PathsSerializer.class) |
| 592 | + public abstract Paths getPaths(); |
| 593 | + } |
| 594 | + |
| 595 | + @JsonPropertyOrder(value = {"type", "format"}, alphabetic = true) |
| 596 | + static abstract class SortedSchemaMixin { |
| 597 | + |
| 598 | + @JsonAnyGetter |
| 599 | + @JsonPropertyOrder(alphabetic = true) |
| 600 | + public abstract Map<String, Object> getExtensions(); |
| 601 | + |
| 602 | + @JsonAnySetter |
| 603 | + public abstract void addExtension(String name, Object value); |
| 604 | + |
| 605 | + @JsonIgnore |
| 606 | + public abstract boolean getExampleSetFlag(); |
| 607 | + |
| 608 | + @JsonInclude(JsonInclude.Include.CUSTOM) |
| 609 | + public abstract Object getExample(); |
| 610 | + |
| 611 | + } |
| 612 | + |
496 | 613 | }
|
0 commit comments