yeptris 0.6.57.1-arm-linux-eabihf → 0.6.58.1-arm-linux-eabihf

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1022 @@
1
+ /* jsonschema.c — the JSON Schema draft 2020-12 keyword subset
2
+ * (#882 slice 2).
3
+ *
4
+ * The schema and the target are both parsed with the same engine; the
5
+ * walk applies the schema tree to the instance tree and emits
6
+ * diagnostics at instance JSON-pointer paths. Subset: type, enum,
7
+ * const, pattern, minimum/maximum/exclusive*, minLength/maxLength
8
+ * (code points), minItems/maxItems, uniqueItems, properties, required,
9
+ * additionalProperties (bool or schema), items (+ prefixItems; the
10
+ * draft-07 array form rides as prefixItems), allOf/anyOf/oneOf/not,
11
+ * $ref (LOCAL "#/..." pointers — $defs and definitions both resolve),
12
+ * boolean schemas. Unknown keywords, and known keywords whose values
13
+ * cannot be read, are IGNORED (2020-12's posture) — an unreadable
14
+ * constraint never guesses.
15
+ *
16
+ * Instance types ride the core-schema resolution (the YAML->JSON data
17
+ * model): "y" is boolean, 1.0 is integer (2020-12: zero fractional
18
+ * part), floats and ints are both numbers.
19
+ *
20
+ * Guards: the walk depth is the DOM's own parse-bounded depth; a
21
+ * consecutive-$ref chain (schema-only cycles that consume no instance
22
+ * structure) caps at 64 expansions and reports DIAG_REF. Every array
23
+ * is bounded; allocation failure flags the context and unwinds.
24
+ */
25
+
26
+ #include <stdio.h>
27
+ #include <string.h>
28
+
29
+ #include <yeptris/validate.h>
30
+
31
+ #include "../memory/allocator.h"
32
+ #include "../parse/numbers.h"
33
+ #include "../resolve/resolver.h"
34
+ #include "internal.h"
35
+
36
+ #define JS_MAX_REF_CHAIN 64
37
+ #define JS_MAX_TOKENS 256
38
+ #define JS_MAX_UNIQUE 4096 /* uniqueItems' hash table cap; over: O(n^2) probe */
39
+
40
+ typedef struct {
41
+ yep_vctx v; /* instance-side context (path, report, guards) */
42
+ const yep_dom* sd; /* the schema document's tree */
43
+ uint32_t sroot; /* the schema's document root */
44
+ } js_ctx;
45
+
46
+ /* ---- schema-side facts -------------------------------------------------- */
47
+
48
+ static const yep_dnode* snode(const js_ctx* j, uint32_t id) {
49
+ return yep_dom_node(j->sd, id);
50
+ }
51
+
52
+ /* First pair of the mapping whose key bytes equal `name`; 1 + *val_out,
53
+ * 0 absent. Only scalar keys match (collection keys never will). */
54
+ static int kw_get(const js_ctx* j, uint32_t map_id, const char* name, uint32_t* val_out) {
55
+ const yep_dnode* m = snode(j, map_id);
56
+ if (m == NULL || m->kind != YEP_DOM_MAPPING) {
57
+ return 0;
58
+ }
59
+ size_t nl = strlen(name);
60
+ uint32_t k = m->first_child;
61
+ while (k != UINT32_MAX) {
62
+ uint32_t v = snode(j, k) ? snode(j, k)->next_sibling : UINT32_MAX;
63
+ const yep_dnode* kn = snode(j, k);
64
+ if (kn != NULL && kn->kind == YEP_DOM_SCALAR) {
65
+ yep_view kv = yep_dom_view(j->sd, kn->value);
66
+ if (kv.len == nl && (nl == 0 || memcmp(kv.p, name, nl) == 0)) {
67
+ if (val_out != NULL) {
68
+ *val_out = v;
69
+ }
70
+ return 1;
71
+ }
72
+ }
73
+ k = v != UINT32_MAX && snode(j, v) ? snode(j, v)->next_sibling : UINT32_MAX;
74
+ }
75
+ return 0;
76
+ }
77
+
78
+ /* A schema-valued keyword: a mapping, or the boolean schemas. */
79
+ static int schema_truth(const js_ctx* j, uint32_t id, int* is_false) {
80
+ const yep_dnode* n = snode(j, id);
81
+ if (n == NULL) {
82
+ *is_false = 0;
83
+ return 0;
84
+ }
85
+ if (n->kind == YEP_DOM_MAPPING) {
86
+ *is_false = 0;
87
+ return 1;
88
+ }
89
+ if (n->kind == YEP_DOM_SCALAR && yep_validate_scalar_tag(n) == YEPTRIS_TAG_BOOL) {
90
+ yep_view v = yep_dom_view(j->sd, n->value);
91
+ *is_false = (v.len == 5 && memcmp(v.p, "false", 5) == 0);
92
+ return 1; /* true/false both carry meaning */
93
+ }
94
+ *is_false = 0;
95
+ return 0; /* an unreadable schema: ignored */
96
+ }
97
+
98
+ static int kw_schema(const js_ctx* j, uint32_t map_id, const char* name, uint32_t* schema_out,
99
+ int* is_false_out) {
100
+ uint32_t val;
101
+ if (!kw_get(j, map_id, name, &val) || val == UINT32_MAX) {
102
+ return 0;
103
+ }
104
+ int is_false = 0;
105
+ if (!schema_truth(j, val, &is_false)) {
106
+ return 0;
107
+ }
108
+ *schema_out = val;
109
+ *is_false_out = is_false;
110
+ return 1;
111
+ }
112
+
113
+ /* ---- instance-side facts ------------------------------------------------ */
114
+
115
+ /* Resolves an alias to the real node id (one hop; the DOM's targets
116
+ * are real nodes). */
117
+ static uint32_t inst_real(const yep_dom* d, uint32_t id) {
118
+ const yep_dnode* n = yep_dom_node(d, id);
119
+ if (n != NULL && n->kind == YEP_DOM_ALIAS && n->target != UINT32_MAX) {
120
+ return n->target;
121
+ }
122
+ return id;
123
+ }
124
+
125
+ /* The instance's JSON type word (7 bytes max); *integer receives 1 when
126
+ * a float also satisfies 2020-12's zero-fraction rule. */
127
+ static void inst_type(const yep_dom* d, uint32_t id, const char** word, int* integer) {
128
+ *integer = 0;
129
+ const yep_dnode* n = yep_dom_node(d, id);
130
+ if (n == NULL) {
131
+ *word = "string";
132
+ return;
133
+ }
134
+ if (n->kind == YEP_DOM_ALIAS) {
135
+ if (n->target != UINT32_MAX) {
136
+ inst_type(d, n->target, word, integer);
137
+ } else {
138
+ *word = "string";
139
+ }
140
+ return;
141
+ }
142
+ if (n->kind == YEP_DOM_SEQUENCE) {
143
+ *word = "array";
144
+ return;
145
+ }
146
+ if (n->kind == YEP_DOM_MAPPING) {
147
+ *word = "object";
148
+ return;
149
+ }
150
+ switch (yep_validate_scalar_tag(n)) {
151
+ case YEPTRIS_TAG_NULL:
152
+ *word = "null";
153
+ break;
154
+ case YEPTRIS_TAG_BOOL:
155
+ *word = "boolean";
156
+ break;
157
+ case YEPTRIS_TAG_INT:
158
+ *word = "integer";
159
+ *integer = 1;
160
+ break;
161
+ case YEPTRIS_TAG_FLOAT: {
162
+ yep_view v = yep_dom_view(d, n->value);
163
+ double dv;
164
+ /* 2020-12: "integer" means a zero fractional part. The range
165
+ * guard must read the PARSED value (it once sat before the
166
+ * parse, saw dv==0, folded constant-true, and left the cast
167
+ * UB on out-of-range/NaN doubles). No libm: in int64 range a
168
+ * cast round-trips iff the fractional part is zero; outside
169
+ * it every finite double is already integral; NaN is not. */
170
+ if (yep_num_f64(v.p, v.len, &dv) == 0) {
171
+ const int in_i64 = dv >= -9223372036854775808.0 && dv < 9223372036854775808.0;
172
+ if (in_i64 ? dv == (double)(int64_t)dv : dv == dv) {
173
+ *word = "integer";
174
+ *integer = 1;
175
+ } else {
176
+ *word = "number";
177
+ }
178
+ } else {
179
+ *word = "number";
180
+ }
181
+ break;
182
+ }
183
+ default:
184
+ *word = "string";
185
+ break;
186
+ }
187
+ }
188
+
189
+ /* The instance's numeric value (int or float); 0 when not numeric. */
190
+ static int inst_number(const yep_dom* d, uint32_t id, double* out) {
191
+ const yep_dnode* n = yep_dom_node(d, id);
192
+ if (n == NULL) {
193
+ return 0;
194
+ }
195
+ if (n->kind == YEP_DOM_ALIAS) {
196
+ return n->target != UINT32_MAX && inst_number(d, n->target, out);
197
+ }
198
+ if (n->kind != YEP_DOM_SCALAR) {
199
+ return 0;
200
+ }
201
+ uint8_t tag = yep_validate_scalar_tag(n);
202
+ if (tag != YEPTRIS_TAG_INT && tag != YEPTRIS_TAG_FLOAT) {
203
+ return 0;
204
+ }
205
+ yep_view v = yep_dom_view(d, n->value);
206
+ int64_t iv = 0;
207
+ if (yep_num_i64(v.p, v.len, &iv) == 0) {
208
+ *out = (double)iv;
209
+ return 1;
210
+ }
211
+ return yep_num_f64(v.p, v.len, out) == 0;
212
+ }
213
+
214
+ static int inst_seq_count(const yep_dom* d, uint32_t id, uint32_t* count) {
215
+ const yep_dnode* n = yep_dom_node(d, id);
216
+ if (n == NULL || n->kind != YEP_DOM_SEQUENCE) {
217
+ return 0;
218
+ }
219
+ *count = n->count;
220
+ return 1;
221
+ }
222
+
223
+ /* ---- the walk ----------------------------------------------------------- */
224
+
225
+ static void js_apply(js_ctx* j, uint32_t schema_id, uint32_t inst_id, int depth);
226
+
227
+ /* A suppressed probe: does `schema_id` hold at `inst_id`? Diagnostics
228
+ * during the trial are swallowed. */
229
+ static int js_holds(js_ctx* j, uint32_t schema_id, uint32_t inst_id, int depth) {
230
+ int save = j->v.trial_hit;
231
+ j->v.trial_hit = 0;
232
+ j->v.suppress++;
233
+ js_apply(j, schema_id, inst_id, depth);
234
+ j->v.suppress--;
235
+ int hit = j->v.trial_hit;
236
+ j->v.trial_hit = save;
237
+ return !hit;
238
+ }
239
+
240
+ /* Local "#/..." pointer resolution against the schema document. */
241
+ static int resolve_ref(js_ctx* j, const char* p, uint32_t plen, uint32_t* out) {
242
+ if (plen == 0 || p[0] != '#') {
243
+ return 0; /* remote/anchor forms are out of the subset */
244
+ }
245
+ uint32_t cur = j->sroot;
246
+ size_t i = 1;
247
+ if (i >= plen || p[i] != '/') {
248
+ *out = cur; /* "#" alone is the root */
249
+ return i == plen;
250
+ }
251
+ int tokens = 0;
252
+ while (i < plen) {
253
+ if (p[i] != '/') {
254
+ return 0;
255
+ }
256
+ i++;
257
+ char token[128];
258
+ size_t tl = 0;
259
+ while (i < plen && p[i] != '/') {
260
+ char b = p[i];
261
+ if (b == '~' && i + 1 < plen && (p[i + 1] == '0' || p[i + 1] == '1')) {
262
+ b = p[i + 1] == '0' ? '~' : '/';
263
+ i++;
264
+ } else if (b == '~') {
265
+ return 0; /* a dangling ~ escape */
266
+ }
267
+ if (tl + 1 >= sizeof(token)) {
268
+ return 0;
269
+ }
270
+ token[tl++] = b;
271
+ i++;
272
+ }
273
+ if (++tokens > JS_MAX_TOKENS) {
274
+ return 0;
275
+ }
276
+ const yep_dnode* n = snode(j, cur);
277
+ if (n == NULL) {
278
+ return 0;
279
+ }
280
+ if (n->kind == YEP_DOM_SEQUENCE) {
281
+ /* a decimal array index (JSON pointer: no leading zeros,
282
+ * but be lenient — the pointer either resolves or not) */
283
+ uint32_t idx = 0;
284
+ if (tl == 0) {
285
+ return 0;
286
+ }
287
+ for (size_t t = 0; t < tl; t++) {
288
+ if (token[t] < '0' || token[t] > '9' || idx > 999999) {
289
+ return 0;
290
+ }
291
+ idx = idx * 10 + (uint32_t)(token[t] - '0');
292
+ }
293
+ uint32_t count = 0;
294
+ if (!inst_seq_count(j->sd, cur, &count) || idx >= count) {
295
+ return 0;
296
+ }
297
+ uint32_t ch = n->first_child;
298
+ while (ch != UINT32_MAX && idx > 0) {
299
+ ch = snode(j, ch) ? snode(j, ch)->next_sibling : UINT32_MAX;
300
+ idx--;
301
+ }
302
+ cur = ch;
303
+ } else if (n->kind == YEP_DOM_MAPPING) {
304
+ uint32_t k = n->first_child;
305
+ int found = 0;
306
+ while (k != UINT32_MAX && !found) {
307
+ uint32_t v = snode(j, k) ? snode(j, k)->next_sibling : UINT32_MAX;
308
+ const yep_dnode* kn = snode(j, k);
309
+ if (kn != NULL && kn->kind == YEP_DOM_SCALAR) {
310
+ yep_view kv = yep_dom_view(j->sd, kn->value);
311
+ if (kv.len == tl && (tl == 0 || memcmp(kv.p, token, tl) == 0)) {
312
+ cur = v;
313
+ found = 1;
314
+ }
315
+ }
316
+ k = v != UINT32_MAX && snode(j, v) ? snode(j, v)->next_sibling : UINT32_MAX;
317
+ }
318
+ if (!found) {
319
+ return 0;
320
+ }
321
+ } else {
322
+ return 0; /* a pointer into a scalar */
323
+ }
324
+ }
325
+ *out = cur;
326
+ return cur != UINT32_MAX;
327
+ }
328
+
329
+ /* uniqueItems: canonical hash buckets, canonical equality inside a
330
+ * bucket; allocation failure skips the check (oom flags the walk). */
331
+ static void check_unique(js_ctx* j, uint32_t seq_id, uint32_t inst_off) {
332
+ const yep_dom* d = j->v.dom;
333
+ const yep_dnode* s = yep_dom_node(d, seq_id);
334
+ uint32_t n = s->count;
335
+ if (n < 2) {
336
+ return;
337
+ }
338
+ const yep_allocator* sys = yep_system_allocator();
339
+ uint32_t cap = 8;
340
+ while (cap < n * 2 && cap < JS_MAX_UNIQUE) {
341
+ cap *= 2;
342
+ }
343
+ /* cap never reaches n when n exceeds JS_MAX_UNIQUE: elements beyond
344
+ * a FULL table are unchecked below (a 100%%-full table cannot probe
345
+ * — the linear scan would spin forever). Bounded memory, bounded
346
+ * time; the check degrades to the first `cap` distinct entries. */
347
+ struct {
348
+ uint32_t hash;
349
+ uint32_t node; /* +1 encoded */
350
+ }* tbl = yep_alloc(sys, (size_t)cap * sizeof(*tbl));
351
+ if (tbl == NULL) {
352
+ j->v.oom = 1;
353
+ return;
354
+ }
355
+ memset(tbl, 0, (size_t)cap * sizeof(*tbl));
356
+ uint32_t ch = s->first_child;
357
+ int idx = 0;
358
+ int reported = 0;
359
+ uint32_t inserted = 0;
360
+ while (ch != UINT32_MAX && !reported) {
361
+ if (inserted == cap) {
362
+ break; /* the table is full: stop checking (see the cap note) */
363
+ }
364
+ uint64_t h64 = yep_validate_node_hash(d, ch, 0);
365
+ uint32_t h = (uint32_t)(h64 ^ (h64 >> 32));
366
+ uint32_t i = h & (cap - 1);
367
+ int dup = 0;
368
+ while (tbl[i].node != 0) {
369
+ if (tbl[i].hash == h && yep_validate_node_equal(d, tbl[i].node - 1, d, ch, 0)) {
370
+ dup = 1;
371
+ break;
372
+ }
373
+ i = (i + 1) & (cap - 1);
374
+ }
375
+ if (dup) {
376
+ reported = 1;
377
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_UNIQUE_ITEMS, inst_off,
378
+ "elements %u and %u are equal", (uint32_t)idx, (uint32_t)(idx - 1));
379
+ } else {
380
+ tbl[i].hash = h;
381
+ tbl[i].node = ch + 1;
382
+ inserted++;
383
+ }
384
+ ch = yep_dom_node(d, ch) ? yep_dom_node(d, ch)->next_sibling : UINT32_MAX;
385
+ idx++;
386
+ }
387
+ yep_free(sys, tbl);
388
+ }
389
+
390
+ /* The instance's string length in code points (2020-12 minLength/
391
+ * maxLength). 0 when the instance is not a string. */
392
+ static int inst_str_len(const yep_dom* d, uint32_t id, uint32_t* out) {
393
+ const yep_dnode* n = yep_dom_node(d, id);
394
+ if (n == NULL) {
395
+ return 0;
396
+ }
397
+ if (n->kind == YEP_DOM_ALIAS) {
398
+ return n->target != UINT32_MAX && inst_str_len(d, n->target, out);
399
+ }
400
+ if (n->kind != YEP_DOM_SCALAR || yep_validate_scalar_tag(n) != YEPTRIS_TAG_STR) {
401
+ return 0;
402
+ }
403
+ yep_view v = yep_dom_view(d, n->value);
404
+ uint32_t chars = 0;
405
+ for (uint32_t i = 0; i < v.len; i++) {
406
+ chars += ((((unsigned char)v.p[i]) & 0xC0) != 0x80);
407
+ }
408
+ *out = chars;
409
+ return 1;
410
+ }
411
+
412
+ /* Reads a keyword's numeric value (int or float scalar); 0 unreadable. */
413
+ static int schema_number(const js_ctx* j, uint32_t id, double* out) {
414
+ const yep_dnode* n = snode(j, id);
415
+ if (n == NULL || n->kind != YEP_DOM_SCALAR) {
416
+ return 0;
417
+ }
418
+ uint8_t tag = yep_validate_scalar_tag(n);
419
+ if (tag != YEPTRIS_TAG_INT && tag != YEPTRIS_TAG_FLOAT) {
420
+ return 0;
421
+ }
422
+ yep_view v = yep_dom_view(j->sd, n->value);
423
+ int64_t iv = 0;
424
+ if (yep_num_i64(v.p, v.len, &iv) == 0) {
425
+ *out = (double)iv;
426
+ return 1;
427
+ }
428
+ return yep_num_f64(v.p, v.len, out) == 0;
429
+ }
430
+
431
+ /* Applies one keyword value that must be a string (the type names,
432
+ * pattern sources); 0 when unreadable. */
433
+ static int schema_word(const js_ctx* j, uint32_t id, const char** p, uint32_t* len) {
434
+ const yep_dnode* n = snode(j, id);
435
+ if (n == NULL || n->kind != YEP_DOM_SCALAR || yep_validate_scalar_tag(n) != YEPTRIS_TAG_STR) {
436
+ return 0;
437
+ }
438
+ yep_view v = yep_dom_view(j->sd, n->value);
439
+ if (v.p == NULL) {
440
+ return 0;
441
+ }
442
+ *p = v.p;
443
+ *len = v.len;
444
+ return 1;
445
+ }
446
+
447
+ static void js_apply(js_ctx* j, uint32_t schema_id, uint32_t inst_id, int depth) {
448
+ if (j->v.oom) {
449
+ return;
450
+ }
451
+ if (++depth > YEP_DOM_MAX_DEPTH) {
452
+ if (depth == YEP_DOM_MAX_DEPTH + 1) {
453
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_DEPTH, UINT32_MAX,
454
+ "schema application exceeded the nesting limit");
455
+ }
456
+ return;
457
+ }
458
+ inst_id = inst_real(j->v.dom, inst_id);
459
+ const yep_dnode* s = snode(j, schema_id);
460
+ if (s == NULL) {
461
+ return;
462
+ }
463
+
464
+ /* the boolean schemas */
465
+ if (s->kind != YEP_DOM_MAPPING) {
466
+ if (s->kind == YEP_DOM_SCALAR && yep_validate_scalar_tag(s) == YEPTRIS_TAG_BOOL) {
467
+ yep_view v = yep_dom_view(j->sd, s->value);
468
+ if (v.len == 5 && memcmp(v.p, "false", 5) == 0) {
469
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_FALSE_SCHEMA,
470
+ yep_validate_node_off(j->v.dom, inst_id),
471
+ "the schema is false here");
472
+ }
473
+ }
474
+ return; /* true (or anything unreadable) constrains nothing */
475
+ }
476
+
477
+ /* $ref composes with its siblings (2020-12); the chain guard kills
478
+ * schema-only cycles that consume no instance structure */
479
+ uint32_t ref_val;
480
+ if (kw_get(j, schema_id, "$ref", &ref_val) && ref_val != UINT32_MAX) {
481
+ const char* rp = NULL;
482
+ uint32_t rl = 0;
483
+ if (schema_word(j, ref_val, &rp, &rl)) {
484
+ if (++j->v.ref_depth > JS_MAX_REF_CHAIN) {
485
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_REF, yep_validate_node_off(j->v.dom, inst_id),
486
+ "$ref chain exceeds %d (a cycle?)", JS_MAX_REF_CHAIN);
487
+ j->v.ref_depth = 0;
488
+ return;
489
+ }
490
+ uint32_t target = UINT32_MAX;
491
+ if (!resolve_ref(j, rp, rl, &target)) {
492
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_REF, yep_validate_node_off(j->v.dom, inst_id),
493
+ "$ref \"%.*s\" does not resolve", (int)(rl > 64 ? 64 : rl), rp);
494
+ j->v.ref_depth = 0;
495
+ return;
496
+ }
497
+ js_apply(j, target, inst_id, depth);
498
+ j->v.ref_depth = 0;
499
+ }
500
+ /* an unreadable $ref value: ignored like any unknown keyword */
501
+ } else {
502
+ j->v.ref_depth = 0;
503
+ }
504
+
505
+ /* type: a single word or a list of words */
506
+ {
507
+ uint32_t tv;
508
+ if (kw_get(j, schema_id, "type", &tv) && tv != UINT32_MAX) {
509
+ const yep_dnode* tn = snode(j, tv);
510
+ const char* word = NULL;
511
+ uint32_t wl = 0;
512
+ const char* it = NULL;
513
+ int iis_int = 0;
514
+ inst_type(j->v.dom, inst_id, &it, &iis_int);
515
+ int matched = 0;
516
+ int have = 0;
517
+ if (tn != NULL && tn->kind == YEP_DOM_SCALAR && schema_word(j, tv, &word, &wl)) {
518
+ have = 1;
519
+ matched = (strlen(it) == wl && memcmp(it, word, wl) == 0);
520
+ /* "number" also accepts integers */
521
+ if (!matched && wl == 6 && memcmp(word, "number", 6) == 0 && iis_int) {
522
+ matched = 1;
523
+ }
524
+ } else if (tn != NULL && tn->kind == YEP_DOM_SEQUENCE) {
525
+ have = 1;
526
+ uint32_t ch = tn->first_child;
527
+ while (ch != UINT32_MAX && !matched) {
528
+ const char* w2 = NULL;
529
+ uint32_t l2 = 0;
530
+ if (schema_word(j, ch, &w2, &l2)) {
531
+ matched = (strlen(it) == l2 && memcmp(it, w2, l2) == 0);
532
+ if (!matched && l2 == 6 && memcmp(w2, "number", 6) == 0 && iis_int) {
533
+ matched = 1;
534
+ }
535
+ }
536
+ ch = snode(j, ch) ? snode(j, ch)->next_sibling : UINT32_MAX;
537
+ }
538
+ }
539
+ if (have && !matched) {
540
+ char want[24];
541
+ size_t cl = wl < sizeof(want) - 1 ? wl : sizeof(want) - 1;
542
+ if (cl > 0 && word != NULL) {
543
+ memcpy(want, word, cl);
544
+ }
545
+ want[cl] = '\0';
546
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_TYPE,
547
+ yep_validate_node_off(j->v.dom, inst_id), "expected %s, got %s",
548
+ want, it);
549
+ }
550
+ }
551
+ }
552
+
553
+ /* const / enum */
554
+ {
555
+ uint32_t cv;
556
+ if (kw_get(j, schema_id, "const", &cv) && cv != UINT32_MAX && snode(j, cv) != NULL) {
557
+ if (!yep_validate_node_equal(j->sd, cv, j->v.dom, inst_id, 0)) {
558
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_CONST,
559
+ yep_validate_node_off(j->v.dom, inst_id),
560
+ "value is not the constant");
561
+ }
562
+ }
563
+ uint32_t ev;
564
+ if (kw_get(j, schema_id, "enum", &ev) && ev != UINT32_MAX) {
565
+ const yep_dnode* en = snode(j, ev);
566
+ if (en != NULL && en->kind == YEP_DOM_SEQUENCE) {
567
+ int found = 0;
568
+ uint32_t members = 0;
569
+ uint32_t ch = en->first_child;
570
+ while (ch != UINT32_MAX) {
571
+ members++;
572
+ if (!found && yep_validate_node_equal(j->sd, ch, j->v.dom, inst_id, 0)) {
573
+ found = 1;
574
+ }
575
+ ch = snode(j, ch) ? snode(j, ch)->next_sibling : UINT32_MAX;
576
+ }
577
+ if (!found) {
578
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_ENUM,
579
+ yep_validate_node_off(j->v.dom, inst_id),
580
+ "value is not one of %u enum members", members);
581
+ }
582
+ }
583
+ }
584
+ }
585
+
586
+ /* the numeric bounds (ignored on non-numeric instances) */
587
+ {
588
+ double iv = 0;
589
+ int is_num = inst_number(j->v.dom, inst_id, &iv);
590
+ if (is_num) {
591
+ uint32_t mv;
592
+ if (kw_get(j, schema_id, "minimum", &mv) && mv != UINT32_MAX) {
593
+ double b;
594
+ if (schema_number(j, mv, &b) && iv < b) {
595
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_MINIMUM,
596
+ yep_validate_node_off(j->v.dom, inst_id),
597
+ "%g is less than the minimum %g", iv, b);
598
+ }
599
+ }
600
+ if (kw_get(j, schema_id, "maximum", &mv) && mv != UINT32_MAX) {
601
+ double b;
602
+ if (schema_number(j, mv, &b) && iv > b) {
603
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_MAXIMUM,
604
+ yep_validate_node_off(j->v.dom, inst_id),
605
+ "%g is greater than the maximum %g", iv, b);
606
+ }
607
+ }
608
+ if (kw_get(j, schema_id, "exclusiveMinimum", &mv) && mv != UINT32_MAX) {
609
+ double b;
610
+ if (schema_number(j, mv, &b) && iv <= b) {
611
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_EXCLUSIVE_MINIMUM,
612
+ yep_validate_node_off(j->v.dom, inst_id),
613
+ "%g is not greater than %g", iv, b);
614
+ }
615
+ }
616
+ if (kw_get(j, schema_id, "exclusiveMaximum", &mv) && mv != UINT32_MAX) {
617
+ double b;
618
+ if (schema_number(j, mv, &b) && iv >= b) {
619
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_EXCLUSIVE_MAXIMUM,
620
+ yep_validate_node_off(j->v.dom, inst_id),
621
+ "%g is not less than %g", iv, b);
622
+ }
623
+ }
624
+ }
625
+ }
626
+
627
+ /* string length + pattern (ignored on non-strings) */
628
+ {
629
+ uint32_t sl = 0;
630
+ int is_str = inst_str_len(j->v.dom, inst_id, &sl);
631
+ if (is_str) {
632
+ uint32_t mv;
633
+ if (kw_get(j, schema_id, "minLength", &mv) && mv != UINT32_MAX) {
634
+ double b;
635
+ if (schema_number(j, mv, &b) && (double)sl < b) {
636
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_MIN_LENGTH,
637
+ yep_validate_node_off(j->v.dom, inst_id),
638
+ "length %u is shorter than %g", sl, b);
639
+ }
640
+ }
641
+ if (kw_get(j, schema_id, "maxLength", &mv) && mv != UINT32_MAX) {
642
+ double b;
643
+ if (schema_number(j, mv, &b) && (double)sl > b) {
644
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_MAX_LENGTH,
645
+ yep_validate_node_off(j->v.dom, inst_id),
646
+ "length %u is longer than %g", sl, b);
647
+ }
648
+ }
649
+ uint32_t pv;
650
+ if (kw_get(j, schema_id, "pattern", &pv) && pv != UINT32_MAX) {
651
+ const char* pat = NULL;
652
+ uint32_t pl = 0;
653
+ if (schema_word(j, pv, &pat, &pl)) {
654
+ const yep_dnode* n = yep_dom_node(j->v.dom, inst_id);
655
+ yep_view v = yep_dom_view(j->v.dom, n->value);
656
+ int rc = yep_re_search(pat, pl, v.p != NULL ? v.p : "", v.len);
657
+ if (rc == 0) {
658
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_PATTERN,
659
+ yep_validate_node_off(j->v.dom, inst_id),
660
+ "does not match the pattern");
661
+ }
662
+ /* rc < 0: the pattern is outside the subset — ignored */
663
+ }
664
+ }
665
+ }
666
+ }
667
+
668
+ /* array bounds + uniqueness + items (ignored on non-arrays) */
669
+ {
670
+ uint32_t cnt = 0;
671
+ if (inst_seq_count(j->v.dom, inst_id, &cnt)) {
672
+ uint32_t mv;
673
+ if (kw_get(j, schema_id, "minItems", &mv) && mv != UINT32_MAX) {
674
+ double b;
675
+ if (schema_number(j, mv, &b) && (double)cnt < b) {
676
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_MIN_ITEMS,
677
+ yep_validate_node_off(j->v.dom, inst_id),
678
+ "%u items is fewer than %g", cnt, b);
679
+ }
680
+ }
681
+ if (kw_get(j, schema_id, "maxItems", &mv) && mv != UINT32_MAX) {
682
+ double b;
683
+ if (schema_number(j, mv, &b) && (double)cnt > b) {
684
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_MAX_ITEMS,
685
+ yep_validate_node_off(j->v.dom, inst_id),
686
+ "%u items is more than %g", cnt, b);
687
+ }
688
+ }
689
+ uint32_t uv;
690
+ int uf = 0;
691
+ if (kw_schema(j, schema_id, "uniqueItems", &uv, &uf) && uf == 0) {
692
+ check_unique(j, inst_id, yep_validate_node_off(j->v.dom, inst_id));
693
+ }
694
+ /* prefixItems: per-index schemas; the draft-07 array form
695
+ * of items rides the same semantics */
696
+ uint32_t prefix = 0;
697
+ uint32_t pv;
698
+ if (kw_get(j, schema_id, "prefixItems", &pv) && pv != UINT32_MAX) {
699
+ const yep_dnode* pn = snode(j, pv);
700
+ if (pn != NULL && pn->kind == YEP_DOM_SEQUENCE) {
701
+ uint32_t i = 0;
702
+ uint32_t ch = pn->first_child;
703
+ uint32_t ich = yep_dom_node(j->v.dom, inst_id)->first_child;
704
+ while (ch != UINT32_MAX && ich != UINT32_MAX) {
705
+ uint32_t ns = snode(j, ch) ? snode(j, ch)->next_sibling : UINT32_MAX;
706
+ uint32_t ni = yep_dom_node(j->v.dom, ich)
707
+ ? yep_dom_node(j->v.dom, ich)->next_sibling
708
+ : UINT32_MAX;
709
+ size_t mark = j->v.path_len;
710
+ if (yep_validate_path_push_index(&j->v, i) == 0) {
711
+ js_apply(j, ch, ich, depth);
712
+ yep_validate_path_pop(&j->v, mark);
713
+ }
714
+ prefix++;
715
+ i++;
716
+ ch = ns;
717
+ ich = ni;
718
+ }
719
+ }
720
+ }
721
+ /* items: a single schema over the remaining elements; the
722
+ * draft-07 array form counts as the prefix */
723
+ if (kw_get(j, schema_id, "items", &pv) && pv != UINT32_MAX) {
724
+ const yep_dnode* pn = snode(j, pv);
725
+ int is_false = 0;
726
+ if (pn != NULL && pn->kind == YEP_DOM_SEQUENCE) {
727
+ /* the draft-07 tuple shim: per-index, from scratch */
728
+ uint32_t i = 0;
729
+ uint32_t ch = pn->first_child;
730
+ uint32_t ich = yep_dom_node(j->v.dom, inst_id)->first_child;
731
+ while (ch != UINT32_MAX && ich != UINT32_MAX) {
732
+ uint32_t ns = snode(j, ch) ? snode(j, ch)->next_sibling : UINT32_MAX;
733
+ uint32_t ni = yep_dom_node(j->v.dom, ich)
734
+ ? yep_dom_node(j->v.dom, ich)->next_sibling
735
+ : UINT32_MAX;
736
+ size_t mark = j->v.path_len;
737
+ if (yep_validate_path_push_index(&j->v, i) == 0) {
738
+ js_apply(j, ch, ich, depth);
739
+ yep_validate_path_pop(&j->v, mark);
740
+ }
741
+ i++;
742
+ ch = ns;
743
+ ich = ni;
744
+ }
745
+ } else if (schema_truth(j, pv, &is_false)) {
746
+ /* items applies from prefixItems' count on */
747
+ uint32_t ich = yep_dom_node(j->v.dom, inst_id)->first_child;
748
+ for (uint32_t skip = 0; skip < prefix && ich != UINT32_MAX; skip++) {
749
+ ich = yep_dom_node(j->v.dom, ich)
750
+ ? yep_dom_node(j->v.dom, ich)->next_sibling
751
+ : UINT32_MAX;
752
+ }
753
+ for (uint32_t i = prefix; i < cnt && ich != UINT32_MAX; i++) {
754
+ uint32_t ni = yep_dom_node(j->v.dom, ich)
755
+ ? yep_dom_node(j->v.dom, ich)->next_sibling
756
+ : UINT32_MAX;
757
+ size_t mark = j->v.path_len;
758
+ if (yep_validate_path_push_index(&j->v, i) == 0) {
759
+ js_apply(j, pv, ich, depth);
760
+ yep_validate_path_pop(&j->v, mark);
761
+ }
762
+ ich = ni;
763
+ }
764
+ }
765
+ }
766
+ }
767
+ }
768
+
769
+ /* allOf / anyOf / oneOf / not */
770
+ {
771
+ uint32_t cv;
772
+ if (kw_get(j, schema_id, "allOf", &cv) && cv != UINT32_MAX) {
773
+ const yep_dnode* cn = snode(j, cv);
774
+ if (cn != NULL && cn->kind == YEP_DOM_SEQUENCE) {
775
+ uint32_t ch = cn->first_child;
776
+ while (ch != UINT32_MAX) {
777
+ uint32_t nc = snode(j, ch) ? snode(j, ch)->next_sibling : UINT32_MAX;
778
+ js_apply(j, ch, inst_id, depth);
779
+ ch = nc;
780
+ }
781
+ }
782
+ }
783
+ if (kw_get(j, schema_id, "anyOf", &cv) && cv != UINT32_MAX) {
784
+ const yep_dnode* cn = snode(j, cv);
785
+ if (cn != NULL && cn->kind == YEP_DOM_SEQUENCE) {
786
+ int any = 0;
787
+ uint32_t branches = 0;
788
+ uint32_t ch = cn->first_child;
789
+ while (ch != UINT32_MAX) {
790
+ branches++;
791
+ if (!any && js_holds(j, ch, inst_id, depth)) {
792
+ any = 1;
793
+ }
794
+ ch = snode(j, ch) ? snode(j, ch)->next_sibling : UINT32_MAX;
795
+ }
796
+ if (!any) {
797
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_ANY_OF,
798
+ yep_validate_node_off(j->v.dom, inst_id),
799
+ "none of the %u anyOf branches matched", branches);
800
+ }
801
+ }
802
+ }
803
+ if (kw_get(j, schema_id, "oneOf", &cv) && cv != UINT32_MAX) {
804
+ const yep_dnode* cn = snode(j, cv);
805
+ if (cn != NULL && cn->kind == YEP_DOM_SEQUENCE) {
806
+ uint32_t matches = 0;
807
+ uint32_t branches = 0;
808
+ uint32_t ch = cn->first_child;
809
+ while (ch != UINT32_MAX) {
810
+ branches++;
811
+ if (js_holds(j, ch, inst_id, depth)) {
812
+ matches++;
813
+ }
814
+ ch = snode(j, ch) ? snode(j, ch)->next_sibling : UINT32_MAX;
815
+ }
816
+ if (matches != 1) {
817
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_ONE_OF,
818
+ yep_validate_node_off(j->v.dom, inst_id),
819
+ "%u of the %u oneOf branches matched (exactly one "
820
+ "required)",
821
+ matches, branches);
822
+ }
823
+ }
824
+ }
825
+ if (kw_get(j, schema_id, "not", &cv) && cv != UINT32_MAX) {
826
+ if (js_holds(j, cv, inst_id, depth)) {
827
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_NOT, yep_validate_node_off(j->v.dom, inst_id),
828
+ "the not-schema matched");
829
+ }
830
+ }
831
+ }
832
+
833
+ /* the object keywords (ignored on non-objects) */
834
+ {
835
+ const yep_dnode* n = yep_dom_node(j->v.dom, inst_id);
836
+ if (n != NULL && n->kind == YEP_DOM_MAPPING) {
837
+ /* properties: known keys */
838
+ uint32_t props = UINT32_MAX;
839
+ int have_props = kw_get(j, schema_id, "properties", &props) && props != UINT32_MAX &&
840
+ snode(j, props) != NULL && snode(j, props)->kind == YEP_DOM_MAPPING;
841
+ uint32_t req;
842
+ if (kw_get(j, schema_id, "required", &req) && req != UINT32_MAX) {
843
+ const yep_dnode* rn = snode(j, req);
844
+ if (rn != NULL && rn->kind == YEP_DOM_SEQUENCE) {
845
+ uint32_t ch = rn->first_child;
846
+ while (ch != UINT32_MAX) {
847
+ const char* name = NULL;
848
+ uint32_t nl = 0;
849
+ uint32_t nc = snode(j, ch) ? snode(j, ch)->next_sibling : UINT32_MAX;
850
+ if (schema_word(j, ch, &name, &nl)) {
851
+ uint32_t hit = UINT32_MAX;
852
+ uint32_t k = n->first_child;
853
+ while (k != UINT32_MAX) {
854
+ uint32_t v = yep_dom_node(j->v.dom, k)
855
+ ? yep_dom_node(j->v.dom, k)->next_sibling
856
+ : UINT32_MAX;
857
+ const yep_dnode* kn = yep_dom_node(j->v.dom, k);
858
+ if (kn != NULL && kn->kind == YEP_DOM_SCALAR) {
859
+ yep_view kv = yep_dom_view(j->v.dom, kn->value);
860
+ if (kv.len == nl && (nl == 0 || memcmp(kv.p, name, nl) == 0)) {
861
+ hit = v;
862
+ break;
863
+ }
864
+ }
865
+ k = v != UINT32_MAX && yep_dom_node(j->v.dom, v)
866
+ ? yep_dom_node(j->v.dom, v)->next_sibling
867
+ : UINT32_MAX;
868
+ }
869
+ if (hit == UINT32_MAX) {
870
+ char namebuf[64];
871
+ size_t cl = nl < sizeof(namebuf) - 1 ? nl : sizeof(namebuf) - 1;
872
+ memcpy(namebuf, name, cl);
873
+ namebuf[cl] = '\0';
874
+ /* the diagnostic points at the missing
875
+ * leaf: object path + /key (the ajv
876
+ * instancePath convention) */
877
+ size_t mark = j->v.path_len;
878
+ int pushed = yep_validate_path_push(&j->v, namebuf, cl);
879
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_REQUIRED,
880
+ yep_validate_node_off(j->v.dom, inst_id),
881
+ "missing required property \"%s\"", namebuf);
882
+ if (pushed == 0) {
883
+ yep_validate_path_pop(&j->v, mark);
884
+ }
885
+ }
886
+ }
887
+ ch = nc;
888
+ }
889
+ }
890
+ }
891
+ /* properties walk (values through their schemas) */
892
+ if (have_props) {
893
+ const yep_dnode* pn = snode(j, props);
894
+ uint32_t k = pn->first_child;
895
+ while (k != UINT32_MAX) {
896
+ uint32_t v = snode(j, k) ? snode(j, k)->next_sibling : UINT32_MAX;
897
+ const yep_dnode* kn = snode(j, k);
898
+ if (kn != NULL && kn->kind == YEP_DOM_SCALAR) {
899
+ yep_view kv = yep_dom_view(j->sd, kn->value);
900
+ /* find the child pair by key bytes */
901
+ uint32_t ck = n->first_child;
902
+ while (ck != UINT32_MAX) {
903
+ uint32_t cv = yep_dom_node(j->v.dom, ck)
904
+ ? yep_dom_node(j->v.dom, ck)->next_sibling
905
+ : UINT32_MAX;
906
+ const yep_dnode* ckn = yep_dom_node(j->v.dom, ck);
907
+ if (ckn != NULL && ckn->kind == YEP_DOM_SCALAR) {
908
+ yep_view ckv = yep_dom_view(j->v.dom, ckn->value);
909
+ if (ckv.len == kv.len &&
910
+ (kv.len == 0 || memcmp(ckv.p, kv.p, kv.len) == 0)) {
911
+ size_t mark = j->v.path_len;
912
+ if (yep_validate_path_push(&j->v, kv.p != NULL ? kv.p : "",
913
+ kv.len) == 0) {
914
+ js_apply(j, v, cv, depth);
915
+ yep_validate_path_pop(&j->v, mark);
916
+ }
917
+ break;
918
+ }
919
+ }
920
+ ck = cv != UINT32_MAX && yep_dom_node(j->v.dom, cv)
921
+ ? yep_dom_node(j->v.dom, cv)->next_sibling
922
+ : UINT32_MAX;
923
+ }
924
+ }
925
+ k = v != UINT32_MAX && snode(j, v) ? snode(j, v)->next_sibling : UINT32_MAX;
926
+ }
927
+ }
928
+ /* additionalProperties: the keys properties does not name */
929
+ uint32_t ap;
930
+ int af = 0;
931
+ if (kw_schema(j, schema_id, "additionalProperties", &ap, &af)) {
932
+ /* k steps pair-to-pair: every visited node IS a key */
933
+ uint32_t k = n->first_child;
934
+ while (k != UINT32_MAX) {
935
+ uint32_t v = yep_dom_node(j->v.dom, k) ? yep_dom_node(j->v.dom, k)->next_sibling
936
+ : UINT32_MAX;
937
+ const yep_dnode* kn = yep_dom_node(j->v.dom, k);
938
+ if (kn != NULL && kn->kind == YEP_DOM_SCALAR) {
939
+ yep_view kv = yep_dom_view(j->v.dom, kn->value);
940
+ int known = 0;
941
+ if (have_props) {
942
+ uint32_t pk = snode(j, props)->first_child;
943
+ while (pk != UINT32_MAX && !known) {
944
+ const yep_dnode* pkn = snode(j, pk);
945
+ uint32_t pv =
946
+ snode(j, pk) ? snode(j, pk)->next_sibling : UINT32_MAX;
947
+ if (pkn != NULL && pkn->kind == YEP_DOM_SCALAR) {
948
+ yep_view pw = yep_dom_view(j->sd, pkn->value);
949
+ known = pw.len == kv.len &&
950
+ (kv.len == 0 || memcmp(pw.p, kv.p, kv.len) == 0);
951
+ }
952
+ pk = pv != UINT32_MAX && snode(j, pv) ? snode(j, pv)->next_sibling
953
+ : UINT32_MAX;
954
+ }
955
+ }
956
+ if (!known) {
957
+ char namebuf[64];
958
+ size_t cl = kv.len < sizeof(namebuf) - 1 ? kv.len : sizeof(namebuf) - 1;
959
+ if (cl > 0 && kv.p != NULL) {
960
+ memcpy(namebuf, kv.p, cl);
961
+ }
962
+ namebuf[cl] = '\0';
963
+ if (af) {
964
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_ADDITIONAL_PROPERTY,
965
+ yep_validate_node_off(j->v.dom, k),
966
+ "additional property \"%s\"", namebuf);
967
+ } else {
968
+ size_t mark = j->v.path_len;
969
+ if (yep_validate_path_push(&j->v, namebuf, cl) == 0) {
970
+ js_apply(j, ap, v, depth);
971
+ yep_validate_path_pop(&j->v, mark);
972
+ }
973
+ }
974
+ }
975
+ }
976
+ k = v != UINT32_MAX && yep_dom_node(j->v.dom, v)
977
+ ? yep_dom_node(j->v.dom, v)->next_sibling
978
+ : UINT32_MAX;
979
+ }
980
+ }
981
+ }
982
+ }
983
+ }
984
+
985
+ /* ---- the entry ----------------------------------------------------------- */
986
+
987
+ /* Validates one instance document root against the schema root.
988
+ * Returns 0 ok, -1 OOM. */
989
+ static int js_validate_doc(js_ctx* j, uint32_t inst_root) {
990
+ js_apply(j, j->sroot, inst_root, 0);
991
+ return j->v.oom ? -1 : 0;
992
+ }
993
+
994
+ /* The public entry lives in validate.c (yeptris_json_schema_validate):
995
+ * it parses both sides, builds the report and drives this seam per
996
+ * document (the declaration is internal.h's — the shared SSOT). */
997
+ int yep_jsonschema_run(const yep_dom* schema_dom, uint32_t schema_root, const yep_dom* instance_dom,
998
+ const uint32_t* doc_roots, uint32_t doc_count,
999
+ yeptris_validate_report* rep) {
1000
+ js_ctx j;
1001
+ memset(&j, 0, sizeof(j));
1002
+ j.sd = schema_dom;
1003
+ j.sroot = schema_root;
1004
+ j.v.dom = instance_dom;
1005
+ j.v.rep = rep;
1006
+ j.v.input = instance_dom->input_base;
1007
+ j.v.input_len = instance_dom->input_len;
1008
+ for (uint32_t i = 0; i < doc_count; i++) {
1009
+ j.v.path_len = 0;
1010
+ js_validate_doc(&j, doc_roots[i]);
1011
+ if (j.v.oom) {
1012
+ yep_free(yep_system_allocator(), j.v.line_off);
1013
+ yep_free(yep_system_allocator(), j.v.path);
1014
+ return -1;
1015
+ }
1016
+ }
1017
+ /* the walk's scratch (the lazy line index, the path buffer) dies
1018
+ * here: the report carries only finished diagnostics */
1019
+ yep_free(yep_system_allocator(), j.v.line_off);
1020
+ yep_free(yep_system_allocator(), j.v.path);
1021
+ return 0;
1022
+ }