Skip to content

Commit d5641e3

Browse files
committed
At last, the tests demonstrate that the custom types are being validated
1 parent 810ac68 commit d5641e3

File tree

8 files changed

+154
-45
lines changed

8 files changed

+154
-45
lines changed

core/src/main/java/org/everit/json/schema/AbstractCustomTypeSchema.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,8 @@ protected AbstractCustomTypeSchema(Schema.Builder<? extends AbstractCustomTypeSc
2929
*/
3030
public abstract Visitor buildVisitor(Object subject,ValidatingVisitor owner);
3131

32+
@Override void accept(Visitor visitor) {
33+
visitor.visitCustomTypeSchema(this);
34+
}
35+
3236
}

core/src/main/java/org/everit/json/schema/loader/LoaderConfig.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import java.net.URI;
1212
import java.util.HashMap;
13+
import java.util.List;
1314
import java.util.Map;
1415

1516
import org.everit.json.schema.FormatValidator;
@@ -30,7 +31,9 @@ static LoaderConfig defaultV4Config() {
3031

3132
final Map<String, FormatValidator> formatValidators;
3233

33-
final Map<String, Method> customTypes;
34+
final Map<String, Method> customTypesMap;
35+
36+
final Map<String, List<String>> customTypesKeywordsMap;
3437

3538
final Map<URI, Object> schemasByURI;
3639

@@ -44,17 +47,19 @@ static LoaderConfig defaultV4Config() {
4447

4548
LoaderConfig(SchemaClient schemaClient, Map<String, FormatValidator> formatValidators,
4649
SpecificationVersion specVersion, boolean useDefaults) {
47-
this(schemaClient, formatValidators, emptyMap(), specVersion, useDefaults, false, new JavaUtilRegexpFactory(), emptyMap());
50+
this(schemaClient, formatValidators, emptyMap(), specVersion, useDefaults, false, new JavaUtilRegexpFactory(), emptyMap(), emptyMap());
4851
}
4952

5053
LoaderConfig(SchemaClient schemaClient, Map<String, FormatValidator> formatValidators,
5154
Map<URI, Object> schemasByURI,
5255
SpecificationVersion specVersion, boolean useDefaults, boolean nullableSupport,
5356
RegexpFactory regexpFactory,
54-
Map<String,Method> customTypes) {
57+
Map<String,Method> customTypesMap,
58+
Map<String,List<String>> customTypesKeywordsMap) {
5559
this.schemaClient = requireNonNull(schemaClient, "schemaClient cannot be null");
5660
this.formatValidators = requireNonNull(formatValidators, "formatValidators cannot be null");
57-
this.customTypes = requireNonNull(customTypes, "customTypes cannot be null");
61+
this.customTypesMap = requireNonNull(customTypesMap, "customTypesMap cannot be null");
62+
this.customTypesKeywordsMap = requireNonNull(customTypesKeywordsMap, "customTypesKeywordsMap cannot be null");
5863
if (schemasByURI == null) {
5964
this.schemasByURI = new HashMap<>();
6065
} else {

core/src/main/java/org/everit/json/schema/loader/SchemaExtractor.java

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ abstract class AbstractSchemaExtractor implements SchemaExtractor {
100100

101101
protected JsonObject schemaJson;
102102

103-
private KeyConsumer consumedKeys;
103+
protected KeyConsumer consumedKeys;
104104

105105
final SchemaLoader defaultLoader;
106106

@@ -240,11 +240,13 @@ private ConditionalSchema.Builder buildConditionalSchema() {
240240
}
241241

242242
class TypeBasedSchemaExtractor extends AbstractSchemaExtractor {
243-
private Map<String,Method> customTypes;
243+
private Map<String,Method> customTypesMap;
244+
private Map<String,List<String>> customTypesKeywordsMap;
244245

245-
TypeBasedSchemaExtractor(SchemaLoader defaultLoader,Map<String,Method> customTypes) {
246+
TypeBasedSchemaExtractor(SchemaLoader defaultLoader, Map<String,Method> customTypesMap, Map<String,List<String>> customTypesKeywordsMap) {
246247
super(defaultLoader);
247-
this.customTypes = customTypes;
248+
this.customTypesMap = customTypesMap;
249+
this.customTypesKeywordsMap = customTypesKeywordsMap;
248250
}
249251

250252
@Override List<Schema.Builder<?>> extract() {
@@ -283,12 +285,24 @@ private Schema.Builder<?> loadForExplicitType(String typeString) {
283285
case "object":
284286
return buildObjectSchema();
285287
default:
286-
if(customTypes.containsKey(typeString)) {
288+
if(customTypesMap.containsKey(typeString)) {
287289
// Calling the public static builder method using the
288290
// Java reflection mechanisms
289-
Method builderMethod = customTypes.get(typeString);
291+
Method builderMethod = customTypesMap.get(typeString);
292+
if(builderMethod==null) {
293+
throw new SchemaException(schemaJson.ls.locationOfCurrentObj(), format("type: [%s] builder creation has failed, as type was not found", typeString));
294+
}
295+
296+
List<String> typeKeywords = customTypesKeywordsMap.get(typeString);
297+
if(typeKeywords==null) {
298+
throw new SchemaException(schemaJson.ls.locationOfCurrentObj(), format("type: [%s] builder creation has failed, as type was not found", typeString));
299+
}
290300
try {
291-
return (Schema.Builder<? extends AbstractCustomTypeSchema>) builderMethod.invoke(null);
301+
// Register the listened keywords
302+
typeKeywords.forEach(consumedKeys::keyConsumed);
303+
304+
// Now, obtain the schema loader
305+
return (Schema.Builder<? extends AbstractCustomTypeSchema>) builderMethod.invoke(null,schemaJson.ls, config(), defaultLoader);
292306
} catch(InvocationTargetException ite) {
293307
throw new SchemaException(schemaJson.ls.locationOfCurrentObj(), format("type: [%s] builder creation has failed", typeString));
294308
} catch(IllegalAccessException iae) {

core/src/main/java/org/everit/json/schema/loader/SchemaLoader.java

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import static org.everit.json.schema.loader.SpecificationVersion.DRAFT_6;
1010
import static org.everit.json.schema.loader.SpecificationVersion.DRAFT_7;
1111

12+
import java.lang.reflect.InvocationTargetException;
1213
import java.lang.reflect.Method;
1314
import java.lang.reflect.Modifier;
1415

@@ -74,7 +75,8 @@ public static class SchemaLoaderBuilder {
7475

7576
RegexpFactory regexpFactory = new JavaUtilRegexpFactory();
7677

77-
Map<String,Method> customTypes = new HashMap<>();
78+
Map<String,Method> customTypesMap = new HashMap<>();
79+
Map<String,List<String>> customTypesKeywordsMap = new HashMap<>();
7880

7981
Map<URI, Object> schemasByURI = null;
8082

@@ -89,7 +91,7 @@ public SchemaLoaderBuilder() {
8991
* a Map.Entry with the typeName and the class to register
9092
* @return {@code this}
9193
*/
92-
public SchemaLoaderBuilder addCustomType(Map.Entry<String,Class<? extends AbstractCustomTypeSchema>> entry) {
94+
public SchemaLoaderBuilder addCustomType(Map.Entry<String,Class<?>> entry) {
9395
return addCustomType(entry.getKey(),entry.getValue());
9496
}
9597

@@ -102,28 +104,54 @@ public SchemaLoaderBuilder addCustomType(Map.Entry<String,Class<? extends Abstra
102104
* the class which implements the validation of this custom JSON Schema type
103105
* @return {@code this}
104106
*/
105-
public SchemaLoaderBuilder addCustomType(String typeName,Class<? extends AbstractCustomTypeSchema> clazz) {
107+
public SchemaLoaderBuilder addCustomType(String typeName,Class<?> clazz) {
106108
typeName = requireNonNull(typeName, "the name of the custom type cannot be null");
107109
if(typeName.length() == 0) {
108110
throw new IllegalArgumentException("the name of the custom type must be non-empty");
109111
}
112+
113+
// Checking the pre-conditions
114+
Method method = null;
110115
try {
111-
Method method = clazz.getMethod("builder");
116+
method = clazz.getMethod("schemaBuilderLoader", LoadingState.class, LoaderConfig.class, SchemaLoader.class);
112117
int mods = method.getModifiers();
113118
if(!Modifier.isStatic(mods) || !Modifier.isPublic(mods)) {
114-
throw new IllegalArgumentException("class '" + clazz.getName() + "', manager of custom type '" + typeName + "' must have a public static 'builder()' method");
119+
throw new IllegalArgumentException("class '" + clazz.getName() + "', manager of custom type '" + typeName + "' must have a public static 'schemaBuilderLoader(LoadingState ls, LoaderConfig config, SchemaLoader defaultLoader)' method");
115120
}
116121
Class<?> retClazz = method.getReturnType();
117122
retClazz.asSubclass(Schema.Builder.class);
123+
} catch(NoSuchMethodException nsme) {
124+
throw new IllegalArgumentException("class '" + clazz.getName() + "', manager of custom type '" + typeName + "' must have a 'schemaBuilderLoader(LoadingState ls, LoaderConfig config, SchemaLoader defaultLoader)' method");
125+
} catch(ClassCastException cce) {
126+
throw new IllegalArgumentException("class '" + clazz.getName() + "', manager of custom type '" + typeName + "': 'schemaBuilderLoader(LoadingState ls, LoaderConfig config, SchemaLoader defaultLoader)' method must return an instance of Schema.Builder");
127+
}
128+
129+
List<String> customTypeKeywords = null;
130+
try {
131+
Method kwMethod = clazz.getMethod("schemaKeywords");
132+
int mods = method.getModifiers();
133+
if(!Modifier.isStatic(mods) || !Modifier.isPublic(mods)) {
134+
throw new IllegalArgumentException("class '" + clazz.getName() + "', manager of custom type '" + typeName + "' must have a public static 'schemaKeywords()' method");
135+
}
136+
Class<?> retClazz = kwMethod.getReturnType();
137+
retClazz.asSubclass(List.class);
118138

119-
// If all is ok
120-
customTypes.put(typeName,method);
121-
return this;
139+
// Now, obtain the list
140+
customTypeKeywords = (List<String>)kwMethod.invoke(null);
122141
} catch(NoSuchMethodException nsme) {
123-
throw new IllegalArgumentException("class '" + clazz.getName() + "', manager of custom type '" + typeName + "' must have a 'builder()' method");
142+
throw new IllegalArgumentException("class '" + clazz.getName() + "', manager of custom type '" + typeName + "' must have a 'schemaKeywords()' method");
124143
} catch(ClassCastException cce) {
125-
throw new IllegalArgumentException("class '" + clazz.getName() + "', manager of custom type '" + typeName + "': 'builder()' method must return an instance of Schema.Builder");
144+
throw new IllegalArgumentException("class '" + clazz.getName() + "', manager of custom type '" + typeName + "': 'schemaKeywords()' method must return an instance of List<String>");
145+
} catch(InvocationTargetException ite) {
146+
throw new IllegalArgumentException("class '" + clazz.getName() + "', manager of custom type '" + typeName + "' failed invoking 'schemaKeywords()' method");
147+
} catch(IllegalAccessException iae) {
148+
throw new IllegalArgumentException("class '" + clazz.getName() + "', manager of custom type '" + typeName + "' failed invoking 'schemaKeywords()' method");
126149
}
150+
151+
// If we are here, all is ok
152+
customTypesMap.put(typeName,method);
153+
customTypesKeywordsMap.put(typeName,customTypeKeywords);
154+
return this;
127155
}
128156

129157
/**
@@ -323,7 +351,7 @@ public static Schema load(final JSONObject schemaJson) {
323351
* the custom types to use on the validation process
324352
* @return the created schema
325353
*/
326-
public static Schema load(final JSONObject schemaJson, final Map<String,Class<? extends AbstractCustomTypeSchema>> customTypes) {
354+
public static Schema load(final JSONObject schemaJson, final Map<String,Class<?>> customTypes) {
327355
return SchemaLoader.load(schemaJson, new DefaultSchemaClient(), customTypes);
328356
}
329357

@@ -351,10 +379,10 @@ public static Schema load(final JSONObject schemaJson, final SchemaClient schema
351379
* the custom types to use on the validation process
352380
* @return the created schema
353381
*/
354-
public static Schema load(final JSONObject schemaJson, final SchemaClient schemaClient, final Map<String,Class<? extends AbstractCustomTypeSchema>> customTypes) {
382+
public static Schema load(final JSONObject schemaJson, final SchemaClient schemaClient, final Map<String,Class<?>> customTypes) {
355383
SchemaLoaderBuilder builder = builder();
356384
if(customTypes != null) {
357-
for(Map.Entry<String,Class<? extends AbstractCustomTypeSchema>> customTypeP: customTypes.entrySet()) {
385+
for(Map.Entry<String,Class<?>> customTypeP: customTypes.entrySet()) {
358386
builder.addCustomType(customTypeP);
359387
}
360388
}
@@ -405,7 +433,8 @@ public SchemaLoader(SchemaLoaderBuilder builder) {
405433
builder.useDefaults,
406434
builder.nullableSupport,
407435
builder.regexpFactory,
408-
builder.customTypes);
436+
builder.customTypesMap,
437+
builder.customTypesKeywordsMap);
409438
this.ls = new LoadingState(config,
410439
builder.pointerSchemas,
411440
effectiveRootSchemaJson,
@@ -474,7 +503,7 @@ private AdjacentSchemaExtractionState runSchemaExtractors(JsonObject o) {
474503
new CombinedSchemaLoader(this),
475504
new NotSchemaExtractor(this),
476505
new ConstSchemaExtractor(this),
477-
new TypeBasedSchemaExtractor(this,config.customTypes),
506+
new TypeBasedSchemaExtractor(this,config.customTypesMap,config.customTypesKeywordsMap),
478507
new PropertySnifferSchemaExtractor(this)
479508
);
480509
for (SchemaExtractor extractor : extractors) {

tests/vanilla/src/main/java/org/everit/json/schema/CustomTestSchema.java

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
import java.io.IOException;
44
import java.io.InputStreamReader;
5+
import java.util.List;
56

67
import org.apache.commons.io.IOUtils;
7-
import org.everit.json.schema.loader.SchemaLoader;
88
import org.everit.json.schema.Schema;
99
import org.json.JSONObject;
1010
import org.json.JSONTokener;
@@ -21,14 +21,15 @@
2121
import org.everit.json.schema.regexp.Regexp;
2222

2323
/**
24+
* @author jmfernandez
2425
* {@code String} schema validator.
2526
*/
2627
public class CustomTestSchema extends AbstractCustomTypeSchema {
2728

2829
/**
2930
* Builder class for {@link CustomTestSchema}.
3031
*/
31-
public static class SchemaLoaderBuilder extends Schema.Builder<CustomTestSchema> {
32+
public static class Builder extends Schema.Builder<CustomTestSchema> {
3233

3334
private String rightValue;
3435

@@ -37,16 +38,16 @@ public CustomTestSchema build() {
3738
return new CustomTestSchema(this);
3839
}
3940

40-
public SchemaLoaderBuilder rightValue(final String rightValue) {
41+
public Builder rightValue(final String rightValue) {
4142
this.rightValue = rightValue;
4243
return this;
4344
}
4445
}
4546

46-
public static SchemaLoaderBuilder builder() {
47-
return new SchemaLoaderBuilder();
47+
public static Builder builder() {
48+
return new Builder();
4849
}
49-
50+
5051
private final String rightValue;
5152

5253
public CustomTestSchema() {
@@ -59,15 +60,11 @@ public CustomTestSchema() {
5960
* @param builder
6061
* the builder object containing validation criteria
6162
*/
62-
public CustomTestSchema(final SchemaLoaderBuilder builder) {
63+
public CustomTestSchema(final Builder builder) {
6364
super(builder);
6465
this.rightValue = builder.rightValue;
6566
}
6667

67-
@Override void accept(Visitor visitor) {
68-
visitor.visitCustomTypeSchema(this);
69-
}
70-
7168
@Override
7269
public Visitor buildVisitor(Object subject,ValidatingVisitor owner) {
7370
return new CustomTypeSchemaValidatingVisitor(subject, owner);

tests/vanilla/src/main/java/org/everit/json/schema/CustomTypeSchemaValidatingVisitor.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,17 @@ public CustomTypeSchemaValidatingVisitor(Object subject, ValidatingVisitor owner
2222
this.owner = requireNonNull(owner, "failureReporter cannot be null");
2323
}
2424

25-
void visitCustomTypeSchema(CustomTestSchema customTestSchema) {
26-
this.customTestSchema = customTestSchema;
25+
void visitCustomTypeSchema(AbstractCustomTypeSchema customTestSchema) {
26+
this.customTestSchema = (CustomTestSchema)customTestSchema;
2727
if (owner.passesTypeCheck(String.class, true, false)) {
2828
rightValueSubject = (String) subject;
29-
visitRightValue(customTestSchema.rightValue());
29+
visitRightValue(this.customTestSchema.rightValue());
3030
}
3131
}
3232

3333
void visitRightValue(String rightValue) {
3434
if(rightValue != null && !rightValueSubject.equals(rightValue)) {
35-
ValidationException violation = new ValidationException(customTestSchema,"'"+rightValue+"' is not the right value ('"+rightValueSubject+"')");
35+
ValidationException violation = new ValidationException(customTestSchema,"'"+rightValueSubject+"' is not the right value ('"+rightValue+"')");
3636
owner.failure(violation);
3737
}
3838
}

tests/vanilla/src/main/java/org/everit/json/schema/CustomTypeTest.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
import java.io.InputStreamReader;
55

66
import org.apache.commons.io.IOUtils;
7+
import org.everit.json.schema.loader.CustomTestSchemaLoader;
78
import org.everit.json.schema.loader.SchemaLoader;
89
import org.everit.json.schema.Schema;
10+
import org.everit.json.schema.ValidationException;
911
import org.json.JSONObject;
1012
import org.json.JSONTokener;
1113
import org.junit.Test;
@@ -35,19 +37,34 @@ public void validateCustomType() throws IOException {
3537
IOUtils.toString(
3638
new InputStreamReader(getClass().getResourceAsStream("/org/everit/json/schema/customType/works.json")))
3739
));
40+
41+
// An easy way to register the custom types
42+
HashMap<String,Class<?>> customTypes = new HashMap<>();
43+
customTypes.put("customType",CustomTestSchemaLoader.class);
44+
Schema schema = SchemaLoader.load(jsonSchema,customTypes);
45+
46+
schema.validate(worksJson);
47+
}
48+
49+
50+
@Test(expected = ValidationException.class)
51+
public void dontValidateCustomType() throws IOException {
52+
53+
JSONObject jsonSchema = new JSONObject(new JSONTokener(
54+
IOUtils.toString(
55+
new InputStreamReader(getClass().getResourceAsStream("/org/everit/json/schema/customType/schema.json")))
56+
));
3857

3958
JSONObject failsJson = new JSONObject(new JSONTokener(
4059
IOUtils.toString(
4160
new InputStreamReader(getClass().getResourceAsStream("/org/everit/json/schema/customType/fails.json")))
4261
));
4362

4463
// An easy way to register the custom types
45-
HashMap<String,AbstractCustomTypeSchema> customTypes = new HashMap<>();
46-
customTypes.put("customType",CustomTestSchema.class);
64+
HashMap<String,Class<?>> customTypes = new HashMap<>();
65+
customTypes.put("customType",CustomTestSchemaLoader.class);
4766
Schema schema = SchemaLoader.load(jsonSchema,customTypes);
4867

49-
schema.validate(worksJson);
5068
schema.validate(failsJson);
5169
}
52-
5370
}

0 commit comments

Comments
 (0)