Skip to content

Commit cf11db7

Browse files
authored
fix: use arraySchema when deciding required of an array (#4998) Fixes: #4341
ArraySchema.schema describes the schema of the items. ArraySchema.arraySchema describes the schema of the array itself (i.e., the property). This is the same conceptual bug as #3438, which concerned the old boolean required attribute on @Schema. That older issue was fixed by switching from arraySchema.schema().required() to arraySchema.arraySchema().required().
1 parent 2546511 commit cf11db7

File tree

3 files changed

+182
-1
lines changed

3 files changed

+182
-1
lines changed

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
@@ -745,7 +745,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
745745
propSchemaOrArray == null ?
746746
null :
747747
propSchemaOrArray instanceof io.swagger.v3.oas.annotations.media.ArraySchema ?
748-
((io.swagger.v3.oas.annotations.media.ArraySchema) propSchemaOrArray).schema() :
748+
((io.swagger.v3.oas.annotations.media.ArraySchema) propSchemaOrArray).arraySchema() :
749749
(io.swagger.v3.oas.annotations.media.Schema) propSchemaOrArray;
750750

751751
io.swagger.v3.oas.annotations.media.Schema.AccessMode accessMode = resolveAccessMode(propDef, type, propResolvedSchemaAnnotation);

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

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import io.swagger.v3.core.jackson.ModelResolver;
1414
import io.swagger.v3.core.model.ApiDescription;
1515
import io.swagger.v3.core.util.Configuration;
16+
import io.swagger.v3.core.util.Json;
1617
import io.swagger.v3.core.util.PrimitiveType;
1718
import io.swagger.v3.jaxrs2.matchers.SerializationMatchers;
1819
import io.swagger.v3.jaxrs2.petstore31.PetResource;
@@ -83,6 +84,7 @@
8384
import io.swagger.v3.jaxrs2.resources.Ticket3731BisResource;
8485
import io.swagger.v3.jaxrs2.resources.Ticket3731Resource;
8586
import io.swagger.v3.jaxrs2.resources.Ticket4065Resource;
87+
import io.swagger.v3.jaxrs2.resources.Ticket4341Resource;
8688
import io.swagger.v3.jaxrs2.resources.Ticket4412Resource;
8789
import io.swagger.v3.jaxrs2.resources.Ticket4446Resource;
8890
import io.swagger.v3.jaxrs2.resources.Ticket4483Resource;
@@ -795,6 +797,32 @@ public void test2497() {
795797
assertEquals(openAPI.getComponents().getSchemas().get("User").getRequired().get(0), "issue3438");
796798
}
797799

800+
@Test(description = "array required property resolved from ArraySchema.arraySchema.requiredMode")
801+
public void test4341() {
802+
Reader reader = new Reader(new OpenAPI());
803+
OpenAPI openAPI = reader.read(Ticket4341Resource.class);
804+
Schema userSchema = openAPI.getComponents().getSchemas().get("User");
805+
List<String> required = userSchema.getRequired();
806+
807+
assertTrue(required.contains("requiredArray"));
808+
assertFalse(required.contains("notRequiredArray"));
809+
assertFalse(required.contains("notRequiredArrayWithNotNull"));
810+
assertTrue(required.contains("autoRequiredWithNotNull"));
811+
assertFalse(required.contains("autoNotRequired"));
812+
813+
assertTrue(
814+
required.contains("requiredArrayArraySchemaOnly"),
815+
"arraySchema.requiredMode=REQUIRED should make the array property required " +
816+
"even when items schema is not explicitly provided"
817+
);
818+
819+
assertFalse(
820+
required.contains("requiredItemsOnlyArray"),
821+
"schema(requiredMode=REQUIRED) on items must not make the array property required; " +
822+
"requiredness is controlled by arraySchema.requiredMode"
823+
);
824+
}
825+
798826
@Test(description = "test resource with subresources")
799827
public void testResourceWithSubresources() {
800828
Reader reader = new Reader(new OpenAPI());
@@ -5503,4 +5531,81 @@ public void testTicket4907() {
55035531
SerializationMatchers.assertEqualsToYaml31(openAPI, yaml);
55045532
ModelConverters.reset();
55055533
}
5534+
5535+
@Test(description = "array property metadata is resolved from ArraySchema.arraySchema, items metadata from ArraySchema.schema")
5536+
public void test4341ArraySchemaOtherAttributes() {
5537+
Reader reader = new Reader(new OpenAPI());
5538+
OpenAPI openAPI = reader.read(Ticket4341Resource.class);
5539+
System.out.println(Json.pretty(openAPI));
5540+
5541+
Schema userSchema = openAPI.getComponents().getSchemas().get("User");
5542+
assertNotNull(userSchema, "User schema should be present");
5543+
5544+
@SuppressWarnings("unchecked")
5545+
Map<String, Schema> properties = userSchema.getProperties();
5546+
assertNotNull(properties, "User properties should not be null");
5547+
5548+
Schema metadataArray = properties.get("metadataArray");
5549+
assertNotNull(metadataArray, "metadataArray property should be present");
5550+
assertTrue(metadataArray instanceof ArraySchema, "metadataArray should be an ArraySchema");
5551+
5552+
// Property-level assertions
5553+
assertEquals(
5554+
metadataArray.getDescription(),
5555+
"array-level description",
5556+
"Array property description should come from arraySchema, not items schema"
5557+
);
5558+
5559+
assertEquals(
5560+
metadataArray.getDeprecated(),
5561+
Boolean.TRUE,
5562+
"Array property deprecated should come from arraySchema"
5563+
);
5564+
5565+
assertEquals(
5566+
metadataArray.getReadOnly(),
5567+
Boolean.TRUE,
5568+
"Array property readOnly should be true from arraySchema.accessMode=READ_ONLY"
5569+
);
5570+
assertNotEquals(
5571+
metadataArray.getWriteOnly(),
5572+
Boolean.TRUE,
5573+
"Array property writeOnly should not be true when accessMode=READ_ONLY"
5574+
);
5575+
5576+
// Item-level assertions
5577+
5578+
ArraySchema metadataArraySchema = (ArraySchema) metadataArray;
5579+
Schema items = metadataArraySchema.getItems();
5580+
assertNotNull(items, "Items schema should not be null");
5581+
5582+
assertEquals(
5583+
items.getDescription(),
5584+
"item-level description",
5585+
"Items description should come from schema element of @ArraySchema"
5586+
);
5587+
5588+
assertNotEquals(
5589+
items.getDeprecated(),
5590+
Boolean.TRUE,
5591+
"Items deprecated should not be true when schema.deprecated=false"
5592+
);
5593+
5594+
assertEquals(
5595+
items.getWriteOnly(),
5596+
Boolean.TRUE,
5597+
"Items writeOnly should be true from schema.accessMode=WRITE_ONLY"
5598+
);
5599+
assertNotEquals(
5600+
items.getReadOnly(),
5601+
Boolean.TRUE,
5602+
"Items readOnly should not be true when accessMode=WRITE_ONLY"
5603+
);
5604+
5605+
assertEquals(
5606+
items.getFormat(),
5607+
"email",
5608+
"Items format should come from schema.format"
5609+
);
5610+
}
55065611
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package io.swagger.v3.jaxrs2.resources;
2+
3+
import io.swagger.v3.oas.annotations.media.ArraySchema;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
import javax.validation.constraints.NotNull;
6+
import javax.ws.rs.GET;
7+
import javax.ws.rs.Path;
8+
import java.util.List;
9+
10+
public class Ticket4341Resource {
11+
12+
@GET
13+
@Path("/user")
14+
public User getUsers() {
15+
return null;
16+
}
17+
18+
static class User {
19+
@ArraySchema(
20+
arraySchema = @Schema(requiredMode = Schema.RequiredMode.REQUIRED),
21+
schema = @Schema(type = "string")
22+
)
23+
public List<String> requiredArray;
24+
25+
@ArraySchema(
26+
arraySchema = @Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED),
27+
schema = @Schema(type = "string")
28+
)
29+
public List<String> notRequiredArray;
30+
31+
@ArraySchema(
32+
arraySchema = @Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED),
33+
schema = @Schema(type = "string")
34+
)
35+
@NotNull
36+
public List<String> notRequiredArrayWithNotNull;
37+
38+
@ArraySchema(
39+
arraySchema = @Schema(requiredMode = Schema.RequiredMode.AUTO),
40+
schema = @Schema(type = "string")
41+
)
42+
@NotNull
43+
public List<String> autoRequiredWithNotNull;
44+
45+
@ArraySchema(
46+
arraySchema = @Schema(requiredMode = Schema.RequiredMode.AUTO),
47+
schema = @Schema(type = "string")
48+
)
49+
public List<String> autoNotRequired;
50+
51+
@ArraySchema(
52+
arraySchema = @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
53+
)
54+
public List<String> requiredArrayArraySchemaOnly;
55+
56+
@ArraySchema(
57+
schema = @Schema(type = "string", requiredMode = Schema.RequiredMode.REQUIRED)
58+
)
59+
public List<String> requiredItemsOnlyArray;
60+
61+
@ArraySchema(
62+
arraySchema = @Schema(
63+
description = "array-level description",
64+
deprecated = true,
65+
accessMode = Schema.AccessMode.READ_ONLY
66+
),
67+
schema = @Schema(
68+
description = "item-level description",
69+
deprecated = false,
70+
accessMode = Schema.AccessMode.WRITE_ONLY,
71+
format = "email"
72+
)
73+
)
74+
public List<String> metadataArray;
75+
}
76+
}

0 commit comments

Comments
 (0)