Skip to content

Commit 9f0470d

Browse files
committed
improved error-messages and fixed @MutuallyExclusiveWith
1 parent 506e3b5 commit 9f0470d

1 file changed

Lines changed: 39 additions & 36 deletions

File tree

src/main/java/org/dynapi/jsonschema/gen/AnnotationParser.java

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import org.dynapi.jsonschema.gen.annotations.*;
44
import org.dynapi.jsonschema.gen.schema.*;
5-
import org.json.JSONObject;
65

76
import java.lang.reflect.Field;
87
import java.util.*;
@@ -25,15 +24,13 @@ class AnnotationParser {
2524
List<String> fieldNames = Arrays.stream(clazz.getDeclaredFields()).map(Field::getName).toList();
2625

2726
HiddenProperties hiddenProperties = clazz.getAnnotation(HiddenProperties.class);
28-
List<String> hiddenPropertiesNames = List.of(hiddenProperties != null ? hiddenProperties.value() : new String[0]);
27+
List<String> hiddenPropertiesNames = Arrays.asList(hiddenProperties != null ? hiddenProperties.value() : new String[0]);
2928

30-
List<String> diff = new ArrayList<>(hiddenPropertiesNames);
31-
diff.removeAll(fieldNames);
32-
if (!diff.isEmpty()) {
33-
throw new RuntimeException("Unknown properties to hide: " + String.join(", ", diff));
34-
}
29+
Set<String> diff = diff(fieldNames, hiddenPropertiesNames);
30+
if (!diff.isEmpty())
31+
throw new RuntimeException("Unknown properties to hide of @HiddenProperties of " + clazz.getCanonicalName() + " (" + String.join(", ", diff) + ")");
3532

36-
List<Set<String>> mutuallyExclusivesGroups = new ArrayList<>();
33+
Map<String, Set<String>> mutuallyExclusivesGroups = new HashMap<>();
3734

3835
for (Field field : clazz.getDeclaredFields()) {
3936
if (hiddenPropertiesNames.contains(field.getName()) || field.isAnnotationPresent(Hidden.class)) continue;
@@ -52,47 +49,47 @@ class AnnotationParser {
5249

5350
RequiredIf[] requiredIfAnyOf = field.getAnnotationsByType(RequiredIf.class);
5451
for (RequiredIf requiredIf : requiredIfAnyOf) {
55-
String ifField = field.getName();
56-
if (!fieldNames.contains(ifField)) {
57-
String errorMessage = String.format("Bad @RequiredIf(\"%s\") of property %s of class %s", ifField, field.getName(), clazz.getCanonicalName());
58-
throw new RuntimeException(errorMessage);
59-
}
60-
jsonSchema.requiredIf(ifField, requiredIf.value());
52+
String fieldName = field.getName();
53+
String ifField = requiredIf.value();
54+
if (!fieldNames.contains(ifField))
55+
throw new RuntimeException("unknown field of @RequiredIf of " + clazz.getCanonicalName() + "#" + fieldName + " (" + ifField + ")");
56+
jsonSchema.requiredIf(fieldName, ifField);
6157
}
6258

6359
// finds a mutually exclusive group and adds, or creates a new one
6460
MutuallyExclusiveWith mutuallyExclusiveWith = field.getAnnotation(MutuallyExclusiveWith.class);
6561
if (mutuallyExclusiveWith != null) {
66-
Set<String> group = new HashSet<>();
67-
group.add(field.getName());
68-
group.addAll(Arrays.asList(mutuallyExclusiveWith.value()));
69-
Optional<Set<String>> existingGroup = mutuallyExclusivesGroups.stream().filter(g -> !Collections.disjoint(g, group)).findFirst();
70-
if (existingGroup.isPresent()) {
71-
existingGroup.get().addAll(group);
72-
} else {
73-
mutuallyExclusivesGroups.add(group);
74-
}
62+
String fieldName = field.getName();
63+
Set<String> others = Set.of(mutuallyExclusiveWith.value());
64+
Set<String> unknownFields = diff(fieldNames, others);
65+
if (!unknownFields.isEmpty())
66+
throw new RuntimeException("Unknown field(s) of @MutuallyExclusiveWith of " + clazz.getCanonicalName() + "#" + fieldName + " (" + String.join(", ", unknownFields) + ")");
67+
mutuallyExclusivesGroups.put(fieldName, others);
7568
}
7669

7770
jsonSchema.addProperty(field.getName(), fieldSchema);
7871
}
7972

80-
// weird way of adding mutually exclusive fields
8173
if (!mutuallyExclusivesGroups.isEmpty()) {
82-
AllOf allOf = new AllOf();
83-
for (Set<String> group : mutuallyExclusivesGroups) {
84-
OneOf oneOf = new OneOf();
85-
for (String fieldName : group) {
86-
Set<String> otherFields = new HashSet<>(group);
87-
otherFields.remove(fieldName);
88-
BackdoorSchema backdoor = new BackdoorSchema()
89-
.setDirectly("required", List.of(fieldName))
90-
.setDirectly("not", new JSONObject().put("required", otherFields));
91-
oneOf.addSchema(backdoor);
74+
// we precompute and remove duplicates before adding to the schema
75+
Set<Set<String>> badPairs = new HashSet<>();
76+
for (Map.Entry<String, Set<String>> entry : mutuallyExclusivesGroups.entrySet()) {
77+
String fieldName = entry.getKey();
78+
for (String otherField : entry.getValue()) {
79+
badPairs.add(Set.of(fieldName, otherField));
9280
}
93-
allOf.addSchema(oneOf);
9481
}
95-
BackdoorSchema.addExtraSchemaDataFromTo(allOf, jsonSchema);
82+
83+
Not notAnyOfPair = new Not(new AnyOf(
84+
badPairs
85+
.stream()
86+
.map(badPair ->
87+
(Schema<?, ?>) new BackdoorSchema()
88+
.setDirectly("required", badPair)
89+
)
90+
.toList()
91+
));
92+
BackdoorSchema.addExtraSchemaDataFromTo(notAnyOfPair, jsonSchema);
9693
}
9794

9895
return jsonSchema;
@@ -209,6 +206,12 @@ protected static <T> void applyConstraints(Constraints constraints, Schema<?, T>
209206
}
210207
}
211208

209+
protected static <T> Set<T> diff(Collection<T> allowed, Collection<T> used) {
210+
Set<T> result = new HashSet<>(used);
211+
result.removeAll(allowed);
212+
return result;
213+
}
214+
212215
protected static class StateData {
213216
protected Map<String, Schema<?, ?>> references = new HashMap<>();
214217
}

0 commit comments

Comments
 (0)