@@ -13,6 +13,16 @@ public sealed interface JtdSchema {
1313 /// @return Result containing errors if validation fails
1414 Jtd .Result validate (JsonValue instance );
1515
16+ /// Validates a JSON instance against this schema with optional verbose errors
17+ /// @param instance The JSON value to validate
18+ /// @param verboseErrors Whether to include full JSON values in error messages
19+ /// @return Result containing errors if validation fails
20+ default Jtd .Result validate (JsonValue instance , boolean verboseErrors ) {
21+ // Default implementation delegates to existing validate method
22+ // Individual schema implementations can override for verbose error support
23+ return validate (instance );
24+ }
25+
1626 /// Nullable schema wrapper - allows null values
1727 record NullableSchema (JtdSchema wrapped ) implements JtdSchema {
1828 @ Override
@@ -45,104 +55,123 @@ public Jtd.Result validate(JsonValue instance) {
4555 record TypeSchema (String type ) implements JtdSchema {
4656 @ Override
4757 public Jtd .Result validate (JsonValue instance ) {
58+ return validate (instance , false );
59+ }
60+
61+ @ Override
62+ public Jtd .Result validate (JsonValue instance , boolean verboseErrors ) {
4863 return switch (type ) {
49- case "boolean" -> validateBoolean (instance );
50- case "string" -> validateString (instance );
51- case "timestamp" -> validateTimestamp (instance );
52- case "int8" , "uint8" , "int16" , "uint16" , "int32" , "uint32" -> validateInteger (instance , type );
53- case "float32" , "float64" -> validateFloat (instance , type );
54- default -> Jtd .Result .failure (List .of (
55- "unknown type: " + type
56- ));
64+ case "boolean" -> validateBoolean (instance , verboseErrors );
65+ case "string" -> validateString (instance , verboseErrors );
66+ case "timestamp" -> validateTimestamp (instance , verboseErrors );
67+ case "int8" , "uint8" , "int16" , "uint16" , "int32" , "uint32" -> validateInteger (instance , type , verboseErrors );
68+ case "float32" , "float64" -> validateFloat (instance , type , verboseErrors );
69+ default -> Jtd .Result .failure (Jtd .Error .UNKNOWN_TYPE .message (type ));
5770 };
5871 }
5972
60- Jtd .Result validateBoolean (JsonValue instance ) {
73+ Jtd .Result validateBoolean (JsonValue instance , boolean verboseErrors ) {
6174 if (instance instanceof JsonBoolean ) {
6275 return Jtd .Result .success ();
6376 }
64- return Jtd .Result .failure (List .of (
65- "expected boolean, got " + instance .getClass ().getSimpleName ()
66- ));
77+ String error = verboseErrors
78+ ? Jtd .Error .EXPECTED_BOOLEAN .message (instance , instance .getClass ().getSimpleName ())
79+ : Jtd .Error .EXPECTED_BOOLEAN .message (instance .getClass ().getSimpleName ());
80+ return Jtd .Result .failure (error );
6781 }
6882
69- Jtd .Result validateString (JsonValue instance ) {
83+ Jtd .Result validateString (JsonValue instance , boolean verboseErrors ) {
7084 if (instance instanceof JsonString ) {
7185 return Jtd .Result .success ();
7286 }
73- return Jtd .Result .failure (List .of (
74- "expected string, got " + instance .getClass ().getSimpleName ()
75- ));
87+ String error = verboseErrors
88+ ? Jtd .Error .EXPECTED_STRING .message (instance , instance .getClass ().getSimpleName ())
89+ : Jtd .Error .EXPECTED_STRING .message (instance .getClass ().getSimpleName ());
90+ return Jtd .Result .failure (error );
7691 }
7792
78- Jtd .Result validateTimestamp (JsonValue instance ) {
93+ Jtd .Result validateTimestamp (JsonValue instance , boolean verboseErrors ) {
7994 if (instance instanceof JsonString ignored ) {
8095 throw new AssertionError ("not implemented" );
8196 }
82- return Jtd .Result .failure (List .of (
83- "expected timestamp (string), got " + instance .getClass ().getSimpleName ()
84- ));
97+ String error = verboseErrors
98+ ? Jtd .Error .EXPECTED_TIMESTAMP .message (instance , instance .getClass ().getSimpleName ())
99+ : Jtd .Error .EXPECTED_TIMESTAMP .message (instance .getClass ().getSimpleName ());
100+ return Jtd .Result .failure (error );
85101 }
86102
87- Jtd .Result validateInteger (JsonValue instance , String type ) {
103+ Jtd .Result validateInteger (JsonValue instance , String type , boolean verboseErrors ) {
88104 if (instance instanceof JsonNumber num ) {
89105 Number value = num .toNumber ();
90106 if (value instanceof Double d && d != Math .floor (d )) {
91- return Jtd .Result .failure (List .of (
92- "expected integer, got float"
93- ));
107+ return Jtd .Result .failure (Jtd .Error .EXPECTED_INTEGER .message ());
94108 }
95109 throw new AssertionError ("not implemented" );
96110 }
97- return Jtd .Result .failure (List .of (
98- "expected " + type + ", got " + instance .getClass ().getSimpleName ()
99- ));
111+ String error = verboseErrors
112+ ? Jtd .Error .EXPECTED_NUMERIC_TYPE .message (instance , type , instance .getClass ().getSimpleName ())
113+ : Jtd .Error .EXPECTED_NUMERIC_TYPE .message (type , instance .getClass ().getSimpleName ());
114+ return Jtd .Result .failure (error );
100115 }
101116
102- Jtd .Result validateFloat (JsonValue instance , String type ) {
117+ Jtd .Result validateFloat (JsonValue instance , String type , boolean verboseErrors ) {
103118 if (instance instanceof JsonNumber ) {
104119 return Jtd .Result .success ();
105120 }
106- return Jtd .Result .failure (List .of (
107- "expected " + type + ", got " + instance .getClass ().getSimpleName ()
108- ));
121+ String error = verboseErrors
122+ ? Jtd .Error .EXPECTED_NUMERIC_TYPE .message (instance , type , instance .getClass ().getSimpleName ())
123+ : Jtd .Error .EXPECTED_NUMERIC_TYPE .message (type , instance .getClass ().getSimpleName ());
124+ return Jtd .Result .failure (error );
109125 }
110126 }
111127
112128 /// Enum schema - validates against a set of string values
113129 record EnumSchema (List <String > values ) implements JtdSchema {
114130 @ Override
115131 public Jtd .Result validate (JsonValue instance ) {
132+ return validate (instance , false );
133+ }
134+
135+ @ Override
136+ public Jtd .Result validate (JsonValue instance , boolean verboseErrors ) {
116137 if (instance instanceof JsonString str ) {
117138 if (values .contains (str .value ())) {
118139 return Jtd .Result .success ();
119140 }
120- return Jtd .Result .failure (List .of (
121- "value '" + str .value () + "' not in enum: " + values
122- ));
141+ String error = verboseErrors
142+ ? Jtd .Error .VALUE_NOT_IN_ENUM .message (instance , str .value (), values )
143+ : Jtd .Error .VALUE_NOT_IN_ENUM .message (str .value (), values );
144+ return Jtd .Result .failure (error );
123145 }
124- return Jtd .Result .failure (List .of (
125- "expected string for enum, got " + instance .getClass ().getSimpleName ()
126- ));
146+ String error = verboseErrors
147+ ? Jtd .Error .EXPECTED_STRING_FOR_ENUM .message (instance , instance .getClass ().getSimpleName ())
148+ : Jtd .Error .EXPECTED_STRING_FOR_ENUM .message (instance .getClass ().getSimpleName ());
149+ return Jtd .Result .failure (error );
127150 }
128151 }
129152
130153 /// Elements schema - validates array elements against a schema
131154 record ElementsSchema (JtdSchema elements ) implements JtdSchema {
132155 @ Override
133156 public Jtd .Result validate (JsonValue instance ) {
157+ return validate (instance , false );
158+ }
159+
160+ @ Override
161+ public Jtd .Result validate (JsonValue instance , boolean verboseErrors ) {
134162 if (instance instanceof JsonArray arr ) {
135163 for (JsonValue element : arr .values ()) {
136- Jtd .Result result = elements .validate (element );
164+ Jtd .Result result = elements .validate (element , verboseErrors );
137165 if (!result .isValid ()) {
138166 return result ;
139167 }
140168 }
141169 return Jtd .Result .success ();
142170 }
143- return Jtd .Result .failure (List .of (
144- "expected array, got " + instance .getClass ().getSimpleName ()
145- ));
171+ String error = verboseErrors
172+ ? Jtd .Error .EXPECTED_ARRAY .message (instance , instance .getClass ().getSimpleName ())
173+ : Jtd .Error .EXPECTED_ARRAY .message (instance .getClass ().getSimpleName ());
174+ return Jtd .Result .failure (error );
146175 }
147176 }
148177
@@ -154,10 +183,16 @@ record PropertiesSchema(
154183 ) implements JtdSchema {
155184 @ Override
156185 public Jtd .Result validate (JsonValue instance ) {
186+ return validate (instance , false );
187+ }
188+
189+ @ Override
190+ public Jtd .Result validate (JsonValue instance , boolean verboseErrors ) {
157191 if (!(instance instanceof JsonObject obj )) {
158- return Jtd .Result .failure (List .of (
159- "expected object, got " + instance .getClass ().getSimpleName ()
160- ));
192+ String error = verboseErrors
193+ ? Jtd .Error .EXPECTED_OBJECT .message (instance , instance .getClass ().getSimpleName ())
194+ : Jtd .Error .EXPECTED_OBJECT .message (instance .getClass ().getSimpleName ());
195+ return Jtd .Result .failure (error );
161196 }
162197
163198 // Validate required properties
@@ -167,12 +202,10 @@ public Jtd.Result validate(JsonValue instance) {
167202
168203 JsonValue value = obj .members ().get (key );
169204 if (value == null ) {
170- return Jtd .Result .failure (List .of (
171- "missing required property: " + key
172- ));
205+ return Jtd .Result .failure (Jtd .Error .MISSING_REQUIRED_PROPERTY .message (key ));
173206 }
174207
175- Jtd .Result result = schema .validate (value );
208+ Jtd .Result result = schema .validate (value , verboseErrors );
176209 if (!result .isValid ()) {
177210 return result ;
178211 }
@@ -185,7 +218,7 @@ public Jtd.Result validate(JsonValue instance) {
185218
186219 JsonValue value = obj .members ().get (key );
187220 if (value != null ) {
188- Jtd .Result result = schema .validate (value );
221+ Jtd .Result result = schema .validate (value , verboseErrors );
189222 if (!result .isValid ()) {
190223 return result ;
191224 }
@@ -196,9 +229,7 @@ public Jtd.Result validate(JsonValue instance) {
196229 if (!additionalProperties ) {
197230 for (String key : obj .members ().keySet ()) {
198231 if (!properties .containsKey (key ) && !optionalProperties .containsKey (key )) {
199- return Jtd .Result .failure (List .of (
200- "additional property not allowed: " + key
201- ));
232+ return Jtd .Result .failure (Jtd .Error .ADDITIONAL_PROPERTY_NOT_ALLOWED .message (key ));
202233 }
203234 }
204235 }
@@ -211,14 +242,20 @@ public Jtd.Result validate(JsonValue instance) {
211242 record ValuesSchema (JtdSchema values ) implements JtdSchema {
212243 @ Override
213244 public Jtd .Result validate (JsonValue instance ) {
245+ return validate (instance , false );
246+ }
247+
248+ @ Override
249+ public Jtd .Result validate (JsonValue instance , boolean verboseErrors ) {
214250 if (!(instance instanceof JsonObject obj )) {
215- return Jtd .Result .failure (List .of (
216- "expected object, got " + instance .getClass ().getSimpleName ()
217- ));
251+ String error = verboseErrors
252+ ? Jtd .Error .EXPECTED_OBJECT .message (instance , instance .getClass ().getSimpleName ())
253+ : Jtd .Error .EXPECTED_OBJECT .message (instance .getClass ().getSimpleName ());
254+ return Jtd .Result .failure (error );
218255 }
219256
220257 for (JsonValue value : obj .members ().values ()) {
221- Jtd .Result result = values .validate (value );
258+ Jtd .Result result = values .validate (value , verboseErrors );
222259 if (!result .isValid ()) {
223260 return result ;
224261 }
@@ -235,28 +272,36 @@ record DiscriminatorSchema(
235272 ) implements JtdSchema {
236273 @ Override
237274 public Jtd .Result validate (JsonValue instance ) {
275+ return validate (instance , false );
276+ }
277+
278+ @ Override
279+ public Jtd .Result validate (JsonValue instance , boolean verboseErrors ) {
238280 if (!(instance instanceof JsonObject obj )) {
239- return Jtd .Result .failure (List .of (
240- "expected object, got " + instance .getClass ().getSimpleName ()
241- ));
281+ String error = verboseErrors
282+ ? Jtd .Error .EXPECTED_OBJECT .message (instance , instance .getClass ().getSimpleName ())
283+ : Jtd .Error .EXPECTED_OBJECT .message (instance .getClass ().getSimpleName ());
284+ return Jtd .Result .failure (error );
242285 }
243286
244287 JsonValue discriminatorValue = obj .members ().get (discriminator );
245288 if (!(discriminatorValue instanceof JsonString discStr )) {
246- return Jtd .Result .failure (List .of (
247- "discriminator '" + discriminator + "' must be a string"
248- ));
289+ String error = verboseErrors
290+ ? Jtd .Error .DISCRIMINATOR_MUST_BE_STRING .message (discriminatorValue , discriminator )
291+ : Jtd .Error .DISCRIMINATOR_MUST_BE_STRING .message (discriminator );
292+ return Jtd .Result .failure (error );
249293 }
250294
251295 String discriminatorValueStr = discStr .value ();
252296 JtdSchema variantSchema = mapping .get (discriminatorValueStr );
253297 if (variantSchema == null ) {
254- return Jtd .Result .failure (List .of (
255- "discriminator value '" + discriminatorValueStr + "' not in mapping"
256- ));
298+ String error = verboseErrors
299+ ? Jtd .Error .DISCRIMINATOR_VALUE_NOT_IN_MAPPING .message (discriminatorValue , discriminatorValueStr )
300+ : Jtd .Error .DISCRIMINATOR_VALUE_NOT_IN_MAPPING .message (discriminatorValueStr );
301+ return Jtd .Result .failure (error );
257302 }
258303
259- return variantSchema .validate (instance );
304+ return variantSchema .validate (instance , verboseErrors );
260305 }
261306 }
262307}
0 commit comments