Skip to content

Commit dd028ca

Browse files
committed
schema resolution options - Phase 4: granular schema resolution via @Schema.schemaResolution
1 parent 9f57589 commit dd028ca

File tree

6 files changed

+283
-13
lines changed

6 files changed

+283
-13
lines changed

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,14 @@ enum RequiredMode {
554554
NOT_REQUIRED;
555555
}
556556

557+
enum SchemaResolution {
558+
AUTO,
559+
DEFAULT,
560+
INLINE,
561+
ALL_OF,
562+
ALL_OF_REF;
563+
}
564+
557565
/**
558566
* Allows to specify the dependentRequired value
559567
**
@@ -616,4 +624,17 @@ enum RequiredMode {
616624
*/
617625
@OpenAPI31
618626
String _const() default "";
627+
628+
/**
629+
* Allows to specify the schema resolution mode for object schemas
630+
*
631+
* SchemaResolution.DEFAULT: bundled into components/schemas, $ref with no siblings
632+
* SchemaResolution.INLINE: inline schema, no $ref
633+
* SchemaResolution.ALL_OF: bundled into components/schemas, $ref and any context annotation resolution into allOf
634+
* SchemaResolution.ALL_OF_REF: bundled into components/schemas, $ref into allOf, context annotation resolution into root
635+
*
636+
* @return the schema resolution mode for this schema
637+
*
638+
*/
639+
SchemaResolution schemaResolution() default SchemaResolution.AUTO;
619640
}

modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -681,10 +681,10 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
681681
io.swagger.v3.oas.annotations.media.Schema.RequiredMode requiredMode = resolveRequiredMode(propResolvedSchemaAnnotation);
682682

