Skip to content

Commit bdf8a76

Browse files
committed
refs #3633, #2891, #3433, #3547 - properties and additionalProperties in annotations
1 parent a60b584 commit bdf8a76

File tree

11 files changed

+751
-19
lines changed

11 files changed

+751
-19
lines changed

modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/media/Content.java

+16
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,22 @@
6262
**/
6363
Schema schema() default @Schema();
6464

65+
/**
66+
* The schema properties defined for schema provided in @Schema
67+
*
68+
* @since 2.2.0
69+
* @return the schema properties
70+
*/
71+
SchemaProperty[] schemaProperties() default {};
72+
73+
/**
74+
* The schema properties defined for schema provided in @Schema
75+
*
76+
* @since 2.2.0
77+
* @return the schema properties
78+
*/
79+
Schema additionalPropertiesSchema() default @Schema();
80+
6581
/**
6682
* The schema of the array that defines the type used for the content.
6783
*

modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/media/Schema.java

+19-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.lang.annotation.Retention;
2424
import java.lang.annotation.RetentionPolicy;
2525
import java.lang.annotation.Target;
26-
import java.nio.file.AccessMode;
2726

2827
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
2928
import static java.lang.annotation.ElementType.TYPE;
@@ -329,10 +328,29 @@
329328
*/
330329
Extension[] extensions() default {};
331330

331+
/**
332+
* Allows to specify the additionalProperties value
333+
*
334+
* AdditionalPropertiesValue.TRUE: set to TRUE
335+
* AdditionalPropertiesValue.FALSE: set to FALSE
336+
* AdditionalPropertiesValue.USE_ADDITIONAL_PROPERTIES_ANNOTATION: resolve from @Content.additionalPropertiesSchema
337+
*
338+
* @since 2.2.0
339+
* @return the accessMode for this schema (property)
340+
*
341+
*/
342+
AdditionalPropertiesValue additionalProperties() default AdditionalPropertiesValue.USE_ADDITIONAL_PROPERTIES_ANNOTATION;
343+
332344
enum AccessMode {
333345
AUTO,
334346
READ_ONLY,
335347
WRITE_ONLY,
336348
READ_WRITE;
337349
}
350+
351+
enum AdditionalPropertiesValue {
352+
TRUE,
353+
FALSE,
354+
USE_ADDITIONAL_PROPERTIES_ANNOTATION;
355+
}
338356
}

modules/swagger-core/src/main/java/io/swagger/v3/core/util/AnnotationsUtils.java

+59-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import io.swagger.v3.oas.annotations.links.LinkParameter;
1414
import io.swagger.v3.oas.annotations.media.DiscriminatorMapping;
1515
import io.swagger.v3.oas.annotations.media.ExampleObject;
16+
import io.swagger.v3.oas.annotations.media.SchemaProperty;
1617
import io.swagger.v3.oas.models.Components;
1718
import io.swagger.v3.oas.models.ExternalDocumentation;
1819
import io.swagger.v3.oas.models.examples.Example;
@@ -97,6 +98,7 @@ public static boolean hasSchemaAnnotation(io.swagger.v3.oas.annotations.media.Sc
9798
&& schema.extensions().length == 0
9899
&& !schema.hidden()
99100
&& !schema.enumAsRef()
101+
&& schema.additionalProperties().equals(io.swagger.v3.oas.annotations.media.Schema.AdditionalPropertiesValue.USE_ADDITIONAL_PROPERTIES_ANNOTATION)
100102
) {
101103
return false;
102104
}
@@ -284,6 +286,10 @@ else if (thisSchema == null || thatSchema == null) {
284286
if (!Arrays.equals(thisSchema.extensions(), thatSchema.extensions())) {
285287
return false;
286288
}
289+
if (!thisSchema.additionalProperties().equals(thatSchema.additionalProperties())) {
290+
return false;
291+
}
292+
287293
return true;
288294
}
289295

@@ -539,7 +545,11 @@ public static Optional<Schema> getSchemaFromAnnotation(io.swagger.v3.oas.annotat
539545
((ComposedSchema) schemaObject).addAllOfItem(allOfSchemaObject);
540546
}
541547
}
542-
548+
if (schema.additionalProperties().equals(io.swagger.v3.oas.annotations.media.Schema.AdditionalPropertiesValue.TRUE)) {
549+
schemaObject.additionalProperties(true);
550+
} else if (schema.additionalProperties().equals(io.swagger.v3.oas.annotations.media.Schema.AdditionalPropertiesValue.FALSE)) {
551+
schemaObject.additionalProperties(false);
552+
}
543553

