Skip to content

Commit 3df311a

Browse files
fix: do not process a nullable annotation for container items
1 parent c392d43 commit 3df311a

7 files changed

Lines changed: 302 additions & 31 deletions

File tree

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,9 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
477477
if (annotatedType.getCtxAnnotations() != null) {
478478
strippedCtxAnnotations.addAll(Arrays.stream(
479479
annotatedType.getCtxAnnotations()).filter(
480-
ass -> !ass.annotationType().getName().startsWith("io.swagger") && !ass.annotationType().getName().startsWith("javax.validation.constraints")
480+
ass -> !ass.annotationType().getName().startsWith("io.swagger") &&
481+
!ass.annotationType().getName().startsWith("javax.validation.constraints") &&
482+
!ass.annotationType().getName().equals("javax.annotation.Nullable")
481483
).collect(Collectors.toList()));
482484
}
483485

@@ -1132,7 +1134,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
11321134
if (isInferredObjectSchema(model) && model.get$ref() == null) {
11331135
if (openapi31 && model.getTypes() == null) {
11341136
model.addType("object");
1135-
} else if (!openapi31 && model.getType() == null){
1137+
} else if (!openapi31 && model.getType() == null) {
11361138
model.type("object");
11371139
}
11381140
}

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
import io.swagger.v3.core.converter.ModelConverters;
44
import io.swagger.v3.core.converter.ResolvedSchema;
55
import io.swagger.v3.core.oas.models.ModelWithArrayOfSubclasses;
6+
import io.swagger.v3.core.util.Json;
67
import io.swagger.v3.core.util.Json31;
78

89
import static org.testng.Assert.assertEquals;
910
import static org.testng.Assert.assertNotNull;
11+
12+
import io.swagger.v3.core.util.ResourceUtils;
1013
import org.testng.annotations.Test;
1114

12-
import java.nio.file.Files;
13-
import java.nio.file.Paths;
1415
import com.fasterxml.jackson.databind.ObjectMapper;
1516
import com.fasterxml.jackson.databind.JsonNode;
1617

