Skip to content

Commit 71ee222

Browse files
fix: correct sibling logic when reading type from schema annotation
1 parent 052b47e commit 71ee222

10 files changed

Lines changed: 270 additions & 46 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -808,7 +808,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
808808
});
809809

810810
boolean areSiblingsAllowed = AnnotationsUtils.areSiblingsAllowed(resolvedSchemaResolution, openapi31);
811-
aType = AnnotationsUtils.addTypeWhenSiblingsAllowed(aType, ctxSchema, areSiblingsAllowed);
811+
aType = AnnotationsUtils.addTypeWhenSiblingsAllowed(aType, ctxSchema, openapi31, areSiblingsAllowed);
812812
property = context.resolve(aType);
813813
property = clone(property);
814814
Schema ctxProperty = null;

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

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3125,14 +3125,32 @@ public static boolean areSiblingsAllowed(Schema.SchemaResolution resolvedSchemaR
31253125
return Schema.SchemaResolution.ALL_OF.equals(resolvedSchemaResolution) || Schema.SchemaResolution.ALL_OF_REF.equals(resolvedSchemaResolution) || openapi31;
31263126
}
31273127

3128-
public static AnnotatedType addTypeWhenSiblingsAllowed(AnnotatedType aType, io.swagger.v3.oas.annotations.media.Schema ctxSchema, boolean areSiblingsAllowed) {
3128+
public static AnnotatedType addTypeWhenSiblingsAllowed(AnnotatedType aType,
3129+
io.swagger.v3.oas.annotations.media.Schema ctxSchema,
3130+
boolean openapi31,
3131+
boolean areSiblingsAllowed) {
31293132
if (areSiblingsAllowed && ctxSchema != null) {
31303133
if (!Void.class.equals(ctxSchema.implementation())) {
31313134
aType.setType(ctxSchema.implementation());
3132-
} else if (StringUtils.isNotBlank(ctxSchema.type())) {
3133-
aType.setType(ctxSchema.type().getClass());
3135+
} else if (openapi31) {
3136+
if (isOas31SingleTypeIncludingNull(ctxSchema) && Arrays.asList(ctxSchema.types()).contains(STRING_TYPE)) {
3137+
aType.setType(String.class);
3138+
}
3139+
} else if(StringUtils.isNotBlank(ctxSchema.type()) && STRING_TYPE.equals(ctxSchema.type())) {
3140+
aType.setType(String.class);
31343141
}
31353142
}
31363143
return aType;
31373144
}
3145+
3146+
/**
3147+
* Returns whether the schema annotation defines a single type in {@code types}.
3148+
* Two types where one of them is {@code null} is counted as one type to support defining nullability
3149+
* @param schema the schema annotation
3150+
* @return whether the annotation defines a single type
3151+
*/
3152+
public static boolean isOas31SingleTypeIncludingNull(io.swagger.v3.oas.annotations.media.Schema schema) {
3153+
String[] types = schema.types();
3154+
return types.length == 1 || types.length == 2 && Arrays.asList(types).contains(NULL_TYPE);
3155+
}
31383156
}

