Skip to content

Commit b05863c

Browse files
committed
Fix(core): Prevent redundant schema resolution by fixing AnnotatedType equality
Signed-off-by: potato <[email protected]>
1 parent 8767cea commit b05863c

File tree

2 files changed

+89
-36
lines changed

2 files changed

+89
-36
lines changed

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

Lines changed: 17 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -243,47 +243,28 @@ public AnnotatedType propertyName(String propertyName) {
243243

244244
@Override
245245
public boolean equals(Object o) {
246-
if (this == o) {
247-
return true;
248-
}
249-
if (!(o instanceof AnnotatedType)) {
250-
return false;
251-
}
246+
if (this == o) return true;
247+
if (o == null || getClass() != o.getClass()) return false;
252248
AnnotatedType that = (AnnotatedType) o;
253-
254-
if ((type == null && that.type != null) || (type != null && that.type == null)) {
255-
return false;
256-
}
257-
258-
if (type != null && that.type != null && !type.equals(that.type)) {
259-
return false;
260-
}
261-
return Arrays.equals(this.ctxAnnotations, that.ctxAnnotations);
249+
return skipOverride == that.skipOverride &&
250+
schemaProperty == that.schemaProperty &&
251+
resolveAsRef == that.resolveAsRef &&
252+
resolveEnumAsRef == that.resolveEnumAsRef &&
253+
includePropertiesWithoutJSONView == that.includePropertiesWithoutJSONView &&
254+
skipSchemaName == that.skipSchemaName &&
255+
skipJsonIdentity == that.skipJsonIdentity &&
256+
Objects.equals(type, that.type) &&
257+
Objects.equals(name, that.name) &&
258+
Arrays.equals(ctxAnnotations, that.ctxAnnotations) &&
259+
Objects.equals(jsonViewAnnotation, that.jsonViewAnnotation);
262260
}
263261

264-
265262
@Override
266263
public int hashCode() {
267-
if (ctxAnnotations == null || ctxAnnotations.length == 0) {
268-
return Objects.hash(type, "fixed");
269-
}
270-
List<Annotation> meaningfulAnnotations = new ArrayList<>();
271-
272-
boolean hasDifference = false;
273-
for (Annotation a: ctxAnnotations) {
274-
if(!a.annotationType().getName().startsWith("sun") && !a.annotationType().getName().startsWith("jdk")) {
275-
meaningfulAnnotations.add(a);
276-
} else {
277-
hasDifference = true;
278-
}
279-
}
280-
int result = 1;
281-
result = 31 * result + (type == null ? 0 : Objects.hash(type, "fixed"));
282-
if (hasDifference) {
283-
result = 31 * result + meaningfulAnnotations.hashCode();
284-
} else {
285-
result = 31 * result + Arrays.hashCode(ctxAnnotations);
286-
}
264+
int result = Objects.hash(type, name, skipOverride, schemaProperty, resolveAsRef,
265+
resolveEnumAsRef, jsonViewAnnotation, includePropertiesWithoutJSONView, skipSchemaName,
266+
skipJsonIdentity);
267+
result = 31 * result + Arrays.hashCode(ctxAnnotations);
287268
return result;
288269
}
289270
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package io.swagger.v3.core.converting;
2+
3+
import io.swagger.v3.core.converter.AnnotatedType;
4+
import io.swagger.v3.core.converter.ModelConverter;
5+
import io.swagger.v3.core.converter.ModelConverterContext;
6+
import io.swagger.v3.core.converter.ModelConverterContextImpl;
7+
import io.swagger.v3.oas.models.media.Schema;
8+
import org.testng.annotations.Test;
9+
10+
import java.lang.reflect.Field;
11+
import java.util.Iterator;
12+
import java.util.Set;
13+
14+
import static org.testng.Assert.assertEquals;
15+
import static org.testng.Assert.assertNotNull;
16+
17+
public class AnnotatedTypeCachingTest {
18+
19+
@Test
20+
public void testAnnotatedTypeEqualityIgnoresContextualFields() {
21+
AnnotatedType type1 = new AnnotatedType(String.class)
22+
.propertyName("userStatus");
23+
AnnotatedType type2 = new AnnotatedType(String.class)
24+
.propertyName("city");
25+
assertEquals(type1, type2, "AnnotatedType objects with different contextual fields (e.g., propertyName) should be equal.");
26+
assertEquals(type1.hashCode(), type2.hashCode(), "The hash codes of equal AnnotatedType objects must be the same.");
27+
}
28+
29+
static class User {
30+
public String username;
31+
public String email;
32+
public Address address;
33+
}
34+
35+
static class Address {
36+
public String street;
37+
public String city;
38+
}
39+
40+
private static class DummyModelConverter implements ModelConverter {
41+
@Override
42+
public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterator<ModelConverter> chain) {
43+
if (type.getType().equals(User.class)) {
44+
context.resolve(new AnnotatedType(String.class).propertyName("username"));
45+
context.resolve(new AnnotatedType(String.class).propertyName("email"));
46+
context.resolve(new AnnotatedType(Address.class).propertyName("address"));
47+
return new Schema();
48+
}
49+
if (type.getType().equals(Address.class)) {
50+
context.resolve(new AnnotatedType(String.class).propertyName("street"));
51+
context.resolve(new AnnotatedType(String.class).propertyName("city"));
52+
return new Schema();
53+
}
54+
return new Schema();
55+
}
56+
}
57+
58+
@Test
59+
@SuppressWarnings("unchecked")
60+
public void testCacheHitsForRepeatedStringTypeWithCorrectedEquals() throws Exception {
61+
ModelConverterContextImpl context = new ModelConverterContextImpl(new DummyModelConverter());
62+
Schema userSchema = context.resolve(new AnnotatedType(User.class));
63+
assertNotNull(userSchema);
64+
Field processedTypesField = ModelConverterContextImpl.class.getDeclaredField("processedTypes");
65+
processedTypesField.setAccessible(true);
66+
Set<AnnotatedType> processedTypes = (Set<AnnotatedType>) processedTypesField.get(context);
67+
long stringTypeCount = processedTypes.stream()
68+
.filter(annotatedType -> annotatedType.getType().equals(String.class))
69+
.count();
70+
assertEquals(stringTypeCount, 1, "With the correct equals/hashCode, String type should be added to the cache only once.");
71+
}
72+
}

0 commit comments

Comments
 (0)