Skip to content

Commit 2e927fe

Browse files
committed
Implemented schema validation tools
1 parent 6f1b31e commit 2e927fe

File tree

10 files changed

+708
-0
lines changed

10 files changed

+708
-0
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package org.javawebstack.abstractdata;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import java.util.Objects;
6+
7+
public class AbstractPath {
8+
9+
public static final AbstractPath ROOT = new AbstractPath(null, null);
10+
11+
private final AbstractPath parent;
12+
private final String name;
13+
14+
public AbstractPath(String name) {
15+
this(ROOT, name);
16+
if(name == null || name.isEmpty())
17+
throw new IllegalArgumentException("Name can not be null or empty");
18+
}
19+
20+
private AbstractPath(AbstractPath parent, String name) {
21+
this.parent = parent;
22+
this.name = name;
23+
}
24+
25+
public String getName() {
26+
return name;
27+
}
28+
29+
public AbstractPath getParent() {
30+
return parent;
31+
}
32+
33+
public AbstractPath subPath(String name) {
34+
return new AbstractPath(this, name);
35+
}
36+
37+
public AbstractPath clone() {
38+
return new AbstractPath(
39+
this.parent != null ? this.parent.clone() : null,
40+
name
41+
);
42+
}
43+
44+
public AbstractPath concat(AbstractPath path) {
45+
AbstractPath cloned = clone();
46+
for(String part : path.getParts())
47+
cloned = cloned.subPath(part);
48+
return cloned;
49+
}
50+
51+
public List<String> getParts() {
52+
List<String> parts = parent != null ? parent.getParts() : new ArrayList<>();
53+
if(name != null)
54+
parts.add(name);
55+
return parts;
56+
}
57+
58+
public String toString() {
59+
return String.join(".", getParts());
60+
}
61+
62+
public static AbstractPath parse(String s) {
63+
s = s.trim();
64+
if(s.isEmpty())
65+
return ROOT;
66+
String[] spl = s.split("\\.");
67+
AbstractPath path = new AbstractPath(spl[0]);
68+
for(int i=1; i<spl.length; i++) {
69+
String sub = spl[i];
70+
if(sub.isEmpty())
71+
throw new IllegalArgumentException("Invalid empty sub-path");
72+
path = path.subPath(spl[i]);
73+
}
74+
return path;
75+
}
76+
77+
public boolean equals(Object obj) {
78+
if(!(obj instanceof AbstractPath))
79+
return false;
80+
AbstractPath other = (AbstractPath) obj;
81+
if(parent != null) {
82+
if(!parent.equals(other.parent))
83+
return false;
84+
} else {
85+
if(other.parent != null)
86+
return false;
87+
}
88+
if(name == null)
89+
return other.name == null;
90+
return name.equals(other.name);
91+
}
92+
93+
public int hashCode() {
94+
return Objects.hash(parent, name);
95+
}
96+
97+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package org.javawebstack.abstractdata.schema;
2+
3+
import org.javawebstack.abstractdata.AbstractArray;
4+
import org.javawebstack.abstractdata.AbstractElement;
5+
import org.javawebstack.abstractdata.AbstractPath;
6+
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
import java.util.Locale;
10+
11+
public class AbstractArraySchema implements AbstractSchema {
12+
13+
private AbstractSchema itemSchema;
14+
private Integer min;
15+
private Integer max;
16+
private boolean allowNull = false;
17+
private final List<CustomValidation<AbstractArray>> customValidations = new ArrayList<>();
18+
19+
public AbstractArraySchema itemSchema(AbstractSchema schema) {
20+
this.itemSchema = schema;
21+
return this;
22+
}
23+
24+
public AbstractArraySchema min(int min) {
25+
this.min = min;
26+
return this;
27+
}
28+
29+
public AbstractArraySchema max(int max) {
30+
this.max = max;
31+
return this;
32+
}
33+
34+
public AbstractArraySchema allowNull() {
35+
this.allowNull = true;
36+
return this;
37+
}
38+
39+
public AbstractArraySchema customValidation(CustomValidation<AbstractArray> validation) {
40+
customValidations.add(validation);
41+
return this;
42+
}
43+
44+
public AbstractSchema getItemSchema() {
45+
return itemSchema;
46+
}
47+
48+
public Integer getMin() {
49+
return min;
50+
}
51+
52+
public Integer getMax() {
53+
return max;
54+
}
55+
56+
public List<CustomValidation<AbstractArray>> getCustomValidations() {
57+
return customValidations;
58+
}
59+
60+
public List<SchemaValidationError> validate(AbstractPath path, AbstractElement value) {
61+
List<SchemaValidationError> errors = new ArrayList<>();
62+
if(value.getType() != AbstractElement.Type.ARRAY) {
63+
errors.add(new SchemaValidationError(path, "invalid_type").meta("expected", "array").meta("actual", value.getType().name().toLowerCase(Locale.ROOT)));
64+
return errors;
65+
}
66+
AbstractArray array = value.array();
67+
if(min != null && array.size() < min) {
68+
errors.add(new SchemaValidationError(path, "not_enough_items").meta("min", String.valueOf(min)).meta("actual", String.valueOf(array.size())));
69+
}
70+
if(max != null && array.size() > max) {
71+
errors.add(new SchemaValidationError(path, "too_many_items").meta("max", String.valueOf(max)).meta("actual", String.valueOf(array.size())));
72+
}
73+
if(itemSchema != null) {
74+
for(int i=0; i<array.size(); i++) {
75+
AbstractElement item = array.get(i);
76+
AbstractPath itemPath = path.subPath(String.valueOf(i));
77+
if(item.isNull()) {
78+
if(!allowNull) {
79+
errors.add(new SchemaValidationError(itemPath, "null_not_allowed"));
80+
}
81+
continue;
82+
}
83+
errors.addAll(itemSchema.validate(itemPath, array.get(i)));
84+
}
85+
}
86+
for(CustomValidation<AbstractArray> validation : customValidations) {
87+
errors.addAll(validation.validate(path, array));
88+
}
89+
return errors;
90+
}
91+
92+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package org.javawebstack.abstractdata.schema;
2+
3+
import org.javawebstack.abstractdata.AbstractElement;
4+
import org.javawebstack.abstractdata.AbstractPath;
5+
import org.javawebstack.abstractdata.AbstractPrimitive;
6+
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
import java.util.Locale;
10+
11+
public class AbstractBooleanSchema implements AbstractSchema {
12+
13+
private Boolean staticValue;
14+
private final List<CustomValidation<AbstractPrimitive>> customValidations = new ArrayList<>();
15+
16+
public AbstractBooleanSchema staticValue(boolean value) {
17+
this.staticValue = value;
18+
return this;
19+
}
20+
21+
public AbstractBooleanSchema customValidation(CustomValidation<AbstractPrimitive> validation) {
22+
customValidations.add(validation);
23+
return this;
24+
}
25+
26+
public Boolean getStaticValue() {
27+
return staticValue;
28+
}
29+
30+
public List<CustomValidation<AbstractPrimitive>> getCustomValidations() {
31+
return customValidations;
32+
}
33+
34+
public List<SchemaValidationError> validate(AbstractPath path, AbstractElement value) {
35+
List<SchemaValidationError> errors = new ArrayList<>();
36+
if(value.getType() != AbstractElement.Type.BOOLEAN) {
37+
errors.add(new SchemaValidationError(path, "invalid_type").meta("expected", "boolean").meta("actual", value.getType().name().toLowerCase(Locale.ROOT)));
38+
return errors;
39+
}
40+
if(staticValue != null && staticValue != value.bool()) {
41+
errors.add(new SchemaValidationError(path, "invalid_static_value").meta("expected", staticValue.toString()).meta("actual", value.bool().toString()));
42+
}
43+
for(CustomValidation<AbstractPrimitive> validation : customValidations) {
44+
errors.addAll(validation.validate(path, value.primitive()));
45+
}
46+
return errors;
47+
}
48+
49+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package org.javawebstack.abstractdata.schema;
2+
3+
import org.javawebstack.abstractdata.AbstractElement;
4+
import org.javawebstack.abstractdata.AbstractPath;
5+
import org.javawebstack.abstractdata.AbstractPrimitive;
6+
7+
import java.math.BigDecimal;
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
import java.util.Locale;
11+
12+
public class AbstractNumberSchema implements AbstractSchema {
13+
14+
private boolean integerOnly = false;
15+
private Number min;
16+
private Number max;
17+
private final List<CustomValidation<AbstractPrimitive>> customValidations = new ArrayList<>();
18+
19+
public AbstractNumberSchema min(Number min) {
20+
this.min = min;
21+
return this;
22+
}
23+
24+
public AbstractNumberSchema max(Number max) {
25+
this.max = max;
26+
return this;
27+
}
28+
29+
public AbstractNumberSchema integerOnly() {
30+
this.integerOnly = true;
31+
return this;
32+
}
33+
34+
public AbstractNumberSchema customValidation(CustomValidation<AbstractPrimitive> validation) {
35+
customValidations.add(validation);
36+
return this;
37+
}
38+
39+
public Number getMin() {
40+
return min;
41+
}
42+
43+
public Number getMax() {
44+
return max;
45+
}
46+
47+
public boolean isIntegerOnly() {
48+
return integerOnly;
49+
}
50+
51+
public List<CustomValidation<AbstractPrimitive>> getCustomValidations() {
52+
return customValidations;
53+
}
54+
55+
public List<SchemaValidationError> validate(AbstractPath path, AbstractElement value) {
56+
List<SchemaValidationError> errors = new ArrayList<>();
57+
if(value.getType() != AbstractElement.Type.NUMBER) {
58+
errors.add(new SchemaValidationError(path, "invalid_type").meta("expected", integerOnly ? "integer" : "number").meta("actual", value.getType().name().toLowerCase(Locale.ROOT)));
59+
return errors;
60+
}
61+
Number n = value.number();
62+
BigDecimal dN = (n instanceof Float || n instanceof Double) ? BigDecimal.valueOf(n.doubleValue()) : BigDecimal.valueOf(n.longValue());
63+
if(integerOnly && (n instanceof Float || n instanceof Double)) {
64+
errors.add(new SchemaValidationError(path, "invalid_type").meta("expected", "integer").meta("actual", "number"));
65+
return errors;
66+
}
67+
if(min != null) {
68+
BigDecimal dMin = (min instanceof Float || min instanceof Double) ? BigDecimal.valueOf(min.doubleValue()) : BigDecimal.valueOf(min.longValue());
69+
if(dN.compareTo(dMin) < 0) {
70+
errors.add(new SchemaValidationError(path, "number_smaller_than_min").meta("min", dMin.toPlainString()).meta("actual", dN.toPlainString()));
71+
}
72+
}
73+
if(max != null) {
74+
BigDecimal dMax = (max instanceof Float || min instanceof Double) ? BigDecimal.valueOf(max.doubleValue()) : BigDecimal.valueOf(max.longValue());
75+
if(dN.compareTo(dMax) > 0) {
76+
errors.add(new SchemaValidationError(path, "number_larger_than_max").meta("max", dMax.toPlainString()).meta("actual", dN.toPlainString()));
77+
}
78+
}
79+
for(CustomValidation<AbstractPrimitive> validation : customValidations) {
80+
errors.addAll(validation.validate(path, value.primitive()));
81+
}
82+
return errors;
83+
}
84+
85+
}

0 commit comments

Comments
 (0)