683683
Annotation[] ctxAnnotation31 = null;
684-
684+
Schema.SchemaResolution resolvedSchemaResolution = AnnotationsUtils.resolveSchemaResolution(this.schemaResolution, ctxSchema);
685685
if (
686-
Schema.SchemaResolution.ALL_OF.equals(this.schemaResolution) ||
687-
Schema.SchemaResolution.ALL_OF_REF.equals(this.schemaResolution) ||
686+
Schema.SchemaResolution.ALL_OF.equals(resolvedSchemaResolution) ||
687+
Schema.SchemaResolution.ALL_OF_REF.equals(resolvedSchemaResolution) ||
688688
openapi31) {
689689
List<Annotation> ctxAnnotations31List = new ArrayList<>();
690690
if (annotations != null) {
@@ -712,8 +712,8 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
712712
.components(annotatedType.getComponents())
713713
.propertyName(propName);
714714
if (
715-
Schema.SchemaResolution.ALL_OF.equals(this.schemaResolution) ||
716-
Schema.SchemaResolution.ALL_OF_REF.equals(this.schemaResolution) ||
715+
Schema.SchemaResolution.ALL_OF.equals(resolvedSchemaResolution) ||
716+
Schema.SchemaResolution.ALL_OF_REF.equals(resolvedSchemaResolution) ||
717717
openapi31) {
718718
aType.ctxAnnotations(ctxAnnotation31);
719719
} else {
@@ -746,7 +746,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
746746
property = reResolvedProperty.get();
747747
}
748748

749-
} else if (Schema.SchemaResolution.ALL_OF.equals(this.schemaResolution) || Schema.SchemaResolution.ALL_OF_REF.equals(this.schemaResolution)) {
749+
} else if (Schema.SchemaResolution.ALL_OF.equals(resolvedSchemaResolution) || Schema.SchemaResolution.ALL_OF_REF.equals(resolvedSchemaResolution)) {
750750
Optional<Schema> reResolvedProperty = AnnotationsUtils.getSchemaFromAnnotation(ctxSchema, annotatedType.getComponents(), null, openapi31, null, schemaResolution, context);
751751
if (reResolvedProperty.isPresent()) {
752752
ctxProperty = reResolvedProperty.get();
@@ -795,20 +795,20 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
795795
}
796796

797797
if (context.getDefinedModels().containsKey(pName)) {
798-
if (Schema.SchemaResolution.INLINE.equals(this.schemaResolution)) {
798+
if (Schema.SchemaResolution.INLINE.equals(resolvedSchemaResolution)) {
799799
property = context.getDefinedModels().get(pName);
800-
} else if (Schema.SchemaResolution.ALL_OF.equals(this.schemaResolution) && ctxProperty != null) {
800+
} else if (Schema.SchemaResolution.ALL_OF.equals(resolvedSchemaResolution) && ctxProperty != null) {
801801
property = new Schema()
802802
.addAllOfItem(ctxProperty)
803803
.addAllOfItem(new Schema().$ref(constructRef(pName)));
804-
} else if (Schema.SchemaResolution.ALL_OF_REF.equals(this.schemaResolution) && ctxProperty != null) {
804+
} else if (Schema.SchemaResolution.ALL_OF_REF.equals(resolvedSchemaResolution) && ctxProperty != null) {
805805
property = ctxProperty.addAllOfItem(new Schema().$ref(constructRef(pName)));
806806
} else {
807807
property = new Schema().$ref(constructRef(pName));
808808
}
809809
property = clone(property);
810810
// TODO: why is this needed? is it not handled before?
811-
if (openapi31 || Schema.SchemaResolution.INLINE.equals(this.schemaResolution)) {
811+
if (openapi31 || Schema.SchemaResolution.INLINE.equals(resolvedSchemaResolution)) {
812812
Optional<Schema> reResolvedProperty = AnnotationsUtils.getSchemaFromAnnotation(ctxSchema, annotatedType.getComponents(), null, openapi31, property, this.schemaResolution, context);
813813
if (reResolvedProperty.isPresent()) {
814814
property = reResolvedProperty.get();
@@ -821,11 +821,11 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
821821
}
822822
} else if (property.get$ref() != null) {
823823
if (!openapi31) {
824-
if (Schema.SchemaResolution.ALL_OF.equals(this.schemaResolution) && ctxProperty != null) {
824+
if (Schema.SchemaResolution.ALL_OF.equals(resolvedSchemaResolution) && ctxProperty != null) {
825825
property = new Schema()
826826
.addAllOfItem(ctxProperty)
827827
.addAllOfItem(new Schema().$ref(StringUtils.isNotEmpty(property.get$ref()) ? property.get$ref() : property.getName()));
828-
} else if (Schema.SchemaResolution.ALL_OF_REF.equals(this.schemaResolution) && ctxProperty != null) {
828+
} else if (Schema.SchemaResolution.ALL_OF_REF.equals(resolvedSchemaResolution) && ctxProperty != null) {
829829
property = ctxProperty
830830
.addAllOfItem(new Schema().$ref(StringUtils.isNotEmpty(property.get$ref()) ? property.get$ref() : property.getName()));
831831
} else {
@@ -1040,12 +1040,14 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
10401040
context.defineModel(name, model, annotatedType, null);
10411041
}
10421042

1043+
Schema.SchemaResolution resolvedSchemaResolution = AnnotationsUtils.resolveSchemaResolution(this.schemaResolution, resolvedSchemaAnnotation);
1044+
10431045
if (model != null && annotatedType.isResolveAsRef() &&
10441046
(isComposedSchema || isObjectSchema(model)) &&
10451047
StringUtils.isNotBlank(model.getName()))
10461048
{
10471049
if (context.getDefinedModels().containsKey(model.getName())) {
1048-
if (!Schema.SchemaResolution.INLINE.equals(this.schemaResolution)) {
1050+
if (!Schema.SchemaResolution.INLINE.equals(resolvedSchemaResolution)) {
10491051
model = new Schema().$ref(constructRef(model.getName()));
10501052
}
10511053
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2658,6 +2658,15 @@ public Class<?> additionalPropertiesSchema() {
26582658
return patch.additionalPropertiesSchema();
26592659
}
26602660

2661+
/* We always want the patch to take precedence in schema resolution behavior */
2662+
@Override
2663+
public SchemaResolution schemaResolution() {
2664+
if (!patch.schemaResolution().equals(SchemaResolution.DEFAULT) || master.schemaResolution().equals(SchemaResolution.DEFAULT)) {
2665+
return patch.schemaResolution();
2666+
}
2667+
return master.schemaResolution();
2668+
}
2669+
26612670
};
26622671

26632672
return (io.swagger.v3.oas.annotations.media.Schema)schema;
@@ -2874,4 +2883,10 @@ public io.swagger.v3.oas.annotations.media.Schema[] prefixItems() {
28742883
return (io.swagger.v3.oas.annotations.media.ArraySchema)newArraySchema;
28752884
}
28762885

2886+
public static Schema.SchemaResolution resolveSchemaResolution(Schema.SchemaResolution globalSchemaResolution, io.swagger.v3.oas.annotations.media.Schema schemaAnnotation) {
2887+
if (schemaAnnotation != null && !io.swagger.v3.oas.annotations.media.Schema.SchemaResolution.AUTO.equals(schemaAnnotation.schemaResolution())) {
2888+
return Schema.SchemaResolution.valueOf(schemaAnnotation.schemaResolution().toString());
2889+
}
2890+
return globalSchemaResolution;
2891+
}
28772892
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package io.swagger.v3.jaxrs2;
2+
3+
import io.swagger.v3.core.converter.ModelConverters;
4+
import io.swagger.v3.jaxrs2.matchers.SerializationMatchers;
5+
import io.swagger.v3.jaxrs2.schemaResolution.SchemaResolutionAnnotatedResource;
6+
import io.swagger.v3.oas.integration.SwaggerConfiguration;
7+
import io.swagger.v3.oas.models.OpenAPI;
8+
import org.testng.annotations.Test;
9+
10+
public class SchemaResolutionAnnotationTest {
11+
12+
@Test
13+
public void testSchemaResolutionAnnotation() {
14+
ModelConverters.reset();
15+
Reader reader = new Reader(new SwaggerConfiguration().openAPI(new OpenAPI()));
16+
OpenAPI openAPI = reader.read(SchemaResolutionAnnotatedResource.class);
17+
String yaml = "openapi: 3.0.1\n" +
18+
"paths:\n" +
19+
" /test/inlineSchemaFirst:\n" +
20+
" get:\n" +
21+
" operationId: inlineSchemaFirst\n" +
22+
" responses:\n" +
23+
" default:\n" +
24+
" description: default response\n" +
25+
" content:\n" +
26+
" '*/*':\n" +
27+
" schema:\n" +
28+
" $ref: '#/components/schemas/InlineSchemaFirst'\n" +
29+
" /test/inlineSchemaSecond:\n" +
30+
" get:\n" +
31+
" operationId: inlineSchemaSecond\n" +
32+
" requestBody:\n" +
33+
" content:\n" +
34+
" '*/*':\n" +
35+
" schema:\n" +
36+
" type: object\n" +
37+
" properties:\n" +
38+
" foo:\n" +
39+
" type: string\n" +
40+
" propertySecond1:\n" +
41+
" $ref: '#/components/schemas/InlineSchemaPropertySecond'\n" +
42+
" property2:\n" +
43+
" $ref: '#/components/schemas/InlineSchemaPropertyFirst'\n" +
44+
" description: InlineSchemaSecond API\n" +
45+
" responses:\n" +
46+
" default:\n" +
47+
" description: default response\n" +
48+
" content:\n" +
49+
" '*/*':\n" +
50+
" schema:\n" +
51+
" $ref: '#/components/schemas/InlineSchemaSecond'\n" +
52+
"components:\n" +
53+
" schemas:\n" +
54+
" InlineSchemaFirst:\n" +
55+
" type: object\n" +
56+
" properties:\n" +
57+
" property1:\n" +
58+
" description: InlineSchemaFirst property 1\n" +
59+
" nullable: true\n" +
60+
" allOf:\n" +
61+
" - $ref: '#/components/schemas/InlineSchemaPropertyFirst'\n" +
62+
" property2:\n" +
63+
" type: object\n" +
64+
" properties:\n" +
65+
" bar:\n" +
66+
" type: string\n" +
67+
" description: property\n" +
68+
" example: example\n" +
69+
" InlineSchemaPropertyFirst:\n" +
70+
" type: object\n" +
71+
" properties:\n" +
72+
" bar:\n" +
73+
" type: string\n" +
74+
" description: property\n" +
75+
" example: example\n" +
76+
" InlineSchemaPropertySecond:\n" +
77+
" type: object\n" +
78+
" properties:\n" +
79+
" bar:\n" +
80+
" $ref: '#/components/schemas/InlineSchemaSimple'\n" +
81+
" description: propertysecond\n" +
82+
" nullable: true\n" +
83+
" example: examplesecond\n" +
84+
" InlineSchemaPropertySimple:\n" +
85+
" type: object\n" +
86+
" properties:\n" +
87+
" bar:\n" +
88+
" type: string\n" +
89+
" description: property\n" +
90+
" InlineSchemaSecond:\n" +
91+
" type: object\n" +
92+
" properties:\n" +
93+
" foo:\n" +
94+
" type: string\n" +
95+
" propertySecond1:\n" +
96+
" $ref: '#/components/schemas/InlineSchemaPropertySecond'\n" +
97+
" property2:\n" +
98+
" $ref: '#/components/schemas/InlineSchemaPropertyFirst'\n" +
99+
" description: InlineSchemaSecond API\n" +
100+
" InlineSchemaSimple:\n" +
101+
" type: object\n" +
102+
" properties:\n" +
103+
" property1:\n" +
104+
" type: object\n" +
105+
" properties:\n" +
106+
" bar:\n" +
107+
" type: string\n" +
108+
" description: property\n" +
109+
" property2:\n" +
110+
" description: property 2\n" +
111+
" example: example\n" +
112+
" allOf:\n" +
113+
" - $ref: '#/components/schemas/InlineSchemaPropertySimple'\n";
114+
SerializationMatchers.assertEqualsToYaml(openAPI, yaml);
115+
ModelConverters.reset();
116+
}
117+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package io.swagger.v3.jaxrs2.schemaResolution;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
5+
import javax.ws.rs.GET;
6+
import javax.ws.rs.Path;
7+
8+
@Path("test")
9+
public class SchemaResolutionAnnotatedResource {
10+
11+
@GET
12+
@Path("/inlineSchemaSecond")
13+
public InlineSchemaSecond inlineSchemaSecond(@Schema(description = "InlineSchemaSecond API", schemaResolution = Schema.SchemaResolution.INLINE) InlineSchemaSecond inlineSchemaSecond) {
14+
return null;
15+
}
16+
@GET
17+
@Path("/inlineSchemaFirst")
18+
public InlineSchemaFirst inlineSchemaFirst() {
19+
return null;
20+
}
21+
22+
23+
static class InlineSchemaFirst {
24+
25+
// public String foo;
26+
27+
@Schema(description = "InlineSchemaFirst property 1", nullable = true, schemaResolution = Schema.SchemaResolution.ALL_OF_REF)
28+
public InlineSchemaPropertyFirst property1;
29+
30+
31+
private InlineSchemaPropertyFirst property2;
32+
33+
@Schema(description = " InlineSchemaFirst property 2", example = "example 2", schemaResolution = Schema.SchemaResolution.INLINE)
34+
public InlineSchemaPropertyFirst getProperty2() {
35+
return null;
36+
}
37+
}
38+
39+
static class InlineSchemaSecond {
40+
41+
public String foo;
42+
43+
@Schema(description = "InlineSchemaSecond property 1", nullable = true)
44+
public InlineSchemaPropertySecond propertySecond1;
45+
46+
47+
private InlineSchemaPropertyFirst property2;
48+
49+
@Schema(description = "InlineSchemaSecond property 2", example = "InlineSchemaSecond example 2")
50+
public InlineSchemaPropertyFirst getProperty2() {
51+
return null;
52+
}
53+
}
54+
55+
@Schema(description = "property", example = "example")
56+
static class InlineSchemaPropertyFirst {
57+
public String bar;
58+
}
59+
60+
@Schema(description = "propertysecond", example = "examplesecond")
61+
static class InlineSchemaPropertySecond {
62+
public InlineSchemaSimple bar;
63+
}
64+
65+
static class InlineSchemaSimple {
66+
67+
@Schema(description = "property 1", schemaResolution = Schema.SchemaResolution.INLINE)
68+
public InlineSchemaPropertySimple property1;
69+
70+
71+
private InlineSchemaPropertySimple property2;
72+
73+
@Schema(description = "property 2", example = "example", schemaResolution = Schema.SchemaResolution.ALL_OF_REF)
74+
public InlineSchemaPropertySimple getProperty2() {
75+
return null;
76+
}
77+
}
78+
79+
@Schema(description = "property")
80+
static class InlineSchemaPropertySimple {
81+
public String bar;
82+
}
83+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package io.swagger.v3.jaxrs2.schemaResolution;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
5+
import javax.ws.rs.GET;
6+
import javax.ws.rs.Path;
7+
8+
@Path("test")
9+
public class SchemaResolutionAnnotatedSimpleResource {
10+
11+
@GET
12+
@Path("/inlineSchemaFirst")
13+
public InlineSchemaFirst inlineSchemaFirst() {
14+
return null;
15+
}
16+
17+
18+
static class InlineSchemaFirst {
19+
20+
private InlineSchemaPropertyFirst property2;
21+
22+
@Schema(description = " InlineSchemaFirst property 2", example = "example 2", schemaResolution = Schema.SchemaResolution.INLINE)
23+
public InlineSchemaPropertyFirst getProperty2() {
24+
return null;
25+
}
26+
}
27+
28+
@Schema(description = "property", example = "example")
29+
static class InlineSchemaPropertyFirst {
30+
// public String bar;
31+
}
32+
}

0 commit comments

Comments
 (0)