Skip to content

Commit 290c78d

Browse files
committed
schema resolution options - Phase 1: global inline
1 parent b1a38d6 commit 290c78d

File tree

17 files changed

+710
-19
lines changed

17 files changed

+710
-19
lines changed

modules/swagger-core/src/main/java/io/swagger/v3/core/converter/ModelConverters.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ public ModelConverters(boolean openapi31) {
4242
}
4343
}
4444

45+
public ModelConverters(boolean openapi31, Schema.SchemaResolution schemaResolution) {
46+
converters = new CopyOnWriteArrayList<>();
47+
if (openapi31) {
48+
converters.add(new ModelResolver(Json31.mapper()).openapi31(true));
49+
} else {
50+
converters.add(new ModelResolver(Json.mapper()).schemaResolution(schemaResolution));
51+
}
52+
}
53+
4554
public Set<String> getSkippedPackages() {
4655
return skippedPackages;
4756
}
@@ -61,6 +70,30 @@ public static ModelConverters getInstance(boolean openapi31) {
6170
return SINGLETON;
6271
}
6372

73+
public static void reset() {
74+
synchronized (ModelConverters.class) {
75+
SINGLETON = null;
76+
SINGLETON31 = null;
77+
}
78+
}
79+
80+
public static ModelConverters getInstance(boolean openapi31, Schema.SchemaResolution schemaResolution) {
81+
synchronized (ModelConverters.class) {
82+
if (openapi31) {
83+
if (SINGLETON31 == null) {
84+
SINGLETON31 = new ModelConverters(openapi31);
85+
init(SINGLETON31);
86+
}
87+
return SINGLETON31;
88+
}
89+
if (SINGLETON == null) {
90+
SINGLETON = new ModelConverters(openapi31, schemaResolution);
91+
init(SINGLETON);
92+
}
93+
return SINGLETON;
94+
}
95+
}
96+
6497
private static void init(ModelConverters converter) {
6598
converter.addPackageToSkip("java.lang");
6699
converter.addPackageToSkip("groovy.lang");

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

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ public class ModelResolver extends AbstractModelConverter implements ModelConver
125125

126126
private boolean openapi31;
127127

128+
private Schema.SchemaResolution schemaResolution = Schema.SchemaResolution.DEFAULT;
129+
128130
public ModelResolver(ObjectMapper mapper) {
129131
super(mapper);
130132
}
@@ -725,7 +727,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
725727
property = context.resolve(aType);
726728
property = clone(property);
727729
if (openapi31) {
728-
Optional<Schema> reResolvedProperty = AnnotationsUtils.getSchemaFromAnnotation(ctxSchema, annotatedType.getComponents(), null, openapi31, property, context);
730+
Optional<Schema> reResolvedProperty = AnnotationsUtils.getSchemaFromAnnotation(ctxSchema, annotatedType.getComponents(), null, openapi31, property, schemaResolution, context);
729731
if (reResolvedProperty.isPresent()) {
730732
property = reResolvedProperty.get();
731733
}
@@ -773,10 +775,14 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
773775
}
774776

775777
if (context.getDefinedModels().containsKey(pName)) {
776-
property = new Schema().$ref(constructRef(pName));
778+
if (Schema.SchemaResolution.INLINE.equals(this.schemaResolution)) {
779+
property = context.getDefinedModels().get(pName);
780+
} else {
781+
property = new Schema().$ref(constructRef(pName));
782+
}
777783
property = clone(property);
778-
if (openapi31) {
779-
Optional<Schema> reResolvedProperty = AnnotationsUtils.getSchemaFromAnnotation(ctxSchema, annotatedType.getComponents(), null, openapi31, property, context);
784+
if (openapi31 || Schema.SchemaResolution.INLINE.equals(this.schemaResolution)) {
785+
Optional<Schema> reResolvedProperty = AnnotationsUtils.getSchemaFromAnnotation(ctxSchema, annotatedType.getComponents(), null, openapi31, property, this.schemaResolution, context);
780786
if (reResolvedProperty.isPresent()) {
781787
property = reResolvedProperty.get();
782788
}
@@ -1000,7 +1006,9 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
10001006
StringUtils.isNotBlank(model.getName()))
10011007
{
10021008
if (context.getDefinedModels().containsKey(model.getName())) {
1003-
model = new Schema().$ref(constructRef(model.getName()));
1009+
if (!Schema.SchemaResolution.INLINE.equals(this.schemaResolution)) {
1010+
model = new Schema().$ref(constructRef(model.getName()));
1011+
}
10041012
}
10051013
} else if (model != null && model.get$ref() != null) {
10061014
model = new Schema().$ref(StringUtils.isNotEmpty(model.get$ref()) ? model.get$ref() : model.getName());
@@ -1255,6 +1263,18 @@ private void handleUnwrapped(List<Schema> props, Schema innerModel, String prefi
12551263
}
12561264
}
12571265

1266+
public Schema.SchemaResolution getSchemaResolution() {
1267+
return schemaResolution;
1268+
}
1269+
1270+
public void setSchemaResolution(Schema.SchemaResolution schemaResolution) {
1271+
this.schemaResolution = schemaResolution;
1272+
}
1273+
1274+
public ModelResolver schemaResolution(Schema.SchemaResolution schemaResolution) {
1275+
setSchemaResolution(schemaResolution);
1276+
return this;
1277+
}
12581278

12591279
private class GeneratorWrapper {
12601280

@@ -1870,7 +1890,7 @@ protected Map<String, Schema> resolveDependentSchemas(JavaType a, Annotation[] a
18701890
}
18711891
Annotation[] propAnnotations = new Annotation[]{dependentSchemaAnnotation.schema(), dependentSchemaAnnotation.array()};
18721892
Schema existingSchema = null;
1873-
Optional<Schema> resolvedPropSchema = AnnotationsUtils.getSchemaFromAnnotation(dependentSchemaAnnotation.schema(), components, jsonViewAnnotation, openapi31, null, context);
1893+
Optional<Schema> resolvedPropSchema = AnnotationsUtils.getSchemaFromAnnotation(dependentSchemaAnnotation.schema(), components, jsonViewAnnotation, openapi31, null, Schema.SchemaResolution.DEFAULT, context);
18741894
if (resolvedPropSchema.isPresent()) {
18751895
existingSchema = resolvedPropSchema.get();
18761896
dependentSchemas.put(name, existingSchema);

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

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -587,24 +587,40 @@ public static Optional<Schema> getSchemaFromAnnotation(
587587
boolean openapi31,
588588
Schema existingSchema,
589589
ModelConverterContext context) {
590+
return getSchemaFromAnnotation(schema, components, jsonViewAnnotation, openapi31, existingSchema, Schema.SchemaResolution.DEFAULT, null);
591+
}
592+
public static Optional<Schema> getSchemaFromAnnotation(
593+
io.swagger.v3.oas.annotations.media.Schema schema,
594+
Components components,
595+
JsonView jsonViewAnnotation,
596+
boolean openapi31,
597+
Schema existingSchema,
598+
Schema.SchemaResolution schemaResolution,
599+
ModelConverterContext context) {
590600
if (schema == null || !hasSchemaAnnotation(schema)) {
591-
if (existingSchema == null || !openapi31) {
601+
if (existingSchema == null || (!openapi31 && Schema.SchemaResolution.DEFAULT.equals(schemaResolution))) {
592602
return Optional.empty();
593-
} else if (existingSchema != null && openapi31) {
603+
} else if (existingSchema != null && (openapi31 || Schema.SchemaResolution.INLINE.equals(schemaResolution))) {
594604
return Optional.of(existingSchema);
595605
}
596606
}
597607
Schema schemaObject = null;
598608
if (!openapi31) {
599609
if (existingSchema != null) {
600-
return Optional.of(existingSchema);
610+
if (!Schema.SchemaResolution.DEFAULT.equals(schemaResolution)) {
611+
schemaObject = existingSchema;
612+
} else {
613+
return Optional.of(existingSchema);
614+
}
601615
}
602-
if (schema.oneOf().length > 0 ||
603-
schema.allOf().length > 0 ||
604-
schema.anyOf().length > 0) {
605-
schemaObject = new ComposedSchema();
606-
} else {
607-
schemaObject = new Schema();
616+
if (Schema.SchemaResolution.DEFAULT.equals(schemaResolution)) {
617+
if (schema != null && (schema.oneOf().length > 0 ||
618+
schema.allOf().length > 0 ||
619+
schema.anyOf().length > 0)) {
620+
schemaObject = new ComposedSchema();
621+
} else {
622+
schemaObject = new Schema();
623+
}
608624
}
609625
} else {
610626
if (existingSchema == null) {
@@ -613,6 +629,9 @@ public static Optional<Schema> getSchemaFromAnnotation(
613629
schemaObject = existingSchema;
614630
}
615631
}
632+
if (schema == null) {
633+
return Optional.of(schemaObject);
634+
}
616635
if (StringUtils.isNotBlank(schema.description())) {
617636
schemaObject.setDescription(schema.description());
618637
}
@@ -1956,11 +1975,15 @@ public static void updateAnnotation(Class<?> clazz, io.swagger.v3.oas.annotation
19561975

19571976
}
19581977

1978+
public static Annotation mergeSchemaAnnotations(
1979+
Annotation[] ctxAnnotations, JavaType type) {
1980+
return mergeSchemaAnnotations(ctxAnnotations, type, false);
1981+
}
19591982
/*
19601983
* returns null if no annotations, otherwise either ArraySchema or Schema
19611984
*/
19621985
public static Annotation mergeSchemaAnnotations(
1963-
Annotation[] ctxAnnotations, JavaType type) {
1986+
Annotation[] ctxAnnotations, JavaType type, boolean contextWins) {
19641987
// get type array and schema
19651988
io.swagger.v3.oas.annotations.media.Schema tS = type.getRawClass().getDeclaredAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
19661989
if (!hasSchemaAnnotation(tS)) {
@@ -2020,6 +2043,9 @@ else if (tS != null && tA == null && cS == null && cA != null) {
20202043
}
20212044

20222045
else if (tA != null && cA != null) {
2046+
if (contextWins) {
2047+
return mergeArraySchemaAnnotations(tA, cA);
2048+
}
20232049
return mergeArraySchemaAnnotations(cA, tA);
20242050
}
20252051

@@ -2028,6 +2054,9 @@ else if (tS != null && cS == null && cA == null) {
20282054
}
20292055

20302056
else if (tS != null && cS != null) {
2057+
if (contextWins) {
2058+
return mergeSchemaAnnotations(cS, tS);
2059+
}
20312060
return mergeSchemaAnnotations(tS, cS);
20322061
}
20332062

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package io.swagger.v3.core.resolving;
2+
3+
import io.swagger.v3.core.converter.AnnotatedType;
4+
import io.swagger.v3.core.converter.ModelConverterContextImpl;
5+
import io.swagger.v3.core.jackson.ModelResolver;
6+
import io.swagger.v3.core.matchers.SerializationMatchers;
7+
import io.swagger.v3.oas.annotations.media.Schema;
8+
import org.testng.annotations.Test;
9+
10+
public class InlineResolvingTest extends SwaggerTestBase{
11+
12+
@Test
13+
public void testInlineResolving() {
14+
15+
final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(false).schemaResolution(io.swagger.v3.oas.models.media.Schema.SchemaResolution.INLINE);
16+
final ModelConverterContextImpl c = new ModelConverterContextImpl(modelResolver);
17+
// ModelConverters c = ModelConverters.getInstance(false, io.swagger.v3.oas.models.media.Schema.SchemaResolution.INLINE);
18+
c.resolve(new AnnotatedType(InlineSchemaFirst.class));
19+
20+
String expectedYaml = "InlineSchemaFirst:\n" +
21+
" type: object\n" +
22+
" properties:\n" +
23+
" property1:\n" +
24+
" type: object\n" +
25+
" description: InlineSchemaFirst property 1\n" +
26+
" nullable: true\n" +
27+
" example: example\n" +
28+
" property2:\n" +
29+
" type: object\n" +
30+
" description: ' InlineSchemaFirst property 2'\n" +
31+
" example: example 2\n" +
32+
"InlineSchemaPropertyFirst:\n" +
33+
" type: object\n" +
34+
" description: property\n" +
35+
" example: example\n";
36+
37+
SerializationMatchers.assertEqualsToYaml(c.getDefinedModels(), expectedYaml);
38+
// stringSchemaMap = c.readAll(InlineSchemaSecond.class);
39+
c.resolve(new AnnotatedType(InlineSchemaSecond.class));
40+
expectedYaml = "InlineSchemaFirst:\n" +
41+
" type: object\n" +
42+
" properties:\n" +
43+
" property1:\n" +
44+
" type: object\n" +
45+
" description: InlineSchemaFirst property 1\n" +
46+
" nullable: true\n" +
47+
" example: example\n" +
48+
" property2:\n" +
49+
" type: object\n" +
50+
" description: ' InlineSchemaFirst property 2'\n" +
51+
" example: example 2\n" +
52+
"InlineSchemaPropertyFirst:\n" +
53+
" type: object\n" +
54+
" description: property\n" +
55+
" example: example\n" +
56+
"InlineSchemaPropertySecond:\n" +
57+
" type: object\n" +
58+
" properties:\n" +
59+
" bar:\n" +
60+
" type: object\n" +
61+
" properties:\n" +
62+
" property1:\n" +
63+
" type: object\n" +
64+
" description: property 1\n" +
65+
" property2:\n" +
66+
" type: object\n" +
67+
" description: property 2\n" +
68+
" example: example\n" +
69+
" description: propertysecond\n" +
70+
" nullable: true\n" +
71+
" example: examplesecond\n" +
72+
"InlineSchemaPropertySimple:\n" +
73+
" type: object\n" +
74+
" description: property\n" +
75+
" example: example\n" +
76+
"InlineSchemaSecond:\n" +
77+
" type: object\n" +
78+
" properties:\n" +
79+
" propertySecond1:\n" +
80+
" type: object\n" +
81+
" properties:\n" +
82+
" bar:\n" +
83+
" type: object\n" +
84+
" properties:\n" +
85+
" property1:\n" +
86+
" type: object\n" +
87+
" description: property 1\n" +
88+
" property2:\n" +
89+
" type: object\n" +
90+
" description: property 2\n" +
91+
" example: example\n" +
92+
" description: InlineSchemaSecond property 1\n" +
93+
" nullable: true\n" +
94+
" example: examplesecond\n" +
95+
" property2:\n" +
96+
" type: object\n" +
97+
" description: InlineSchemaSecond property 2\n" +
98+
" example: InlineSchemaSecond example 2\n" +
99+
"InlineSchemaSimple:\n" +
100+
" type: object\n" +
101+
" properties:\n" +
102+
" property1:\n" +
103+
" type: object\n" +
104+
" description: property 1\n" +
105+
" property2:\n" +
106+
" type: object\n" +
107+
" description: property 2\n" +
108+
" example: example\n";
109+
SerializationMatchers.assertEqualsToYaml(c.getDefinedModels(), expectedYaml);
110+
}
111+
112+
static class InlineSchemaFirst {
113+
114+
// public String foo;
115+
116+
@Schema(description = "InlineSchemaFirst property 1", nullable = true)
117+
public InlineSchemaPropertyFirst property1;
118+
119+
120+
private InlineSchemaPropertyFirst property2;
121+
122+
@Schema(description = " InlineSchemaFirst property 2", example = "example 2")
123+
public InlineSchemaPropertyFirst getProperty2() {
124+
return null;
125+
}
126+
}
127+
128+
static class InlineSchemaSecond {
129+
130+
// public String foo;
131+
132+
@Schema(description = "InlineSchemaSecond property 1", nullable = true)
133+
public InlineSchemaPropertySecond propertySecond1;
134+
135+
136+
private InlineSchemaPropertyFirst property2;
137+
138+
@Schema(description = "InlineSchemaSecond property 2", example = "InlineSchemaSecond example 2")
139+
public InlineSchemaPropertyFirst getProperty2() {
140+
return null;
141+
}
142+
}
143+
144+
@Schema(description = "property", example = "example")
145+
static class InlineSchemaPropertyFirst {
146+
// public String bar;
147+
}
148+
149+
@Schema(description = "propertysecond", example = "examplesecond")
150+
static class InlineSchemaPropertySecond {
151+
public InlineSchemaSimple bar;
152+
}
153+
154+
static class InlineSchemaSimple {
155+
156+
@Schema(description = "property 1")
157+
public InlineSchemaPropertySimple property1;
158+
159+
160+
private InlineSchemaPropertySimple property2;
161+
162+
@Schema(description = "property 2", example = "example")
163+
public InlineSchemaPropertySimple getProperty2() {
164+
return null;
165+
}
166+
}
167+
168+
@Schema(description = "property")
169+
static class InlineSchemaPropertySimple {
170+
// public String bar;
171+
}
172+
}

0 commit comments

Comments
 (0)