544554
return Optional.of(schemaObject);
545555
}
@@ -1044,6 +1054,46 @@ public static Optional<Content> getContent(io.swagger.v3.oas.annotations.media.C
10441054
MediaType mediaType = new MediaType();
10451055
if (components != null) {
10461056
getSchema(annotationContent, components, jsonViewAnnotation).ifPresent(mediaType::setSchema);
1057+
if (annotationContent.schemaProperties().length > 0) {
1058+
if (mediaType.getSchema() == null) {
1059+
mediaType.schema(new Schema<Object>().type("object"));
1060+
}
1061+
Schema oSchema = mediaType.getSchema();
1062+
for (SchemaProperty sp: annotationContent.schemaProperties()) {
1063+
Class<?> schemaImplementation = sp.schema().implementation();
1064+
boolean isArray = false;
1065+
if (schemaImplementation == Void.class) {
1066+
schemaImplementation = sp.array().schema().implementation();
1067+
if (schemaImplementation != Void.class) {
1068+
isArray = true;
1069+
}
1070+
}
1071+
getSchema(sp.schema(), sp.array(), isArray, schemaImplementation, components, jsonViewAnnotation)
1072+
.ifPresent(s -> {
1073+
if ("array".equals(oSchema.getType())) {
1074+
oSchema.getItems().addProperty(sp.name(), s);
1075+
} else {
1076+
oSchema.addProperty(sp.name(), s);
1077+
}
1078+
});
1079+
1080+
}
1081+
}
1082+
if (
1083+
hasSchemaAnnotation(annotationContent.additionalPropertiesSchema()) &&
1084+
mediaType.getSchema() != null &&
1085+
!Boolean.TRUE.equals(mediaType.getSchema().getAdditionalProperties()) &&
1086+
!Boolean.FALSE.equals(mediaType.getSchema().getAdditionalProperties())) {
1087+
getSchemaFromAnnotation(annotationContent.additionalPropertiesSchema(), components, jsonViewAnnotation)
1088+
.ifPresent(s -> {
1089+
if ("array".equals(mediaType.getSchema().getType())) {
1090+
mediaType.getSchema().getItems().additionalProperties(s);
1091+
} else {
1092+
mediaType.getSchema().additionalProperties(s);
1093+
}
1094+
}
1095+
);
1096+
}
10471097
} else {
10481098
mediaType.setSchema(schema);
10491099
}
@@ -1726,6 +1776,14 @@ public Extension[] extensions() {
17261776
public Class<? extends Annotation> annotationType() {
17271777
return io.swagger.v3.oas.annotations.media.Schema.class;
17281778
}
1779+
1780+
@Override
1781+
public AdditionalPropertiesValue additionalProperties() {
1782+
if (!master.additionalProperties().equals(AdditionalPropertiesValue.USE_ADDITIONAL_PROPERTIES_ANNOTATION) || patch.additionalProperties().equals(AdditionalPropertiesValue.USE_ADDITIONAL_PROPERTIES_ANNOTATION)) {
1783+
return master.additionalProperties();
1784+
}
1785+
return patch.additionalProperties();
1786+
}
17291787
};
17301788

17311789
return (io.swagger.v3.oas.annotations.media.Schema)schema;

modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/ReaderTest.java

+151
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import io.swagger.v3.core.model.ApiDescription;
1414
import io.swagger.v3.core.util.PrimitiveType;
1515
import io.swagger.v3.jaxrs2.matchers.SerializationMatchers;
16+
import io.swagger.v3.jaxrs2.resources.SchemaPropertiesResource;
1617
import io.swagger.v3.jaxrs2.resources.SingleExampleResource;
1718
import io.swagger.v3.jaxrs2.resources.BasicFieldsResource;
1819
import io.swagger.v3.jaxrs2.resources.BookStoreTicket2646;
@@ -2811,4 +2812,154 @@ public void testTicket3731() {
28112812
openAPI = reader.read(Ticket3731BisResource.class);
28122813
SerializationMatchers.assertEqualsToYaml(openAPI, yaml);
28132814
}
2815+
2816+
@Test(description = "Test SchemaProperties and additionalProperties annotations")
2817+
public void testSchemaProperties() {
2818+
Reader reader = new Reader(new OpenAPI());
2819+
2820+
OpenAPI openAPI = reader.read(SchemaPropertiesResource.class);
2821+
String yaml = "openapi: 3.0.1\n" +
2822+
"paths:\n" +
2823+
" /:\n" +
2824+
" get:\n" +
2825+
" summary: Simple get operation\n" +
2826+
" description: Defines a simple get operation with no inputs and a complex output\n" +
2827+
" object\n" +
2828+
" operationId: getWithPayloadResponse\n" +
2829+
" responses:\n" +
2830+
" \"200\":\n" +
2831+
" description: voila!\n" +
2832+
" content:\n" +
2833+
" application/json:\n" +
2834+
" schema:\n" +
2835+
" type: object\n" +
2836+
" properties:\n" +
2837+
" foo:\n" +
2838+
" maximum: 1\n" +
2839+
" type: integer\n" +
2840+
" default:\n" +
2841+
" description: boo\n" +
2842+
" content:\n" +
2843+
" application/json:\n" +
2844+
" schema:\n" +
2845+
" maxProperties: 3\n" +
2846+
" type: object\n" +
2847+
" properties:\n" +
2848+
" foo:\n" +
2849+
" maximum: 1\n" +
2850+
" type: integer\n" +
2851+
" description: various properties\n" +
2852+
" \"400\":\n" +
2853+
" description: additionalProperties schema\n" +
2854+
" content:\n" +
2855+
" application/json:\n" +
2856+
" schema:\n" +
2857+
" maxProperties: 2\n" +
2858+
" type: object\n" +
2859+
" additionalProperties:\n" +
2860+
" type: string\n" +
2861+
" \"401\":\n" +
2862+
" description: additionalProperties boolean\n" +
2863+
" content:\n" +
2864+
" application/json:\n" +
2865+
" schema:\n" +
2866+
" maxProperties: 2\n" +
2867+
" type: object\n" +
2868+
" additionalProperties: false\n" +
2869+
" deprecated: true\n" +
2870+
" /one:\n" +
2871+
" get:\n" +
2872+
" operationId: requestBodySchemaPropertyNoSchema\n" +
2873+
" requestBody:\n" +
2874+
" content:\n" +
2875+
" application/yaml:\n" +
2876+
" schema:\n" +
2877+
" type: object\n" +
2878+
" properties:\n" +
2879+
" foo:\n" +
2880+
" type: string\n" +
2881+
" responses:\n" +
2882+
" default:\n" +
2883+
" description: default response\n" +
2884+
" content:\n" +
2885+
" application/json:\n" +
2886+
" schema:\n" +
2887+
" $ref: '#/components/schemas/MultipleBaseBean'\n" +
2888+
" /two:\n" +
2889+
" get:\n" +
2890+
" operationId: requestBodySchemaPropertySchema\n" +
2891+
" requestBody:\n" +
2892+
" content:\n" +
2893+
" application/yaml:\n" +
2894+
" schema:\n" +
2895+
" required:\n" +
2896+
" - foo\n" +
2897+
" type: object\n" +
2898+
" properties:\n" +
2899+
" foo:\n" +
2900+
" type: string\n" +
2901+
" responses:\n" +
2902+
" default:\n" +
2903+
" description: default response\n" +
2904+
" content:\n" +
2905+
" application/json:\n" +
2906+
" schema:\n" +
2907+
" $ref: '#/components/schemas/MultipleBaseBean'\n" +
2908+
" /three:\n" +
2909+
" get:\n" +
2910+
" operationId: requestBodySchemaPropertySchemaArray\n" +
2911+
" requestBody:\n" +
2912+
" content:\n" +
2913+
" application/yaml:\n" +
2914+
" schema:\n" +
2915+
" type: array\n" +
2916+
" items:\n" +
2917+
" required:\n" +
2918+
" - foo\n" +
2919+
" type: object\n" +
2920+
" properties:\n" +
2921+
" foo:\n" +
2922+
" type: string\n" +
2923+
" responses:\n" +
2924+
" default:\n" +
2925+
" description: default response\n" +
2926+
" content:\n" +
2927+
" application/json:\n" +
2928+
" schema:\n" +
2929+
" $ref: '#/components/schemas/MultipleBaseBean'\n" +
2930+
"components:\n" +
2931+
" schemas:\n" +
2932+
" MultipleBaseBean:\n" +
2933+
" type: object\n" +
2934+
" properties:\n" +
2935+
" beanType:\n" +
2936+
" type: string\n" +
2937+
" a:\n" +
2938+
" type: integer\n" +
2939+
" format: int32\n" +
2940+
" b:\n" +
2941+
" type: string\n" +
2942+
" description: MultipleBaseBean\n" +
2943+
" MultipleSub1Bean:\n" +
2944+
" type: object\n" +
2945+
" description: MultipleSub1Bean\n" +
2946+
" allOf:\n" +
2947+
" - $ref: '#/components/schemas/MultipleBaseBean'\n" +
2948+
" - type: object\n" +
2949+
" properties:\n" +
2950+
" c:\n" +
2951+
" type: integer\n" +
2952+
" format: int32\n" +
2953+
" MultipleSub2Bean:\n" +
2954+
" type: object\n" +
2955+
" description: MultipleSub2Bean\n" +
2956+
" allOf:\n" +
2957+
" - $ref: '#/components/schemas/MultipleBaseBean'\n" +
2958+
" - type: object\n" +
2959+
" properties:\n" +
2960+
" d:\n" +
2961+
" type: integer\n" +
2962+
" format: int32\n";
2963+
SerializationMatchers.assertEqualsToYaml(openAPI, yaml);
2964+
}
28142965
}

0 commit comments

Comments
 (0)