modules/swagger-core/src/test/java/io/swagger/v3/core/converting/Issue5061Test.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,16 +142,16 @@ public static class ModelWithDifferentCombinationOfNumberFieldsWithExamples {
142142
@io.swagger.v3.oas.annotations.media.Schema(example = "5 lacs per annum")
143143
String stringFieldType;
144144

145-
@io.swagger.v3.oas.annotations.media.Schema(type = "string", example = "5 lacs per annum")
145+
@io.swagger.v3.oas.annotations.media.Schema(types = {"string"}, example = "5 lacs per annum")
146146
String stringFieldTypeWithExplicitStringSchemaType;
147147

148-
@io.swagger.v3.oas.annotations.media.Schema(type = "number", example = "10")
148+
@io.swagger.v3.oas.annotations.media.Schema(types = {"number"}, example = "10")
149149
String stringFieldTypeWithExplicitNumberSchemaType;
150150

151-
@io.swagger.v3.oas.annotations.media.Schema(type = "integer", example = "5")
151+
@io.swagger.v3.oas.annotations.media.Schema(types = {"integer"}, example = "5")
152152
String stringFieldTypeWithExplicitIntegerSchemaType;
153153

154-
@io.swagger.v3.oas.annotations.media.Schema(type = "string", example = "13.37")
154+
@io.swagger.v3.oas.annotations.media.Schema(types = {"string"}, example = "13.37")
155155
BigDecimal bigDecimalFieldTypeWithExplicitStringSchemaType;
156156

157157
@io.swagger.v3.oas.annotations.media.Schema(example = "13.37")

modules/swagger-core/src/test/java/io/swagger/v3/core/converting/Issue5168Test.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ public static class ModelWithDifferentCombinationOfBooleanFieldsWithExamples {
5555
@io.swagger.v3.oas.annotations.media.Schema(example = "true")
5656
String stringFieldType;
5757

58-
@io.swagger.v3.oas.annotations.media.Schema(type = "boolean", example = "true")
58+
@io.swagger.v3.oas.annotations.media.Schema(types = {"boolean"}, example = "true")
5959
String stringFieldTypeWithExplicitBooleanSchemaType;
6060

61-
@io.swagger.v3.oas.annotations.media.Schema(type = "string", example = "true")
61+
@io.swagger.v3.oas.annotations.media.Schema(types = {"string"}, example = "true")
6262
boolean booleanFieldTypeWithExplicitStringSchemaType;
6363

6464
@io.swagger.v3.oas.annotations.media.Schema(example = "true")
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
package io.swagger.v3.core.resolving;
2+
3+
import io.swagger.v3.core.converter.ModelConverters;
4+
import io.swagger.v3.core.matchers.SerializationMatchers;
5+
import io.swagger.v3.oas.models.media.Schema;
6+
import org.testng.annotations.Test;
7+
8+
import java.math.BigDecimal;
9+
import java.util.Map;
10+
11+
public class SchemaAnnotationSetsTypeTest {
12+
13+
@Test(description = "Setting type in @Schema is applied for OAS 3.0")
14+
public void testTypeInSchemaAnnotationIsAppliedForOAS30() {
15+
String expectedYaml = "ClassWithFieldsUsingOAS30Type:\n" +
16+
" type: object\n" +
17+
" properties:\n" +
18+
" inferred:\n" +
19+
" type: number\n" +
20+
" amount:\n" +
21+
" type: boolean\n" +
22+
" count:\n" +
23+
" type: boolean\n" +
24+
" flag:\n" +
25+
" type: integer\n" +
26+
" format: int32\n" +
27+
" unit:\n" +
28+
" type: string\n" +
29+
" enum:\n" +
30+
" - DAY\n" +
31+
" - WEEK\n" +
32+
" - MONTH";
33+
34+
Map<String, Schema> stringSchemaMap = ModelConverters.getInstance().readAll(ClassWithFieldsUsingOAS30Type.class);
35+
SerializationMatchers.assertEqualsToYaml(stringSchemaMap, expectedYaml);
36+
}
37+
38+
@Test(description = "Setting types in @Schema is ignored for OAS 3.0")
39+
public void testTypesInSchemaAnnotationIsIgnoredForOAS30() {
40+
String expectedYaml = "ClassWithFieldsUsingOAS31Types:\n" +
41+
" type: object\n" +
42+
" properties:\n" +
43+
" inferred:\n" +
44+
" type: number\n" +
45+
" amount:\n" +
46+
" type: number\n" +
47+
" count:\n" +
48+
" type: integer\n" +
49+
" format: int32\n" +
50+
" flag:\n" +
51+
" type: boolean\n" +
52+
" unit:\n" +
53+
" type: string\n" +
54+
" enum:\n" +
55+
" - DAY\n" +
56+
" - WEEK\n" +
57+
" - MONTH";
58+
59+
Map<String, io.swagger.v3.oas.models.media.Schema> stringSchemaMap = ModelConverters.getInstance().readAll(ClassWithFieldsUsingOAS31Types.class);
60+
SerializationMatchers.assertEqualsToYaml(stringSchemaMap, expectedYaml);
61+
}
62+
63+
@Test(description = "Setting type in @Schema is ignored for OAS 3.1")
64+
public void testTypeInSchemaAnnotationIsIgnoredForOAS31() {
65+
String expectedYaml = "ClassWithFieldsUsingOAS30Type:\n" +
66+
" type: object\n" +
67+
" properties:\n" +
68+
" inferred:\n" +
69+
" type: number\n" +
70+
" amount:\n" +
71+
" type: number\n" +
72+
" count:\n" +
73+
" type: integer\n" +
74+
" format: int32\n" +
75+
" flag:\n" +
76+
" type: boolean\n" +
77+
" unit:\n" +
78+
" type: string\n" +
79+
" enum:\n" +
80+
" - DAY\n" +
81+
" - WEEK\n" +
82+
" - MONTH";
83+
84+
Map<String, io.swagger.v3.oas.models.media.Schema> stringSchemaMap = ModelConverters.getInstance(true)
85+
.readAll(ClassWithFieldsUsingOAS30Type.class);
86+
SerializationMatchers.assertEqualsToYaml31(stringSchemaMap, expectedYaml);
87+
}
88+
89+
@Test(description = "Setting types in @Schema is applied for OAS 3.1")
90+
public void testTypesInSchemaAnnotationIsAppliedForOAS31() {
91+
String expectedYaml = "ClassWithFieldsUsingOAS31Types:\n" +
92+
" type: object\n" +
93+
" properties:\n" +
94+
" inferred:\n" +
95+
" type: number\n" +
96+
" amount:\n" +
97+
" type: boolean\n" +
98+
" count:\n" +
99+
" type: boolean\n" +
100+
" format: int32\n" +
101+
" flag:\n" +
102+
" type: integer\n" +
103+
" unit:\n" +
104+
" type: string\n" +
105+
" enum:\n" +
106+
" - DAY\n" +
107+
" - WEEK\n" +
108+
" - MONTH";
109+
110+
Map<String, io.swagger.v3.oas.models.media.Schema> stringSchemaMap = ModelConverters.getInstance(true)
111+
.readAll(ClassWithFieldsUsingOAS31Types.class);
112+
SerializationMatchers.assertEqualsToYaml31(stringSchemaMap, expectedYaml);
113+
}
114+
115+
@Test(description = "Setting several types in @Schema is applied for OAS 3.1")
116+
public void testSeveralTypesInSchemaAnnotationIsAppliedForOAS31() {
117+
String expectedYaml = "ClassWithFieldsUsingOAS31TypesDefiningSeveralTypes:\n" +
118+
" type: object\n" +
119+
" properties:\n" +
120+
" inferred:\n" +
121+
" type: number\n" +
122+
" amount:\n" +
123+
" type:\n" +
124+
" - boolean\n" +
125+
" - number\n" +
126+
" count:\n" +
127+
" type:\n" +
128+
" - boolean\n" +
129+
" - number\n" +
130+
" format: int32\n" +
131+
" flag:\n" +
132+
" type:\n" +
133+
" - integer\n" +
134+
" - number\n" +
135+
" unit:\n" +
136+
" type: string\n" +
137+
" enum:\n" +
138+
" - DAY\n" +
139+
" - WEEK\n" +
140+
" - MONTH";
141+
142+
Map<String, io.swagger.v3.oas.models.media.Schema> stringSchemaMap = ModelConverters.getInstance(true)
143+
.readAll(ClassWithFieldsUsingOAS31TypesDefiningSeveralTypes.class);
144+
SerializationMatchers.assertEqualsToYaml31(stringSchemaMap, expectedYaml);
145+
}
146+
147+
private static class ClassWithFieldsUsingOAS30Type {
148+
@io.swagger.v3.oas.annotations.media.Schema
149+
public BigDecimal inferred;
150+
@io.swagger.v3.oas.annotations.media.Schema(type = "boolean")
151+
public BigDecimal amount;
152+
@io.swagger.v3.oas.annotations.media.Schema(type = "boolean")
153+
public Integer count;
154+
@io.swagger.v3.oas.annotations.media.Schema(type = "integer")
155+
public Boolean flag;
156+
@io.swagger.v3.oas.annotations.media.Schema
157+
public Frequency unit;
158+
159+
enum Frequency { DAY, WEEK, MONTH }
160+
}
161+
162+
private static class ClassWithFieldsUsingOAS31Types {
163+
@io.swagger.v3.oas.annotations.media.Schema
164+
public BigDecimal inferred;
165+
@io.swagger.v3.oas.annotations.media.Schema(types = {"boolean"})
166+
public BigDecimal amount;
167+
@io.swagger.v3.oas.annotations.media.Schema(types = {"boolean"})
168+
public Integer count;
169+
@io.swagger.v3.oas.annotations.media.Schema(types = {"integer"})
170+
public Boolean flag;
171+
@io.swagger.v3.oas.annotations.media.Schema
172+
public Frequency unit;
173+
174+
enum Frequency { DAY, WEEK, MONTH }
175+
}
176+
177+
private static class ClassWithFieldsUsingOAS31TypesDefiningSeveralTypes {
178+
@io.swagger.v3.oas.annotations.media.Schema
179+
public BigDecimal inferred;
180+
@io.swagger.v3.oas.annotations.media.Schema(types = {"boolean", "number"})
181+
public BigDecimal amount;
182+
@io.swagger.v3.oas.annotations.media.Schema(types = {"boolean", "number"})
183+
public Integer count;
184+
@io.swagger.v3.oas.annotations.media.Schema(types = {"integer", "number"})
185+
public Boolean flag;
186+
@io.swagger.v3.oas.annotations.media.Schema
187+
public Frequency unit;
188+
189+
enum Frequency { DAY, WEEK, MONTH }
190+
}
191+
192+
}

modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/Ticket4679Test.java

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,27 @@
22

33
import io.swagger.v3.core.converter.ModelConverters;
44
import io.swagger.v3.core.matchers.SerializationMatchers;
5-
import io.swagger.v3.core.util.Yaml;
6-
import io.swagger.v3.core.util.Yaml31;
75
import io.swagger.v3.oas.annotations.media.Schema;
86
import org.testng.annotations.Test;
97

108
import java.util.Map;
119

12-
public class Ticket4679Test extends SwaggerTestBase{
10+
public class Ticket4679Test extends SwaggerTestBase {
1311

1412
@Test(description = "Custom schema implementation in property overrides type value")
15-
public void testCustomSchemaImplementation() {
13+
public void testCustomSchemaImplementationOAS31() {
1614

1715
String expectedYaml = "ModelWithCustomSchemaImplementationInProperty:\n" +
18-
" type: object\n" +
19-
" properties:\n" +
20-
" exampleField:\n" +
21-
" type: integer\n" +
22-
" format: int32\n" +
23-
" secondExampleField:\n" +
24-
" type: string\n";
25-
26-
Map<String, io.swagger.v3.oas.models.media.Schema> stringSchemaMap = ModelConverters.getInstance(true).readAll(ModelWithCustomSchemaImplementationInProperty.class);
16+
" type: object\n" +
17+
" properties:\n" +
18+
" exampleField:\n" +
19+
" type: integer\n" +
20+
" format: int32\n" +
21+
" secondExampleField:\n" +
22+
" type: string\n";
23+
24+
Map<String, io.swagger.v3.oas.models.media.Schema> stringSchemaMap = ModelConverters.getInstance(true)
25+
.readAll(ModelWithCustomSchemaImplementationInProperty.class);
2726
SerializationMatchers.assertEqualsToYaml31(stringSchemaMap, expectedYaml);
2827
}
2928

@@ -32,7 +31,7 @@ static class ModelWithCustomSchemaImplementationInProperty {
3231
@Schema(implementation = Integer.class)
3332
private String exampleField;
3433

35-
@Schema(type = "string")
34+
@Schema(types = {"string"})
3635
private Integer secondExampleField;
3736

3837
public String getExampleField() {

0 commit comments

Comments
 (0)