Skip to content

Commit 239c7fd

Browse files
authored
feat: dependent required equals, auto enum validation, JSON of Path and Reader (#49)
* feat: dependent required equals, auto enum validation * test: adds test for new Reader and Path factory methods
1 parent 8577e13 commit 239c7fd

35 files changed

+508
-52
lines changed

src/main/java/org/hisp/dhis/jsontree/JsonAbstractArray.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.hisp.dhis.jsontree;
22

3+
import org.hisp.dhis.jsontree.Validation.Rule;
34
import org.hisp.dhis.jsontree.internal.Surly;
5+
import org.hisp.dhis.jsontree.validation.JsonValidator;
46

57
import java.util.Collection;
68
import java.util.Iterator;
@@ -111,4 +113,15 @@ default <T> int count( Function<E, T> toValue, Predicate<T> test ) {
111113
default E first( Predicate<E> test ) {
112114
return stream().filter( test ).findFirst().orElseGet( () -> get( size() ) );
113115
}
116+
117+
/**
118+
* @param schema the schema to validate all elements of this array against
119+
* @param rules optional set of {@link Rule}s to check, empty includes all
120+
* @throws JsonSchemaException in case this value does not match the given schema
121+
* @throws IllegalArgumentException in case the given schema is not an interface
122+
* @since 1.0
123+
*/
124+
default void validateEach(Class<? extends JsonAbstractObject<?>> schema, Rule... rules) {
125+
forEach( e -> JsonValidator.validate( e, schema, rules ) );
126+
}
114127
}

src/main/java/org/hisp/dhis/jsontree/JsonArray.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ default <E extends JsonValue> JsonMultiMap<E> getMultiMap( int index, Class<E> a
114114
return JsonAbstractCollection.asMultiMap( getObject( index ), as );
115115
}
116116

117+
118+
117119
/**
118120
* Maps this array to a lazy transformed list view where each element of the original array is transformed by the
119121
* given function when accessed.

src/main/java/org/hisp/dhis/jsontree/JsonMixed.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package org.hisp.dhis.jsontree;
22

3+
import java.io.Reader;
4+
import java.nio.file.Path;
5+
36
import static org.hisp.dhis.jsontree.Validation.NodeType.ARRAY;
47
import static org.hisp.dhis.jsontree.Validation.NodeType.BOOLEAN;
58
import static org.hisp.dhis.jsontree.Validation.NodeType.INTEGER;
@@ -73,4 +76,22 @@ static JsonMixed of( String json, JsonTypedAccessStore store ) {
7376
static JsonMixed ofNonStandard( String json ) {
7477
return of( JsonNode.ofNonStandard( json ) );
7578
}
79+
80+
/**
81+
* @param file a JSON file in UTF-8 encoding
82+
* @return root of the virtual tree representing the given JSON input
83+
* @since 1.0
84+
*/
85+
static JsonMixed of( Path file ) {
86+
return of(JsonNode.of( file ));
87+
}
88+
89+
/**
90+
* @param json JSON input
91+
* @return root of the virtual tree representing the given JSON input
92+
* @since 1.0
93+
*/
94+
static JsonMixed of( Reader json ) {
95+
return of(JsonNode.of( json, null ));
96+
}
7697
}

src/main/java/org/hisp/dhis/jsontree/JsonNode.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,14 @@
2929

3030
import org.hisp.dhis.jsontree.internal.Surly;
3131

32+
import java.io.IOException;
33+
import java.io.Reader;
3234
import java.io.Serializable;
35+
import java.io.UncheckedIOException;
36+
import java.nio.charset.Charset;
37+
import java.nio.charset.StandardCharsets;
38+
import java.nio.file.Files;
39+
import java.nio.file.Path;
3340
import java.util.Iterator;
3441
import java.util.Map.Entry;
3542
import java.util.Optional;
@@ -132,6 +139,61 @@ static JsonNode of( String json, GetListener onGet ) {
132139
return JsonTree.of( json, onGet ).get( "$" );
133140
}
134141

