Skip to content

Commit f67b8a0

Browse files
seonwooj0810claude
authored andcommitted
fix: let explicit @Schema(format) override type-derived format (#5185)
`@Schema(format = "uri-reference")` on a `java.net.URI` field was ignored because `resolveSchemaMembers` only applied the annotation format when the schema had no format yet. For `java.net.URI` the `PrimitiveType` already sets `format: uri`, so the user-specified `uri-reference` was dropped. An explicit `format` on `@Schema` expresses the user's intent and must take precedence over a format derived from the property type. A URI field without an explicit format still resolves to the default `uri`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 6e74355 commit f67b8a0

2 files changed

Lines changed: 139 additions & 1 deletion

File tree

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3181,7 +3181,9 @@ protected void resolveSchemaMembers(Schema schema, Annotated a, Annotation[] ann
31813181
schema.title(title);
31823182
}
31833183
String format = resolveFormat(a, annotations, schemaAnnotation);
3184-
if (StringUtils.isNotBlank(format) && StringUtils.isBlank(schema.getFormat())) {
3184+
if (StringUtils.isNotBlank(format)) {
3185+
// An explicit format on @Schema is the user's intent and must take precedence
3186+
// over a format derived from the property type (e.g. java.net.URI -> "uri").
31853187
schema.format(format);
31863188
}
31873189
Object defaultValue = resolveDefaultValue(a, annotations, schemaAnnotation);
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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.oas.annotations.media.Schema;
7+
import org.testng.annotations.Test;
8+
9+
import javax.validation.constraints.Email;
10+
import java.net.URI;
11+
12+
import static io.swagger.v3.core.resolving.SwaggerTestBase.mapper;
13+
import static org.testng.Assert.assertEquals;
14+
import static org.testng.Assert.assertNotNull;
15+
import static org.testng.Assert.assertTrue;
16+
17+
public class Ticket5185Test {
18+
19+
@Test
20+
public void testExplicitFormatOverridesTypeDerivedFormat() {
21+
final ModelResolver modelResolver = new ModelResolver(mapper());
22+
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
23+
24+
io.swagger.v3.oas.models.media.Schema schema = context.resolve(new AnnotatedType(UriExample.class));
25+
26+
assertNotNull(schema);
27+
io.swagger.v3.oas.models.media.Schema uriReference =
28+
(io.swagger.v3.oas.models.media.Schema) schema.getProperties().get("uriReferenceValue");
29+
assertNotNull(uriReference);
30+
assertEquals(uriReference.getFormat(), "uri-reference");
31+
32+
// A URI field without an explicit @Schema(format=...) still resolves to the default "uri" format
33+
io.swagger.v3.oas.models.media.Schema plainUri =
34+
(io.swagger.v3.oas.models.media.Schema) schema.getProperties().get("uriValue");
35+
assertNotNull(plainUri);
36+
assertEquals(plainUri.getFormat(), "uri");
37+
}
38+
39+
@Test
40+
public void testExplicitFormatPrecedenceForStringAndNumericTypes() {
41+
final ModelResolver modelResolver = new ModelResolver(mapper());
42+
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
43+
44+
io.swagger.v3.oas.models.media.Schema schema = context.resolve(new AnnotatedType(ExplicitFormatExample.class));
45+
46+
assertNotNull(schema);
47+
assertProperty(schema, "uuidValue", "string", "uuid");
48+
assertProperty(schema, "emailValue", "string", "email");
49+
assertProperty(schema, "emailValidatedValue", "string", "email");
50+
assertProperty(schema, "beanValidationEmailValue", "string", "email");
51+
assertProperty(schema, "longValue", "integer", "int64");
52+
assertProperty(schema, "integerAsInt64Value", "integer", "int64");
53+
}
54+
55+
@Test
56+
public void testExplicitFormatOverridesTypeDerivedFormatOpenApi31() {
57+
final ModelResolver modelResolver = new ModelResolver(mapper());
58+
modelResolver.setOpenapi31(true);
59+
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
60+
61+
io.swagger.v3.oas.models.media.Schema schema = context.resolve(new AnnotatedType(UriExample.class));
62+
63+
assertNotNull(schema);
64+
io.swagger.v3.oas.models.media.Schema uriReference =
65+
(io.swagger.v3.oas.models.media.Schema) schema.getProperties().get("uriReferenceValue");
66+
assertNotNull(uriReference);
67+
assertEquals(uriReference.getFormat(), "uri-reference");
68+
69+
io.swagger.v3.oas.models.media.Schema plainUri =
70+
(io.swagger.v3.oas.models.media.Schema) schema.getProperties().get("uriValue");
71+
assertNotNull(plainUri);
72+
assertEquals(plainUri.getFormat(), "uri");
73+
}
74+
75+
@Test
76+
public void testExplicitFormatPrecedenceForStringAndNumericTypesOpenApi31() {
77+
final ModelResolver modelResolver = new ModelResolver(mapper());
78+
modelResolver.setOpenapi31(true);
79+
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
80+
81+
io.swagger.v3.oas.models.media.Schema schema = context.resolve(new AnnotatedType(ExplicitFormatExample.class));
82+
83+
assertNotNull(schema);
84+
assertProperty31(schema, "uuidValue", "string", "uuid");
85+
assertProperty31(schema, "emailValue", "string", "email");
86+
assertProperty31(schema, "emailValidatedValue", "string", "email");
87+
assertProperty31(schema, "beanValidationEmailValue", "string", "email");
88+
assertProperty31(schema, "longValue", "integer", "int64");
89+
assertProperty31(schema, "integerAsInt64Value", "integer", "int64");
90+
}
91+
92+
private static void assertProperty(io.swagger.v3.oas.models.media.Schema schema, String propertyName, String type, String format) {
93+
io.swagger.v3.oas.models.media.Schema property =
94+
(io.swagger.v3.oas.models.media.Schema) schema.getProperties().get(propertyName);
95+
assertNotNull(property);
96+
assertEquals(property.getType(), type);
97+
assertEquals(property.getFormat(), format);
98+
}
99+
100+
private static void assertProperty31(io.swagger.v3.oas.models.media.Schema schema, String propertyName, String type, String format) {
101+
io.swagger.v3.oas.models.media.Schema property =
102+
(io.swagger.v3.oas.models.media.Schema) schema.getProperties().get(propertyName);
103+
assertNotNull(property);
104+
assertNotNull(property.getTypes());
105+
assertTrue(property.getTypes().contains(type));
106+
assertEquals(property.getFormat(), format);
107+
}
108+
109+
static class UriExample {
110+
@Schema(format = "uri-reference")
111+
public URI uriReferenceValue;
112+
113+
public URI uriValue;
114+
}
115+
116+
static class ExplicitFormatExample {
117+
@Schema(format = "uuid")
118+
public String uuidValue;
119+
120+
@Schema(format = "email")
121+
public String emailValue;
122+
123+
@Schema(format = "email")
124+
@Email
125+
public String emailValidatedValue;
126+
127+
@Email
128+
public String beanValidationEmailValue;
129+
130+
@Schema(format = "int64")
131+
public Long longValue;
132+
133+
@Schema(format = "int64")
134+
public Integer integerAsInt64Value;
135+
}
136+
}

0 commit comments

Comments
 (0)