@@ -21,7 +22,7 @@ public class ArrayOfSubclassTest {
2122
public void extractSubclassArray_oas31() throws Exception {
2223
ResolvedSchema schema = ModelConverters.getInstance(true).readAllAsResolvedSchema(ModelWithArrayOfSubclasses.Holder.class);
2324
assertNotNull(schema);
24-
String expectedJson = new String(Files.readAllBytes(Paths.get("src/test/resources/converting/ArrayOfSubclassTest_expected31.json")));
25+
String expectedJson = ResourceUtils.loadClassResource(getClass(), "converting/ArrayOfSubclassTest_expected31.json");
2526
String actualJson = Json31.pretty(schema);
2627
ObjectMapper mapper = new ObjectMapper();
2728
JsonNode expectedNode = mapper.readTree(expectedJson);
@@ -33,8 +34,8 @@ public void extractSubclassArray_oas31() throws Exception {
3334
public void extractSubclassArray_oas30() throws Exception {
3435
ResolvedSchema schema = ModelConverters.getInstance(false).readAllAsResolvedSchema(ModelWithArrayOfSubclasses.Holder.class);
3536
assertNotNull(schema);
36-
String expectedJson = new String(Files.readAllBytes(Paths.get("src/test/resources/converting/ArrayOfSubclassTest_expected30.json")));
37-
String actualJson = Json31.pretty(schema);
37+
String expectedJson = ResourceUtils.loadClassResource(getClass(), "converting/ArrayOfSubclassTest_expected30.json");
38+
String actualJson = Json.pretty(schema);
3839
ObjectMapper mapper = new ObjectMapper();
3940
JsonNode expectedNode = mapper.readTree(expectedJson);
4041
JsonNode actualNode = mapper.readTree(actualJson);

modules/swagger-core/src/test/java/io/swagger/v3/core/issues/Issue5001Test.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import io.swagger.v3.core.converter.AnnotatedType;
44
import io.swagger.v3.core.converter.ModelConverterContextImpl;
5+
import io.swagger.v3.core.converter.ModelConverters;
6+
import io.swagger.v3.core.converter.ResolvedSchema;
57
import io.swagger.v3.core.jackson.ModelResolver;
68
import io.swagger.v3.core.util.Configuration;
79
import io.swagger.v3.core.util.Json;
@@ -10,6 +12,7 @@
1012
import org.testng.annotations.Test;
1113

1214
import javax.annotation.Nullable;
15+
import java.util.List;
1316
import java.util.Set;
1417

1518
import static org.testng.Assert.*;
@@ -18,11 +21,11 @@
1821
* Reproduces GitHub Issue #5001
1922
* Native support for @Nullable annotations to generate proper nullable types
2023
*
21-
* Tests that @Nullable annotation is recognized and generates appropriate nullable output:
24+
* <p>Tests that @Nullable annotation is recognized and generates appropriate nullable output:
2225
* - OAS 3.0: nullable keyword
2326
* - OAS 3.1: type array with "null"
2427
*
25-
* Note: This test uses javax.annotation.Nullable which is automatically transformed to
28+
* <p>Note: This test uses javax.annotation.Nullable which is automatically transformed to
2629
* jakarta.annotation.Nullable in the swagger-core-jakarta module via the Eclipse Transformer.
2730
*
2831
* @see <a href="https://github.com/swagger-api/swagger-core/issues/5001">...</a>
@@ -33,7 +36,7 @@ public class Issue5001Test {
3336
* Tests @Nullable annotation with OAS 3.1 (type array output)
3437
*/
3538
@Test
36-
public void testNullableWithOAS31() throws Exception {
39+
public void testNullableWithOAS31() {
3740
final ModelResolver modelResolver = new ModelResolver(Json31.mapper());
3841
Configuration configuration = new Configuration();
3942
configuration.setOpenAPI31(true);
@@ -72,7 +75,7 @@ public void testNullableWithOAS31() throws Exception {
7275
* Tests @Nullable annotation with OAS 3.0 (nullable keyword output)
7376
*/
7477
@Test
75-
public void testNullableWithOAS30() throws Exception {
78+
public void testNullableWithOAS30() {
7679
final ModelResolver modelResolver = new ModelResolver(Json.mapper());
7780
Configuration configuration = new Configuration();
7881
configuration.setOpenAPI31(false);
@@ -103,7 +106,7 @@ public void testNullableWithOAS30() throws Exception {
103106
* Tests explicit @Schema annotations with OAS 3.1
104107
*/
105108
@Test
106-
public void testExplicitSchemaAnnotationsWithOAS31() throws Exception {
109+
public void testExplicitSchemaAnnotationsWithOAS31() {
107110
final ModelResolver modelResolver = new ModelResolver(Json31.mapper());
108111
Configuration configuration = new Configuration();
109112
configuration.setOpenAPI31(true);
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package io.swagger.v3.core.issues;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import io.swagger.v3.core.converter.ModelConverters;
6+
import io.swagger.v3.core.converter.ResolvedSchema;
7+
import io.swagger.v3.core.util.Json;
8+
import io.swagger.v3.core.util.Json31;
9+
import io.swagger.v3.core.util.ResourceUtils;
10+
import org.testng.annotations.Test;
11+
12+
import javax.annotation.Nullable;
13+
import java.io.IOException;
14+
import java.util.List;
15+
16+
import static org.testng.Assert.assertEquals;
17+
18+
/**
19+
* Reproduces GitHub Issue #5077
20+
* Issue using Nullable annotation for OAS 3.1
21+
*
22+
* <p>Tests that @Nullable annotation does not affect the item in a list:
23+
*
24+
* <p>Note: This test uses javax.annotation.Nullable which is automatically transformed to
25+
* jakarta.annotation.Nullable in the swagger-core-jakarta module via the Eclipse Transformer.
26+
*
27+
*/
28+
public class Issue5077Test {
29+
30+
@Test
31+
public void testOAS31() throws IOException {
32+
ResolvedSchema schema = ModelConverters.getInstance(true)
33+
.readAllAsResolvedSchema(ModelWithObjectAndList.class);
34+
35+
String expectedJson = ResourceUtils.loadClassResource(getClass(), "specFiles/NullableFieldsOAS31.json");
36+
String actualJson = Json31.pretty(schema);
37+
ObjectMapper mapper = new ObjectMapper();
38+
JsonNode expectedNode = mapper.readTree(expectedJson);
39+
JsonNode actualNode = mapper.readTree(actualJson);
40+
assertEquals(actualNode, expectedNode);
41+
}
42+
43+
@Test
44+
public void testOAS30() throws IOException {
45+
ResolvedSchema schema = ModelConverters.getInstance()
46+
.readAllAsResolvedSchema(ModelWithObjectAndList.class);
47+
48+
String expectedJson = ResourceUtils.loadClassResource(getClass(), "specFiles/NullableFieldsOAS30.json");
49+
String actualJson = Json.pretty(schema);
50+
ObjectMapper mapper = new ObjectMapper();
51+
JsonNode expectedNode = mapper.readTree(expectedJson);
52+
JsonNode actualNode = mapper.readTree(actualJson);
53+
assertEquals(actualNode, expectedNode);
54+
}
55+
56+
public static class ModelWithObjectAndList {
57+
58+
@Nullable
59+
private String nullableString;
60+
61+
private String requiredString;
62+
63+
@Nullable
64+
private Model nullableModel;
65+
66+
@Nullable
67+
private List<String> strings;
68+
69+
@Nullable
70+
private List<Model> models;
71+
72+
public String getNullableString() {
73+
return nullableString;
74+
}
75+
76+
public void setNullableString(String nullableString) {
77+
this.nullableString = nullableString;
78+
}
79+
80+
public String getRequiredString() {
81+
return requiredString;
82+
}
83+
84+
public void setRequiredString(String requiredString) {
85+
this.requiredString = requiredString;
86+
}
87+
88+
public Model getNullableModel() {
89+
return nullableModel;
90+
}
91+
92+
public void setNullableModel(Model model) {
93+
this.nullableModel = model;
94+
}
95+
96+
public List<Model> getModels() {
97+
return models;
98+
}
99+
100+
public void setModels(List<Model> models) {
101+
this.models = models;
102+
}
103+
104+
@Nullable
105+
public List<String> getStrings() {
106+
return strings;
107+
}
108+
109+
public void setStrings(@Nullable List<String> strings) {
110+
this.strings = strings;
111+
}
112+
}
113+
114+
public static class Model {
115+
private String field;
116+
117+
public String getField() {
118+
return field;
119+
}
120+
121+
public void setField(String field) {
122+
this.field = field;
123+
}
124+
}
125+
126+
}
Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"schema" : {
3-
"description" : "The holder",
3+
"type" : "object",
44
"properties" : {
55
"name" : {
66
"type" : "string"
@@ -9,34 +9,36 @@
99
"type" : "string"
1010
},
1111
"baseArray" : {
12+
"minItems" : 0,
13+
"uniqueItems" : true,
1214
"type" : "array",
1315
"description" : "Thingy",
1416
"items" : {
1517
"$ref" : "#/components/schemas/Base"
16-
},
17-
"minItems" : 0,
18-
"uniqueItems" : true
18+
}
1919
}
20-
}
20+
},
21+
"description" : "The holder"
2122
},
2223
"referencedSchemas" : {
2324
"Base" : {
25+
"type" : "object",
26+
"properties" : {
27+
"name" : {
28+
"type" : "string"
29+
}
30+
},
2431
"description" : "Stuff",
2532
"discriminator" : {
2633
"propertyName" : "name",
2734
"mapping" : {
2835
"a" : "#/components/schemas/SubA",
2936
"b" : "#/components/schemas/SubB"
3037
}
31-
},
32-
"properties" : {
33-
"name" : {
34-
"type" : "string"
35-
}
3638
}
3739
},
3840
"Holder" : {
39-
"description" : "The holder",
41+
"type" : "object",
4042
"properties" : {
4143
"name" : {
4244
"type" : "string"
@@ -45,18 +47,19 @@
4547
"type" : "string"
4648
},
4749
"baseArray" : {
50+
"minItems" : 0,
51+
"uniqueItems" : true,
4852
"type" : "array",
4953
"description" : "Thingy",
5054
"items" : {
5155
"$ref" : "#/components/schemas/Base"
52-
},
53-
"minItems" : 0,
54-
"uniqueItems" : true
56+
}
5557
}
56-
}
58+
},
59+
"description" : "The holder"
5760
},
5861
"SubA" : {
59-
"description" : "The SubA class",
62+
"type" : "object",
6063
"properties" : {
6164
"name" : {
6265
"type" : "string"
@@ -65,18 +68,20 @@
6568
"type" : "integer",
6669
"format" : "int64"
6770
}
68-
}
71+
},
72+
"description" : "The SubA class"
6973
},
7074
"SubB" : {
71-
"description" : "The SubB class",
75+
"type" : "object",
7276
"properties" : {
7377
"name" : {
7478
"type" : "string"
7579
},
7680
"friend" : {
7781
"type" : "string"
7882
}
79-
}
83+
},
84+
"description" : "The SubB class"
8085
}
8186
}
8287
}

0 commit comments

Comments
 (0)