142+
/**
143+
* @param file a JSON file in UTF-8 encoding
144+
* @return the given JSON input as {@link JsonNode} tree
145+
* @since 1.0
146+
*/
147+
static JsonNode of( Path file ) {
148+
return of(file, null);
149+
}
150+
151+
/**
152+
* @param file a JSON file in UTF-8 encoding
153+
* @param onGet to observe all path lookup in the returned tree, may be null
154+
* @return the given JSON input as {@link JsonNode} tree
155+
* @since 1.0
156+
*/
157+
static JsonNode of( Path file, GetListener onGet ) {
158+
return of(file, StandardCharsets.UTF_8, onGet);
159+
}
160+
161+
/**
162+
* @param file a JSON file in the given encoding
163+
* @param encoding of the given file
164+
* @param onGet to observe all path lookup in the returned tree, may be null
165+
* @return the given JSON input as {@link JsonNode} tree
166+
* @implNote not optimized, added to allow transparent change of implementation later
167+
* @since 1.0
168+
*/
169+
static JsonNode of( Path file, Charset encoding, GetListener onGet ) {
170+
try {
171+
return of( Files.readString( file, encoding ), onGet );
172+
} catch ( IOException ex ) {
173+
throw new UncheckedIOException( ex );
174+
}
175+
}
176+
177+
/**
178+
* @param json JSON input
179+
* @param onGet to observe all path lookup in the returned tree, may be null
180+
* @return the given JSON input as {@link JsonNode} tree
181+
* @since 1.0
182+
* @implNote not optimized, added to allow transparent change of implementation later
183+
*/
184+
static JsonNode of( Reader json, GetListener onGet ) {
185+
char[] buffer = new char[4096]; // a usual FS block size
186+
StringBuilder jsonChars = new StringBuilder(0);
187+
int numChars;
188+
try {
189+
while ( (numChars = json.read( buffer )) >= 0 )
190+
jsonChars.append( buffer, 0, numChars );
191+
} catch ( IOException ex ) {
192+
throw new UncheckedIOException( ex );
193+
}
194+
return of(jsonChars.toString(), onGet);
195+
}
196+
135197
/**
136198
* @return the type of the node as derived from the node beginning
137199
*/

src/main/java/org/hisp/dhis/jsontree/JsonValue.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import org.hisp.dhis.jsontree.internal.Maybe;
3131
import org.hisp.dhis.jsontree.internal.Surly;
3232

33+
import java.io.Reader;
34+
import java.nio.file.Path;
3335
import java.util.List;
3436
import java.util.Optional;
3537
import java.util.function.Function;
@@ -107,6 +109,24 @@ static JsonValue of( String json, JsonTypedAccessStore store ) {
107109
return json == null || "null".equals( json ) ? JsonVirtualTree.NULL : JsonMixed.of( json, store );
108110
}
109111

