Skip to content

Commit 014d247

Browse files
committed
Update nested data type validation
1 parent d49e3d7 commit 014d247

File tree

5 files changed

+116
-93
lines changed

5 files changed

+116
-93
lines changed

src/main/java/com/relogiclabs/json/schema/internal/message/MatchReport.java

Lines changed: 0 additions & 31 deletions
This file was deleted.

src/main/java/com/relogiclabs/json/schema/internal/util/CollectionHelper.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,8 @@ public static <T> void addToList(Collection<T> source, T... elements) {
5353
public static <T> void addToList(Collection<T> source, Collection<? extends T>... collections) {
5454
for(var c : collections) if(c != null) source.addAll(c);
5555
}
56+
57+
public static <T> T getLast(List<T> list) {
58+
return list != null && !list.isEmpty() ? list.get(list.size() - 1) : null;
59+
}
5660
}

src/main/java/com/relogiclabs/json/schema/tree/ExceptionRegistry.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,35 @@ public class ExceptionRegistry implements Iterable<Exception> {
1212
private int disableException;
1313

1414
private final Queue<Exception> exceptions;
15+
@Getter private final Queue<Exception> tryBuffer;
1516
@Getter @Setter private boolean throwException;
16-
@Getter @Setter private int cutoffLimit = 200;
17+
@Getter @Setter private int cutoffLimit = 500;
1718

1819
public ExceptionRegistry(boolean throwException) {
1920
this.throwException = throwException;
2021
this.exceptions = new LinkedList<>();
22+
this.tryBuffer = new LinkedList<>();
2123
}
2224

23-
public void tryAdd(Exception exception) {
24-
if(disableException == 0 && exceptions.size() < cutoffLimit)
25-
exceptions.add(exception);
25+
private boolean addException(Queue<Exception> queue, Exception exception) {
26+
if(queue.size() <= cutoffLimit) queue.add(exception);
27+
return false;
2628
}
2729

28-
public void tryThrow(RuntimeException exception) {
29-
if(throwException && disableException == 0) throw exception;
30+
public boolean failWith(RuntimeException exception) {
31+
exception.fillInStackTrace();
32+
if(disableException > 0) return addException(tryBuffer, exception);
33+
if(throwException) throw exception;
34+
return addException(exceptions, exception);
3035
}
3136

3237
public <T> T tryExecute(Supplier<T> function) {
3338
try {
3439
disableException += 1;
3540
return function.get();
3641
} finally {
37-
disableException -= 1;
42+
if(disableException >= 1) disableException -= 1;
43+
else throw new IllegalStateException("Invalid runtime state");
3844
}
3945
}
4046

src/main/java/com/relogiclabs/json/schema/type/JDataType.java

Lines changed: 21 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,22 @@
55
import com.relogiclabs.json.schema.internal.builder.JDataTypeBuilder;
66
import com.relogiclabs.json.schema.internal.message.ActualHelper;
77
import com.relogiclabs.json.schema.internal.message.ExpectedHelper;
8-
import com.relogiclabs.json.schema.internal.message.MatchReport;
98
import com.relogiclabs.json.schema.message.ErrorDetail;
109
import com.relogiclabs.json.schema.util.Reference;
1110
import lombok.EqualsAndHashCode;
1211
import lombok.Getter;
1312

14-
import static com.relogiclabs.json.schema.internal.message.MatchReport.AliasError;
15-
import static com.relogiclabs.json.schema.internal.message.MatchReport.ArgumentError;
16-
import static com.relogiclabs.json.schema.internal.message.MatchReport.Success;
17-
import static com.relogiclabs.json.schema.internal.message.MatchReport.TypeError;
1813
import static com.relogiclabs.json.schema.internal.message.MessageHelper.DataTypeArgumentFailed;
1914
import static com.relogiclabs.json.schema.internal.message.MessageHelper.DataTypeMismatch;
20-
import static com.relogiclabs.json.schema.internal.message.MessageHelper.InvalidNestedDataType;
2115
import static com.relogiclabs.json.schema.internal.util.CollectionHelper.asList;
22-
import static com.relogiclabs.json.schema.internal.util.StreamHelper.allTrue;
2316
import static com.relogiclabs.json.schema.internal.util.StringHelper.concat;
2417
import static com.relogiclabs.json.schema.internal.util.StringHelper.quote;
25-
import static com.relogiclabs.json.schema.message.ErrorCode.DTYP03;
18+
import static com.relogiclabs.json.schema.message.ErrorCode.DEFI03;
19+
import static com.relogiclabs.json.schema.message.ErrorCode.DEFI04;
20+
import static com.relogiclabs.json.schema.message.ErrorCode.DTYP04;
21+
import static com.relogiclabs.json.schema.message.ErrorCode.DTYP05;
22+
import static com.relogiclabs.json.schema.message.ErrorCode.DTYP06;
23+
import static com.relogiclabs.json.schema.message.ErrorCode.DTYP07;
2624
import static com.relogiclabs.json.schema.message.MessageFormatter.formatForSchema;
2725
import static java.util.Objects.requireNonNull;
2826
import static org.apache.commons.lang3.StringUtils.isEmpty;
@@ -32,6 +30,7 @@
3230
@EqualsAndHashCode
3331
public final class JDataType extends JBranch implements NestedMode {
3432
static final String NESTED_MARKER = "*";
33+
static final String DATA_TYPE_NAME = "DATA_TYPE_NAME";
3534
private final JsonType jsonType;
3635
private final JAlias alias;
3736
private final boolean nested;
@@ -50,53 +49,29 @@ public static JDataType from(JDataTypeBuilder builder) {
5049

5150
@Override
5251
public boolean match(JNode node) {
53-
if(!nested) return isMatchCurrent(node);
54-
if(!(node instanceof JComposite composite)) return false;
55-
return composite.components().stream().map(this::isMatchCurrent).allMatch(allTrue());
56-
}
57-
58-
private boolean isMatchCurrent(JNode node) {
59-
return matchCurrent(node, new Reference<>()) == Success;
60-
}
61-
62-
private MatchReport matchCurrent(JNode node, Reference<String> error) {
63-
var result = jsonType.match(node, error) ? Success : TypeError;
64-
if(alias == null || result != Success) return result;
65-
var validator = getRuntime().getDefinitions().get(alias);
66-
if(validator == null) return AliasError;
67-
result = validator.match(node) ? Success : ArgumentError;
68-
return result;
69-
}
70-
71-
boolean matchForReport(JNode node) {
72-
if(!nested) return matchForReport(node, false);
73-
if(!(node instanceof JComposite composite))
74-
return failWith(new JsonSchemaException(
75-
new ErrorDetail(DTYP03, InvalidNestedDataType),
76-
ExpectedHelper.asInvalidNestedDataType(this),
77-
ActualHelper.asInvalidNestedDataType(node)));
78-
boolean result = true;
79-
for(var c : composite.components()) result &= matchForReport(c, true);
80-
return result;
81-
}
82-
83-
private boolean matchForReport(JNode node, boolean nested) {
8452
Reference<String> error = new Reference<>();
85-
var result = matchCurrent(node, error);
86-
if(result == TypeError) return failWith(new JsonSchemaException(
87-
new ErrorDetail(TypeError.getCode(nested),
53+
if(!jsonType.match(node, error)) return failTypeWith(new JsonSchemaException(
54+
new ErrorDetail(nested ? DTYP06 : DTYP04,
8855
formatMessage(DataTypeMismatch, error.getValue())),
8956
ExpectedHelper.asDataTypeMismatch(this),
9057
ActualHelper.asDataTypeMismatch(node)));
91-
if(result == AliasError) return failWith(new DefinitionNotFoundException(formatForSchema(
92-
AliasError.getCode(nested), "No definition found for " + quote(alias), this)));
93-
if(result == ArgumentError) return failWith(new JsonSchemaException(
94-
new ErrorDetail(ArgumentError.getCode(nested), DataTypeArgumentFailed),
58+
if(alias == null) return true;
59+
var validator = getRuntime().getDefinitions().get(alias);
60+
if(validator == null) return failWith(new DefinitionNotFoundException(
61+
formatForSchema(nested ? DEFI04 : DEFI03, "No definition found for "
62+
+ quote(alias), this)));
63+
if(!validator.match(node)) return failWith(new JsonSchemaException(
64+
new ErrorDetail(nested ? DTYP07 : DTYP05, DataTypeArgumentFailed),
9565
ExpectedHelper.asDataTypeArgumentFailed(this),
9666
ActualHelper.asDataTypeArgumentFailed(node)));
9767
return true;
9868
}
9969

70+
private boolean failTypeWith(JsonSchemaException exception) {
71+
exception.setAttribute(DATA_TYPE_NAME, toString(true));
72+
return failWith(exception);
73+
}
74+
10075
private static String formatMessage(String main, String optional) {
10176
return isEmpty(optional) ? main : concat(main, " (", uncapitalize(optional), ")");
10277
}

src/main/java/com/relogiclabs/json/schema/type/JValidator.java

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,28 @@
55
import com.relogiclabs.json.schema.internal.message.ActualHelper;
66
import com.relogiclabs.json.schema.internal.message.ExpectedHelper;
77
import com.relogiclabs.json.schema.message.ErrorDetail;
8+
import com.relogiclabs.json.schema.message.ExpectedDetail;
89
import lombok.EqualsAndHashCode;
910
import lombok.Getter;
1011

1112
import java.util.ArrayList;
13+
import java.util.LinkedList;
1214
import java.util.List;
15+
import java.util.Queue;
1316

17+
import static com.relogiclabs.json.schema.internal.message.MessageHelper.InvalidNonCompositeType;
1418
import static com.relogiclabs.json.schema.internal.message.MessageHelper.ValidationFailed;
1519
import static com.relogiclabs.json.schema.internal.util.CollectionHelper.addToList;
20+
import static com.relogiclabs.json.schema.internal.util.CollectionHelper.getLast;
1621
import static com.relogiclabs.json.schema.internal.util.StreamHelper.allTrue;
1722
import static com.relogiclabs.json.schema.internal.util.StreamHelper.anyTrue;
23+
import static com.relogiclabs.json.schema.internal.util.StringHelper.concat;
1824
import static com.relogiclabs.json.schema.internal.util.StringHelper.join;
25+
import static com.relogiclabs.json.schema.message.ErrorCode.DTYP03;
1926
import static com.relogiclabs.json.schema.message.ErrorCode.VALD01;
27+
import static com.relogiclabs.json.schema.type.JDataType.DATA_TYPE_NAME;
2028
import static java.util.Collections.unmodifiableCollection;
29+
import static lombok.AccessLevel.NONE;
2130

2231
@Getter
2332
@EqualsAndHashCode
@@ -30,13 +39,18 @@ public final class JValidator extends JBranch {
3039
private final List<JReceiver> receivers;
3140
private final boolean optional;
3241

42+
@Getter(NONE)
43+
@EqualsAndHashCode.Exclude
44+
private final Queue<Exception> exceptions;
45+
3346
private JValidator(JValidatorBuilder builder) {
3447
super(builder);
3548
value = builder.value();
3649
functions = builder.functions();
3750
dataTypes = builder.dataTypes();
3851
receivers = builder.receivers();
3952
optional = builder.optional();
53+
exceptions = new LinkedList<>();
4054
getRuntime().getReceivers().register(receivers);
4155
var nodes = new ArrayList<JNode>();
4256
addToList(nodes, value);
@@ -70,19 +84,74 @@ public boolean match(JNode node) {
7084
}
7185

7286
private boolean matchDataType(JNode node) {
73-
if(getRuntime().tryExecute(() -> checkDataType(node))) return true;
74-
dataTypes.stream().filter(d -> !d.getNested()).forEach(d -> d.matchForReport(node));
75-
dataTypes.stream().filter(d -> d.getNested()).forEach(d -> d.matchForReport(node));
87+
if(getRuntime().getExceptions().tryExecute(() -> checkDataType(node))) return true;
88+
saveTryBuffer();
89+
for(var e : exceptions) failWith((RuntimeException) e);
7690
return false;
7791
}
7892

93+
private static List<Exception> processTryBuffer(Queue<Exception> buffer) {
94+
var list = new ArrayList<Exception>(buffer.size());
95+
for(var e : buffer) {
96+
var result = mergeException(getLast(list), e);
97+
if(result != null) list.set(list.size() - 1, result);
98+
else list.add(e);
99+
}
100+
return list;
101+
}
102+
103+
private static JsonSchemaException mergeException(Exception ex1, Exception ex2) {
104+
if(!(ex1 instanceof JsonSchemaException e1)) return null;
105+
if(!(ex2 instanceof JsonSchemaException e2)) return null;
106+
if(!e1.getCode().equals(e2.getCode())) return null;
107+
var a1 = e1.getAttribute(DATA_TYPE_NAME);
108+
var a2 = e2.getAttribute(DATA_TYPE_NAME);
109+
if(a1 == null || a2 == null) return null;
110+
var result = new JsonSchemaException(e1.getError(), mergeExpected(e1, e2), e2.getActual());
111+
result.setAttribute(DATA_TYPE_NAME, a1 + a2);
112+
return result;
113+
}
114+
115+
private static ExpectedDetail mergeExpected(JsonSchemaException ex1,
116+
JsonSchemaException ex2) {
117+
var typeName = ex2.getAttribute(DATA_TYPE_NAME);
118+
ExpectedDetail detail = ex1.getExpected();
119+
return new ExpectedDetail(detail.getContext(),
120+
concat(detail.getMessage(), " or ", typeName));
121+
}
122+
79123
private boolean checkDataType(JNode node) {
80-
var list1 = dataTypes.stream().filter(d -> !d.getNested()).map(d -> d.match(node)).toList();
81-
var result1 = list1.stream().anyMatch(anyTrue());
82-
var list2 = dataTypes.stream().filter(d -> d.getNested() && (d.isApplicable(node) || !result1))
83-
.map(d -> d.match(node)).toList();
84-
var result2 = list2.stream().anyMatch(anyTrue()) || list2.size() == 0;
85-
return (result1 || list1.size() == 0) && result2;
124+
var list1 = dataTypes.stream().filter(d -> !d.getNested()).toList();
125+
var result1 = anyMatch(list1, node);
126+
var list2 = dataTypes.stream().filter(d -> d.getNested()
127+
&& (d.isApplicable(node) || !result1)).toList();
128+
if(list2.isEmpty()) return result1 || list1.isEmpty();
129+
if(!(node instanceof JComposite composite))
130+
return failWith(new JsonSchemaException(
131+
new ErrorDetail(DTYP03, InvalidNonCompositeType),
132+
ExpectedHelper.asInvalidNonCompositeType(list2.get(0)),
133+
ActualHelper.asInvalidNonCompositeType(node)));
134+
saveTryBuffer();
135+
var result2 = composite.components().stream().allMatch(n -> anyMatch(list2, n));
136+
return (result1 || list1.isEmpty()) && result2;
137+
}
138+
139+
private boolean anyMatch(List<JDataType> list, JNode node) {
140+
getTryBuffer().clear();
141+
for(var d : list) if(d.match(node)) return true;
142+
saveTryBuffer();
143+
return false;
144+
}
145+
146+
private Queue<Exception> getTryBuffer() {
147+
return getRuntime().getExceptions().getTryBuffer();
148+
}
149+
150+
private void saveTryBuffer() {
151+
Queue<Exception> tryBuffer = getTryBuffer();
152+
if(tryBuffer.isEmpty()) return;
153+
exceptions.addAll(processTryBuffer(tryBuffer));
154+
tryBuffer.clear();
86155
}
87156

88157
@Override

0 commit comments

Comments
 (0)