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 b9cfc4c661..7149e587c5 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 @@ -45,7 +45,7 @@ public ModelConverters(boolean openapi31) { public ModelConverters(boolean openapi31, Schema.SchemaResolution schemaResolution) { converters = new CopyOnWriteArrayList<>(); if (openapi31) { - converters.add(new ModelResolver(Json31.mapper()).openapi31(true)); + converters.add(new ModelResolver(Json31.mapper()).openapi31(true).schemaResolution(schemaResolution)); } else { converters.add(new ModelResolver(Json.mapper()).schemaResolution(schemaResolution)); } @@ -81,7 +81,7 @@ public static ModelConverters getInstance(boolean openapi31, Schema.SchemaResolu synchronized (ModelConverters.class) { if (openapi31) { if (SINGLETON31 == null) { - SINGLETON31 = new ModelConverters(openapi31); + SINGLETON31 = new ModelConverters(openapi31, Schema.SchemaResolution.DEFAULT); init(SINGLETON31); } return SINGLETON31; 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 1cb9e12bbf..dc9e74add1 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 @@ -682,7 +682,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context Annotation[] ctxAnnotation31 = null; - if (openapi31) { + if (Schema.SchemaResolution.ALL_OF.equals(this.schemaResolution) || openapi31) { List ctxAnnotations31List = new ArrayList<>(); if (annotations != null) { for (Annotation a : annotations) { @@ -701,7 +701,6 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context AnnotatedType aType = new AnnotatedType() .type(propType) - .ctxAnnotations(openapi31 ? ctxAnnotation31 : annotations) .parent(model) .resolveAsRef(annotatedType.isResolveAsRef()) .jsonViewAnnotation(annotatedType.getJsonViewAnnotation()) @@ -709,7 +708,11 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context .schemaProperty(true) .components(annotatedType.getComponents()) .propertyName(propName); - + if (Schema.SchemaResolution.ALL_OF.equals(this.schemaResolution) || openapi31) { + aType.ctxAnnotations(ctxAnnotation31); + } else { + aType.ctxAnnotations(annotations); + } final AnnotatedMember propMember = member; aType.jsonUnwrappedHandler(t -> { JsonUnwrapped uw = propMember.getAnnotation(JsonUnwrapped.class); @@ -726,6 +729,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context }); property = context.resolve(aType); property = clone(property); + Schema ctxProperty = null; if (openapi31) { Optional reResolvedProperty = AnnotationsUtils.getSchemaFromAnnotation(ctxSchema, annotatedType.getComponents(), null, openapi31, property, schemaResolution, context); if (reResolvedProperty.isPresent()) { @@ -736,6 +740,16 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context property = reResolvedProperty.get(); } + } else if (Schema.SchemaResolution.ALL_OF.equals(this.schemaResolution)) { + Optional reResolvedProperty = AnnotationsUtils.getSchemaFromAnnotation(ctxSchema, annotatedType.getComponents(), null, openapi31, null, schemaResolution, context); + if (reResolvedProperty.isPresent()) { + ctxProperty = reResolvedProperty.get(); + } + reResolvedProperty = AnnotationsUtils.getArraySchema(ctxArraySchema, annotatedType.getComponents(), null, openapi31, ctxProperty); + if (reResolvedProperty.isPresent()) { + ctxProperty = reResolvedProperty.get(); + } + } if (property != null) { Boolean required = md.getRequired(); @@ -777,10 +791,15 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context if (context.getDefinedModels().containsKey(pName)) { if (Schema.SchemaResolution.INLINE.equals(this.schemaResolution)) { property = context.getDefinedModels().get(pName); + } else if (Schema.SchemaResolution.ALL_OF.equals(this.schemaResolution) && ctxProperty != null) { + property = new Schema() + .addAllOfItem(ctxProperty) + .addAllOfItem(new Schema().$ref(constructRef(pName))); } else { property = new Schema().$ref(constructRef(pName)); } property = clone(property); + // TODO: why is this needed? is it not handled before? if (openapi31 || Schema.SchemaResolution.INLINE.equals(this.schemaResolution)) { Optional reResolvedProperty = AnnotationsUtils.getSchemaFromAnnotation(ctxSchema, annotatedType.getComponents(), null, openapi31, property, this.schemaResolution, context); if (reResolvedProperty.isPresent()) { @@ -794,7 +813,13 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context } } else if (property.get$ref() != null) { if (!openapi31) { - property = new Schema().$ref(StringUtils.isNotEmpty(property.get$ref()) ? property.get$ref() : property.getName()); + if (Schema.SchemaResolution.ALL_OF.equals(this.schemaResolution) && ctxProperty != null) { + property = new Schema() + .addAllOfItem(ctxProperty) + .addAllOfItem(new Schema().$ref(StringUtils.isNotEmpty(property.get$ref()) ? property.get$ref() : property.getName())); + } else { + property = new Schema().$ref(StringUtils.isNotEmpty(property.get$ref()) ? property.get$ref() : property.getName()); + } } else { if (StringUtils.isEmpty(property.get$ref())) { property.$ref(property.getName()); @@ -807,9 +832,12 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context if (property != null && io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED.equals(requiredMode)) { addRequiredItem(model, property.getName()); } + if (ctxProperty == null) { + ctxProperty = property; + } final boolean applyNotNullAnnotations = io.swagger.v3.oas.annotations.media.Schema.RequiredMode.AUTO.equals(requiredMode); annotations = addGenericTypeArgumentAnnotationsForOptionalField(propDef, annotations); - applyBeanValidatorAnnotations(propDef, property, annotations, model, applyNotNullAnnotations); + applyBeanValidatorAnnotations(propDef, ctxProperty, annotations, model, applyNotNullAnnotations); props.add(property); } 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 ef17f1c7d3..bdfbc2e691 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 @@ -621,6 +621,12 @@ public static Optional getSchemaFromAnnotation( } else { schemaObject = new Schema(); } + } else if (Schema.SchemaResolution.ALL_OF.equals(schemaResolution)) { + if (existingSchema == null) { + schemaObject = new Schema(); + } else { + schemaObject = existingSchema; + } } } else { if (existingSchema == null) { diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ParameterProcessor.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ParameterProcessor.java index 5c977bdfed..d219d52a55 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ParameterProcessor.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ParameterProcessor.java @@ -42,6 +42,19 @@ public static Parameter applyAnnotations( String[] methodTypes, JsonView jsonViewAnnotation, boolean openapi31) { + return applyAnnotations(parameter, type, annotations, components, classTypes, methodTypes, jsonViewAnnotation, openapi31, null); + } + + public static Parameter applyAnnotations( + Parameter parameter, + Type type, + List annotations, + Components components, + String[] classTypes, + String[] methodTypes, + JsonView jsonViewAnnotation, + boolean openapi31, + Schema.SchemaResolution schemaResolution) { final AnnotationsHelper helper = new AnnotationsHelper(annotations, type); if (helper.isContext()) { @@ -59,17 +72,57 @@ public static Parameter applyAnnotations( if (paramSchemaOrArrayAnnotation != null) { reworkedAnnotations.add(paramSchemaOrArrayAnnotation); } + io.swagger.v3.oas.annotations.media.Schema ctxSchema = AnnotationsUtils.getSchemaAnnotation(annotations.toArray(new Annotation[0])); + io.swagger.v3.oas.annotations.media.ArraySchema ctxArraySchema = AnnotationsUtils.getArraySchemaAnnotation(annotations.toArray(new Annotation[0])); + Annotation[] ctxAnnotation31 = null; + + if (Schema.SchemaResolution.ALL_OF.equals(schemaResolution)) { + List ctxAnnotations31List = new ArrayList<>(); + if (annotations != null) { + for (Annotation a : annotations) { + if ( + !(a instanceof io.swagger.v3.oas.annotations.media.Schema) && + !(a instanceof io.swagger.v3.oas.annotations.media.ArraySchema)) { + ctxAnnotations31List.add(a); + } + } + ctxAnnotation31 = ctxAnnotations31List.toArray(new Annotation[ctxAnnotations31List.size()]); + } + } AnnotatedType annotatedType = new AnnotatedType() .type(type) .resolveAsRef(true) .skipOverride(true) - .jsonViewAnnotation(jsonViewAnnotation) - .ctxAnnotations(reworkedAnnotations.toArray(new Annotation[reworkedAnnotations.size()])); + .jsonViewAnnotation(jsonViewAnnotation); + + if (Schema.SchemaResolution.ALL_OF.equals(schemaResolution)) { + annotatedType.ctxAnnotations(ctxAnnotation31); + } else { + annotatedType.ctxAnnotations(reworkedAnnotations.toArray(new Annotation[reworkedAnnotations.size()])); + } - final ResolvedSchema resolvedSchema = ModelConverters.getInstance(openapi31).resolveAsResolvedSchema(annotatedType); + final ResolvedSchema resolvedSchema = ModelConverters.getInstance(openapi31, schemaResolution).resolveAsResolvedSchema(annotatedType); if (resolvedSchema.schema != null) { - parameter.setSchema(resolvedSchema.schema); + Schema resSchema = AnnotationsUtils.clone(resolvedSchema.schema, openapi31); + Schema ctxSchemaObject = null; + if (Schema.SchemaResolution.ALL_OF.equals(schemaResolution)) { + Optional reResolvedSchema = AnnotationsUtils.getSchemaFromAnnotation(ctxSchema, annotatedType.getComponents(), null, openapi31, null, schemaResolution, null); + if (reResolvedSchema.isPresent()) { + ctxSchemaObject = reResolvedSchema.get(); + } + reResolvedSchema = AnnotationsUtils.getArraySchema(ctxArraySchema, annotatedType.getComponents(), null, openapi31, ctxSchemaObject); + if (reResolvedSchema.isPresent()) { + ctxSchemaObject = reResolvedSchema.get(); + } + + } + if (Schema.SchemaResolution.ALL_OF.equals(schemaResolution) && ctxSchemaObject != null) { + resSchema = new Schema() + .addAllOfItem(ctxSchemaObject) + .addAllOfItem(resolvedSchema.schema); + } + parameter.setSchema(resSchema); } resolvedSchema.referencedSchemas.forEach(components::addSchemas); diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/AllofResolvingTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/AllofResolvingTest.java new file mode 100644 index 0000000000..bbec9310c1 --- /dev/null +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/AllofResolvingTest.java @@ -0,0 +1,165 @@ +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 AllofResolvingTest extends SwaggerTestBase { + + @Test + public void testAllofResolving() { + + final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(false).schemaResolution(io.swagger.v3.oas.models.media.Schema.SchemaResolution.ALL_OF); + final ModelConverterContextImpl c = new ModelConverterContextImpl(modelResolver); + // ModelConverters c = ModelConverters.getInstance(false, io.swagger.v3.oas.models.media.Schema.SchemaResolution.INLINE); + c.resolve(new AnnotatedType(UserSchema.class)); + + String expectedYaml = "UserProperty:\n" + + " type: object\n" + + " description: Represents a user-specific property\n" + + " example: User-specific example value\n" + + "UserSchema:\n" + + " type: object\n" + + " properties:\n" + + " propertyOne:\n" + + " allOf:\n" + + " - type: object\n" + + " description: First user schema property\n" + + " nullable: true\n" + + " - $ref: '#/components/schemas/UserProperty'\n" + + " propertyTwo:\n" + + " allOf:\n" + + " - type: object\n" + + " description: Second user schema property\n" + + " example: example value for propertyTwo\n" + + " - $ref: '#/components/schemas/UserProperty'\n" + + " propertyThree:\n" + + " allOf:\n" + + " - type: object\n" + + " description: \"Third user schema property, with example for testing\"\n" + + " example: example value for propertyThree\n" + + " - $ref: '#/components/schemas/UserProperty'\n"; + + SerializationMatchers.assertEqualsToYaml(c.getDefinedModels(), expectedYaml); + // stringSchemaMap = c.readAll(InlineSchemaSecond.class); + c.resolve(new AnnotatedType(OrderSchema.class)); + expectedYaml = "BasicProperty:\n" + + " type: object\n" + + " description: Represents a basic schema property\n" + + "OrderProperty:\n" + + " type: object\n" + + " properties:\n" + + " basicProperty:\n" + + " $ref: '#/components/schemas/BasicProperty'\n" + + " description: Represents an order-specific property\n" + + " example: Order-specific example value\n" + + "OrderSchema:\n" + + " type: object\n" + + " properties:\n" + + " propertyOne:\n" + + " allOf:\n" + + " - type: object\n" + + " description: First order schema property\n" + + " nullable: true\n" + + " - $ref: '#/components/schemas/OrderProperty'\n" + + " userProperty:\n" + + " allOf:\n" + + " - type: object\n" + + " description: \"Order schema property, references UserProperty\"\n" + + " example: example value for userProperty\n" + + " - $ref: '#/components/schemas/UserProperty'\n" + + "UserProperty:\n" + + " type: object\n" + + " description: Represents a user-specific property\n" + + " example: User-specific example value\n" + + "UserSchema:\n" + + " type: object\n" + + " properties:\n" + + " propertyOne:\n" + + " allOf:\n" + + " - type: object\n" + + " description: First user schema property\n" + + " nullable: true\n" + + " - $ref: '#/components/schemas/UserProperty'\n" + + " propertyTwo:\n" + + " allOf:\n" + + " - type: object\n" + + " description: Second user schema property\n" + + " example: example value for propertyTwo\n" + + " - $ref: '#/components/schemas/UserProperty'\n" + + " propertyThree:\n" + + " allOf:\n" + + " - type: object\n" + + " description: \"Third user schema property, with example for testing\"\n" + + " example: example value for propertyThree\n" + + " - $ref: '#/components/schemas/UserProperty'\n"; + SerializationMatchers.assertEqualsToYaml(c.getDefinedModels(), expectedYaml); + } + + // Renamed class to better describe what it represents + static class UserSchema { + + @Schema(description = "First user schema property", nullable = true) + public UserProperty propertyOne; + + private UserProperty propertyTwo; + + @Schema(description = "Second user schema property", example = "example value for propertyTwo") + public UserProperty getPropertyTwo() { + return propertyTwo; + } + + // Third property with no specific annotation. It's good to add some description or example for clarity + @Schema(description = "Third user schema property, with example for testing", example = "example value for propertyThree") + public UserProperty getPropertyThree() { + return null; // returning null as per the test scenario + } + } + + // Renamed class to represent a different entity for the schema test + static class OrderSchema { + + @Schema(description = "First order schema property", nullable = true) + public OrderProperty propertyOne; + + private UserProperty userProperty; + + @Schema(description = "Order schema property, references UserProperty", example = "example value for userProperty") + public UserProperty getUserProperty() { + return userProperty; + } + } + + // Renamed properties to make them clearer about their role in the schema + @Schema(description = "Represents a user-specific property", example = "User-specific example value") + static class UserProperty { + // public String value; + } + + @Schema(description = "Represents an order-specific property", example = "Order-specific example value") + static class OrderProperty { + public BasicProperty basicProperty; + } + + static class BasicSchema { + + @Schema(description = "First basic schema property") + public BasicProperty propertyOne; + + private BasicProperty propertyTwo; + + @Schema(description = "Second basic schema property", example = "example value for propertyTwo") + public BasicProperty getPropertyTwo() { + return propertyTwo; + } + } + + // Renamed to represent a basic property common in various schemas + @Schema(description = "Represents a basic schema property") + static class BasicProperty { + // public String value; + } +} 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 76099eb352..27a4216144 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 @@ -552,7 +552,7 @@ public T init() throws OpenApiConfigurationException { if (objectMapperProcessor != null) { ObjectMapper mapper = IntegrationObjectMapperFactory.createJson(); objectMapperProcessor.processJsonObjectMapper(mapper); - ModelConverters.getInstance(Boolean.TRUE.equals(openApiConfiguration.isOpenAPI31())).addConverter(new ModelResolver(mapper)); + ModelConverters.getInstance(Boolean.TRUE.equals(openApiConfiguration.isOpenAPI31()), openApiConfiguration.getSchemaResolution()).addConverter(new ModelResolver(mapper)); objectMapperProcessor.processOutputJsonObjectMapper(outputJsonMapper); objectMapperProcessor.processOutputYamlObjectMapper(outputYamlMapper); diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/DefaultParameterExtension.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/DefaultParameterExtension.java index ed958c0a33..541dda6b52 100644 --- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/DefaultParameterExtension.java +++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/DefaultParameterExtension.java @@ -120,7 +120,7 @@ public ResolvedParameter extractParameters(List annotations, annotations, components, classConsumes == null ? new String[0] : classConsumes.value(), - methodConsumes == null ? new String[0] : methodConsumes.value(), jsonViewAnnotation, openapi31); + methodConsumes == null ? new String[0] : methodConsumes.value(), jsonViewAnnotation, openapi31, this.schemaResolution); if (unknownParameter != null) { if (StringUtils.isNotBlank(unknownParameter.getIn()) && !"form".equals(unknownParameter.getIn())) { extractParametersResult.parameters.add(unknownParameter); @@ -141,7 +141,8 @@ public ResolvedParameter extractParameters(List annotations, classConsumes == null ? new String[0] : classConsumes.value(), methodConsumes == null ? new String[0] : methodConsumes.value(), jsonViewAnnotation, - openapi31); + openapi31, + this.schemaResolution); if (processedParameter != null) { extractParametersResult.parameters.add(processedParameter); } @@ -264,7 +265,9 @@ private boolean handleAdditionalAnnotation(List parameters, List parameters, List cls, final List globalParameters = new ArrayList<>(); // look for constructor-level annotated properties - globalParameters.addAll(ReaderUtils.collectConstructorParameters(cls, components, classConsumes, null)); + globalParameters.addAll(ReaderUtils.collectConstructorParameters(cls, components, classConsumes, null, config.getSchemaResolution())); // look for field-level annotated properties globalParameters.addAll(ReaderUtils.collectFieldParameters(cls, components, classConsumes, null)); @@ -1525,8 +1525,11 @@ protected ResolvedParameter getParameters(Type type, List annotation LOGGER.debug("trying extension {}", extension); extension.setOpenAPI31(Boolean.TRUE.equals(config.isOpenAPI31())); - - return extension.extractParameters(annotations, type, typesToSkip, components, classConsumes, methodConsumes, true, jsonViewAnnotation, chain); + Schema.SchemaResolution curSchemaResolution = config.getSchemaResolution(); + extension.setSchemaResolution(config.getSchemaResolution()); + ResolvedParameter resolvedParameter = extension.extractParameters(annotations, type, typesToSkip, components, classConsumes, methodConsumes, true, jsonViewAnnotation, chain); + ((SwaggerConfiguration)config).setSchemaResolution(curSchemaResolution); + return resolvedParameter; } private Set extractOperationIdFromPathItem(PathItem path) { diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/AbstractOpenAPIExtension.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/AbstractOpenAPIExtension.java index a8792d4e2a..197e248b24 100644 --- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/AbstractOpenAPIExtension.java +++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/AbstractOpenAPIExtension.java @@ -6,6 +6,7 @@ import io.swagger.v3.jaxrs2.ResolvedParameter; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.Schema; import java.lang.annotation.Annotation; import java.lang.reflect.Method; @@ -17,6 +18,7 @@ public abstract class AbstractOpenAPIExtension implements OpenAPIExtension { protected boolean openapi31; + protected Schema.SchemaResolution schemaResolution; @Override public String extractOperationMethod(Method method, Iterator chain) { @@ -68,4 +70,9 @@ protected JavaType constructType(Type type) { public void setOpenAPI31(boolean openapi31) { this.openapi31 = openapi31; } + + @Override + public void setSchemaResolution(Schema.SchemaResolution schemaResolution) { + this.schemaResolution = schemaResolution; + } } diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/OpenAPIExtension.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/OpenAPIExtension.java index d966120a0d..b82e8b581d 100644 --- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/OpenAPIExtension.java +++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/OpenAPIExtension.java @@ -4,6 +4,7 @@ import io.swagger.v3.jaxrs2.ResolvedParameter; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.Schema; import java.lang.annotation.Annotation; import java.lang.reflect.Method; @@ -31,4 +32,8 @@ ResolvedParameter extractParameters(List annotations, Type type, Set default void setOpenAPI31(boolean openapi31) { //todo: override me! } + + default void setSchemaResolution(Schema.SchemaResolution schemaResolution) { + //todo: override me! + } } diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/util/ReaderUtils.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/util/ReaderUtils.java index 8933a0497e..581bc0bbb4 100644 --- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/util/ReaderUtils.java +++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/util/ReaderUtils.java @@ -7,6 +7,7 @@ import io.swagger.v3.jaxrs2.ext.OpenAPIExtensions; import io.swagger.v3.oas.integration.api.OpenAPIConfiguration; import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.parameters.Parameter; import org.apache.commons.lang3.StringUtils; @@ -35,6 +36,9 @@ public class ReaderUtils { private static final String OPTIONS_METHOD = "options"; private static final String PATH_DELIMITER = "/"; + public static List collectConstructorParameters(Class cls, Components components, javax.ws.rs.Consumes classConsumes, JsonView jsonViewAnnotation) { + return collectConstructorParameters(cls, components, classConsumes, jsonViewAnnotation, null); + } /** * Collects constructor-level parameters from class. * @@ -42,7 +46,7 @@ public class ReaderUtils { * @param components * @return the collection of supported parameters */ - public static List collectConstructorParameters(Class cls, Components components, javax.ws.rs.Consumes classConsumes, JsonView jsonViewAnnotation) { + public static List collectConstructorParameters(Class cls, Components components, javax.ws.rs.Consumes classConsumes, JsonView jsonViewAnnotation, Schema.SchemaResolution schemaResolution) { if (cls.isLocalClass() || (cls.isMemberClass() && !Modifier.isStatic(cls.getModifiers()))) { return Collections.emptyList(); } @@ -77,7 +81,7 @@ public static List collectConstructorParameters(Class cls, Compone components, classConsumes == null ? new String[0] : classConsumes.value(), null, - jsonViewAnnotation); + jsonViewAnnotation, false, schemaResolution); if (processedParameter != null) { parameters.add(processedParameter); } 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 62575c2f0e..e3ae222c2c 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,8 +98,6 @@ 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; @@ -145,9 +143,7 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.stream.Collectors; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; @@ -4169,189 +4165,4 @@ 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/SchemaResolutionAllOfTest.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/SchemaResolutionAllOfTest.java new file mode 100644 index 0000000000..4235b7aecc --- /dev/null +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/SchemaResolutionAllOfTest.java @@ -0,0 +1,60 @@ +package io.swagger.v3.jaxrs2; + +import io.swagger.v3.jaxrs2.matchers.SerializationMatchers; +import io.swagger.v3.jaxrs2.schemaResolution.SchemaResolutionResourceSimple; +import io.swagger.v3.oas.integration.SwaggerConfiguration; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.media.Schema; +import org.testng.annotations.Test; + +public class SchemaResolutionAllOfTest { + + @Test + public void testSchemaResolutionAllOf() { + Reader reader = new Reader(new SwaggerConfiguration().openAPI(new OpenAPI()).schemaResolution(Schema.SchemaResolution.ALL_OF)); + OpenAPI openAPI = reader.read(SchemaResolutionResourceSimple.class); + String yaml = "openapi: 3.0.1\n" + + "paths:\n" + + " /test/inlineSchemaFirst:\n" + + " get:\n" + + " operationId: inlineSchemaFirst\n" + + " responses:\n" + + " default:\n" + + " description: InlineSchemaFirst Response API\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " $ref: '#/components/schemas/InlineSchemaFirst'\n" + + " /test/inlineSchemaSecond:\n" + + " get:\n" + + " operationId: inlineSchemaFirst_1\n" + + " requestBody:\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " allOf:\n" + + " - description: InlineSchemaSecond API\n" + + " - $ref: '#/components/schemas/InlineSchemaFirst'\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*': {}\n" + + "components:\n" + + " schemas:\n" + + " InlineSchemaFirst:\n" + + " type: object\n" + + " properties:\n" + + " property1:\n" + + " $ref: '#/components/schemas/InlineSchemaPropertyFirst'\n" + + " InlineSchemaPropertyFirst:\n" + + " type: object\n" + + " properties:\n" + + " bar:\n" + + " type: string\n" + + " description: property\n" + + " nullable: true\n" + + " example: example\n"; + SerializationMatchers.assertEqualsToYaml(openAPI, yaml); + } +} diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/SchemaResolutionInlineTest.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/SchemaResolutionInlineTest.java new file mode 100644 index 0000000000..320e99b751 --- /dev/null +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/SchemaResolutionInlineTest.java @@ -0,0 +1,235 @@ +package io.swagger.v3.jaxrs2; + +import io.swagger.v3.core.converter.ModelConverters; +import io.swagger.v3.jaxrs2.matchers.SerializationMatchers; +import io.swagger.v3.jaxrs2.schemaResolution.SchemaResolutionResource; +import io.swagger.v3.oas.integration.SwaggerConfiguration; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.media.Schema; +import org.testng.annotations.Test; + +public class SchemaResolutionInlineTest { + @Test + public void testSchemaResolutionInline() { + 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" + + " requestBody:\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" + + " description: InlineSchemaSecond API\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" + + " description: InlineSchemaSecond API\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 index 01e691ba1e..6f909e1d44 100644 --- 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 @@ -9,7 +9,7 @@ public class SchemaResolutionResource { @GET @Path("/inlineSchemaSecond") - public InlineSchemaSecond inlineSchemaSecond() { + public InlineSchemaSecond inlineSchemaSecond(@Schema(description = "InlineSchemaSecond API") InlineSchemaSecond inlineSchemaSecond) { return null; } @GET 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 index 61ac55e6b6..fc043bc168 100644 --- 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 @@ -1,20 +1,32 @@ package io.swagger.v3.jaxrs2.schemaResolution; +import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import javax.ws.rs.GET; import javax.ws.rs.Path; +import javax.ws.rs.core.Response; @Path("test") public class SchemaResolutionResourceSimple { + @GET @Path("/inlineSchemaFirst") - public InlineSchemaFirst inlineSchemaFirst() { + @ApiResponse(description = "InlineSchemaFirst Response API", content = @Content(schema = @Schema(implementation = InlineSchemaFirst.class))) + public Response inlineSchemaFirst() { return null; } + @GET + @Path("/inlineSchemaSecond") + public void inlineSchemaFirst(@Schema(description = "InlineSchemaSecond API") InlineSchemaFirst inlineSchemaFirst) { + } + + + static class InlineSchemaFirst { // public String foo; diff --git a/pom.xml b/pom.xml index 355de17851..7a1473c3f8 100644 --- a/pom.xml +++ b/pom.xml @@ -654,7 +654,7 @@ 4.5.14 1.16.0 - 2.22.2 + 3.5.0 3.2.1 2.22.2