yeptris 0.6.57.1-arm-linux-eabihf → 0.6.59.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,1392 @@
1
+ /* jsonschema.c — the JSON Schema draft 2020-12 keyword subset
2
+ * (#882 slice 2; #885 item 2 sweep).
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*, multipleOf (libm-free
8
+ * quotient integrality), minLength/maxLength (code points),
9
+ * minItems/maxItems, uniqueItems, contains (+ minContains/maxContains
10
+ * via suppressed probes), properties, required, patternProperties
11
+ * (the pattern kernel over KEY strings), propertyNames (the schema
12
+ * applies to the key string itself), minProperties/maxProperties,
13
+ * additionalProperties (bool or schema; excludes properties AND
14
+ * patternProperties matches), dependentRequired, dependentSchemas,
15
+ * if/then/else (the if-schema rides a suppressed probe), items
16
+ * (+ prefixItems; the draft-07 array form rides as prefixItems),
17
+ * allOf/anyOf/oneOf/not, $ref (LOCAL "#/..." pointers — $defs and
18
+ * definitions both resolve), boolean schemas. Unknown keywords, and
19
+ * known keywords whose values cannot be read, are IGNORED (2020-12's
20
+ * posture) — an unreadable constraint never guesses. Carried out of
21
+ * the subset (documented in test/conformance/jsonschema/MATRIX.md):
22
+ * unevaluatedItems/unevaluatedProperties (annotation plumbing),
23
+ * $anchor/$id/$dynamicRef (pointer/base-URI machinery), remote $ref,
24
+ * contentMediaType/contentSchema (annotation-only in 2020-12).
25
+ *
26
+ * Instance types ride the core-schema resolution (the YAML->JSON data
27
+ * model): "y" is boolean, 1.0 is integer (2020-12: zero fractional
28
+ * part), floats and ints are both numbers.
29
+ *
30
+ * Guards: the walk depth is the DOM's own parse-bounded depth; a
31
+ * consecutive-$ref chain (schema-only cycles that consume no instance
32
+ * structure) caps at 64 expansions and reports DIAG_REF. Every array
33
+ * is bounded; allocation failure flags the context and unwinds.
34
+ */
35
+
36
+ #include <stdio.h>
37
+ #include <string.h>
38
+
39
+ #include <yeptris/validate.h>
40
+
41
+ #include "../memory/allocator.h"
42
+ #include "../parse/numbers.h"
43
+ #include "../resolve/resolver.h"
44
+ #include "internal.h"
45
+
46
+ #define JS_MAX_REF_CHAIN 64
47
+ #define JS_MAX_TOKENS 256
48
+ #define JS_MAX_UNIQUE 4096 /* uniqueItems' hash table cap; over: O(n^2) probe */
49
+
50
+ typedef struct {
51
+ yep_vctx v; /* instance-side context (path, report, guards) */
52
+ const yep_dom* sd; /* the schema document's tree */
53
+ uint32_t sroot; /* the schema's document root */
54
+ /* the compiled schema's retained pattern programs (NULL: the
55
+ * one-shot face — every search compiles fresh, identical verdicts) */
56
+ const yep_re_cache* cache;
57
+ } js_ctx;
58
+
59
+ /* ---- schema-side facts -------------------------------------------------- */
60
+
61
+ static const yep_dnode* snode(const js_ctx* j, uint32_t id) {
62
+ return yep_dom_node(j->sd, id);
63
+ }
64
+
65
+ /* First pair of the mapping whose key bytes equal `name`; 1 + *val_out,
66
+ * 0 absent. Only scalar keys match (collection keys never will). */
67
+ static int kw_get(const js_ctx* j, uint32_t map_id, const char* name, uint32_t* val_out) {
68
+ const yep_dnode* m = snode(j, map_id);
69
+ if (m == NULL || m->kind != YEP_DOM_MAPPING) {
70
+ return 0;
71
+ }
72
+ size_t nl = strlen(name);
73
+ uint32_t k = m->first_child;
74
+ while (k != UINT32_MAX) {
75
+ uint32_t v = snode(j, k) ? snode(j, k)->next_sibling : UINT32_MAX;
76
+ const yep_dnode* kn = snode(j, k);
77
+ if (kn != NULL && kn->kind == YEP_DOM_SCALAR) {
78
+ yep_view kv = yep_dom_view(j->sd, kn->value);
79
+ if (kv.len == nl && (nl == 0 || memcmp(kv.p, name, nl) == 0)) {
80
+ if (val_out != NULL) {
81
+ *val_out = v;
82
+ }
83
+ return 1;
84
+ }
85
+ }
86
+ k = v != UINT32_MAX && snode(j, v) ? snode(j, v)->next_sibling : UINT32_MAX;
87
+ }
88
+ return 0;
89
+ }
90
+
91
+ /* A schema-valued keyword: a mapping, or the boolean schemas. */
92
+ static int schema_truth(const js_ctx* j, uint32_t id, int* is_false) {
93
+ const yep_dnode* n = snode(j, id);
94
+ if (n == NULL) {
95
+ *is_false = 0;
96
+ return 0;
97
+ }
98
+ if (n->kind == YEP_DOM_MAPPING) {
99
+ *is_false = 0;
100
+ return 1;
101
+ }
102
+ if (n->kind == YEP_DOM_SCALAR && yep_validate_scalar_tag(n) == YEPTRIS_TAG_BOOL) {
103
+ yep_view v = yep_dom_view(j->sd, n->value);
104
+ *is_false = (v.len == 5 && memcmp(v.p, "false", 5) == 0);
105
+ return 1; /* true/false both carry meaning */
106
+ }
107
+ *is_false = 0;
108
+ return 0; /* an unreadable schema: ignored */
109
+ }
110
+
111
+ static int kw_schema(const js_ctx* j, uint32_t map_id, const char* name, uint32_t* schema_out,
112
+ int* is_false_out) {
113
+ uint32_t val;
114
+ if (!kw_get(j, map_id, name, &val) || val == UINT32_MAX) {
115
+ return 0;
116
+ }
117
+ int is_false = 0;
118
+ if (!schema_truth(j, val, &is_false)) {
119
+ return 0;
120
+ }
121
+ *schema_out = val;
122
+ *is_false_out = is_false;
123
+ return 1;
124
+ }
125
+
126
+ /* ---- instance-side facts ------------------------------------------------ */
127
+
128
+ /* Resolves an alias to the real node id (one hop; the DOM's targets
129
+ * are real nodes). */
130
+ static uint32_t inst_real(const yep_dom* d, uint32_t id) {
131
+ const yep_dnode* n = yep_dom_node(d, id);
132
+ if (n != NULL && n->kind == YEP_DOM_ALIAS && n->target != UINT32_MAX) {
133
+ return n->target;
134
+ }
135
+ return id;
136
+ }
137
+
138
+ /* The instance's JSON type word (7 bytes max); *integer receives 1 when
139
+ * a float also satisfies 2020-12's zero-fraction rule. */
140
+ static void inst_type(const yep_dom* d, uint32_t id, const char** word, int* integer) {
141
+ *integer = 0;
142
+ const yep_dnode* n = yep_dom_node(d, id);
143
+ if (n == NULL) {
144
+ *word = "string";
145
+ return;
146
+ }
147
+ if (n->kind == YEP_DOM_ALIAS) {
148
+ if (n->target != UINT32_MAX) {
149
+ inst_type(d, n->target, word, integer);
150
+ } else {
151
+ *word = "string";
152
+ }
153
+ return;
154
+ }
155
+ if (n->kind == YEP_DOM_SEQUENCE) {
156
+ *word = "array";
157
+ return;
158
+ }
159
+ if (n->kind == YEP_DOM_MAPPING) {
160
+ *word = "object";
161
+ return;
162
+ }
163
+ switch (yep_validate_scalar_tag(n)) {
164
+ case YEPTRIS_TAG_NULL:
165
+ *word = "null";
166
+ break;
167
+ case YEPTRIS_TAG_BOOL:
168
+ *word = "boolean";
169
+ break;
170
+ case YEPTRIS_TAG_INT:
171
+ *word = "integer";
172
+ *integer = 1;
173
+ break;
174
+ case YEPTRIS_TAG_FLOAT: {
175
+ yep_view v = yep_dom_view(d, n->value);
176
+ double dv;
177
+ /* 2020-12: "integer" means a zero fractional part. The range
178
+ * guard must read the PARSED value (it once sat before the
179
+ * parse, saw dv==0, folded constant-true, and left the cast
180
+ * UB on out-of-range/NaN doubles). No libm: in int64 range a
181
+ * cast round-trips iff the fractional part is zero; outside
182
+ * it every finite double is already integral; NaN is not. */
183
+ if (yep_num_f64(v.p, v.len, &dv) == 0) {
184
+ const int in_i64 = dv >= -9223372036854775808.0 && dv < 9223372036854775808.0;
185
+ if (in_i64 ? dv == (double)(int64_t)dv : dv == dv) {
186
+ *word = "integer";
187
+ *integer = 1;
188
+ } else {
189
+ *word = "number";
190
+ }
191
+ } else {
192
+ *word = "number";
193
+ }
194
+ break;
195
+ }
196
+ default:
197
+ *word = "string";
198
+ break;
199
+ }
200
+ }
201
+
202
+ /* The instance's numeric value (int or float); 0 when not numeric. */
203
+ static int inst_number(const yep_dom* d, uint32_t id, double* out) {
204
+ const yep_dnode* n = yep_dom_node(d, id);
205
+ if (n == NULL) {
206
+ return 0;
207
+ }
208
+ if (n->kind == YEP_DOM_ALIAS) {
209
+ return n->target != UINT32_MAX && inst_number(d, n->target, out);
210
+ }
211
+ if (n->kind != YEP_DOM_SCALAR) {
212
+ return 0;
213
+ }
214
+ uint8_t tag = yep_validate_scalar_tag(n);
215
+ if (tag != YEPTRIS_TAG_INT && tag != YEPTRIS_TAG_FLOAT) {
216
+ return 0;
217
+ }
218
+ yep_view v = yep_dom_view(d, n->value);
219
+ int64_t iv = 0;
220
+ if (yep_num_i64(v.p, v.len, &iv) == 0) {
221
+ *out = (double)iv;
222
+ return 1;
223
+ }
224
+ return yep_num_f64(v.p, v.len, out) == 0;
225
+ }
226
+
227
+ static int inst_seq_count(const yep_dom* d, uint32_t id, uint32_t* count) {
228
+ const yep_dnode* n = yep_dom_node(d, id);
229
+ if (n == NULL || n->kind != YEP_DOM_SEQUENCE) {
230
+ return 0;
231
+ }
232
+ *count = n->count;
233
+ return 1;
234
+ }
235
+
236
+ /* Finds an instance pair by key bytes (scalar keys only): the VALUE
237
+ * node id, UINT32_MAX absent. *key_out receives the KEY node id when
238
+ * non-NULL (patternProperties/propertyNames walk the key itself). */
239
+ static uint32_t inst_find_key(const yep_dom* d, uint32_t map_id, const char* name, size_t nl,
240
+ uint32_t* key_out) {
241
+ const yep_dnode* n = yep_dom_node(d, map_id);
242
+ if (n == NULL || n->kind != YEP_DOM_MAPPING) {
243
+ return UINT32_MAX;
244
+ }
245
+ uint32_t k = n->first_child;
246
+ while (k != UINT32_MAX) {
247
+ uint32_t v = yep_dom_node(d, k) ? yep_dom_node(d, k)->next_sibling : UINT32_MAX;
248
+ const yep_dnode* kn = yep_dom_node(d, k);
249
+ if (kn != NULL && kn->kind == YEP_DOM_SCALAR) {
250
+ yep_view kv = yep_dom_view(d, kn->value);
251
+ if (kv.len == nl && (nl == 0 || memcmp(kv.p, name, nl) == 0)) {
252
+ if (key_out != NULL) {
253
+ *key_out = k;
254
+ }
255
+ return v;
256
+ }
257
+ }
258
+ k = v != UINT32_MAX && yep_dom_node(d, v) ? yep_dom_node(d, v)->next_sibling : UINT32_MAX;
259
+ }
260
+ return UINT32_MAX;
261
+ }
262
+
263
+ /* ---- the walk ----------------------------------------------------------- */
264
+
265
+ static void js_apply(js_ctx* j, uint32_t schema_id, uint32_t inst_id, int depth);
266
+
267
+ /* the object-keyword walkers (defined after js_apply) */
268
+ static void check_pattern_properties(js_ctx* j, uint32_t pp_id, uint32_t inst_id, int depth);
269
+ static void check_property_names(js_ctx* j, uint32_t pn_id, uint32_t inst_id, int depth);
270
+ static void check_dependent_required(js_ctx* j, uint32_t dr_id, uint32_t inst_id, int depth);
271
+ static void check_dependent_schemas(js_ctx* j, uint32_t ds_id, uint32_t inst_id, int depth);
272
+
273
+ /* A suppressed probe: does `schema_id` hold at `inst_id`? Diagnostics
274
+ * during the trial are swallowed. */
275
+ static int js_holds(js_ctx* j, uint32_t schema_id, uint32_t inst_id, int depth) {
276
+ int save = j->v.trial_hit;
277
+ j->v.trial_hit = 0;
278
+ j->v.suppress++;
279
+ js_apply(j, schema_id, inst_id, depth);
280
+ j->v.suppress--;
281
+ int hit = j->v.trial_hit;
282
+ j->v.trial_hit = save;
283
+ return !hit;
284
+ }
285
+
286
+ /* Local "#/..." pointer resolution against the schema document. */
287
+ static int resolve_ref(js_ctx* j, const char* p, uint32_t plen, uint32_t* out) {
288
+ if (plen == 0 || p[0] != '#') {
289
+ return 0; /* remote/anchor forms are out of the subset */
290
+ }
291
+ uint32_t cur = j->sroot;
292
+ size_t i = 1;
293
+ if (i >= plen || p[i] != '/') {
294
+ *out = cur; /* "#" alone is the root */
295
+ return i == plen;
296
+ }
297
+ int tokens = 0;
298
+ while (i < plen) {
299
+ if (p[i] != '/') {
300
+ return 0;
301
+ }
302
+ i++;
303
+ char token[128];
304
+ size_t tl = 0;
305
+ while (i < plen && p[i] != '/') {
306
+ char b = p[i];
307
+ if (b == '~' && i + 1 < plen && (p[i + 1] == '0' || p[i + 1] == '1')) {
308
+ b = p[i + 1] == '0' ? '~' : '/';
309
+ i++;
310
+ } else if (b == '~') {
311
+ return 0; /* a dangling ~ escape */
312
+ }
313
+ if (tl + 1 >= sizeof(token)) {
314
+ return 0;
315
+ }
316
+ token[tl++] = b;
317
+ i++;
318
+ }
319
+ if (++tokens > JS_MAX_TOKENS) {
320
+ return 0;
321
+ }
322
+ const yep_dnode* n = snode(j, cur);
323
+ if (n == NULL) {
324
+ return 0;
325
+ }
326
+ if (n->kind == YEP_DOM_SEQUENCE) {
327
+ /* a decimal array index (JSON pointer: no leading zeros,
328
+ * but be lenient — the pointer either resolves or not) */
329
+ uint32_t idx = 0;
330
+ if (tl == 0) {
331
+ return 0;
332
+ }
333
+ for (size_t t = 0; t < tl; t++) {
334
+ if (token[t] < '0' || token[t] > '9' || idx > 999999) {
335
+ return 0;
336
+ }
337
+ idx = idx * 10 + (uint32_t)(token[t] - '0');
338
+ }
339
+ uint32_t count = 0;
340
+ if (!inst_seq_count(j->sd, cur, &count) || idx >= count) {
341
+ return 0;
342
+ }
343
+ uint32_t ch = n->first_child;
344
+ while (ch != UINT32_MAX && idx > 0) {
345
+ ch = snode(j, ch) ? snode(j, ch)->next_sibling : UINT32_MAX;
346
+ idx--;
347
+ }
348
+ cur = ch;
349
+ } else if (n->kind == YEP_DOM_MAPPING) {
350
+ uint32_t k = n->first_child;
351
+ int found = 0;
352
+ while (k != UINT32_MAX && !found) {
353
+ uint32_t v = snode(j, k) ? snode(j, k)->next_sibling : UINT32_MAX;
354
+ const yep_dnode* kn = snode(j, k);
355
+ if (kn != NULL && kn->kind == YEP_DOM_SCALAR) {
356
+ yep_view kv = yep_dom_view(j->sd, kn->value);
357
+ if (kv.len == tl && (tl == 0 || memcmp(kv.p, token, tl) == 0)) {
358
+ cur = v;
359
+ found = 1;
360
+ }
361
+ }
362
+ k = v != UINT32_MAX && snode(j, v) ? snode(j, v)->next_sibling : UINT32_MAX;
363
+ }
364
+ if (!found) {
365
+ return 0;
366
+ }
367
+ } else {
368
+ return 0; /* a pointer into a scalar */
369
+ }
370
+ }
371
+ *out = cur;
372
+ return cur != UINT32_MAX;
373
+ }
374
+
375
+ /* uniqueItems: canonical hash buckets, canonical equality inside a
376
+ * bucket; allocation failure skips the check (oom flags the walk). */
377
+ static void check_unique(js_ctx* j, uint32_t seq_id, uint32_t inst_off) {
378
+ const yep_dom* d = j->v.dom;
379
+ const yep_dnode* s = yep_dom_node(d, seq_id);
380
+ uint32_t n = s->count;
381
+ if (n < 2) {
382
+ return;
383
+ }
384
+ const yep_allocator* sys = yep_system_allocator();
385
+ uint32_t cap = 8;
386
+ while (cap < n * 2 && cap < JS_MAX_UNIQUE) {
387
+ cap *= 2;
388
+ }
389
+ /* cap never reaches n when n exceeds JS_MAX_UNIQUE: elements beyond
390
+ * a FULL table are unchecked below (a 100%%-full table cannot probe
391
+ * — the linear scan would spin forever). Bounded memory, bounded
392
+ * time; the check degrades to the first `cap` distinct entries. */
393
+ struct {
394
+ uint32_t hash;
395
+ uint32_t node; /* +1 encoded */
396
+ }* tbl = yep_alloc(sys, (size_t)cap * sizeof(*tbl));
397
+ if (tbl == NULL) {
398
+ j->v.oom = 1;
399
+ return;
400
+ }
401
+ memset(tbl, 0, (size_t)cap * sizeof(*tbl));
402
+ uint32_t ch = s->first_child;
403
+ int idx = 0;
404
+ int reported = 0;
405
+ uint32_t inserted = 0;
406
+ while (ch != UINT32_MAX && !reported) {
407
+ if (inserted == cap) {
408
+ break; /* the table is full: stop checking (see the cap note) */
409
+ }
410
+ uint64_t h64 = yep_validate_node_hash(d, ch, 0);
411
+ uint32_t h = (uint32_t)(h64 ^ (h64 >> 32));
412
+ uint32_t i = h & (cap - 1);
413
+ int dup = 0;
414
+ while (tbl[i].node != 0) {
415
+ if (tbl[i].hash == h && yep_validate_node_equal(d, tbl[i].node - 1, d, ch, 0)) {
416
+ dup = 1;
417
+ break;
418
+ }
419
+ i = (i + 1) & (cap - 1);
420
+ }
421
+ if (dup) {
422
+ reported = 1;
423
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_UNIQUE_ITEMS, inst_off,
424
+ "elements %u and %u are equal", (uint32_t)idx, (uint32_t)(idx - 1));
425
+ } else {
426
+ tbl[i].hash = h;
427
+ tbl[i].node = ch + 1;
428
+ inserted++;
429
+ }
430
+ ch = yep_dom_node(d, ch) ? yep_dom_node(d, ch)->next_sibling : UINT32_MAX;
431
+ idx++;
432
+ }
433
+ yep_free(sys, tbl);
434
+ }
435
+
436
+ /* The instance's string length in code points (2020-12 minLength/
437
+ * maxLength). 0 when the instance is not a string. */
438
+ static int inst_str_len(const yep_dom* d, uint32_t id, uint32_t* out) {
439
+ const yep_dnode* n = yep_dom_node(d, id);
440
+ if (n == NULL) {
441
+ return 0;
442
+ }
443
+ if (n->kind == YEP_DOM_ALIAS) {
444
+ return n->target != UINT32_MAX && inst_str_len(d, n->target, out);
445
+ }
446
+ if (n->kind != YEP_DOM_SCALAR || yep_validate_scalar_tag(n) != YEPTRIS_TAG_STR) {
447
+ return 0;
448
+ }
449
+ yep_view v = yep_dom_view(d, n->value);
450
+ uint32_t chars = 0;
451
+ for (uint32_t i = 0; i < v.len; i++) {
452
+ chars += ((((unsigned char)v.p[i]) & 0xC0) != 0x80);
453
+ }
454
+ *out = chars;
455
+ return 1;
456
+ }
457
+
458
+ /* Reads a keyword's numeric value (int or float scalar); 0 unreadable. */
459
+ static int schema_number(const js_ctx* j, uint32_t id, double* out) {
460
+ const yep_dnode* n = snode(j, id);
461
+ if (n == NULL || n->kind != YEP_DOM_SCALAR) {
462
+ return 0;
463
+ }
464
+ uint8_t tag = yep_validate_scalar_tag(n);
465
+ if (tag != YEPTRIS_TAG_INT && tag != YEPTRIS_TAG_FLOAT) {
466
+ return 0;
467
+ }
468
+ yep_view v = yep_dom_view(j->sd, n->value);
469
+ int64_t iv = 0;
470
+ if (yep_num_i64(v.p, v.len, &iv) == 0) {
471
+ *out = (double)iv;
472
+ return 1;
473
+ }
474
+ return yep_num_f64(v.p, v.len, out) == 0;
475
+ }
476
+
477
+ /* Applies one keyword value that must be a string (the type names,
478
+ * pattern sources); 0 when unreadable. */
479
+ static int schema_word(const js_ctx* j, uint32_t id, const char** p, uint32_t* len) {
480
+ const yep_dnode* n = snode(j, id);
481
+ if (n == NULL || n->kind != YEP_DOM_SCALAR || yep_validate_scalar_tag(n) != YEPTRIS_TAG_STR) {
482
+ return 0;
483
+ }
484
+ yep_view v = yep_dom_view(j->sd, n->value);
485
+ if (v.p == NULL) {
486
+ return 0;
487
+ }
488
+ *p = v.p;
489
+ *len = v.len;
490
+ return 1;
491
+ }
492
+
493
+ static void js_apply(js_ctx* j, uint32_t schema_id, uint32_t inst_id, int depth) {
494
+ if (j->v.oom) {
495
+ return;
496
+ }
497
+ if (++depth > YEP_DOM_MAX_DEPTH) {
498
+ if (depth == YEP_DOM_MAX_DEPTH + 1) {
499
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_DEPTH, UINT32_MAX,
500
+ "schema application exceeded the nesting limit");
501
+ }
502
+ return;
503
+ }
504
+ inst_id = inst_real(j->v.dom, inst_id);
505
+ const yep_dnode* s = snode(j, schema_id);
506
+ if (s == NULL) {
507
+ return;
508
+ }
509
+
510
+ /* the boolean schemas */
511
+ if (s->kind != YEP_DOM_MAPPING) {
512
+ if (s->kind == YEP_DOM_SCALAR && yep_validate_scalar_tag(s) == YEPTRIS_TAG_BOOL) {
513
+ yep_view v = yep_dom_view(j->sd, s->value);
514
+ if (v.len == 5 && memcmp(v.p, "false", 5) == 0) {
515
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_FALSE_SCHEMA,
516
+ yep_validate_node_off(j->v.dom, inst_id),
517
+ "the schema is false here");
518
+ }
519
+ }
520
+ return; /* true (or anything unreadable) constrains nothing */
521
+ }
522
+
523
+ /* $ref composes with its siblings (2020-12); the chain guard kills
524
+ * schema-only cycles that consume no instance structure */
525
+ uint32_t ref_val;
526
+ if (kw_get(j, schema_id, "$ref", &ref_val) && ref_val != UINT32_MAX) {
527
+ const char* rp = NULL;
528
+ uint32_t rl = 0;
529
+ if (schema_word(j, ref_val, &rp, &rl)) {
530
+ if (++j->v.ref_depth > JS_MAX_REF_CHAIN) {
531
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_REF, yep_validate_node_off(j->v.dom, inst_id),
532
+ "$ref chain exceeds %d (a cycle?)", JS_MAX_REF_CHAIN);
533
+ j->v.ref_depth = 0;
534
+ return;
535
+ }
536
+ uint32_t target = UINT32_MAX;
537
+ if (!resolve_ref(j, rp, rl, &target)) {
538
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_REF, yep_validate_node_off(j->v.dom, inst_id),
539
+ "$ref \"%.*s\" does not resolve", (int)(rl > 64 ? 64 : rl), rp);
540
+ j->v.ref_depth = 0;
541
+ return;
542
+ }
543
+ js_apply(j, target, inst_id, depth);
544
+ j->v.ref_depth = 0;
545
+ }
546
+ /* an unreadable $ref value: ignored like any unknown keyword */
547
+ } else {
548
+ j->v.ref_depth = 0;
549
+ }
550
+
551
+ /* type: a single word or a list of words */
552
+ {
553
+ uint32_t tv;
554
+ if (kw_get(j, schema_id, "type", &tv) && tv != UINT32_MAX) {
555
+ const yep_dnode* tn = snode(j, tv);
556
+ const char* word = NULL;
557
+ uint32_t wl = 0;
558
+ const char* it = NULL;
559
+ int iis_int = 0;
560
+ inst_type(j->v.dom, inst_id, &it, &iis_int);
561
+ int matched = 0;
562
+ int have = 0;
563
+ if (tn != NULL && tn->kind == YEP_DOM_SCALAR && schema_word(j, tv, &word, &wl)) {
564
+ have = 1;
565
+ matched = (strlen(it) == wl && memcmp(it, word, wl) == 0);
566
+ /* "number" also accepts integers */
567
+ if (!matched && wl == 6 && memcmp(word, "number", 6) == 0 && iis_int) {
568
+ matched = 1;
569
+ }
570
+ } else if (tn != NULL && tn->kind == YEP_DOM_SEQUENCE) {
571
+ have = 1;
572
+ uint32_t ch = tn->first_child;
573
+ while (ch != UINT32_MAX && !matched) {
574
+ const char* w2 = NULL;
575
+ uint32_t l2 = 0;
576
+ if (schema_word(j, ch, &w2, &l2)) {
577
+ matched = (strlen(it) == l2 && memcmp(it, w2, l2) == 0);
578
+ if (!matched && l2 == 6 && memcmp(w2, "number", 6) == 0 && iis_int) {
579
+ matched = 1;
580
+ }
581
+ }
582
+ ch = snode(j, ch) ? snode(j, ch)->next_sibling : UINT32_MAX;
583
+ }
584
+ }
585
+ if (have && !matched) {
586
+ char want[24];
587
+ size_t cl = wl < sizeof(want) - 1 ? wl : sizeof(want) - 1;
588
+ if (cl > 0 && word != NULL) {
589
+ memcpy(want, word, cl);
590
+ }
591
+ want[cl] = '\0';
592
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_TYPE,
593
+ yep_validate_node_off(j->v.dom, inst_id), "expected %s, got %s",
594
+ want, it);
595
+ }
596
+ }
597
+ }
598
+
599
+ /* const / enum */
600
+ {
601
+ uint32_t cv;
602
+ if (kw_get(j, schema_id, "const", &cv) && cv != UINT32_MAX && snode(j, cv) != NULL) {
603
+ if (!yep_validate_node_equal(j->sd, cv, j->v.dom, inst_id, 0)) {
604
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_CONST,
605
+ yep_validate_node_off(j->v.dom, inst_id),
606
+ "value is not the constant");
607
+ }
608
+ }
609
+ uint32_t ev;
610
+ if (kw_get(j, schema_id, "enum", &ev) && ev != UINT32_MAX) {
611
+ const yep_dnode* en = snode(j, ev);
612
+ if (en != NULL && en->kind == YEP_DOM_SEQUENCE) {
613
+ int found = 0;
614
+ uint32_t members = 0;
615
+ uint32_t ch = en->first_child;
616
+ while (ch != UINT32_MAX) {
617
+ members++;
618
+ if (!found && yep_validate_node_equal(j->sd, ch, j->v.dom, inst_id, 0)) {
619
+ found = 1;
620
+ }
621
+ ch = snode(j, ch) ? snode(j, ch)->next_sibling : UINT32_MAX;
622
+ }
623
+ if (!found) {
624
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_ENUM,
625
+ yep_validate_node_off(j->v.dom, inst_id),
626
+ "value is not one of %u enum members", members);
627
+ }
628
+ }
629
+ }
630
+ }
631
+
632
+ /* the numeric bounds (ignored on non-numeric instances) */
633
+ {
634
+ double iv = 0;
635
+ int is_num = inst_number(j->v.dom, inst_id, &iv);
636
+ if (is_num) {
637
+ uint32_t mv;
638
+ if (kw_get(j, schema_id, "minimum", &mv) && mv != UINT32_MAX) {
639
+ double b;
640
+ if (schema_number(j, mv, &b) && iv < b) {
641
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_MINIMUM,
642
+ yep_validate_node_off(j->v.dom, inst_id),
643
+ "%g is less than the minimum %g", iv, b);
644
+ }
645
+ }
646
+ if (kw_get(j, schema_id, "maximum", &mv) && mv != UINT32_MAX) {
647
+ double b;
648
+ if (schema_number(j, mv, &b) && iv > b) {
649
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_MAXIMUM,
650
+ yep_validate_node_off(j->v.dom, inst_id),
651
+ "%g is greater than the maximum %g", iv, b);
652
+ }
653
+ }
654
+ if (kw_get(j, schema_id, "exclusiveMinimum", &mv) && mv != UINT32_MAX) {
655
+ double b;
656
+ if (schema_number(j, mv, &b) && iv <= b) {
657
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_EXCLUSIVE_MINIMUM,
658
+ yep_validate_node_off(j->v.dom, inst_id),
659
+ "%g is not greater than %g", iv, b);
660
+ }
661
+ }
662
+ if (kw_get(j, schema_id, "exclusiveMaximum", &mv) && mv != UINT32_MAX) {
663
+ double b;
664
+ if (schema_number(j, mv, &b) && iv >= b) {
665
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_EXCLUSIVE_MAXIMUM,
666
+ yep_validate_node_off(j->v.dom, inst_id),
667
+ "%g is not less than %g", iv, b);
668
+ }
669
+ }
670
+ if (kw_get(j, schema_id, "multipleOf", &mv) && mv != UINT32_MAX) {
671
+ double m;
672
+ if (schema_number(j, mv, &m)) {
673
+ /* integrality of iv/m, libm-free: NaN fails (q==q),
674
+ * +-inf fails (q-q is NaN); every finite |q| >= 2^53
675
+ * is already an integer at double precision, and
676
+ * below that the int64 cast round-trips iff q is
677
+ * integral. A 0 divisor (never a legal schema) lands
678
+ * in inf/NaN and fails every instance. */
679
+ double q = iv / m;
680
+ int ok = (q == q) && (q - q == 0);
681
+ if (ok) {
682
+ double aq = q < 0 ? -q : q;
683
+ ok = aq >= 9007199254740992.0 /* 2^53 */ || q == (double)(int64_t)q;
684
+ }
685
+ if (!ok) {
686
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_MULTIPLE_OF,
687
+ yep_validate_node_off(j->v.dom, inst_id),
688
+ "%g is not a multiple of %g", iv, m);
689
+ }
690
+ }
691
+ }
692
+ }
693
+ }
694
+
695
+ /* string length + pattern (ignored on non-strings) */
696
+ {
697
+ uint32_t sl = 0;
698
+ int is_str = inst_str_len(j->v.dom, inst_id, &sl);
699
+ if (is_str) {
700
+ uint32_t mv;
701
+ if (kw_get(j, schema_id, "minLength", &mv) && mv != UINT32_MAX) {
702
+ double b;
703
+ if (schema_number(j, mv, &b) && (double)sl < b) {
704
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_MIN_LENGTH,
705
+ yep_validate_node_off(j->v.dom, inst_id),
706
+ "length %u is shorter than %g", sl, b);
707
+ }
708
+ }
709
+ if (kw_get(j, schema_id, "maxLength", &mv) && mv != UINT32_MAX) {
710
+ double b;
711
+ if (schema_number(j, mv, &b) && (double)sl > b) {
712
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_MAX_LENGTH,
713
+ yep_validate_node_off(j->v.dom, inst_id),
714
+ "length %u is longer than %g", sl, b);
715
+ }
716
+ }
717
+ uint32_t pv;
718
+ if (kw_get(j, schema_id, "pattern", &pv) && pv != UINT32_MAX) {
719
+ const char* pat = NULL;
720
+ uint32_t pl = 0;
721
+ if (schema_word(j, pv, &pat, &pl)) {
722
+ const yep_dnode* n = yep_dom_node(j->v.dom, inst_id);
723
+ yep_view v = yep_dom_view(j->v.dom, n->value);
724
+ int rc = yep_re_cache_search(j->cache, pat, pl, v.p != NULL ? v.p : "", v.len);
725
+ if (rc == 0) {
726
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_PATTERN,
727
+ yep_validate_node_off(j->v.dom, inst_id),
728
+ "does not match the pattern");
729
+ }
730
+ /* rc < 0: the pattern is outside the subset — ignored */
731
+ }
732
+ }
733
+ }
734
+ }
735
+
736
+ /* array bounds + uniqueness + items (ignored on non-arrays) */
737
+ {
738
+ uint32_t cnt = 0;
739
+ if (inst_seq_count(j->v.dom, inst_id, &cnt)) {
740
+ uint32_t mv;
741
+ if (kw_get(j, schema_id, "minItems", &mv) && mv != UINT32_MAX) {
742
+ double b;
743
+ if (schema_number(j, mv, &b) && (double)cnt < b) {
744
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_MIN_ITEMS,
745
+ yep_validate_node_off(j->v.dom, inst_id),
746
+ "%u items is fewer than %g", cnt, b);
747
+ }
748
+ }
749
+ if (kw_get(j, schema_id, "maxItems", &mv) && mv != UINT32_MAX) {
750
+ double b;
751
+ if (schema_number(j, mv, &b) && (double)cnt > b) {
752
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_MAX_ITEMS,
753
+ yep_validate_node_off(j->v.dom, inst_id),
754
+ "%u items is more than %g", cnt, b);
755
+ }
756
+ }
757
+ uint32_t uv;
758
+ int uf = 0;
759
+ if (kw_schema(j, schema_id, "uniqueItems", &uv, &uf) && uf == 0) {
760
+ check_unique(j, inst_id, yep_validate_node_off(j->v.dom, inst_id));
761
+ }
762
+ /* contains: count the items the schema holds on (suppressed
763
+ * probes), then the minContains/maxContains qualifiers
764
+ * (defaults: at least one; no maximum). min/maxContains
765
+ * without contains are annotations — ignored. */
766
+ uint32_t ctv;
767
+ if (kw_get(j, schema_id, "contains", &ctv) && ctv != UINT32_MAX) {
768
+ int ctf = 0;
769
+ if (snode(j, ctv) != NULL && schema_truth(j, ctv, &ctf)) {
770
+ uint32_t matches = 0;
771
+ uint32_t ich = yep_dom_node(j->v.dom, inst_id)
772
+ ? yep_dom_node(j->v.dom, inst_id)->first_child
773
+ : UINT32_MAX;
774
+ for (uint32_t i = 0; i < cnt && ich != UINT32_MAX; i++) {
775
+ uint32_t ni = yep_dom_node(j->v.dom, ich)
776
+ ? yep_dom_node(j->v.dom, ich)->next_sibling
777
+ : UINT32_MAX;
778
+ if (js_holds(j, ctv, ich, depth)) {
779
+ matches++;
780
+ }
781
+ ich = ni;
782
+ }
783
+ double minc = 1.0;
784
+ double maxc = -1.0; /* unset */
785
+ uint32_t mc;
786
+ int have_minc = 0;
787
+ if (kw_get(j, schema_id, "minContains", &mc) && mc != UINT32_MAX) {
788
+ if (schema_number(j, mc, &minc)) {
789
+ have_minc = 1;
790
+ } else {
791
+ minc = 1.0; /* an unreadable qualifier: default */
792
+ }
793
+ }
794
+ if (kw_get(j, schema_id, "maxContains", &mc) && mc != UINT32_MAX) {
795
+ if (!schema_number(j, mc, &maxc)) {
796
+ maxc = -1.0;
797
+ }
798
+ }
799
+ if ((double)matches < minc) {
800
+ yep_validate_emit(
801
+ &j->v, have_minc ? YEPTRIS_DIAG_MIN_CONTAINS : YEPTRIS_DIAG_CONTAINS,
802
+ yep_validate_node_off(j->v.dom, inst_id),
803
+ "%u of %u items match the contains schema (need %g)", matches, cnt,
804
+ minc);
805
+ } else if (maxc >= 0.0 && (double)matches > maxc) {
806
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_MAX_CONTAINS,
807
+ yep_validate_node_off(j->v.dom, inst_id),
808
+ "%u of %u items match the contains schema (max %g)",
809
+ matches, cnt, maxc);
810
+ }
811
+ }
812
+ }
813
+ /* prefixItems: per-index schemas; the draft-07 array form
814
+ * of items rides the same semantics */
815
+ uint32_t prefix = 0;
816
+ uint32_t pv;
817
+ if (kw_get(j, schema_id, "prefixItems", &pv) && pv != UINT32_MAX) {
818
+ const yep_dnode* pn = snode(j, pv);
819
+ if (pn != NULL && pn->kind == YEP_DOM_SEQUENCE) {
820
+ uint32_t i = 0;
821
+ uint32_t ch = pn->first_child;
822
+ uint32_t ich = yep_dom_node(j->v.dom, inst_id)->first_child;
823
+ while (ch != UINT32_MAX && ich != UINT32_MAX) {
824
+ uint32_t ns = snode(j, ch) ? snode(j, ch)->next_sibling : UINT32_MAX;
825
+ uint32_t ni = yep_dom_node(j->v.dom, ich)
826
+ ? yep_dom_node(j->v.dom, ich)->next_sibling
827
+ : UINT32_MAX;
828
+ size_t mark = j->v.path_len;
829
+ if (yep_validate_path_push_index(&j->v, i) == 0) {
830
+ js_apply(j, ch, ich, depth);
831
+ yep_validate_path_pop(&j->v, mark);
832
+ }
833
+ prefix++;
834
+ i++;
835
+ ch = ns;
836
+ ich = ni;
837
+ }
838
+ }
839
+ }
840
+ /* items: a single schema over the remaining elements; the
841
+ * draft-07 array form counts as the prefix */
842
+ if (kw_get(j, schema_id, "items", &pv) && pv != UINT32_MAX) {
843
+ const yep_dnode* pn = snode(j, pv);
844
+ int is_false = 0;
845
+ if (pn != NULL && pn->kind == YEP_DOM_SEQUENCE) {
846
+ /* the draft-07 tuple shim: per-index, from scratch */
847
+ uint32_t i = 0;
848
+ uint32_t ch = pn->first_child;
849
+ uint32_t ich = yep_dom_node(j->v.dom, inst_id)->first_child;
850
+ while (ch != UINT32_MAX && ich != UINT32_MAX) {
851
+ uint32_t ns = snode(j, ch) ? snode(j, ch)->next_sibling : UINT32_MAX;
852
+ uint32_t ni = yep_dom_node(j->v.dom, ich)
853
+ ? yep_dom_node(j->v.dom, ich)->next_sibling
854
+ : UINT32_MAX;
855
+ size_t mark = j->v.path_len;
856
+ if (yep_validate_path_push_index(&j->v, i) == 0) {
857
+ js_apply(j, ch, ich, depth);
858
+ yep_validate_path_pop(&j->v, mark);
859
+ }
860
+ i++;
861
+ ch = ns;
862
+ ich = ni;
863
+ }
864
+ } else if (schema_truth(j, pv, &is_false)) {
865
+ /* items applies from prefixItems' count on */
866
+ uint32_t ich = yep_dom_node(j->v.dom, inst_id)->first_child;
867
+ for (uint32_t skip = 0; skip < prefix && ich != UINT32_MAX; skip++) {
868
+ ich = yep_dom_node(j->v.dom, ich)
869
+ ? yep_dom_node(j->v.dom, ich)->next_sibling
870
+ : UINT32_MAX;
871
+ }
872
+ for (uint32_t i = prefix; i < cnt && ich != UINT32_MAX; i++) {
873
+ uint32_t ni = yep_dom_node(j->v.dom, ich)
874
+ ? yep_dom_node(j->v.dom, ich)->next_sibling
875
+ : UINT32_MAX;
876
+ size_t mark = j->v.path_len;
877
+ if (yep_validate_path_push_index(&j->v, i) == 0) {
878
+ js_apply(j, pv, ich, depth);
879
+ yep_validate_path_pop(&j->v, mark);
880
+ }
881
+ ich = ni;
882
+ }
883
+ }
884
+ }
885
+ }
886
+ }
887
+
888
+ /* allOf / anyOf / oneOf / not */
889
+ {
890
+ uint32_t cv;
891
+ if (kw_get(j, schema_id, "allOf", &cv) && cv != UINT32_MAX) {
892
+ const yep_dnode* cn = snode(j, cv);
893
+ if (cn != NULL && cn->kind == YEP_DOM_SEQUENCE) {
894
+ uint32_t ch = cn->first_child;
895
+ while (ch != UINT32_MAX) {
896
+ uint32_t nc = snode(j, ch) ? snode(j, ch)->next_sibling : UINT32_MAX;
897
+ js_apply(j, ch, inst_id, depth);
898
+ ch = nc;
899
+ }
900
+ }
901
+ }
902
+ if (kw_get(j, schema_id, "anyOf", &cv) && cv != UINT32_MAX) {
903
+ const yep_dnode* cn = snode(j, cv);
904
+ if (cn != NULL && cn->kind == YEP_DOM_SEQUENCE) {
905
+ int any = 0;
906
+ uint32_t branches = 0;
907
+ uint32_t ch = cn->first_child;
908
+ while (ch != UINT32_MAX) {
909
+ branches++;
910
+ if (!any && js_holds(j, ch, inst_id, depth)) {
911
+ any = 1;
912
+ }
913
+ ch = snode(j, ch) ? snode(j, ch)->next_sibling : UINT32_MAX;
914
+ }
915
+ if (!any) {
916
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_ANY_OF,
917
+ yep_validate_node_off(j->v.dom, inst_id),
918
+ "none of the %u anyOf branches matched", branches);
919
+ }
920
+ }
921
+ }
922
+ if (kw_get(j, schema_id, "oneOf", &cv) && cv != UINT32_MAX) {
923
+ const yep_dnode* cn = snode(j, cv);
924
+ if (cn != NULL && cn->kind == YEP_DOM_SEQUENCE) {
925
+ uint32_t matches = 0;
926
+ uint32_t branches = 0;
927
+ uint32_t ch = cn->first_child;
928
+ while (ch != UINT32_MAX) {
929
+ branches++;
930
+ if (js_holds(j, ch, inst_id, depth)) {
931
+ matches++;
932
+ }
933
+ ch = snode(j, ch) ? snode(j, ch)->next_sibling : UINT32_MAX;
934
+ }
935
+ if (matches != 1) {
936
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_ONE_OF,
937
+ yep_validate_node_off(j->v.dom, inst_id),
938
+ "%u of the %u oneOf branches matched (exactly one "
939
+ "required)",
940
+ matches, branches);
941
+ }
942
+ }
943
+ }
944
+ if (kw_get(j, schema_id, "not", &cv) && cv != UINT32_MAX) {
945
+ if (js_holds(j, cv, inst_id, depth)) {
946
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_NOT, yep_validate_node_off(j->v.dom, inst_id),
947
+ "the not-schema matched");
948
+ }
949
+ }
950
+ }
951
+
952
+ /* if/then/else: the if-schema rides a suppressed probe; exactly one
953
+ * of then/else applies. An unreadable if (or then/else) is ignored. */
954
+ {
955
+ uint32_t ifv;
956
+ if (kw_get(j, schema_id, "if", &ifv) && ifv != UINT32_MAX) {
957
+ int iff = 0;
958
+ if (schema_truth(j, ifv, &iff)) {
959
+ uint32_t tv = UINT32_MAX, ev = UINT32_MAX;
960
+ int tf = 0, ef = 0;
961
+ int have_t = kw_schema(j, schema_id, "then", &tv, &tf);
962
+ int have_e = kw_schema(j, schema_id, "else", &ev, &ef);
963
+ if (js_holds(j, ifv, inst_id, depth)) {
964
+ if (have_t) {
965
+ js_apply(j, tv, inst_id, depth);
966
+ }
967
+ } else if (have_e) {
968
+ js_apply(j, ev, inst_id, depth);
969
+ }
970
+ }
971
+ }
972
+ }
973
+
974
+ /* the object keywords (ignored on non-objects) */
975
+ {
976
+ const yep_dnode* n = yep_dom_node(j->v.dom, inst_id);
977
+ if (n != NULL && n->kind == YEP_DOM_MAPPING) {
978
+ const uint32_t pairs = n->count / 2;
979
+ /* properties: known keys */
980
+ uint32_t props = UINT32_MAX;
981
+ int have_props = kw_get(j, schema_id, "properties", &props) && props != UINT32_MAX &&
982
+ snode(j, props) != NULL && snode(j, props)->kind == YEP_DOM_MAPPING;
983
+ /* patternProperties: pattern -> schema over the KEY strings
984
+ * (readable once; additionalProperties must exclude its
985
+ * matches too) */
986
+ uint32_t pp_id = UINT32_MAX;
987
+ {
988
+ uint32_t ppid;
989
+ if (kw_get(j, schema_id, "patternProperties", &ppid) && ppid != UINT32_MAX) {
990
+ const yep_dnode* pnm = snode(j, ppid);
991
+ if (pnm != NULL && pnm->kind == YEP_DOM_MAPPING) {
992
+ pp_id = ppid;
993
+ }
994
+ }
995
+ }
996
+ uint32_t req;
997
+ if (kw_get(j, schema_id, "required", &req) && req != UINT32_MAX) {
998
+ const yep_dnode* rn = snode(j, req);
999
+ if (rn != NULL && rn->kind == YEP_DOM_SEQUENCE) {
1000
+ uint32_t ch = rn->first_child;
1001
+ while (ch != UINT32_MAX) {
1002
+ const char* name = NULL;
1003
+ uint32_t nl = 0;
1004
+ uint32_t nc = snode(j, ch) ? snode(j, ch)->next_sibling : UINT32_MAX;
1005
+ if (schema_word(j, ch, &name, &nl) &&
1006
+ inst_find_key(j->v.dom, inst_id, name, nl, NULL) == UINT32_MAX) {
1007
+ char namebuf[64];
1008
+ size_t cl = nl < sizeof(namebuf) - 1 ? nl : sizeof(namebuf) - 1;
1009
+ memcpy(namebuf, name, cl);
1010
+ namebuf[cl] = '\0';
1011
+ /* the diagnostic points at the missing
1012
+ * leaf: object path + /key (the ajv
1013
+ * instancePath convention) */
1014
+ size_t mark = j->v.path_len;
1015
+ int pushed = yep_validate_path_push(&j->v, namebuf, cl);
1016
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_REQUIRED,
1017
+ yep_validate_node_off(j->v.dom, inst_id),
1018
+ "missing required property \"%s\"", namebuf);
1019
+ if (pushed == 0) {
1020
+ yep_validate_path_pop(&j->v, mark);
1021
+ }
1022
+ }
1023
+ ch = nc;
1024
+ }
1025
+ }
1026
+ }
1027
+ /* properties walk (values through their schemas) */
1028
+ if (have_props) {
1029
+ const yep_dnode* pn = snode(j, props);
1030
+ uint32_t k = pn->first_child;
1031
+ while (k != UINT32_MAX) {
1032
+ uint32_t v = snode(j, k) ? snode(j, k)->next_sibling : UINT32_MAX;
1033
+ const yep_dnode* kn = snode(j, k);
1034
+ if (kn != NULL && kn->kind == YEP_DOM_SCALAR) {
1035
+ yep_view kv = yep_dom_view(j->sd, kn->value);
1036
+ /* find the child pair by key bytes */
1037
+ uint32_t ck = n->first_child;
1038
+ while (ck != UINT32_MAX) {
1039
+ uint32_t cv = yep_dom_node(j->v.dom, ck)
1040
+ ? yep_dom_node(j->v.dom, ck)->next_sibling
1041
+ : UINT32_MAX;
1042
+ const yep_dnode* ckn = yep_dom_node(j->v.dom, ck);
1043
+ if (ckn != NULL && ckn->kind == YEP_DOM_SCALAR) {
1044
+ yep_view ckv = yep_dom_view(j->v.dom, ckn->value);
1045
+ if (ckv.len == kv.len &&
1046
+ (kv.len == 0 || memcmp(ckv.p, kv.p, kv.len) == 0)) {
1047
+ size_t mark = j->v.path_len;
1048
+ if (yep_validate_path_push(&j->v, kv.p != NULL ? kv.p : "",
1049
+ kv.len) == 0) {
1050
+ js_apply(j, v, cv, depth);
1051
+ yep_validate_path_pop(&j->v, mark);
1052
+ }
1053
+ break;
1054
+ }
1055
+ }
1056
+ ck = cv != UINT32_MAX && yep_dom_node(j->v.dom, cv)
1057
+ ? yep_dom_node(j->v.dom, cv)->next_sibling
1058
+ : UINT32_MAX;
1059
+ }
1060
+ }
1061
+ k = v != UINT32_MAX && snode(j, v) ? snode(j, v)->next_sibling : UINT32_MAX;
1062
+ }
1063
+ }
1064
+ /* patternProperties: every pattern that matches a key
1065
+ * applies its schema to that key's value */
1066
+ if (pp_id != UINT32_MAX) {
1067
+ check_pattern_properties(j, pp_id, inst_id, depth);
1068
+ }
1069
+ /* propertyNames: the schema applies to each KEY string */
1070
+ {
1071
+ uint32_t pnv;
1072
+ if (kw_get(j, schema_id, "propertyNames", &pnv) && pnv != UINT32_MAX) {
1073
+ int pnf = 0;
1074
+ if (snode(j, pnv) != NULL && schema_truth(j, pnv, &pnf)) {
1075
+ check_property_names(j, pnv, inst_id, depth);
1076
+ }
1077
+ }
1078
+ }
1079
+ /* min/maxProperties */
1080
+ {
1081
+ uint32_t mv;
1082
+ if (kw_get(j, schema_id, "minProperties", &mv) && mv != UINT32_MAX) {
1083
+ double b;
1084
+ if (schema_number(j, mv, &b) && (double)pairs < b) {
1085
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_MIN_PROPERTIES,
1086
+ yep_validate_node_off(j->v.dom, inst_id),
1087
+ "%u properties is fewer than %g", pairs, b);
1088
+ }
1089
+ }
1090
+ if (kw_get(j, schema_id, "maxProperties", &mv) && mv != UINT32_MAX) {
1091
+ double b;
1092
+ if (schema_number(j, mv, &b) && (double)pairs > b) {
1093
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_MAX_PROPERTIES,
1094
+ yep_validate_node_off(j->v.dom, inst_id),
1095
+ "%u properties is more than %g", pairs, b);
1096
+ }
1097
+ }
1098
+ }
1099
+ /* dependentRequired: name -> the names that must ride along */
1100
+ {
1101
+ uint32_t drv;
1102
+ if (kw_get(j, schema_id, "dependentRequired", &drv) && drv != UINT32_MAX) {
1103
+ check_dependent_required(j, drv, inst_id, depth);
1104
+ }
1105
+ }
1106
+ /* dependentSchemas: when the named key is present, its
1107
+ * schema applies to the whole object (walked directly, like
1108
+ * allOf: the inner diagnostics carry the failure) */
1109
+ {
1110
+ uint32_t dsv;
1111
+ if (kw_get(j, schema_id, "dependentSchemas", &dsv) && dsv != UINT32_MAX) {
1112
+ check_dependent_schemas(j, dsv, inst_id, depth);
1113
+ }
1114
+ }
1115
+ /* additionalProperties: the keys properties does not name */
1116
+ uint32_t ap;
1117
+ int af = 0;
1118
+ if (kw_schema(j, schema_id, "additionalProperties", &ap, &af)) {
1119
+ /* k steps pair-to-pair: every visited node IS a key */
1120
+ uint32_t k = n->first_child;
1121
+ while (k != UINT32_MAX) {
1122
+ uint32_t v = yep_dom_node(j->v.dom, k) ? yep_dom_node(j->v.dom, k)->next_sibling
1123
+ : UINT32_MAX;
1124
+ const yep_dnode* kn = yep_dom_node(j->v.dom, k);
1125
+ if (kn != NULL && kn->kind == YEP_DOM_SCALAR) {
1126
+ yep_view kv = yep_dom_view(j->v.dom, kn->value);
1127
+ int known = 0;
1128
+ if (have_props) {
1129
+ uint32_t pk = snode(j, props)->first_child;
1130
+ while (pk != UINT32_MAX && !known) {
1131
+ const yep_dnode* pkn = snode(j, pk);
1132
+ uint32_t pv =
1133
+ snode(j, pk) ? snode(j, pk)->next_sibling : UINT32_MAX;
1134
+ if (pkn != NULL && pkn->kind == YEP_DOM_SCALAR) {
1135
+ yep_view pw = yep_dom_view(j->sd, pkn->value);
1136
+ known = pw.len == kv.len &&
1137
+ (kv.len == 0 || memcmp(pw.p, kv.p, kv.len) == 0);
1138
+ }
1139
+ pk = pv != UINT32_MAX && snode(j, pv) ? snode(j, pv)->next_sibling
1140
+ : UINT32_MAX;
1141
+ }
1142
+ }
1143
+ /* patternProperties matches take the key out of
1144
+ * additionalProperties' scope too (2020-12) */
1145
+ if (!known && pp_id != UINT32_MAX) {
1146
+ const yep_dnode* pnm = snode(j, pp_id);
1147
+ uint32_t pk = pnm->first_child;
1148
+ while (pk != UINT32_MAX && !known) {
1149
+ const yep_dnode* pkn = snode(j, pk);
1150
+ uint32_t pv =
1151
+ snode(j, pk) ? snode(j, pk)->next_sibling : UINT32_MAX;
1152
+ const char* pat = NULL;
1153
+ uint32_t pl = 0;
1154
+ if (pkn != NULL && pkn->kind == YEP_DOM_SCALAR &&
1155
+ schema_word(j, pk, &pat, &pl) &&
1156
+ yep_re_cache_search(j->cache, pat, pl, kv.p != NULL ? kv.p : "",
1157
+ kv.len) == 1) {
1158
+ known = 1;
1159
+ }
1160
+ pk = pv != UINT32_MAX && snode(j, pv) ? snode(j, pv)->next_sibling
1161
+ : UINT32_MAX;
1162
+ }
1163
+ }
1164
+ if (!known) {
1165
+ char namebuf[64];
1166
+ size_t cl = kv.len < sizeof(namebuf) - 1 ? kv.len : sizeof(namebuf) - 1;
1167
+ if (cl > 0 && kv.p != NULL) {
1168
+ memcpy(namebuf, kv.p, cl);
1169
+ }
1170
+ namebuf[cl] = '\0';
1171
+ if (af) {
1172
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_ADDITIONAL_PROPERTY,
1173
+ yep_validate_node_off(j->v.dom, k),
1174
+ "additional property \"%s\"", namebuf);
1175
+ } else {
1176
+ size_t mark = j->v.path_len;
1177
+ if (yep_validate_path_push(&j->v, namebuf, cl) == 0) {
1178
+ js_apply(j, ap, v, depth);
1179
+ yep_validate_path_pop(&j->v, mark);
1180
+ }
1181
+ }
1182
+ }
1183
+ }
1184
+ k = v != UINT32_MAX && yep_dom_node(j->v.dom, v)
1185
+ ? yep_dom_node(j->v.dom, v)->next_sibling
1186
+ : UINT32_MAX;
1187
+ }
1188
+ }
1189
+ }
1190
+ }
1191
+ }
1192
+
1193
+ /* ---- the object-keyword walkers ------------------------------------------ */
1194
+
1195
+ /* Copies a bounded name for diagnostics/detail text: the copied
1196
+ * length (the caller pushes paths with it — keys may hold NULs). */
1197
+ static size_t name_copy(char* buf, size_t cap, const char* p, size_t len) {
1198
+ size_t cl = len < cap - 1 ? len : cap - 1;
1199
+ if (cl > 0 && p != NULL) {
1200
+ memcpy(buf, p, cl);
1201
+ }
1202
+ buf[cl] = '\0';
1203
+ return cl;
1204
+ }
1205
+
1206
+ /* patternProperties: pattern -> schema over the KEY strings; every
1207
+ * matching pattern applies its schema to the key's value (a false
1208
+ * schema rides js_apply's FALSE_SCHEMA diagnostic). */
1209
+ static void check_pattern_properties(js_ctx* j, uint32_t pp_id, uint32_t inst_id, int depth) {
1210
+ const yep_dom* d = j->v.dom;
1211
+ const yep_dnode* pnm = snode(j, pp_id);
1212
+ const yep_dnode* inst = yep_dom_node(d, inst_id);
1213
+ if (pnm == NULL || inst == NULL) {
1214
+ return;
1215
+ }
1216
+ uint32_t k = inst->first_child;
1217
+ while (k != UINT32_MAX && !j->v.oom) {
1218
+ uint32_t v = yep_dom_node(d, k) ? yep_dom_node(d, k)->next_sibling : UINT32_MAX;
1219
+ const yep_dnode* kn = yep_dom_node(d, k);
1220
+ uint32_t nk =
1221
+ v != UINT32_MAX && yep_dom_node(d, v) ? yep_dom_node(d, v)->next_sibling : UINT32_MAX;
1222
+ if (kn != NULL && kn->kind == YEP_DOM_SCALAR && v != UINT32_MAX) {
1223
+ yep_view kv = yep_dom_view(d, kn->value);
1224
+ uint32_t pk = pnm->first_child;
1225
+ while (pk != UINT32_MAX && !j->v.oom) {
1226
+ const yep_dnode* pkn = snode(j, pk);
1227
+ uint32_t pv = snode(j, pk) ? snode(j, pk)->next_sibling : UINT32_MAX;
1228
+ uint32_t np =
1229
+ pv != UINT32_MAX && snode(j, pv) ? snode(j, pv)->next_sibling : UINT32_MAX;
1230
+ const char* pat = NULL;
1231
+ uint32_t pl = 0;
1232
+ if (pkn != NULL && pkn->kind == YEP_DOM_SCALAR && pv != UINT32_MAX &&
1233
+ schema_word(j, pk, &pat, &pl) &&
1234
+ yep_re_cache_search(j->cache, pat, pl, kv.p != NULL ? kv.p : "", kv.len) == 1) {
1235
+ char namebuf[64];
1236
+ size_t cl = name_copy(namebuf, sizeof(namebuf), kv.p, kv.len);
1237
+ size_t mark = j->v.path_len;
1238
+ if (yep_validate_path_push(&j->v, namebuf, cl) == 0) {
1239
+ js_apply(j, pv, v, depth);
1240
+ yep_validate_path_pop(&j->v, mark);
1241
+ }
1242
+ }
1243
+ pk = np;
1244
+ }
1245
+ }
1246
+ k = nk;
1247
+ }
1248
+ }
1249
+
1250
+ /* propertyNames: the schema applies to each KEY (the key string IS the
1251
+ * instance; a false propertyNames rides FALSE_SCHEMA at the key). */
1252
+ static void check_property_names(js_ctx* j, uint32_t pn_id, uint32_t inst_id, int depth) {
1253
+ const yep_dom* d = j->v.dom;
1254
+ const yep_dnode* inst = yep_dom_node(d, inst_id);
1255
+ if (inst == NULL) {
1256
+ return;
1257
+ }
1258
+ uint32_t k = inst->first_child;
1259
+ while (k != UINT32_MAX && !j->v.oom) {
1260
+ uint32_t v = yep_dom_node(d, k) ? yep_dom_node(d, k)->next_sibling : UINT32_MAX;
1261
+ const yep_dnode* kn = yep_dom_node(d, k);
1262
+ uint32_t nk =
1263
+ v != UINT32_MAX && yep_dom_node(d, v) ? yep_dom_node(d, v)->next_sibling : UINT32_MAX;
1264
+ if (kn != NULL && kn->kind == YEP_DOM_SCALAR) {
1265
+ yep_view kv = yep_dom_view(d, kn->value);
1266
+ char namebuf[64];
1267
+ size_t cl = name_copy(namebuf, sizeof(namebuf), kv.p, kv.len);
1268
+ size_t mark = j->v.path_len;
1269
+ if (yep_validate_path_push(&j->v, namebuf, cl) == 0) {
1270
+ js_apply(j, pn_id, k, depth);
1271
+ yep_validate_path_pop(&j->v, mark);
1272
+ }
1273
+ }
1274
+ k = nk;
1275
+ }
1276
+ }
1277
+
1278
+ /* dependentRequired: name -> the names required alongside it. A
1279
+ * present trigger with an absent dependent is DIAG_DEPENDENT_REQUIRED
1280
+ * at the object (the missing leaf's path, the required convention). */
1281
+ static void check_dependent_required(js_ctx* j, uint32_t dr_id, uint32_t inst_id, int depth) {
1282
+ (void)depth;
1283
+ const yep_dom* d = j->v.dom;
1284
+ const yep_dnode* dm = snode(j, dr_id);
1285
+ if (dm == NULL || dm->kind != YEP_DOM_MAPPING) {
1286
+ return;
1287
+ }
1288
+ uint32_t sk = dm->first_child;
1289
+ while (sk != UINT32_MAX && !j->v.oom) {
1290
+ uint32_t sv = snode(j, sk) ? snode(j, sk)->next_sibling : UINT32_MAX;
1291
+ uint32_t ns = sv != UINT32_MAX && snode(j, sv) ? snode(j, sv)->next_sibling : UINT32_MAX;
1292
+ const yep_dnode* skn = snode(j, sk);
1293
+ if (skn != NULL && skn->kind == YEP_DOM_SCALAR && sv != UINT32_MAX) {
1294
+ yep_view trig = yep_dom_view(j->sd, skn->value);
1295
+ const yep_dnode* ln = snode(j, sv);
1296
+ if (trig.p != NULL && ln != NULL && ln->kind == YEP_DOM_SEQUENCE &&
1297
+ inst_find_key(d, inst_id, trig.p, trig.len, NULL) != UINT32_MAX) {
1298
+ char trigbuf[64];
1299
+ name_copy(trigbuf, sizeof(trigbuf), trig.p, trig.len);
1300
+ uint32_t rk = ln->first_child;
1301
+ while (rk != UINT32_MAX && !j->v.oom) {
1302
+ uint32_t rn = snode(j, rk) ? snode(j, rk)->next_sibling : UINT32_MAX;
1303
+ const char* name = NULL;
1304
+ uint32_t nl = 0;
1305
+ if (schema_word(j, rk, &name, &nl) &&
1306
+ inst_find_key(d, inst_id, name, nl, NULL) == UINT32_MAX) {
1307
+ /* the detail text is printf-composed: the view
1308
+ * is NOT NUL-terminated — %s reads past the
1309
+ * DOM allocation (ASan-caught); copy first */
1310
+ char namebuf[64];
1311
+ size_t cl = name_copy(namebuf, sizeof(namebuf), name, nl);
1312
+ size_t mark = j->v.path_len;
1313
+ int pushed = yep_validate_path_push(&j->v, namebuf, cl);
1314
+ yep_validate_emit(&j->v, YEPTRIS_DIAG_DEPENDENT_REQUIRED,
1315
+ yep_validate_node_off(j->v.dom, inst_id),
1316
+ "\"%s\" requires \"%s\"", trigbuf, namebuf);
1317
+ if (pushed == 0) {
1318
+ yep_validate_path_pop(&j->v, mark);
1319
+ }
1320
+ }
1321
+ rk = rn;
1322
+ }
1323
+ }
1324
+ }
1325
+ sk = ns;
1326
+ }
1327
+ }
1328
+
1329
+ /* dependentSchemas: when the named key is present, its schema applies
1330
+ * to the whole object. */
1331
+ static void check_dependent_schemas(js_ctx* j, uint32_t ds_id, uint32_t inst_id, int depth) {
1332
+ const yep_dom* d = j->v.dom;
1333
+ const yep_dnode* dm = snode(j, ds_id);
1334
+ if (dm == NULL || dm->kind != YEP_DOM_MAPPING) {
1335
+ return;
1336
+ }
1337
+ uint32_t sk = dm->first_child;
1338
+ while (sk != UINT32_MAX && !j->v.oom) {
1339
+ uint32_t sv = snode(j, sk) ? snode(j, sk)->next_sibling : UINT32_MAX;
1340
+ uint32_t ns = sv != UINT32_MAX && snode(j, sv) ? snode(j, sv)->next_sibling : UINT32_MAX;
1341
+ const yep_dnode* skn = snode(j, sk);
1342
+ if (skn != NULL && skn->kind == YEP_DOM_SCALAR && sv != UINT32_MAX) {
1343
+ yep_view trig = yep_dom_view(j->sd, skn->value);
1344
+ if (trig.p != NULL && inst_find_key(d, inst_id, trig.p, trig.len, NULL) != UINT32_MAX) {
1345
+ js_apply(j, sv, inst_id, depth);
1346
+ }
1347
+ }
1348
+ sk = ns;
1349
+ }
1350
+ }
1351
+
1352
+ /* ---- the entry ----------------------------------------------------------- */
1353
+
1354
+ /* Validates one instance document root against the schema root.
1355
+ * Returns 0 ok, -1 OOM. */
1356
+ static int js_validate_doc(js_ctx* j, uint32_t inst_root) {
1357
+ js_apply(j, j->sroot, inst_root, 0);
1358
+ return j->v.oom ? -1 : 0;
1359
+ }
1360
+
1361
+ /* The public entries live in validate.c/compile.c (yeptris_json_schema_
1362
+ * validate compiles+validates+frees through the compiled handle; the
1363
+ * compiled face drives this seam per instance stream): they parse the
1364
+ * sides, build the report and hand over the schema DOM + the retained
1365
+ * pattern cache (the declarations are internal.h's — the shared SSOT). */
1366
+ int yep_jsonschema_run(const yep_dom* schema_dom, uint32_t schema_root, const yep_dom* instance_dom,
1367
+ const uint32_t* doc_roots, uint32_t doc_count, yeptris_validate_report* rep,
1368
+ const yep_re_cache* cache) {
1369
+ js_ctx j;
1370
+ memset(&j, 0, sizeof(j));
1371
+ j.sd = schema_dom;
1372
+ j.sroot = schema_root;
1373
+ j.cache = cache;
1374
+ j.v.dom = instance_dom;
1375
+ j.v.rep = rep;
1376
+ j.v.input = instance_dom->input_base;
1377
+ j.v.input_len = instance_dom->input_len;
1378
+ for (uint32_t i = 0; i < doc_count; i++) {
1379
+ j.v.path_len = 0;
1380
+ js_validate_doc(&j, doc_roots[i]);
1381
+ if (j.v.oom) {
1382
+ yep_free(yep_system_allocator(), j.v.line_off);
1383
+ yep_free(yep_system_allocator(), j.v.path);
1384
+ return -1;
1385
+ }
1386
+ }
1387
+ /* the walk's scratch (the lazy line index, the path buffer) dies
1388
+ * here: the report carries only finished diagnostics */
1389
+ yep_free(yep_system_allocator(), j.v.line_off);
1390
+ yep_free(yep_system_allocator(), j.v.path);
1391
+ return 0;
1392
+ }