22
33import org .dynapi .jsonschema .gen .annotations .*;
44import org .dynapi .jsonschema .gen .schema .*;
5- import org .json .JSONObject ;
65
76import java .lang .reflect .Field ;
87import 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