diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/ModelConverters.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/ModelConverters.java index 0bb9c21f02..b9cfc4c661 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/ModelConverters.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/ModelConverters.java @@ -42,6 +42,15 @@ public ModelConverters(boolean openapi31) { } } + public ModelConverters(boolean openapi31, Schema.SchemaResolution schemaResolution) { + converters = new CopyOnWriteArrayList<>(); + if (openapi31) { + converters.add(new ModelResolver(Json31.mapper()).openapi31(true)); + } else { + converters.add(new ModelResolver(Json.mapper()).schemaResolution(schemaResolution)); + } + } + public Set getSkippedPackages() { return skippedPackages; } @@ -61,6 +70,30 @@ public static ModelConverters getInstance(boolean openapi31) { return SINGLETON; } + public static void reset() { + synchronized (ModelConverters.class) { + SINGLETON = null; + SINGLETON31 = null; + } + } + + public static ModelConverters getInstance(boolean openapi31, Schema.SchemaResolution schemaResolution) { + synchronized (ModelConverters.class) { + if (openapi31) { + if (SINGLETON31 == null) { + SINGLETON31 = new ModelConverters(openapi31); + init(SINGLETON31); + } + return SINGLETON31; + } + if (SINGLETON == null) { + SINGLETON = new ModelConverters(openapi31, schemaResolution); + init(SINGLETON); + } + return SINGLETON; + } + } + private static void init(ModelConverters converter) { converter.addPackageToSkip("java.lang"); converter.addPackageToSkip("groovy.lang"); diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java index 66634f693f..453de12edd 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java @@ -125,6 +125,8 @@ public class ModelResolver extends AbstractModelConverter implements ModelConver private boolean openapi31; + private Schema.SchemaResolution schemaResolution = Schema.SchemaResolution.DEFAULT; + public ModelResolver(ObjectMapper mapper) { super(mapper); } @@ -719,7 +721,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context property = context.resolve(aType); property = clone(property); if (openapi31) { - Optional reResolvedProperty = AnnotationsUtils.getSchemaFromAnnotation(ctxSchema, annotatedType.getComponents(), null, openapi31, property, context); + Optional reResolvedProperty = AnnotationsUtils.getSchemaFromAnnotation(ctxSchema, annotatedType.getComponents(), null, openapi31, property, schemaResolution, context); if (reResolvedProperty.isPresent()) { property = reResolvedProperty.get(); } @@ -767,10 +769,14 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context } if (context.getDefinedModels().containsKey(pName)) { - property = new Schema().$ref(constructRef(pName)); + if (Schema.SchemaResolution.INLINE.equals(this.schemaResolution)) { + property = context.getDefinedModels().get(pName); + } else { + property = new Schema().$ref(constructRef(pName)); + } property = clone(property); - if (openapi31) { - Optional reResolvedProperty = AnnotationsUtils.getSchemaFromAnnotation(ctxSchema, annotatedType.getComponents(), null, openapi31, property, context); + if (openapi31 || Schema.SchemaResolution.INLINE.equals(this.schemaResolution)) { + Optional reResolvedProperty = AnnotationsUtils.getSchemaFromAnnotation(ctxSchema, annotatedType.getComponents(), null, openapi31, property, this.schemaResolution, context); if (reResolvedProperty.isPresent()) { property = reResolvedProperty.get(); } @@ -994,7 +1000,9 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context StringUtils.isNotBlank(model.getName())) { if (context.getDefinedModels().containsKey(model.getName())) { - model = new Schema().$ref(constructRef(model.getName())); + if (!Schema.SchemaResolution.INLINE.equals(this.schemaResolution)) { + model = new Schema().$ref(constructRef(model.getName())); + } } } else if (model != null && model.get$ref() != null) { model = new Schema().$ref(StringUtils.isNotEmpty(model.get$ref()) ? model.get$ref() : model.getName()); @@ -1249,6 +1257,18 @@ private void handleUnwrapped(List props, Schema innerModel, String prefi } } + public Schema.SchemaResolution getSchemaResolution() { + return schemaResolution; + } + + public void setSchemaResolution(Schema.SchemaResolution schemaResolution) { + this.schemaResolution = schemaResolution; + } + + public ModelResolver schemaResolution(Schema.SchemaResolution schemaResolution) { + setSchemaResolution(schemaResolution); + return this; + } private class GeneratorWrapper { @@ -1864,7 +1884,7 @@ protected Map resolveDependentSchemas(JavaType a, Annotation[] a } Annotation[] propAnnotations = new Annotation[]{dependentSchemaAnnotation.schema(), dependentSchemaAnnotation.array()}; Schema existingSchema = null; - Optional resolvedPropSchema = AnnotationsUtils.getSchemaFromAnnotation(dependentSchemaAnnotation.schema(), components, jsonViewAnnotation, openapi31, null, context); + Optional resolvedPropSchema = AnnotationsUtils.getSchemaFromAnnotation(dependentSchemaAnnotation.schema(), components, jsonViewAnnotation, openapi31, null, Schema.SchemaResolution.DEFAULT, context); if (resolvedPropSchema.isPresent()) { existingSchema = resolvedPropSchema.get(); dependentSchemas.put(name, existingSchema); diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/AnnotationsUtils.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/AnnotationsUtils.java index e1ed50c9f2..ef17f1c7d3 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/AnnotationsUtils.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/AnnotationsUtils.java @@ -587,24 +587,40 @@ public static Optional getSchemaFromAnnotation( boolean openapi31, Schema existingSchema, ModelConverterContext context) { + return getSchemaFromAnnotation(schema, components, jsonViewAnnotation, openapi31, existingSchema, Schema.SchemaResolution.DEFAULT, null); + } + public static Optional getSchemaFromAnnotation( + io.swagger.v3.oas.annotations.media.Schema schema, + Components components, + JsonView jsonViewAnnotation, + boolean openapi31, + Schema existingSchema, + Schema.SchemaResolution schemaResolution, + ModelConverterContext context) { if (schema == null || !hasSchemaAnnotation(schema)) { - if (existingSchema == null || !openapi31) { + if (existingSchema == null || (!openapi31 && Schema.SchemaResolution.DEFAULT.equals(schemaResolution))) { return Optional.empty(); - } else if (existingSchema != null && openapi31) { + } else if (existingSchema != null && (openapi31 || Schema.SchemaResolution.INLINE.equals(schemaResolution))) { return Optional.of(existingSchema); } } Schema schemaObject = null; if (!openapi31) { if (existingSchema != null) { - return Optional.of(existingSchema); + if (!Schema.SchemaResolution.DEFAULT.equals(schemaResolution)) { + schemaObject = existingSchema; + } else { + return Optional.of(existingSchema); + } } - if (schema.oneOf().length > 0 || - schema.allOf().length > 0 || - schema.anyOf().length > 0) { - schemaObject = new ComposedSchema(); - } else { - schemaObject = new Schema(); + if (Schema.SchemaResolution.DEFAULT.equals(schemaResolution)) { + if (schema != null && (schema.oneOf().length > 0 || + schema.allOf().length > 0 || + schema.anyOf().length > 0)) { + schemaObject = new ComposedSchema(); + } else { + schemaObject = new Schema(); + } } } else { if (existingSchema == null) { @@ -613,6 +629,9 @@ public static Optional getSchemaFromAnnotation( schemaObject = existingSchema; } } + if (schema == null) { + return Optional.of(schemaObject); + } if (StringUtils.isNotBlank(schema.description())) { schemaObject.setDescription(schema.description()); } @@ -1956,11 +1975,15 @@ public static void updateAnnotation(Class clazz, io.swagger.v3.oas.annotation } + public static Annotation mergeSchemaAnnotations( + Annotation[] ctxAnnotations, JavaType type) { + return mergeSchemaAnnotations(ctxAnnotations, type, false); + } /* * returns null if no annotations, otherwise either ArraySchema or Schema */ public static Annotation mergeSchemaAnnotations( - Annotation[] ctxAnnotations, JavaType type) { + Annotation[] ctxAnnotations, JavaType type, boolean contextWins) { // get type array and schema io.swagger.v3.oas.annotations.media.Schema tS = type.getRawClass().getDeclaredAnnotation(io.swagger.v3.oas.annotations.media.Schema.class); if (!hasSchemaAnnotation(tS)) { @@ -2020,6 +2043,9 @@ else if (tS != null && tA == null && cS == null && cA != null) { } else if (tA != null && cA != null) { + if (contextWins) { + return mergeArraySchemaAnnotations(tA, cA); + } return mergeArraySchemaAnnotations(cA, tA); } @@ -2028,6 +2054,9 @@ else if (tS != null && cS == null && cA == null) { } else if (tS != null && cS != null) { + if (contextWins) { + return mergeSchemaAnnotations(cS, tS); + } return mergeSchemaAnnotations(tS, cS); } diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/InlineResolvingTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/InlineResolvingTest.java new file mode 100644 index 0000000000..42afe5c0f9 --- /dev/null +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/InlineResolvingTest.java @@ -0,0 +1,172 @@ +package io.swagger.v3.core.resolving; + +import io.swagger.v3.core.converter.AnnotatedType; +import io.swagger.v3.core.converter.ModelConverterContextImpl; +import io.swagger.v3.core.jackson.ModelResolver; +import io.swagger.v3.core.matchers.SerializationMatchers; +import io.swagger.v3.oas.annotations.media.Schema; +import org.testng.annotations.Test; + +public class InlineResolvingTest extends SwaggerTestBase{ + + @Test + public void testInlineResolving() { + + final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(false).schemaResolution(io.swagger.v3.oas.models.media.Schema.SchemaResolution.INLINE); + final ModelConverterContextImpl c = new ModelConverterContextImpl(modelResolver); + // ModelConverters c = ModelConverters.getInstance(false, io.swagger.v3.oas.models.media.Schema.SchemaResolution.INLINE); + c.resolve(new AnnotatedType(InlineSchemaFirst.class)); + + String expectedYaml = "InlineSchemaFirst:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " description: InlineSchemaFirst property 1\n" + + " nullable: true\n" + + " example: example\n" + + " property2:\n" + + " type: object\n" + + " description: ' InlineSchemaFirst property 2'\n" + + " example: example 2\n" + + "InlineSchemaPropertyFirst:\n" + + " type: object\n" + + " description: property\n" + + " example: example\n"; + + SerializationMatchers.assertEqualsToYaml(c.getDefinedModels(), expectedYaml); + // stringSchemaMap = c.readAll(InlineSchemaSecond.class); + c.resolve(new AnnotatedType(InlineSchemaSecond.class)); + expectedYaml = "InlineSchemaFirst:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " description: InlineSchemaFirst property 1\n" + + " nullable: true\n" + + " example: example\n" + + " property2:\n" + + " type: object\n" + + " description: ' InlineSchemaFirst property 2'\n" + + " example: example 2\n" + + "InlineSchemaPropertyFirst:\n" + + " type: object\n" + + " description: property\n" + + " example: example\n" + + "InlineSchemaPropertySecond:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " description: property 1\n" + + " property2:\n" + + " type: object\n" + + " description: property 2\n" + + " example: example\n" + + " description: propertysecond\n" + + " nullable: true\n" + + " example: examplesecond\n" + + "InlineSchemaPropertySimple:\n" + + " type: object\n" + + " description: property\n" + + " example: example\n" + + "InlineSchemaSecond:\n" + + " type: object\n" + + " properties:\n" + + " propertySecond1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " description: property 1\n" + + " property2:\n" + + " type: object\n" + + " description: property 2\n" + + " example: example\n" + + " description: InlineSchemaSecond property 1\n" + + " nullable: true\n" + + " example: examplesecond\n" + + " property2:\n" + + " type: object\n" + + " description: InlineSchemaSecond property 2\n" + + " example: InlineSchemaSecond example 2\n" + + "InlineSchemaSimple:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " description: property 1\n" + + " property2:\n" + + " type: object\n" + + " description: property 2\n" + + " example: example\n"; + SerializationMatchers.assertEqualsToYaml(c.getDefinedModels(), expectedYaml); + } + + static class InlineSchemaFirst { + + // public String foo; + + @Schema(description = "InlineSchemaFirst property 1", nullable = true) + public InlineSchemaPropertyFirst property1; + + + private InlineSchemaPropertyFirst property2; + + @Schema(description = " InlineSchemaFirst property 2", example = "example 2") + public InlineSchemaPropertyFirst getProperty2() { + return null; + } + } + + static class InlineSchemaSecond { + + // public String foo; + + @Schema(description = "InlineSchemaSecond property 1", nullable = true) + public InlineSchemaPropertySecond propertySecond1; + + + private InlineSchemaPropertyFirst property2; + + @Schema(description = "InlineSchemaSecond property 2", example = "InlineSchemaSecond example 2") + public InlineSchemaPropertyFirst getProperty2() { + return null; + } + } + + @Schema(description = "property", example = "example") + static class InlineSchemaPropertyFirst { + // public String bar; + } + + @Schema(description = "propertysecond", example = "examplesecond") + static class InlineSchemaPropertySecond { + public InlineSchemaSimple bar; + } + + static class InlineSchemaSimple { + + @Schema(description = "property 1") + public InlineSchemaPropertySimple property1; + + + private InlineSchemaPropertySimple property2; + + @Schema(description = "property 2", example = "example") + public InlineSchemaPropertySimple getProperty2() { + return null; + } + } + + @Schema(description = "property") + static class InlineSchemaPropertySimple { + // public String bar; + } +} diff --git a/modules/swagger-gradle-plugin/src/main/java/io/swagger/v3/plugins/gradle/tasks/ResolveTask.java b/modules/swagger-gradle-plugin/src/main/java/io/swagger/v3/plugins/gradle/tasks/ResolveTask.java index 87fcdc415f..c885bd593a 100644 --- a/modules/swagger-gradle-plugin/src/main/java/io/swagger/v3/plugins/gradle/tasks/ResolveTask.java +++ b/modules/swagger-gradle-plugin/src/main/java/io/swagger/v3/plugins/gradle/tasks/ResolveTask.java @@ -77,6 +77,8 @@ public enum Format {JSON, YAML, JSONANDYAML}; private Boolean convertToOpenAPI31 = false; + private String schemaResolution; + private String defaultResponseCode; @Input @@ -386,6 +388,19 @@ public void setConvertToOpenAPI31(Boolean convertToOpenAPI31) { } } + /** + * @since 2.2.24 + */ + @Input + @Optional + public String getSchemaResolution() { + return schemaResolution; + } + + public void setSchemaResolution(String schemaResolution) { + this.schemaResolution = schemaResolution; + } + @TaskAction public void resolve() throws GradleException { if (skip) { @@ -505,6 +520,9 @@ public void resolve() throws GradleException { method=swaggerLoaderClass.getDeclaredMethod("setConvertToOpenAPI31", Boolean.class); method.invoke(swaggerLoader, convertToOpenAPI31); + method=swaggerLoaderClass.getDeclaredMethod("setSchemaResolution", String.class); + method.invoke(swaggerLoader, schemaResolution); + method=swaggerLoaderClass.getDeclaredMethod("resolve"); Map specs = (Map)method.invoke(swaggerLoader); diff --git a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/GenericOpenApiContext.java b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/GenericOpenApiContext.java index ff14aa1c5e..76099eb352 100644 --- a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/GenericOpenApiContext.java +++ b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/GenericOpenApiContext.java @@ -76,6 +76,8 @@ public class GenericOpenApiContext implements O private Boolean convertToOpenAPI31; + private Schema.SchemaResolution schemaResolution; + public long getCacheTTL() { return cacheTTL; } @@ -330,6 +332,28 @@ public T convertToOpenAPI31(Boolean convertToOpenAPI31) { return (T) this; } + /** + * @since 2.2.24 + */ + public Schema.SchemaResolution getSchemaResolution() { + return schemaResolution; + } + + /** + * @since 2.2.24 + */ + public void setSchemaResolution(Schema.SchemaResolution schemaResolution) { + this.schemaResolution = schemaResolution; + } + + /** + * @since 2.2.24 + */ + public T schemaResolution(Schema.SchemaResolution schemaResolution) { + this.schemaResolution = schemaResolution; + return (T) this; + } + protected void register() { OpenApiContextLocator.getInstance().putOpenApiContext(id, this); } @@ -467,6 +491,9 @@ public T init() throws OpenApiConfigurationException { ((SwaggerConfiguration) openApiConfiguration).setId(id); ((SwaggerConfiguration) openApiConfiguration).setOpenAPI31(openAPI31); ((SwaggerConfiguration) openApiConfiguration).setConvertToOpenAPI31(convertToOpenAPI31); + if (schemaResolution != null) { + ((SwaggerConfiguration) openApiConfiguration).setSchemaResolution(schemaResolution); + } } openApiConfiguration = mergeParentConfiguration(openApiConfiguration, parent); @@ -559,6 +586,10 @@ public T init() throws OpenApiConfigurationException { if (openApiConfiguration.isConvertToOpenAPI31() != null && this.convertToOpenAPI31 == null) { this.convertToOpenAPI31 = openApiConfiguration.isConvertToOpenAPI31(); } + + if (openApiConfiguration.getSchemaResolution() != null && this.getSchemaResolution() == null) { + this.schemaResolution = openApiConfiguration.getSchemaResolution(); + } register(); return (T) this; } @@ -635,6 +666,10 @@ private OpenAPIConfiguration mergeParentConfiguration(OpenAPIConfiguration confi merged.setDefaultResponseCode(parentConfig.getDefaultResponseCode()); } + if (merged.getSchemaResolution() == null) { + merged.setSchemaResolution(parentConfig.getSchemaResolution()); + } + return merged; } diff --git a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/SwaggerConfiguration.java b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/SwaggerConfiguration.java index 1bd9041905..c9896626e3 100644 --- a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/SwaggerConfiguration.java +++ b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/SwaggerConfiguration.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.integration.api.OpenAPIConfiguration; import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.media.Schema; import java.util.Collection; import java.util.Map; @@ -40,6 +41,8 @@ public class SwaggerConfiguration implements OpenAPIConfiguration { private Boolean convertToOpenAPI31; + private Schema.SchemaResolution schemaResolution = Schema.SchemaResolution.DEFAULT; + @Override public String getDefaultResponseCode() { return defaultResponseCode; @@ -373,4 +376,18 @@ public SwaggerConfiguration convertToOpenAPI31(Boolean convertToOpenAPI31) { this.setConvertToOpenAPI31(convertToOpenAPI31); return this; } + + @Override + public Schema.SchemaResolution getSchemaResolution() { + return schemaResolution; + } + + public void setSchemaResolution(Schema.SchemaResolution schemaResolution) { + this.schemaResolution = schemaResolution; + } + + public SwaggerConfiguration schemaResolution(Schema.SchemaResolution schemaResolution) { + this.setSchemaResolution(schemaResolution); + return this; + } } diff --git a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/api/OpenAPIConfiguration.java b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/api/OpenAPIConfiguration.java index ae52702a9c..4854c2f5a1 100644 --- a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/api/OpenAPIConfiguration.java +++ b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/api/OpenAPIConfiguration.java @@ -1,6 +1,7 @@ package io.swagger.v3.oas.integration.api; import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.media.Schema; import java.util.Collection; import java.util.Map; @@ -68,4 +69,9 @@ public interface OpenAPIConfiguration { * @since 2.2.17 */ public String getDefaultResponseCode(); + + /** + * @since 2.2.24 + */ + public Schema.SchemaResolution getSchemaResolution(); } diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/Reader.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/Reader.java index e7396b9f46..082a1372fd 100644 --- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/Reader.java +++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/Reader.java @@ -1165,7 +1165,7 @@ protected Operation parseMethod( final Class subResource = getSubResourceWithJaxRsSubresourceLocatorSpecs(method); Schema returnTypeSchema = null; if (!shouldIgnoreClass(returnType.getTypeName()) && !method.getGenericReturnType().equals(subResource)) { - ResolvedSchema resolvedSchema = ModelConverters.getInstance(config.isOpenAPI31()).resolveAsResolvedSchema(new AnnotatedType(returnType).resolveAsRef(true).jsonViewAnnotation(jsonViewAnnotation).components(components)); + ResolvedSchema resolvedSchema = ModelConverters.getInstance(config.isOpenAPI31(), config.getSchemaResolution()).resolveAsResolvedSchema(new AnnotatedType(returnType).resolveAsRef(true).jsonViewAnnotation(jsonViewAnnotation).components(components)); if (resolvedSchema.schema != null) { returnTypeSchema = resolvedSchema.schema; Content content = new Content(); @@ -1318,7 +1318,7 @@ private boolean shouldIgnoreClass(String className) { } ignore = rawClassName.startsWith("javax.ws.rs."); ignore = ignore || rawClassName.equalsIgnoreCase("void"); - ignore = ignore || ModelConverters.getInstance(config.isOpenAPI31()).isRegisteredAsSkippedClass(rawClassName); + ignore = ignore || ModelConverters.getInstance(config.isOpenAPI31(), config.getSchemaResolution()).isRegisteredAsSkippedClass(rawClassName); return ignore; } diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletConfigContextUtils.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletConfigContextUtils.java index aa26058aaa..a2555f4f0b 100644 --- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletConfigContextUtils.java +++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletConfigContextUtils.java @@ -63,6 +63,11 @@ public class ServletConfigContextUtils { public static final String OPENAPI_CONFIGURATION_CONVERT_TO_OPENAPI_31_KEY = "openApi.configuration.convertToOpenAPI31"; + /** + * @since 2.2.24 + */ + public static final String OPENAPI_CONFIGURATION_SCHEMA_RESOLUTION_KEY = "openApi.configuration.schemaResolution"; + public static Set resolveResourcePackages(ServletConfig servletConfig) { if (!isServletConfigAvailable(servletConfig)) { return null; diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletOpenApiConfigurationLoader.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletOpenApiConfigurationLoader.java index baeb5dce76..f54f53769f 100644 --- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletOpenApiConfigurationLoader.java +++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletOpenApiConfigurationLoader.java @@ -6,6 +6,7 @@ import io.swagger.v3.oas.integration.api.OpenAPIConfigBuilder; import io.swagger.v3.oas.integration.api.OpenAPIConfiguration; import io.swagger.v3.oas.integration.api.OpenApiConfigurationLoader; +import io.swagger.v3.oas.models.media.Schema; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,6 +25,7 @@ import static io.swagger.v3.jaxrs2.integration.ServletConfigContextUtils.OPENAPI_CONFIGURATION_READALLRESOURCES_KEY; import static io.swagger.v3.jaxrs2.integration.ServletConfigContextUtils.OPENAPI_CONFIGURATION_READER_KEY; import static io.swagger.v3.jaxrs2.integration.ServletConfigContextUtils.OPENAPI_CONFIGURATION_SCANNER_KEY; +import static io.swagger.v3.jaxrs2.integration.ServletConfigContextUtils.OPENAPI_CONFIGURATION_SCHEMA_RESOLUTION_KEY; import static io.swagger.v3.jaxrs2.integration.ServletConfigContextUtils.OPENAPI_CONFIGURATION_SKIPRESOLVEAPPPATH_KEY; import static io.swagger.v3.jaxrs2.integration.ServletConfigContextUtils.OPENAPI_CONFIGURATION_SORTOUTPUT_KEY; import static io.swagger.v3.jaxrs2.integration.ServletConfigContextUtils.OPENAPI_CONFIGURATION_ALWAYSRESOLVEAPPPATH_KEY; @@ -70,7 +72,9 @@ public OpenAPIConfiguration load(String path) throws IOException { .openAPI31(getBooleanInitParam(servletConfig, OPENAPI_CONFIGURATION_OPENAPI_31_KEY)) .convertToOpenAPI31(getBooleanInitParam(servletConfig, OPENAPI_CONFIGURATION_CONVERT_TO_OPENAPI_31_KEY)) .modelConverterClasses(resolveModelConverterClasses(servletConfig)); - + if (getInitParam(servletConfig, OPENAPI_CONFIGURATION_SCHEMA_RESOLUTION_KEY) != null) { + configuration.schemaResolution(Schema.SchemaResolution.valueOf(getInitParam(servletConfig, OPENAPI_CONFIGURATION_SCHEMA_RESOLUTION_KEY))); + } return configuration; } diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/SwaggerLoader.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/SwaggerLoader.java index c9375dbe5d..199cda1dd6 100644 --- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/SwaggerLoader.java +++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/SwaggerLoader.java @@ -10,6 +10,7 @@ import io.swagger.v3.oas.integration.SwaggerConfiguration; import io.swagger.v3.oas.integration.api.OpenApiContext; import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.media.Schema; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; @@ -49,6 +50,8 @@ public class SwaggerLoader { private Boolean convertToOpenAPI31 = false; + private String schemaResolution; + /** * @since 2.0.6 */ @@ -250,6 +253,20 @@ public void setConvertToOpenAPI31(Boolean convertToOpenAPI31) { this.convertToOpenAPI31 = convertToOpenAPI31; } + /** + * @since 2.2.24 + */ + public String getSchemaResolution() { + return schemaResolution; + } + + /** + * @since 2.2.24 + */ + public void setSchemaResolution(String schemaResolution) { + this.schemaResolution = schemaResolution; + } + public Map resolve() throws Exception{ Set ignoredRoutesSet = null; @@ -300,6 +317,7 @@ public Map resolve() throws Exception{ .alwaysResolveAppPath(alwaysResolveAppPath) .skipResolveAppPath(skipResolveAppPath) .openAPI31(openAPI31) + .schemaResolution(Schema.SchemaResolution.valueOf(schemaResolution)) .convertToOpenAPI31(convertToOpenAPI31); try { GenericOpenApiContextBuilder builder = new JaxrsOpenApiContextBuilder() diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/ReaderTest.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/ReaderTest.java index 0f32a4dd30..0deea6487e 100644 --- a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/ReaderTest.java +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/ReaderTest.java @@ -98,6 +98,8 @@ import io.swagger.v3.jaxrs2.resources.generics.ticket3694.Ticket3694ResourceSimpleSameReturn; import io.swagger.v3.jaxrs2.resources.rs.ProcessTokenRestService; import io.swagger.v3.jaxrs2.resources.ticket3624.Service; +import io.swagger.v3.jaxrs2.schemaResolution.SchemaResolutionResource; +import io.swagger.v3.jaxrs2.schemaResolution.SchemaResolutionResourceSimple; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.integration.SwaggerConfiguration; import io.swagger.v3.oas.models.Components; @@ -4143,4 +4145,189 @@ public void test4483Response() { " type: string\n"; SerializationMatchers.assertEqualsToYaml(openAPI, yaml); } + + @Test + public void testSchemaResolution() { + ModelConverters.reset(); + Reader reader = new Reader(new SwaggerConfiguration().openAPI(new OpenAPI()).schemaResolution(Schema.SchemaResolution.INLINE)); + OpenAPI openAPI = reader.read(SchemaResolutionResource.class); + String yaml = "openapi: 3.0.1\n" + + "paths:\n" + + " /test/inlineSchemaFirst:\n" + + " get:\n" + + " operationId: inlineSchemaFirst\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: InlineSchemaFirst property 1\n" + + " nullable: true\n" + + " example: example\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: ' InlineSchemaFirst property 2'\n" + + " example: example 2\n" + + " /test/inlineSchemaSecond:\n" + + " get:\n" + + " operationId: inlineSchemaSecond\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " type: object\n" + + " properties:\n" + + " foo:\n" + + " type: string\n" + + " propertySecond1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 1\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 2\n" + + " example: example\n" + + " description: InlineSchemaSecond property 1\n" + + " nullable: true\n" + + " example: examplesecond\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: InlineSchemaSecond property 2\n" + + " example: InlineSchemaSecond example 2\n" + + "components:\n" + + " schemas:\n" + + " InlineSchemaFirst:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: InlineSchemaFirst property 1\n" + + " nullable: true\n" + + " example: example\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: ' InlineSchemaFirst property 2'\n" + + " example: example 2\n" + + " InlineSchemaPropertyFirst:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property\n" + + " example: example\n" + + " InlineSchemaPropertySecond:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 1\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 2\n" + + " example: example\n" + + " description: propertysecond\n" + + " nullable: true\n" + + " example: examplesecond\n" + + " InlineSchemaPropertySimple:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property\n" + + " example: example\n" + + " InlineSchemaSecond:\n" + + " type: object\n" + + " properties:\n" + + " foo:\n" + + " type: string\n" + + " propertySecond1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 1\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 2\n" + + " example: example\n" + + " description: InlineSchemaSecond property 1\n" + + " nullable: true\n" + + " example: examplesecond\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: InlineSchemaSecond property 2\n" + + " example: InlineSchemaSecond example 2\n" + + " InlineSchemaSimple:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 1\n" + + " property2:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property 2\n" + + " example: example\n\n"; + SerializationMatchers.assertEqualsToYaml(openAPI, yaml); + ModelConverters.reset(); + } } diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/schemaResolution/SchemaResolutionResource.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/schemaResolution/SchemaResolutionResource.java new file mode 100644 index 0000000000..01e691ba1e --- /dev/null +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/schemaResolution/SchemaResolutionResource.java @@ -0,0 +1,82 @@ +package io.swagger.v3.jaxrs2.schemaResolution; + +import io.swagger.v3.oas.annotations.media.Schema; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +@Path("test") +public class SchemaResolutionResource { + + @GET + @Path("/inlineSchemaSecond") + public InlineSchemaSecond inlineSchemaSecond() { + return null; + } + @GET + @Path("/inlineSchemaFirst") + public InlineSchemaFirst inlineSchemaFirst() { + return null; + } + + + static class InlineSchemaFirst { + + // public String foo; + + @Schema(description = "InlineSchemaFirst property 1", nullable = true) + public InlineSchemaPropertyFirst property1; + + + private InlineSchemaPropertyFirst property2; + + @Schema(description = " InlineSchemaFirst property 2", example = "example 2") + public InlineSchemaPropertyFirst getProperty2() { + return null; + } + } + + static class InlineSchemaSecond { + + public String foo; + + @Schema(description = "InlineSchemaSecond property 1", nullable = true) + public InlineSchemaPropertySecond propertySecond1; + + + private InlineSchemaPropertyFirst property2; + + @Schema(description = "InlineSchemaSecond property 2", example = "InlineSchemaSecond example 2") + public InlineSchemaPropertyFirst getProperty2() { + return null; + } + } + + @Schema(description = "property", example = "example") + static class InlineSchemaPropertyFirst { + public String bar; + } + + @Schema(description = "propertysecond", example = "examplesecond") + static class InlineSchemaPropertySecond { + public InlineSchemaSimple bar; + } + + static class InlineSchemaSimple { + + @Schema(description = "property 1") + public InlineSchemaPropertySimple property1; + + + private InlineSchemaPropertySimple property2; + + @Schema(description = "property 2", example = "example") + public InlineSchemaPropertySimple getProperty2() { + return null; + } + } + + @Schema(description = "property") + static class InlineSchemaPropertySimple { + public String bar; + } +} diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/schemaResolution/SchemaResolutionResourceSimple.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/schemaResolution/SchemaResolutionResourceSimple.java new file mode 100644 index 0000000000..61ac55e6b6 --- /dev/null +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/schemaResolution/SchemaResolutionResourceSimple.java @@ -0,0 +1,30 @@ +package io.swagger.v3.jaxrs2.schemaResolution; + +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +@Path("test") +public class SchemaResolutionResourceSimple { + + @GET + @Path("/inlineSchemaFirst") + public InlineSchemaFirst inlineSchemaFirst() { + return null; + } + + + static class InlineSchemaFirst { + + // public String foo; + + @Schema(description = "InlineSchemaFirst property 1", nullable = true) + public InlineSchemaPropertyFirst property1; + } + + @Schema(description = "property", example = "example") + static class InlineSchemaPropertyFirst { + public String bar; + } +} diff --git a/modules/swagger-maven-plugin/src/main/java/io/swagger/v3/plugin/maven/SwaggerMojo.java b/modules/swagger-maven-plugin/src/main/java/io/swagger/v3/plugin/maven/SwaggerMojo.java index 05911233d7..60bafc3d44 100644 --- a/modules/swagger-maven-plugin/src/main/java/io/swagger/v3/plugin/maven/SwaggerMojo.java +++ b/modules/swagger-maven-plugin/src/main/java/io/swagger/v3/plugin/maven/SwaggerMojo.java @@ -11,6 +11,7 @@ import io.swagger.v3.oas.integration.SwaggerConfiguration; import io.swagger.v3.oas.integration.api.OpenApiContext; import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.media.Schema; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; @@ -184,6 +185,7 @@ private void setDefaultsIfMissing(SwaggerConfiguration config) { if (config.isConvertToOpenAPI31() == null) { config.setConvertToOpenAPI31(convertToOpenAPI31); } + } /** @@ -355,6 +357,10 @@ private SwaggerConfiguration mergeConfig(OpenAPI openAPIInput, SwaggerConfigurat config.openAPI31(openapi31); } + if (StringUtils.isNotBlank(schemaResolution)) { + config.schemaResolution(Schema.SchemaResolution.valueOf(schemaResolution)); + } + return config; } @@ -453,6 +459,12 @@ private boolean isCollectionNotBlank(Collection collection) { @Parameter(property = "resolve.convertToOpenAPI31") private Boolean convertToOpenAPI31; + /** + * @since 2.2.24 + */ + @Parameter(property = "resolve.schemaResolution") + private String schemaResolution; + private String projectEncoding = "UTF-8"; private SwaggerConfiguration config; diff --git a/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/Schema.java b/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/Schema.java index 8872a74c2c..8f76ad9ce8 100644 --- a/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/Schema.java +++ b/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/Schema.java @@ -1,6 +1,7 @@ package io.swagger.v3.oas.models.media; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.models.annotations.OpenAPI30; import io.swagger.v3.oas.models.annotations.OpenAPI31; import io.swagger.v3.oas.models.Components; @@ -44,6 +45,26 @@ public String toString() { } } + public static final String SCHEMA_RESOLUTION_PROPERTY = "schema-resolution"; + public enum SchemaResolution { + @JsonProperty("default") + DEFAULT("default"), + @JsonProperty("inline") + INLINE("inline"), + @JsonProperty("all-of") + ALL_OF("all-of"); + private String value; + + SchemaResolution(String value) { + this.value = value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + } + protected T _default; private String name;