Skip to content

Commit

Permalink
Implemented schema validation tools
Browse files Browse the repository at this point in the history
  • Loading branch information
JanHolger committed Nov 2, 2023
1 parent 6f1b31e commit 2e927fe
Show file tree
Hide file tree
Showing 10 changed files with 708 additions and 0 deletions.
97 changes: 97 additions & 0 deletions src/main/java/org/javawebstack/abstractdata/AbstractPath.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package org.javawebstack.abstractdata;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class AbstractPath {

public static final AbstractPath ROOT = new AbstractPath(null, null);

private final AbstractPath parent;
private final String name;

public AbstractPath(String name) {
this(ROOT, name);
if(name == null || name.isEmpty())
throw new IllegalArgumentException("Name can not be null or empty");
}

private AbstractPath(AbstractPath parent, String name) {
this.parent = parent;
this.name = name;
}

public String getName() {
return name;
}

public AbstractPath getParent() {
return parent;
}

public AbstractPath subPath(String name) {
return new AbstractPath(this, name);
}

public AbstractPath clone() {
return new AbstractPath(
this.parent != null ? this.parent.clone() : null,
name
);
}

public AbstractPath concat(AbstractPath path) {
AbstractPath cloned = clone();
for(String part : path.getParts())
cloned = cloned.subPath(part);
return cloned;
}

public List<String> getParts() {
List<String> parts = parent != null ? parent.getParts() : new ArrayList<>();
if(name != null)
parts.add(name);
return parts;
}

public String toString() {
return String.join(".", getParts());
}

public static AbstractPath parse(String s) {
s = s.trim();
if(s.isEmpty())
return ROOT;
String[] spl = s.split("\\.");
AbstractPath path = new AbstractPath(spl[0]);
for(int i=1; i<spl.length; i++) {
String sub = spl[i];
if(sub.isEmpty())
throw new IllegalArgumentException("Invalid empty sub-path");
path = path.subPath(spl[i]);
}
return path;
}

public boolean equals(Object obj) {
if(!(obj instanceof AbstractPath))
return false;
AbstractPath other = (AbstractPath) obj;
if(parent != null) {
if(!parent.equals(other.parent))
return false;
} else {
if(other.parent != null)
return false;
}
if(name == null)
return other.name == null;
return name.equals(other.name);
}

public int hashCode() {
return Objects.hash(parent, name);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package org.javawebstack.abstractdata.schema;

import org.javawebstack.abstractdata.AbstractArray;
import org.javawebstack.abstractdata.AbstractElement;
import org.javawebstack.abstractdata.AbstractPath;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

public class AbstractArraySchema implements AbstractSchema {

private AbstractSchema itemSchema;
private Integer min;
private Integer max;
private boolean allowNull = false;
private final List<CustomValidation<AbstractArray>> customValidations = new ArrayList<>();

public AbstractArraySchema itemSchema(AbstractSchema schema) {
this.itemSchema = schema;
return this;
}

public AbstractArraySchema min(int min) {
this.min = min;
return this;
}

public AbstractArraySchema max(int max) {
this.max = max;
return this;
}

public AbstractArraySchema allowNull() {
this.allowNull = true;
return this;
}

public AbstractArraySchema customValidation(CustomValidation<AbstractArray> validation) {
customValidations.add(validation);
return this;
}

public AbstractSchema getItemSchema() {
return itemSchema;
}

public Integer getMin() {
return min;
}

public Integer getMax() {
return max;
}

public List<CustomValidation<AbstractArray>> getCustomValidations() {
return customValidations;
}

public List<SchemaValidationError> validate(AbstractPath path, AbstractElement value) {
List<SchemaValidationError> errors = new ArrayList<>();
if(value.getType() != AbstractElement.Type.ARRAY) {
errors.add(new SchemaValidationError(path, "invalid_type").meta("expected", "array").meta("actual", value.getType().name().toLowerCase(Locale.ROOT)));
return errors;
}
AbstractArray array = value.array();
if(min != null && array.size() < min) {
errors.add(new SchemaValidationError(path, "not_enough_items").meta("min", String.valueOf(min)).meta("actual", String.valueOf(array.size())));
}
if(max != null && array.size() > max) {
errors.add(new SchemaValidationError(path, "too_many_items").meta("max", String.valueOf(max)).meta("actual", String.valueOf(array.size())));
}
if(itemSchema != null) {
for(int i=0; i<array.size(); i++) {
AbstractElement item = array.get(i);
AbstractPath itemPath = path.subPath(String.valueOf(i));
if(item.isNull()) {
if(!allowNull) {
errors.add(new SchemaValidationError(itemPath, "null_not_allowed"));
}
continue;
}
errors.addAll(itemSchema.validate(itemPath, array.get(i)));
}
}
for(CustomValidation<AbstractArray> validation : customValidations) {
errors.addAll(validation.validate(path, array));
}
return errors;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.javawebstack.abstractdata.schema;

import org.javawebstack.abstractdata.AbstractElement;
import org.javawebstack.abstractdata.AbstractPath;
import org.javawebstack.abstractdata.AbstractPrimitive;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

public class AbstractBooleanSchema implements AbstractSchema {

private Boolean staticValue;
private final List<CustomValidation<AbstractPrimitive>> customValidations = new ArrayList<>();

public AbstractBooleanSchema staticValue(boolean value) {
this.staticValue = value;
return this;
}

public AbstractBooleanSchema customValidation(CustomValidation<AbstractPrimitive> validation) {
customValidations.add(validation);
return this;
}

public Boolean getStaticValue() {
return staticValue;
}

public List<CustomValidation<AbstractPrimitive>> getCustomValidations() {
return customValidations;
}

public List<SchemaValidationError> validate(AbstractPath path, AbstractElement value) {
List<SchemaValidationError> errors = new ArrayList<>();
if(value.getType() != AbstractElement.Type.BOOLEAN) {
errors.add(new SchemaValidationError(path, "invalid_type").meta("expected", "boolean").meta("actual", value.getType().name().toLowerCase(Locale.ROOT)));
return errors;
}
if(staticValue != null && staticValue != value.bool()) {
errors.add(new SchemaValidationError(path, "invalid_static_value").meta("expected", staticValue.toString()).meta("actual", value.bool().toString()));
}
for(CustomValidation<AbstractPrimitive> validation : customValidations) {
errors.addAll(validation.validate(path, value.primitive()));
}
return errors;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package org.javawebstack.abstractdata.schema;

import org.javawebstack.abstractdata.AbstractElement;
import org.javawebstack.abstractdata.AbstractPath;
import org.javawebstack.abstractdata.AbstractPrimitive;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

public class AbstractNumberSchema implements AbstractSchema {

private boolean integerOnly = false;
private Number min;
private Number max;
private final List<CustomValidation<AbstractPrimitive>> customValidations = new ArrayList<>();

public AbstractNumberSchema min(Number min) {
this.min = min;
return this;
}

public AbstractNumberSchema max(Number max) {
this.max = max;
return this;
}

public AbstractNumberSchema integerOnly() {
this.integerOnly = true;
return this;
}

public AbstractNumberSchema customValidation(CustomValidation<AbstractPrimitive> validation) {
customValidations.add(validation);
return this;
}

public Number getMin() {
return min;
}

public Number getMax() {
return max;
}

public boolean isIntegerOnly() {
return integerOnly;
}

public List<CustomValidation<AbstractPrimitive>> getCustomValidations() {
return customValidations;
}

public List<SchemaValidationError> validate(AbstractPath path, AbstractElement value) {
List<SchemaValidationError> errors = new ArrayList<>();
if(value.getType() != AbstractElement.Type.NUMBER) {
errors.add(new SchemaValidationError(path, "invalid_type").meta("expected", integerOnly ? "integer" : "number").meta("actual", value.getType().name().toLowerCase(Locale.ROOT)));
return errors;
}
Number n = value.number();
BigDecimal dN = (n instanceof Float || n instanceof Double) ? BigDecimal.valueOf(n.doubleValue()) : BigDecimal.valueOf(n.longValue());
if(integerOnly && (n instanceof Float || n instanceof Double)) {
errors.add(new SchemaValidationError(path, "invalid_type").meta("expected", "integer").meta("actual", "number"));
return errors;
}
if(min != null) {
BigDecimal dMin = (min instanceof Float || min instanceof Double) ? BigDecimal.valueOf(min.doubleValue()) : BigDecimal.valueOf(min.longValue());
if(dN.compareTo(dMin) < 0) {
errors.add(new SchemaValidationError(path, "number_smaller_than_min").meta("min", dMin.toPlainString()).meta("actual", dN.toPlainString()));
}
}
if(max != null) {
BigDecimal dMax = (max instanceof Float || min instanceof Double) ? BigDecimal.valueOf(max.doubleValue()) : BigDecimal.valueOf(max.longValue());
if(dN.compareTo(dMax) > 0) {
errors.add(new SchemaValidationError(path, "number_larger_than_max").meta("max", dMax.toPlainString()).meta("actual", dN.toPlainString()));
}
}
for(CustomValidation<AbstractPrimitive> validation : customValidations) {
errors.addAll(validation.validate(path, value.primitive()));
}
return errors;
}

}
Loading

0 comments on commit 2e927fe

Please sign in to comment.