112+
/**
113+
* @param file a JSON file in UTF-8 encoding
114+
* @return root of the virtual tree representing the given JSON input
115+
* @since 1.0
116+
*/
117+
static JsonValue of( Path file ) {
118+
return of(JsonNode.of( file ));
119+
}
120+
121+
/**
122+
* @param json JSON input
123+
* @return root of the virtual tree representing the given JSON input
124+
* @since 1.0
125+
*/
126+
static JsonValue of( Reader json ) {
127+
return of(JsonNode.of( json, null ));
128+
}
129+
110130
/**
111131
* If the {@link JsonValue} is not yet proxied, that means it is the unchanged underlying virtual tree, the returned
112132
* type is {@link JsonMixed}.

src/main/java/org/hisp/dhis/jsontree/Validation.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44
import org.hisp.dhis.jsontree.internal.Surly;
55

66
import java.io.Serializable;
7-
import java.lang.annotation.Annotation;
87
import java.lang.annotation.ElementType;
9-
import java.lang.annotation.Inherited;
108
import java.lang.annotation.Retention;
119
import java.lang.annotation.RetentionPolicy;
1210
import java.lang.annotation.Target;
@@ -171,6 +169,9 @@ public String toString() {
171169

172170
/**
173171
* Corresponds to JSON schema validation specified as {@code enum}.
172+
* <p>
173+
* If all values are strings and all start with a letter and none is {@code true}, {@code false} or {@code null}
174+
* then the strings do not have to be quoted.
174175
*
175176
* @return value must be equal to one of the given JSON values
176177
*/
@@ -183,6 +184,14 @@ public String toString() {
183184
*/
184185
Class<? extends Enum> enumeration() default Enum.class;
185186

187+
/**
188+
* {@link YesNo#AUTO} is not case-insensitive.
189+
*
190+
* @return to allow {@link #enumeration()} names to be of different case
191+
* @since 1.0
192+
*/
193+
YesNo caseInsensitive() default YesNo.AUTO;
194+
186195
/*
187196
Validations for Strings
188197
*/

src/main/java/org/hisp/dhis/jsontree/validation/JsonValidator.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.hisp.dhis.jsontree.validation;
22

33
import org.hisp.dhis.jsontree.JsonAbstractObject;
4+
import org.hisp.dhis.jsontree.JsonArray;
45
import org.hisp.dhis.jsontree.JsonMixed;
56
import org.hisp.dhis.jsontree.JsonPathException;
67
import org.hisp.dhis.jsontree.JsonSchemaException;

src/main/java/org/hisp/dhis/jsontree/validation/ObjectValidation.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import static org.hisp.dhis.jsontree.Validation.NodeType.NUMBER;
4646
import static org.hisp.dhis.jsontree.Validation.NodeType.OBJECT;
4747
import static org.hisp.dhis.jsontree.Validation.NodeType.STRING;
48+
import static org.hisp.dhis.jsontree.Validation.YesNo.AUTO;
4849
import static org.hisp.dhis.jsontree.Validation.YesNo.YES;
4950

5051
/**
@@ -221,7 +222,8 @@ private static PropertyValidation fromItems( AnnotatedElement src ) {
221222
@Surly
222223
private static PropertyValidation toPropertyValidation( Class<?> type ) {
223224
ValueValidation values = !type.isPrimitive() ? null : new ValueValidation( YES, Set.of(), Set.of(), List.of() );
224-
return new PropertyValidation( anyOfTypes( type ), values, null, null, null, null, null );
225+
StringValidation strings = !type.isEnum() ? null : new StringValidation( anyOfStrings( type ), AUTO,-1, -1, "" );
226+
return new PropertyValidation( anyOfTypes( type ), values, strings, null, null, null, null );
225227
}
226228

227229
@Surly
@@ -239,7 +241,7 @@ private static PropertyValidation toPropertyValidation( @Surly Validation src )
239241

240242
@Maybe
241243
private static ValueValidation toValueValidation( @Surly Validation src ) {
242-
boolean oneOfValuesEmpty = src.oneOfValues().length == 0;
244+
boolean oneOfValuesEmpty = src.oneOfValues().length == 0 || isAutoUnquotedJsonStrings( src.oneOfValues() );
243245
boolean dependentRequiresEmpty = src.dependentRequired().length == 0;
244246
if ( src.required().isAuto() && oneOfValuesEmpty && dependentRequiresEmpty ) return null;
245247
Set<String> oneOfValues = oneOfValuesEmpty
@@ -248,11 +250,29 @@ private static ValueValidation toValueValidation( @Surly Validation src ) {
248250
return new ValueValidation( src.required(), Set.of( src.dependentRequired() ), oneOfValues, List.of() );
249251
}
250252

253+
private static boolean isAutoUnquotedJsonStrings(String[] values) {
254+
return values.length > 0 && Stream.of( values ).allMatch( v -> !v.isEmpty()
255+
&& Character.isLetter( v.charAt( 0 ) )
256+
&& !"true".equals( v )
257+
&& !"false".equals( v )
258+
&& !"null".equals( v ));
259+
}
260+
251261
@Maybe
252262
private static StringValidation toStringValidation( @Surly Validation src ) {
253-
if ( src.enumeration() == Enum.class && src.minLength() < 0 && src.maxLength() < 0 && src.pattern().isEmpty() )
263+
if ( src.enumeration() == Enum.class && src.minLength() < 0 && src.maxLength() < 0 && src.pattern().isEmpty()
264+
&& src.caseInsensitive().isAuto() && !isAutoUnquotedJsonStrings( src.oneOfValues() ))
254265
return null;
255-
return new StringValidation( src.enumeration(), src.minLength(), src.maxLength(), src.pattern() );
266+
Set<String> anyOfStrings = anyOfStrings( src.enumeration() );
267+
if (anyOfStrings.isEmpty() && isAutoUnquotedJsonStrings( src.oneOfValues() ))
268+
anyOfStrings = Set.of(src.oneOfValues());
269+
return new StringValidation(anyOfStrings, src.caseInsensitive(), src.minLength(), src.maxLength(), src.pattern() );
270+
}
271+
272+
private static Set<String> anyOfStrings( @Surly Class<?> type ) {
273+
return type == Enum.class || !type.isEnum()
274+
? Set.of()
275+
: Set.copyOf( Stream.of( type.getEnumConstants() ).map( e -> ((Enum<?>)e).name() ).toList() );
256276
}
257277

258278
@Maybe

0 commit comments

Comments
 (0)