Skip to content

Commit 4d24932

Browse files
committed
tidy
1 parent 3b9f390 commit 4d24932

File tree

9 files changed

+180
-220
lines changed

9 files changed

+180
-220
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package json.java21.jtd;
2+
3+
/// Lightweight breadcrumb trail for human-readable error paths
4+
record Crumbs(String value) {
5+
static Crumbs root() {
6+
return new Crumbs("#");
7+
}
8+
9+
Crumbs withObjectField(String name) {
10+
return new Crumbs(value + "→field:" + name);
11+
}
12+
13+
Crumbs withArrayIndex(int idx) {
14+
return new Crumbs(value + "→item:" + idx);
15+
}
16+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package json.java21.jtd;
2+
3+
import jdk.sandbox.java.util.json.JsonValue;
4+
5+
/// Stack frame for iterative validation with path and offset tracking
6+
record Frame(JtdSchema schema, JsonValue instance, String ptr, Crumbs crumbs, String discriminatorKey) {
7+
/// Constructor for normal validation without discriminator context
8+
Frame(JtdSchema schema, JsonValue instance, String ptr, Crumbs crumbs) {
9+
this(schema, instance, ptr, crumbs, null);
10+
}
11+
12+
@Override
13+
public String toString() {
14+
final var kind = schema.getClass().getSimpleName();
15+
final var tag = (schema instanceof JtdSchema.RefSchema r) ? "(ref=" + r.ref() + ")" : "";
16+
return "Frame[schema=" + kind + tag + ", instance=" + instance + ", ptr=" + ptr +
17+
", crumbs=" + crumbs + ", discriminatorKey=" + discriminatorKey + "]";
18+
}
19+
}

json-java21-jtd/src/main/java/json/java21/jtd/Jtd.java

Lines changed: 24 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -17,38 +17,7 @@ public class Jtd {
1717

1818
/// Top-level definitions map for ref resolution
1919
private final Map<String, JtdSchema> definitions = new java.util.HashMap<>();
20-
21-
/// Stack frame for iterative validation with path and offset tracking
22-
record Frame(JtdSchema schema, JsonValue instance, String ptr, Crumbs crumbs, String discriminatorKey) {
23-
/// Constructor for normal validation without discriminator context
24-
Frame(JtdSchema schema, JsonValue instance, String ptr, Crumbs crumbs) {
25-
this(schema, instance, ptr, crumbs, null);
26-
}
27-
28-
@Override
29-
public String toString() {
30-
final var kind = schema.getClass().getSimpleName();
31-
final var tag = (schema instanceof JtdSchema.RefSchema r) ? "(ref=" + r.ref() + ")" : "";
32-
return "Frame[schema=" + kind + tag + ", instance=" + instance + ", ptr=" + ptr +
33-
", crumbs=" + crumbs + ", discriminatorKey=" + discriminatorKey + "]";
34-
}
35-
}
36-
37-
/// Lightweight breadcrumb trail for human-readable error paths
38-
record Crumbs(String value) {
39-
static Crumbs root() {
40-
return new Crumbs("#");
41-
}
42-
43-
Crumbs withObjectField(String name) {
44-
return new Crumbs(value + "→field:" + name);
45-
}
46-
47-
Crumbs withArrayIndex(int idx) {
48-
return new Crumbs(value + "→item:" + idx);
49-
}
50-
}
51-
20+
5221
/// Extracts offset from JsonValue implementation classes
5322
static int offsetOf(JsonValue v) {
5423
return switch (v) {
@@ -65,8 +34,8 @@ static int offsetOf(JsonValue v) {
6534
/// Creates an enriched error message with offset and path information
6635
static String enrichedError(String baseMessage, Frame frame, JsonValue contextValue) {
6736
int off = offsetOf(contextValue);
68-
String ptr = frame.ptr;
69-
String via = frame.crumbs.value();
37+
String ptr = frame.ptr();
38+
String via = frame.crumbs().value();
7039
return "[off=" + off + " ptr=" + ptr + " via=" + via + "] " + baseMessage;
7140
}
7241

@@ -106,25 +75,25 @@ Result validateWithStack(JtdSchema schema, JsonValue instance) {
10675
stack.push(rootFrame);
10776

10877
LOG.fine(() -> "Starting stack validation - schema=" +
109-
rootFrame.schema.getClass().getSimpleName() +
110-
(rootFrame.schema instanceof JtdSchema.RefSchema r ? "(ref=" + r.ref() + ")" : "") +
78+
rootFrame.schema().getClass().getSimpleName() +
79+
(rootFrame.schema() instanceof JtdSchema.RefSchema r ? "(ref=" + r.ref() + ")" : "") +
11180
", ptr=#");
11281

11382
// Process frames iteratively
11483
while (!stack.isEmpty()) {
11584
Frame frame = stack.pop();
116-
LOG.fine(() -> "Processing frame - schema: " + frame.schema.getClass().getSimpleName() +
117-
(frame.schema instanceof JtdSchema.RefSchema r ? "(ref=" + r.ref() + ")" : "") +
118-
", ptr: " + frame.ptr + ", off: " + offsetOf(frame.instance));
85+
LOG.fine(() -> "Processing frame - schema: " + frame.schema().getClass().getSimpleName() +
86+
(frame.schema() instanceof JtdSchema.RefSchema r ? "(ref=" + r.ref() + ")" : "") +
87+
", ptr: " + frame.ptr() + ", off: " + offsetOf(frame.instance()));
11988

12089
// Validate current frame
121-
if (!frame.schema.validateWithFrame(frame, errors, false)) {
122-
LOG.fine(() -> "Validation failed for frame at " + frame.ptr + " with " + errors.size() + " errors");
90+
if (!frame.schema().validateWithFrame(frame, errors, false)) {
91+
LOG.fine(() -> "Validation failed for frame at " + frame.ptr() + " with " + errors.size() + " errors");
12392
continue; // Continue processing other frames even if this one failed
12493
}
12594

12695
// Handle special validations for PropertiesSchema
127-
if (frame.schema instanceof JtdSchema.PropertiesSchema propsSchema) {
96+
if (frame.schema() instanceof JtdSchema.PropertiesSchema propsSchema) {
12897
validatePropertiesSchema(frame, propsSchema, errors);
12998
}
13099

@@ -179,8 +148,8 @@ void validatePropertiesSchema(Frame frame, JtdSchema.PropertiesSchema propsSchem
179148

180149
/// Pushes child frames for complex schema types
181150
void pushChildFrames(Frame frame, java.util.Deque<Frame> stack) {
182-
JtdSchema schema = frame.schema;
183-
JsonValue instance = frame.instance;
151+
JtdSchema schema = frame.schema();
152+
JsonValue instance = frame.instance();
184153

185154
LOG.finer(() -> "Pushing child frames for schema type: " + schema.getClass().getSimpleName());
186155

@@ -189,8 +158,8 @@ void pushChildFrames(Frame frame, java.util.Deque<Frame> stack) {
189158
if (instance instanceof JsonArray arr) {
190159
int index = 0;
191160
for (JsonValue element : arr.values()) {
192-
String childPtr = frame.ptr + "/" + index;
193-
Crumbs childCrumbs = frame.crumbs.withArrayIndex(index);
161+
String childPtr = frame.ptr() + "/" + index;
162+
Crumbs childCrumbs = frame.crumbs().withArrayIndex(index);
194163
Frame childFrame = new Frame(elementsSchema.elements(), element, childPtr, childCrumbs);
195164
stack.push(childFrame);
196165
LOG.finer(() -> "Pushed array element frame at " + childPtr);
@@ -214,8 +183,8 @@ void pushChildFrames(Frame frame, java.util.Deque<Frame> stack) {
214183
JsonValue value = obj.members().get(key);
215184

216185
if (value != null) {
217-
String childPtr = frame.ptr + "/" + key;
218-
Crumbs childCrumbs = frame.crumbs.withObjectField(key);
186+
String childPtr = frame.ptr() + "/" + key;
187+
Crumbs childCrumbs = frame.crumbs().withObjectField(key);
219188
Frame childFrame = new Frame(entry.getValue(), value, childPtr, childCrumbs);
220189
stack.push(childFrame);
221190
LOG.finer(() -> "Pushed required property frame at " + childPtr);
@@ -235,8 +204,8 @@ void pushChildFrames(Frame frame, java.util.Deque<Frame> stack) {
235204
JsonValue value = obj.members().get(key);
236205

237206
if (value != null) {
238-
String childPtr = frame.ptr + "/" + key;
239-
Crumbs childCrumbs = frame.crumbs.withObjectField(key);
207+
String childPtr = frame.ptr() + "/" + key;
208+
Crumbs childCrumbs = frame.crumbs().withObjectField(key);
240209
Frame childFrame = new Frame(childSchema, value, childPtr, childCrumbs);
241210
stack.push(childFrame);
242211
LOG.finer(() -> "Pushed optional property frame at " + childPtr);
@@ -250,8 +219,8 @@ void pushChildFrames(Frame frame, java.util.Deque<Frame> stack) {
250219
for (var entry : obj.members().entrySet()) {
251220
String key = entry.getKey();
252221
JsonValue value = entry.getValue();
253-
String childPtr = frame.ptr + "/" + key;
254-
Crumbs childCrumbs = frame.crumbs.withObjectField(key);
222+
String childPtr = frame.ptr() + "/" + key;
223+
Crumbs childCrumbs = frame.crumbs().withObjectField(key);
255224
Frame childFrame = new Frame(valuesSchema.values(), value, childPtr, childCrumbs);
256225
stack.push(childFrame);
257226
LOG.finer(() -> "Pushed values schema frame at " + childPtr);
@@ -266,7 +235,7 @@ void pushChildFrames(Frame frame, java.util.Deque<Frame> stack) {
266235
JtdSchema variantSchema = discSchema.mapping().get(discriminatorValueStr);
267236
if (variantSchema != null) {
268237

269-
Frame variantFrame = new Frame(variantSchema, instance, frame.ptr, frame.crumbs, discSchema.discriminator());
238+
Frame variantFrame = new Frame(variantSchema, instance, frame.ptr(), frame.crumbs(), discSchema.discriminator());
270239
stack.push(variantFrame);
271240
LOG.finer(() -> "Pushed discriminator variant frame for " + discriminatorValueStr + " with discriminator key: " + discSchema.discriminator());
272241
}
@@ -276,8 +245,8 @@ void pushChildFrames(Frame frame, java.util.Deque<Frame> stack) {
276245
case JtdSchema.RefSchema refSchema -> {
277246
try {
278247
JtdSchema resolved = refSchema.target();
279-
Frame resolvedFrame = new Frame(resolved, instance, frame.ptr,
280-
frame.crumbs, frame.discriminatorKey());
248+
Frame resolvedFrame = new Frame(resolved, instance, frame.ptr(),
249+
frame.crumbs(), frame.discriminatorKey());
281250
pushChildFrames(resolvedFrame, stack);
282251
LOG.finer(() -> "Pushed ref schema resolved to " +
283252
resolved.getClass().getSimpleName() + " for ref: " + refSchema.ref());

json-java21-jtd/src/main/java/json/java21/jtd/JtdSchema.java

Lines changed: 23 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ sealed interface JtdSchema {
2020
/// @param errors List to accumulate error messages
2121
/// @param verboseErrors Whether to include full JSON values in error messages
2222
/// @return true if validation passes, false if validation fails
23-
default boolean validateWithFrame(Jtd.Frame frame, java.util.List<String> errors, boolean verboseErrors) {
23+
default boolean validateWithFrame(Frame frame, java.util.List<String> errors, boolean verboseErrors) {
2424
// Default implementation delegates to existing validate method for backward compatibility
2525
Jtd.Result result = validate(frame.instance(), verboseErrors);
2626
if (!result.isValid()) {
@@ -50,8 +50,9 @@ public Jtd.Result validate(JsonValue instance) {
5050
return wrapped.validate(instance);
5151
}
5252

53+
@SuppressWarnings("ClassEscapesDefinedScope")
5354
@Override
54-
public boolean validateWithFrame(Jtd.Frame frame, java.util.List<String> errors, boolean verboseErrors) {
55+
public boolean validateWithFrame(Frame frame, java.util.List<String> errors, boolean verboseErrors) {
5556
if (frame.instance() instanceof JsonNull) {
5657
return true;
5758
}
@@ -67,8 +68,9 @@ public Jtd.Result validate(JsonValue instance) {
6768
return Jtd.Result.success();
6869
}
6970

71+
@SuppressWarnings("ClassEscapesDefinedScope")
7072
@Override
71-
public boolean validateWithFrame(Jtd.Frame frame, java.util.List<String> errors, boolean verboseErrors) {
73+
public boolean validateWithFrame(Frame frame, java.util.List<String> errors, boolean verboseErrors) {
7274
// Empty schema accepts any JSON value
7375
return true;
7476
}
@@ -89,10 +91,11 @@ public Jtd.Result validate(JsonValue instance) {
8991
return target().validate(instance);
9092
}
9193

94+
@SuppressWarnings("ClassEscapesDefinedScope")
9295
@Override
93-
public boolean validateWithFrame(Jtd.Frame frame, java.util.List<String> errors, boolean verboseErrors) {
96+
public boolean validateWithFrame(Frame frame, java.util.List<String> errors, boolean verboseErrors) {
9497
JtdSchema resolved = target();
95-
Jtd.Frame resolvedFrame = new Jtd.Frame(resolved, frame.instance(), frame.ptr(),
98+
Frame resolvedFrame = new Frame(resolved, frame.instance(), frame.ptr(),
9699
frame.crumbs(), frame.discriminatorKey());
97100
return resolved.validateWithFrame(resolvedFrame, errors, verboseErrors);
98101
}
@@ -127,8 +130,9 @@ public Jtd.Result validate(JsonValue instance, boolean verboseErrors) {
127130
};
128131
}
129132

133+
@SuppressWarnings("ClassEscapesDefinedScope")
130134
@Override
131-
public boolean validateWithFrame(Jtd.Frame frame, java.util.List<String> errors, boolean verboseErrors) {
135+
public boolean validateWithFrame(Frame frame, java.util.List<String> errors, boolean verboseErrors) {
132136
Jtd.Result result = validate(frame.instance(), verboseErrors);
133137
if (!result.isValid()) {
134138
// Enrich errors with offset and path information
@@ -178,60 +182,7 @@ Jtd.Result validateTimestamp(JsonValue instance, boolean verboseErrors) {
178182
: Jtd.Error.EXPECTED_TIMESTAMP.message(instance.getClass().getSimpleName());
179183
return Jtd.Result.failure(error);
180184
}
181-
182-
/// Package-protected static validation for RFC 3339 timestamp format with leap second support
183-
/// RFC 3339 grammar: date-time = full-date "T" full-time
184-
/// Supports leap seconds (seconds = 60 when minutes = 59)
185-
static boolean isValidRfc3339Timestamp(String timestamp) {
186-
// RFC 3339 regex pattern with leap second support
187-
String rfc3339Pattern = "^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})$";
188-
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(rfc3339Pattern);
189-
java.util.regex.Matcher matcher = pattern.matcher(timestamp);
190-
191-
if (!matcher.matches()) {
192-
return false;
193-
}
194-
195-
try {
196-
int year = Integer.parseInt(matcher.group(1));
197-
int month = Integer.parseInt(matcher.group(2));
198-
int day = Integer.parseInt(matcher.group(3));
199-
int hour = Integer.parseInt(matcher.group(4));
200-
int minute = Integer.parseInt(matcher.group(5));
201-
int second = Integer.parseInt(matcher.group(6));
202-
203-
// Validate basic date/time components
204-
if (year < 1 || month < 1 || month > 12 || day < 1 || day > 31) {
205-
return false;
206-
}
207-
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
208-
return false;
209-
}
210-
211-
// Handle leap seconds: seconds = 60 is valid only if minutes = 59
212-
if (second == 60) {
213-
return minute == 59;
214-
// For leap seconds, we accept the format but don't validate the specific date
215-
// This matches RFC 8927 behavior - format validation only
216-
}
217-
218-
if (second < 0 || second > 59) {
219-
return false;
220-
}
221-
222-
// For normal timestamps, delegate to OffsetDateTime.parse for full validation
223-
try {
224-
OffsetDateTime.parse(timestamp, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
225-
return true;
226-
} catch (Exception e) {
227-
return false;
228-
}
229-
230-
} catch (NumberFormatException e) {
231-
return false;
232-
}
233-
}
234-
185+
235186
Jtd.Result validateInteger(JsonValue instance, String type, boolean verboseErrors) {
236187
if (instance instanceof JsonNumber num) {
237188
Number value = num.toNumber();
@@ -314,8 +265,9 @@ public Jtd.Result validate(JsonValue instance, boolean verboseErrors) {
314265
return Jtd.Result.failure(error);
315266
}
316267

268+
@SuppressWarnings("ClassEscapesDefinedScope")
317269
@Override
318-
public boolean validateWithFrame(Jtd.Frame frame, java.util.List<String> errors, boolean verboseErrors) {
270+
public boolean validateWithFrame(Frame frame, java.util.List<String> errors, boolean verboseErrors) {
319271
Jtd.Result result = validate(frame.instance(), verboseErrors);
320272
if (!result.isValid()) {
321273
// Enrich errors with offset and path information
@@ -357,8 +309,9 @@ public Jtd.Result validate(JsonValue instance, boolean verboseErrors) {
357309
return Jtd.Result.failure(error);
358310
}
359311

312+
@SuppressWarnings("ClassEscapesDefinedScope")
360313
@Override
361-
public boolean validateWithFrame(Jtd.Frame frame, java.util.List<String> errors, boolean verboseErrors) {
314+
public boolean validateWithFrame(Frame frame, java.util.List<String> errors, boolean verboseErrors) {
362315
JsonValue instance = frame.instance();
363316

364317
if (!(instance instanceof JsonArray)) {
@@ -444,11 +397,12 @@ public Jtd.Result validate(JsonValue instance, boolean verboseErrors) {
444397
return Jtd.Result.success();
445398
}
446399

400+
@SuppressWarnings("ClassEscapesDefinedScope")
447401
@Override
448-
public boolean validateWithFrame(Jtd.Frame frame, java.util.List<String> errors, boolean verboseErrors) {
402+
public boolean validateWithFrame(Frame frame, java.util.List<String> errors, boolean verboseErrors) {
449403
JsonValue instance = frame.instance();
450404

451-
if (!(instance instanceof JsonObject obj)) {
405+
if (!(instance instanceof JsonObject)) {
452406
String error = verboseErrors
453407
? Jtd.Error.EXPECTED_OBJECT.message(instance, instance.getClass().getSimpleName())
454408
: Jtd.Error.EXPECTED_OBJECT.message(instance.getClass().getSimpleName());
@@ -494,11 +448,12 @@ public Jtd.Result validate(JsonValue instance, boolean verboseErrors) {
494448
return Jtd.Result.success();
495449
}
496450

451+
@SuppressWarnings("ClassEscapesDefinedScope")
497452
@Override
498-
public boolean validateWithFrame(Jtd.Frame frame, java.util.List<String> errors, boolean verboseErrors) {
453+
public boolean validateWithFrame(Frame frame, java.util.List<String> errors, boolean verboseErrors) {
499454
JsonValue instance = frame.instance();
500455

501-
if (!(instance instanceof JsonObject obj)) {
456+
if (!(instance instanceof JsonObject)) {
502457
String error = verboseErrors
503458
? Jtd.Error.EXPECTED_OBJECT.message(instance, instance.getClass().getSimpleName())
504459
: Jtd.Error.EXPECTED_OBJECT.message(instance.getClass().getSimpleName());
@@ -564,8 +519,9 @@ public Jtd.Result validate(JsonValue instance, boolean verboseErrors) {
564519
return variantSchema.validate(instance, verboseErrors);
565520
}
566521

522+
@SuppressWarnings("ClassEscapesDefinedScope")
567523
@Override
568-
public boolean validateWithFrame(Jtd.Frame frame, java.util.List<String> errors, boolean verboseErrors) {
524+
public boolean validateWithFrame(Frame frame, java.util.List<String> errors, boolean verboseErrors) {
569525
JsonValue instance = frame.instance();
570526

571527
if (!(instance instanceof JsonObject obj)) {

0 commit comments

Comments
 (0)