Skip to content

Commit

Permalink
Java, adds output class instantiation for Map and List use cases (#285)
Browse files Browse the repository at this point in the history
* Adds test of array instantiation with an output class

* Fixes format test error

* Adds method checking to see if there is a list output class

* Adds test of type object schema that has a map output type
  • Loading branch information
spacether committed Nov 10, 2023
1 parent a152f5a commit fbd7844
Show file tree
Hide file tree
Showing 8 changed files with 292 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ private static Object castToAllowedTypes(Object arg, List<Object> pathToItem, Pa
Object fixedVal = castToAllowedTypes(val, newPathToItem, pathToType);
argFixed.put(key, fixedVal);
}
return new FrozenMap(argFixed);
return new FrozenMap<>(argFixed);
} else if (arg instanceof Boolean) {
pathToType.put(pathToItem, Boolean.class);
return arg;
Expand All @@ -63,7 +63,7 @@ private static Object castToAllowedTypes(Object arg, List<Object> pathToItem, Pa
argFixed.add(fixedVal);
i += 1;
}
return new FrozenList(argFixed);
return new FrozenList<>(argFixed);
} else if (arg instanceof ZonedDateTime) {
pathToType.put(pathToItem, String.class);
return arg.toString();
Expand Down Expand Up @@ -104,7 +104,7 @@ private static PathToSchemasMap getPathToSchemas(Class<Schema> cls, Object arg,
return pathToSchemasMap;
}

private static LinkedHashMap<String, Object> getProperties(Object arg, List<Object> pathToItem, PathToSchemasMap pathToSchemas) {
private static FrozenMap<String, Object> getProperties(Object arg, List<Object> pathToItem, PathToSchemasMap pathToSchemas) {
LinkedHashMap<String, Object> properties = new LinkedHashMap<>();
Map<String, Object> castArg = (Map<String, Object>) arg;
for(Map.Entry<String, Object> entry: castArg.entrySet()) {
Expand All @@ -116,7 +116,7 @@ private static LinkedHashMap<String, Object> getProperties(Object arg, List<Obje
Object castValue = getNewInstance(propertyClass, value, propertyPathToItem, pathToSchemas);
properties.put(propertyName, castValue);
}
return new FrozenMap(properties);
return new FrozenMap<>(properties);
}

private static FrozenList<Object> getItems(Object arg, List<Object> pathToItem, PathToSchemasMap pathToSchemas) {
Expand All @@ -131,37 +131,35 @@ private static FrozenList<Object> getItems(Object arg, List<Object> pathToItem,
items.add(castItem);
i += 1;
}
return new FrozenList(items);
}

private static Map<Class<?>, Class<?>> getTypeToOutputClass(Class<?> cls) {
try {
// This must be implemented in Schemas that are generics as a static method
Method method = cls.getMethod("typeToOutputClass");
Map<Class<?>, Class<?>> typeToOutputClass = (Map<Class<?>, Class<?>>) method.invoke(null);
return typeToOutputClass;
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
return null;
}
return new FrozenList<>(items);
}

private static Object getNewInstance(Class<Schema> cls, Object arg, List<Object> pathToItem, PathToSchemasMap pathToSchemas) {
Object usedArg;
if (!(arg instanceof Map || arg instanceof List)) {
// str, int, float, boolean, null, FileIO, bytes
return arg;
}
if (arg instanceof Map) {
usedArg = getProperties(arg, pathToItem, pathToSchemas);
FrozenMap<String, Object> usedArg = getProperties(arg, pathToItem, pathToSchemas);
try {
Method method = cls.getMethod("getMapOutputInstance", FrozenMap.class);
return method.invoke(null, usedArg);
} catch (NoSuchMethodException e) {
return usedArg;
} catch (InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
} else if (arg instanceof List) {
usedArg = getItems(arg, pathToItem, pathToSchemas);
} else {
// str, int, float, boolean, null, FileIO, bytes
return arg;
}
Class<?> argType = arg.getClass();
Map<Class<?>, Class<?>> typeToOutputClass = getTypeToOutputClass(cls);
if (typeToOutputClass == null) {
return usedArg;
FrozenList<Object> usedArg = getItems(arg, pathToItem, pathToSchemas);
try {
Method method = cls.getMethod("getListOutputInstance", FrozenList.class);
return method.invoke(null, usedArg);
} catch (NoSuchMethodException e) {
return usedArg;
} catch (InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
Class<?> outputClass = typeToOutputClass.get(argType);
// TODO add class instantiation here
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@ private Void validateNumericFormat(Number arg, String format, ValidationMetadata
// there is a json schema test where 1.0 validates as an integer
BigInteger intArg;
if (arg instanceof Float || arg instanceof Double) {
Double doubleArg = (Double) arg;
if (Math.floor((Double) arg) != doubleArg) {
Double doubleArg;
if (arg instanceof Float) {
doubleArg = arg.doubleValue();
} else {
doubleArg = (Double) arg;
}
if (Math.floor(doubleArg) != doubleArg) {
throw new RuntimeException(
"Invalid non-integer value " + arg + " for format " + format + " at " + validationMetadata.pathToItem()
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,35 @@ public static FrozenList<Object> validate(List<Object> arg, SchemaConfiguration
}
}

class ArrayWithOutputClsSchemaList extends FrozenList<String> {
ArrayWithOutputClsSchemaList(FrozenList<? extends String> m) {
super(m);
}

public static ArrayWithOutputClsSchemaList of(List<Object> arg, SchemaConfiguration configuration) {
return ArrayWithOutputClsSchema.validate(arg, configuration);
}
}

record ArrayWithOutputClsSchema(LinkedHashSet<Class<?>> type, Class<?> items) implements Schema {
public static ArrayWithOutputClsSchema withDefaults() {
LinkedHashSet<Class<?>> type = new LinkedHashSet<>();
// can't use ImmutableList because it does not allow null values in entries
// can't use Collections.unmodifiableList because Collections.UnmodifiableList is not public + extensible
type.add(FrozenList.class);
Class<?> items = StringSchema.class;
return new ArrayWithOutputClsSchema(type, items);
}

public static ArrayWithOutputClsSchemaList getListOutputInstance(FrozenList<? extends String> arg) {
return new ArrayWithOutputClsSchemaList(arg);
}

public static ArrayWithOutputClsSchemaList validate(List<Object> arg, SchemaConfiguration configuration) {
return Schema.validate(ArrayWithOutputClsSchema.class, arg, configuration);
}
}

public class ArrayTypeSchemaTest {
static final SchemaConfiguration configuration = new SchemaConfiguration(JsonSchemaKeywordFlags.ofNone());

Expand Down Expand Up @@ -58,4 +87,29 @@ public void testValidateArrayWithItemsSchema() {
finalInList, configuration
));
}

@Test
public void testValidateArrayWithOutputClsSchema() {
// map with only item works
List<Object> inList = new ArrayList<>();
inList.add("abc");
ArrayWithOutputClsSchemaList validatedValue = ArrayWithOutputClsSchema.validate(inList, configuration);
List<Object> outList = new ArrayList<>();
outList.add("abc");
Assert.assertEquals(validatedValue, outList);

// map with no items works
inList = new ArrayList<>();
validatedValue = ArrayWithOutputClsSchema.validate(inList, configuration);
outList = new ArrayList<>();
Assert.assertEquals(validatedValue, outList);

// invalid prop type fails
inList = new ArrayList<>();
inList.add(1);
List<Object> finalInList = inList;
Assert.assertThrows(RuntimeException.class, () -> ArrayWithOutputClsSchema.validate(
finalInList, configuration
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,35 @@ public static FrozenMap<String, Object> validate(Map<String, Object> arg, Schema
}
}

class ObjectWithOutputTypeSchemaMap extends FrozenMap<String, Object> {
ObjectWithOutputTypeSchemaMap(FrozenMap<? extends String, ?> m) {
super(m);
}

public static ObjectWithOutputTypeSchemaMap of(Map<String, Object> arg, SchemaConfiguration configuration) {
return ObjectWithOutputTypeSchema.validate(arg, configuration);
}
}


record ObjectWithOutputTypeSchema(LinkedHashSet<Class<?>> type, LinkedHashMap<String, Class<?>> properties) implements Schema {
public static ObjectWithOutputTypeSchema withDefaults() {
LinkedHashSet<Class<?>> type = new LinkedHashSet<>();
type.add(FrozenMap.class);
LinkedHashMap<String, Class<?>> properties = new LinkedHashMap<>();
properties.put("someString", StringSchema.class);
return new ObjectWithOutputTypeSchema(type, properties);
}

public static ObjectWithOutputTypeSchemaMap getMapOutputInstance(FrozenMap<? extends String, ?> arg) {
return new ObjectWithOutputTypeSchemaMap(arg);
}

public static ObjectWithOutputTypeSchemaMap validate(Map<String, Object> arg, SchemaConfiguration configuration) {
return Schema.validate(ObjectWithOutputTypeSchema.class, arg, configuration);
}
}

public class ObjectTypeSchemaTest {
static final SchemaConfiguration configuration = new SchemaConfiguration(JsonSchemaKeywordFlags.ofNone());

Expand Down Expand Up @@ -161,4 +190,33 @@ public void testValidateObjectWithPropsAndAddpropsSchema() {
invalidAddpropMap, configuration
));
}

@Test
public void testValidateObjectWithOutputTypeSchema() {
// map with only property works
Map<String, Object> inMap = new LinkedHashMap<>();
inMap.put("someString", "abc");
ObjectWithOutputTypeSchemaMap validatedValue = ObjectWithOutputTypeSchema.validate(inMap, configuration);
LinkedHashMap<String, String> outMap = new LinkedHashMap<>();
outMap.put("someString", "abc");
Assert.assertEquals(validatedValue, outMap);

// map with additional unvalidated property works
inMap = new LinkedHashMap<>();
inMap.put("someString", "abc");
inMap.put("someOtherString", "def");
validatedValue = ObjectWithOutputTypeSchema.validate(inMap, configuration);
outMap = new LinkedHashMap<>();
outMap.put("someString", "abc");
outMap.put("someOtherString", "def");
Assert.assertEquals(validatedValue, outMap);

// invalid prop type fails
inMap = new LinkedHashMap<>();
inMap.put("someString", 1);
Map<String, Object> finalInMap = inMap;
Assert.assertThrows(RuntimeException.class, () -> ObjectWithOutputTypeSchema.validate(
finalInMap, configuration
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public interface Schema extends SchemaValidator {
Object fixedVal = castToAllowedTypes(val, newPathToItem, pathToType);
argFixed.put(key, fixedVal);
}
return new FrozenMap(argFixed);
return new FrozenMap<>(argFixed);
} else if (arg instanceof Boolean) {
pathToType.put(pathToItem, Boolean.class);
return arg;
Expand All @@ -63,7 +63,7 @@ public interface Schema extends SchemaValidator {
argFixed.add(fixedVal);
i += 1;
}
return new FrozenList(argFixed);
return new FrozenList<>(argFixed);
} else if (arg instanceof ZonedDateTime) {
pathToType.put(pathToItem, String.class);
return arg.toString();
Expand Down Expand Up @@ -104,7 +104,7 @@ public interface Schema extends SchemaValidator {
return pathToSchemasMap;
}

private static LinkedHashMap<String, Object> getProperties(Object arg, List<Object> pathToItem, PathToSchemasMap pathToSchemas) {
private static FrozenMap<String, Object> getProperties(Object arg, List<Object> pathToItem, PathToSchemasMap pathToSchemas) {
LinkedHashMap<String, Object> properties = new LinkedHashMap<>();
Map<String, Object> castArg = (Map<String, Object>) arg;
for(Map.Entry<String, Object> entry: castArg.entrySet()) {
Expand All @@ -116,7 +116,7 @@ public interface Schema extends SchemaValidator {
Object castValue = getNewInstance(propertyClass, value, propertyPathToItem, pathToSchemas);
properties.put(propertyName, castValue);
}
return new FrozenMap(properties);
return new FrozenMap<>(properties);
}

private static FrozenList<Object> getItems(Object arg, List<Object> pathToItem, PathToSchemasMap pathToSchemas) {
Expand All @@ -131,37 +131,35 @@ public interface Schema extends SchemaValidator {
items.add(castItem);
i += 1;
}
return new FrozenList(items);
}

private static Map<Class<?>, Class<?>> getTypeToOutputClass(Class<?> cls) {
try {
// This must be implemented in Schemas that are generics as a static method
Method method = cls.getMethod("typeToOutputClass");
Map<Class<?>, Class<?>> typeToOutputClass = (Map<Class<?>, Class<?>>) method.invoke(null);
return typeToOutputClass;
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
return null;
}
return new FrozenList<>(items);
}

private static Object getNewInstance(Class<Schema> cls, Object arg, List<Object> pathToItem, PathToSchemasMap pathToSchemas) {
Object usedArg;
if (!(arg instanceof Map || arg instanceof List)) {
// str, int, float, boolean, null, FileIO, bytes
return arg;
}
if (arg instanceof Map) {
usedArg = getProperties(arg, pathToItem, pathToSchemas);
FrozenMap<String, Object> usedArg = getProperties(arg, pathToItem, pathToSchemas);
try {
Method method = cls.getMethod("getMapOutputInstance", FrozenMap.class);
return method.invoke(null, usedArg);
} catch (NoSuchMethodException e) {
return usedArg;
} catch (InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
} else if (arg instanceof List) {
usedArg = getItems(arg, pathToItem, pathToSchemas);
} else {
// str, int, float, boolean, null, FileIO, bytes
return arg;
}
Class<?> argType = arg.getClass();
Map<Class<?>, Class<?>> typeToOutputClass = getTypeToOutputClass(cls);
if (typeToOutputClass == null) {
return usedArg;
FrozenList<Object> usedArg = getItems(arg, pathToItem, pathToSchemas);
try {
Method method = cls.getMethod("getListOutputInstance", FrozenList.class);
return method.invoke(null, usedArg);
} catch (NoSuchMethodException e) {
return usedArg;
} catch (InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
Class<?> outputClass = typeToOutputClass.get(argType);
// TODO add class instantiation here
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@ public class FormatValidator implements KeywordValidator {
// there is a json schema test where 1.0 validates as an integer
BigInteger intArg;
if (arg instanceof Float || arg instanceof Double) {
Double doubleArg = (Double) arg;
if (Math.floor((Double) arg) != doubleArg) {
Double doubleArg;
if (arg instanceof Float) {
doubleArg = arg.doubleValue();
} else {
doubleArg = (Double) arg;
}
if (Math.floor(doubleArg) != doubleArg) {
throw new RuntimeException(
"Invalid non-integer value " + arg + " for format " + format + " at " + validationMetadata.pathToItem()
);
Expand Down
Loading

0 comments on commit fbd7844

Please sign in to comment.