diff --git a/modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/media/Schema.java b/modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/media/Schema.java index 776d2f9f54..b5d02b0a83 100644 --- a/modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/media/Schema.java +++ b/modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/media/Schema.java @@ -399,6 +399,24 @@ @OpenAPI31 String $anchor() default ""; + /** + * Provides the $vocabulary related to schema + * + * @since 2.2.14 / OpenAPI 3.1 + * @return $vocabulary schema + */ + @OpenAPI31 + String $vocabulary() default ""; + + /** + * Provides the $dynamicAnchor related to schema + * + * @since 2.2.14 / OpenAPI 3.1 + * @return $dynamicAnchor schema + */ + @OpenAPI31 + String $dynamicAnchor() default ""; + /** * Provides the content encoding related to this schema * 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 b45f867b2b..8139c90363 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 @@ -2386,6 +2386,20 @@ protected String resolveId(Annotated a, Annotation[] annotations, io.swagger.v3. return null; } + protected String resolve$vocabulary(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) { + if (schema != null && StringUtils.isNotBlank(schema.$vocabulary())) { + return schema.$vocabulary(); + } + return null; + } + + protected String resolve$dynamicAnchor(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) { + if (schema != null && StringUtils.isNotBlank(schema.$dynamicAnchor())) { + return schema.$dynamicAnchor(); + } + return null; + } + protected String resolveContentEncoding(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) { if (schema != null && StringUtils.isNotBlank(schema.contentEncoding())) { return schema.contentEncoding(); @@ -2567,16 +2581,24 @@ protected void resolveSchemaMembers(Schema schema, AnnotatedType annotatedType, thenSchema = buildRefSchemaIfObject(thenSchema, context); schema.setThen(thenSchema); } - if (!Void.class.equals(schemaAnnotation.contentSchema())) { - Schema contentSchema = resolve(new AnnotatedType(schemaAnnotation.contentSchema()), context, next); - contentSchema = buildRefSchemaIfObject(contentSchema, context); - schema.setContentSchema(contentSchema); - } if (!Void.class.equals(schemaAnnotation.unevaluatedProperties())) { Schema unevaluatedProperties = resolve(new AnnotatedType(schemaAnnotation.unevaluatedProperties()), context, next); unevaluatedProperties = buildRefSchemaIfObject(unevaluatedProperties, context); schema.setUnevaluatedProperties(unevaluatedProperties); } + + if (schemaAnnotation.additionalProperties().equals(io.swagger.v3.oas.annotations.media.Schema.AdditionalPropertiesValue.TRUE)) { + schema.additionalProperties(true); + } else if (schemaAnnotation.additionalProperties().equals(io.swagger.v3.oas.annotations.media.Schema.AdditionalPropertiesValue.FALSE)) { + schema.additionalProperties(false); + } else { + if (!schemaAnnotation.additionalPropertiesSchema().equals(Void.class)) { + Schema additionalPropertiesSchema = resolve(new AnnotatedType(schemaAnnotation.additionalPropertiesSchema()), context, next); + additionalPropertiesSchema = buildRefSchemaIfObject(additionalPropertiesSchema, context); + schema.additionalProperties(additionalPropertiesSchema); + } + } + final Map> dependentRequired = resolveDependentRequired(a, annotations, schemaAnnotation); if (dependentRequired != null && !dependentRequired.isEmpty()) { schema.setDependentRequired(dependentRequired); @@ -2739,6 +2761,14 @@ protected void resolveSchemaMembers(Schema schema, Annotated a, Annotation[] ann if ($comment != null) { schema.set$comment($comment); } + String $vocabulary = resolve$vocabulary(a, annotations, schemaAnnotation); + if ($vocabulary != null) { + schema.set$vocabulary($vocabulary); + } + String $dynamicAnchor = resolve$dynamicAnchor(a, annotations, schemaAnnotation); + if ($dynamicAnchor != null) { + schema.$dynamicAnchor($dynamicAnchor); + } String contentEncoding = resolveContentEncoding(a, annotations, schemaAnnotation); if (contentEncoding != null) { schema.setContentEncoding(contentEncoding); 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 054bce7067..8a1e4ced80 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 @@ -556,6 +556,9 @@ public static Optional getSchemaFromAnnotation( } Schema schemaObject = null; if (!openapi31) { + if (existingSchema != null) { + return Optional.of(existingSchema); + } if (schema.oneOf().length > 0 || schema.allOf().length > 0 || schema.anyOf().length > 0) { @@ -597,6 +600,12 @@ public static Optional getSchemaFromAnnotation( if (StringUtils.isNotBlank(schema.$anchor())) { schemaObject.set$anchor(schema.$anchor()); } + if (StringUtils.isNotBlank(schema.$vocabulary())) { + schemaObject.set$vocabulary(schema.$vocabulary()); + } + if (StringUtils.isNotBlank(schema.$dynamicAnchor())) { + schemaObject.set$dynamicAnchor(schema.$dynamicAnchor()); + } if (StringUtils.isNotBlank(schema.contentEncoding())) { schemaObject.setContentEncoding(schema.contentEncoding()); } @@ -810,6 +819,9 @@ public static Schema resolveSchemaFromType(Class schemaImplementation, Compon } public static Schema resolveSchemaFromType(Class schemaImplementation, Components components, JsonView jsonViewAnnotation, boolean openapi31) { + return resolveSchemaFromType(schemaImplementation, components, jsonViewAnnotation, openapi31, null, null); + } + public static Schema resolveSchemaFromType(Class schemaImplementation, Components components, JsonView jsonViewAnnotation, boolean openapi31, io.swagger.v3.oas.annotations.media.Schema schemaAnnotation, io.swagger.v3.oas.annotations.media.ArraySchema arrayAnnotation) { Schema schemaObject; PrimitiveType primitiveType = PrimitiveType.fromType(schemaImplementation); if (primitiveType != null) { @@ -836,6 +848,10 @@ public static Schema resolveSchemaFromType(Class schemaImplementation, Compon } } } + Optional reResolvedSchema = getSchemaFromAnnotation(schemaAnnotation, components, jsonViewAnnotation, openapi31, schemaObject); + if (reResolvedSchema.isPresent()) { + schemaObject = reResolvedSchema.get(); + } if (StringUtils.isBlank(schemaObject.get$ref()) && StringUtils.isBlank(schemaObject.getType())) { // default to string schemaObject.setType("string"); @@ -1531,7 +1547,7 @@ public static Optional getSchema(io.swagger.v3.oas.annotations JsonView jsonViewAnnotation, boolean openapi31) { if (schemaImplementation != Void.class) { - Schema schemaObject = resolveSchemaFromType(schemaImplementation, components, jsonViewAnnotation, openapi31); + Schema schemaObject = resolveSchemaFromType(schemaImplementation, components, jsonViewAnnotation, openapi31, schemaAnnotation, arrayAnnotation); if (StringUtils.isNotBlank(schemaAnnotation.format())) { schemaObject.setFormat(schemaAnnotation.format()); } @@ -2237,6 +2253,22 @@ public Class contains() { return patch.$anchor(); } + @Override + public String $vocabulary() { + if (StringUtils.isNotBlank(master.$vocabulary()) || StringUtils.isBlank(patch.$vocabulary())) { + return master.$vocabulary(); + } + return patch.$vocabulary(); + } + + @Override + public String $dynamicAnchor() { + if (StringUtils.isNotBlank(master.$dynamicAnchor()) || StringUtils.isBlank(patch.$dynamicAnchor())) { + return master.$dynamicAnchor(); + } + return patch.$dynamicAnchor(); + } + @Override public String contentEncoding() { if (StringUtils.isNotBlank(master.contentEncoding()) || StringUtils.isBlank(patch.contentEncoding())) { 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 404520c6e4..740b56b572 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 @@ -1225,7 +1225,15 @@ protected void resolveResponseSchemaFromReturnType( ApiResponse opResponse = operation.getResponses().get(response.responseCode()); if (opResponse != null) { if (opResponse.getContent() != null) { - for (MediaType mediaType : opResponse.getContent().values()) { + for (String key : opResponse.getContent().keySet()) { + MediaType mediaType = opResponse.getContent().get(key); + Optional content = Arrays.stream(response.content()).filter(c -> c.mediaType().equals(key)).findFirst(); + if (content.isPresent()) { + Optional reResolvedSchema = AnnotationsUtils.getSchemaFromAnnotation(content.get().schema(), components, null, config.isOpenAPI31(), schema); + if (reResolvedSchema.isPresent()) { + schema = reResolvedSchema.get(); + } + } mediaType.schema(schema); } } else { 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 ded46a96e2..0db5b4ed0d 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 @@ -18,6 +18,7 @@ import io.swagger.v3.jaxrs2.resources.ResponseReturnTypeResource; import io.swagger.v3.jaxrs2.resources.SchemaPropertiesResource; import io.swagger.v3.jaxrs2.resources.SiblingsResource; +import io.swagger.v3.jaxrs2.resources.SiblingsResourceSimple; import io.swagger.v3.jaxrs2.resources.SingleExampleResource; import io.swagger.v3.jaxrs2.resources.BasicFieldsResource; import io.swagger.v3.jaxrs2.resources.BookStoreTicket2646; @@ -3469,4 +3470,42 @@ public void testSiblings() { " description: child\n"; SerializationMatchers.assertEqualsToYaml31(openAPI, yaml); } + + @Test + public void testSiblingsOnResource() { + Reader reader = new Reader(new SwaggerConfiguration().openAPI(new OpenAPI()).openAPI31(true)); + + OpenAPI openAPI = reader.read(SiblingsResourceSimple.class); + String yaml = "openapi: 3.1.0\n" + + "paths:\n" + + " /test:\n" + + " get:\n" + + " operationId: getCart\n" + + " responses:\n" + + " \"300\":\n" + + " description: aaa\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " $ref: '#/components/schemas/PetSimple'\n" + + " description: resource pet\n" + + " readOnly: true\n" + + " /test/impl:\n" + + " get:\n" + + " operationId: getCartImpl\n" + + " responses:\n" + + " \"300\":\n" + + " description: aaa\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " $ref: '#/components/schemas/PetSimple'\n" + + " description: resource pet\n" + + " readOnly: true\n" + + "components:\n" + + " schemas:\n" + + " PetSimple:\n" + + " description: Pet\n"; + SerializationMatchers.assertEqualsToYaml31(openAPI, yaml); + } } diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/resources/SiblingsResourceSimple.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/resources/SiblingsResourceSimple.java new file mode 100644 index 0000000000..cac69b3557 --- /dev/null +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/resources/SiblingsResourceSimple.java @@ -0,0 +1,51 @@ +package io.swagger.v3.jaxrs2.resources; + +import io.swagger.v3.jaxrs2.resources.siblings.Pet; +import io.swagger.v3.jaxrs2.resources.siblings.PetSimple; +import io.swagger.v3.oas.annotations.Operation; +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; + +@Path("/test") +public class SiblingsResourceSimple { + @GET + @Operation( + responses = { + @ApiResponse( + responseCode = "300", + description = "aaa", + useReturnTypeSchema = true, + content = { + @Content( + mediaType = "*/*", + schema = @Schema( + description = "resource pet", + accessMode = Schema.AccessMode.READ_ONLY)) + })}) + public PetSimple getCart() { + return null; + } + + @Path("/impl") + @GET + @Operation( + responses = { + @ApiResponse( + responseCode = "300", + description = "aaa", + content = { + @Content( + mediaType = "*/*", + schema = @Schema( + description = "resource pet", + implementation = PetSimple.class, + accessMode = Schema.AccessMode.READ_ONLY)) + })}) + public PetSimple getCartImpl() { + return null; + } +} diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/resources/siblings/PetSimple.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/resources/siblings/PetSimple.java new file mode 100644 index 0000000000..49e36b4eec --- /dev/null +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/resources/siblings/PetSimple.java @@ -0,0 +1,8 @@ +package io.swagger.v3.jaxrs2.resources.siblings; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "Pet") +public class PetSimple { + +} 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 dd41701ea2..c367870b67 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 @@ -153,13 +153,13 @@ public Schema specVersion(SpecVersion specVersion) { private String $anchor; /** - * @since 2.2.8 (OpenAPI 3.1.0) + * @since 2.2.14 (OpenAPI 3.1.0) */ @OpenAPI31 private String $vocabulary; /** - * @since 2.2.8 (OpenAPI 3.1.0) + * @since 2.2.14 (OpenAPI 3.1.0) */ @OpenAPI31 private String $dynamicAnchor;