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,939 @@
1
+ /* validate.c — the schema-family validation walk (#882 slice 1).
2
+ *
3
+ * One engine pass builds the DOM (the eager route: input-borrowed
4
+ * spans, no tape), one walk checks it against the family:
5
+ *
6
+ * - both families: explicit-tag consistency (the tag asserts a type
7
+ * the content cannot carry) and mapping-key uniqueness (spec
8
+ * 3.2.1.3: keys compare by canonical form — 0o13 and 0xB are ONE
9
+ * key; strings byte-wise; collections structurally);
10
+ * - 12_JSON only: mapping keys must be string scalars; plain scalars
11
+ * must match a JSON production (spec 10.2.2's `*` -> Error row);
12
+ * explicit non-JSON tags rejected.
13
+ *
14
+ * 11_COMPAT is the parse-compat resolver, not a validation family —
15
+ * the entry rejects it (ARG).
16
+ *
17
+ * MECE: parse/ owns the grammar (this walk only reads finished
18
+ * nodes), resolve/ owns typing (the family resolvers), emit/ owns
19
+ * bytes; diagnostics shape lives in validate.h. Allocation: the
20
+ * report and the scratch (line index, path, dedup table) come from
21
+ * the system allocator and are freed before return — nothing borrows
22
+ * the caller's buffer beyond the call.
23
+ */
24
+
25
+ #include <stdio.h>
26
+ #include <stdlib.h>
27
+ #include <string.h>
28
+
29
+ #include <yeptris/dom.h>
30
+ #include <yeptris/validate.h>
31
+
32
+ #include "../common/error.h"
33
+ #include "../doc.h"
34
+ #include "../memory/allocator.h"
35
+ #include "../parse/numbers.h"
36
+ #include "../resolve/resolver.h"
37
+ #include "../tape_in.h"
38
+ #include "internal.h"
39
+
40
+ /* ---- line/col resolution ---------------------------------------------- */
41
+
42
+ /* Builds the line-start index once, on the first positioned diagnostic;
43
+ * valid documents never pay. Returns 0 ok, -1 OOM (positions unknown). */
44
+ static int line_index_build(yep_vctx* c) {
45
+ if (c->line_off != NULL) {
46
+ return 0;
47
+ }
48
+ const yep_allocator* sys = yep_system_allocator();
49
+ uint32_t cap = 256;
50
+ uint32_t n = 0;
51
+ uint32_t* off = yep_alloc(sys, cap * sizeof(uint32_t));
52
+ if (off == NULL) {
53
+ return -1;
54
+ }
55
+ off[n++] = 0;
56
+ const char* p = c->input;
57
+ for (size_t i = 0; i < c->input_len; i++) {
58
+ if (p[i] == '\n') {
59
+ if (n == cap) {
60
+ uint32_t ncap = cap * 2;
61
+ uint32_t* no = yep_alloc(sys, (size_t)ncap * sizeof(uint32_t));
62
+ if (no == NULL) {
63
+ yep_free(sys, off);
64
+ return -1;
65
+ }
66
+ memcpy(no, off, (size_t)n * sizeof(uint32_t));
67
+ yep_free(sys, off);
68
+ off = no;
69
+ cap = ncap;
70
+ }
71
+ off[n++] = (uint32_t)(i + 1);
72
+ }
73
+ }
74
+ c->line_off = off;
75
+ c->line_cnt = n;
76
+ c->line_cap = cap;
77
+ return 0;
78
+ }
79
+
80
+ /* 1-based line/col of a byte offset; columns are code points (the
81
+ * libyaml mark semantics — multibyte characters advance one). */
82
+ static void line_col_of(yep_vctx* c, uint32_t off, uint32_t* line, uint32_t* col) {
83
+ *line = 0;
84
+ *col = 0;
85
+ if (off == UINT32_MAX || off >= c->input_len) {
86
+ return;
87
+ }
88
+ if (line_index_build(c) != 0) {
89
+ return;
90
+ }
91
+ uint32_t lo = 0, hi = c->line_cnt; /* find the last line start <= off */
92
+ while (lo + 1 < hi) {
93
+ uint32_t mid = lo + (hi - lo) / 2;
94
+ if (c->line_off[mid] <= off) {
95
+ lo = mid;
96
+ } else {
97
+ hi = mid;
98
+ }
99
+ }
100
+ uint32_t start = c->line_off[lo];
101
+ uint32_t chars = 0;
102
+ for (uint32_t i = start; i <= off; i++) {
103
+ chars += ((((unsigned char)c->input[i]) & 0xC0) != 0x80);
104
+ }
105
+ *line = lo + 1;
106
+ *col = chars;
107
+ }
108
+
109
+ /* ---- path scratch ------------------------------------------------------ */
110
+
111
+ static int path_reserve(yep_vctx* c, size_t need) {
112
+ if (c->path_len + need <= c->path_cap) {
113
+ return 0;
114
+ }
115
+ const yep_allocator* sys = yep_system_allocator();
116
+ size_t ncap = c->path_cap ? c->path_cap * 2 : 256;
117
+ while (ncap < c->path_len + need) {
118
+ ncap *= 2;
119
+ }
120
+ char* np = yep_alloc(sys, ncap);
121
+ if (np == NULL) {
122
+ c->oom = 1;
123
+ return -1;
124
+ }
125
+ if (c->path != NULL) {
126
+ memcpy(np, c->path, c->path_len);
127
+ yep_free(sys, c->path);
128
+ }
129
+ c->path = np;
130
+ c->path_cap = ncap;
131
+ return 0;
132
+ }
133
+
134
+ int yep_validate_path_push(yep_vctx* c, const char* seg, size_t seg_len) {
135
+ /* JSON-pointer escapes: ~ -> ~0, / -> ~1 */
136
+ size_t need = 1 + seg_len + 2; /* worst case: every byte escapes */
137
+ for (size_t i = 0; i < seg_len; i++) {
138
+ if (seg[i] == '~' || seg[i] == '/') {
139
+ need++;
140
+ }
141
+ }
142
+ if (path_reserve(c, need) != 0) {
143
+ return -1;
144
+ }
145
+ c->path[c->path_len++] = '/';
146
+ for (size_t i = 0; i < seg_len; i++) {
147
+ char b = seg[i];
148
+ if (b == '~') {
149
+ c->path[c->path_len++] = '~';
150
+ c->path[c->path_len++] = '0';
151
+ } else if (b == '/') {
152
+ c->path[c->path_len++] = '~';
153
+ c->path[c->path_len++] = '1';
154
+ } else {
155
+ c->path[c->path_len++] = b;
156
+ }
157
+ }
158
+ return 0;
159
+ }
160
+
161
+ int yep_validate_path_push_index(yep_vctx* c, uint32_t idx) {
162
+ char buf[12];
163
+ int n = snprintf(buf, sizeof(buf), "%u", idx);
164
+ return yep_validate_path_push(c, buf, (size_t)n);
165
+ }
166
+
167
+ void yep_validate_path_pop(yep_vctx* c, size_t mark) {
168
+ c->path_len = mark;
169
+ }
170
+
171
+ /* ---- the report --------------------------------------------------------- */
172
+
173
+ void yep_validate_emit(yep_vctx* c, int code, uint32_t off, const char* detail_fmt, ...) {
174
+ if (c->suppress > 0) {
175
+ c->trial_hit = 1; /* a probe (anyOf/oneOf/not): swallow and mark */
176
+ return;
177
+ }
178
+ yeptris_validate_report* r = c->rep;
179
+ if (r->count >= YEP_VALIDATE_MAX_DIAGS) {
180
+ /* the cap: the LAST slot becomes the LIMIT sentinel */
181
+ r->truncated = 1;
182
+ yeptris_diagnostic* last = &r->diags[r->count - 1];
183
+ memset(last, 0, sizeof(*last));
184
+ last->code = YEPTRIS_DIAG_LIMIT;
185
+ snprintf(last->detail, sizeof(last->detail), "diagnostics truncated at %d",
186
+ YEP_VALIDATE_MAX_DIAGS);
187
+ return;
188
+ }
189
+ yeptris_diagnostic* d = &r->diags[r->count];
190
+ memset(d, 0, sizeof(*d));
191
+ d->code = (uint32_t)code;
192
+ if (off != UINT32_MAX && off < c->input_len) {
193
+ d->offset = off;
194
+ line_col_of(c, off, &d->line, &d->col);
195
+ }
196
+ size_t plen = c->path_len < YEPTRIS_DIAG_PATH_MAX - 1 ? c->path_len : YEPTRIS_DIAG_PATH_MAX - 1;
197
+ if (c->path != NULL) {
198
+ memcpy(d->path, c->path, plen);
199
+ }
200
+ va_list ap;
201
+ va_start(ap, detail_fmt);
202
+ vsnprintf(d->detail, sizeof(d->detail), detail_fmt, ap);
203
+ va_end(ap);
204
+ r->count++;
205
+ }
206
+
207
+ /* ---- node facts --------------------------------------------------------- */
208
+
209
+ uint32_t yep_validate_node_off(const yep_dom* d, uint32_t id) {
210
+ uint32_t guard = 0;
211
+ while (id != UINT32_MAX && guard++ < YEP_DOM_MAX_DEPTH) {
212
+ const yep_dnode* n = yep_dom_node(d, id);
213
+ if (n == NULL) {
214
+ return UINT32_MAX;
215
+ }
216
+ if (n->kind == YEP_DOM_SCALAR || n->kind == YEP_DOM_ALIAS) {
217
+ if (n->value.len > 0 && (n->value.off & YEP_SV_INPUT) == 0) {
218
+ return n->value.off & YEP_SV_OFF; /* borrowed from the input */
219
+ }
220
+ return UINT32_MAX; /* arena-owned bytes: no input position */
221
+ }
222
+ id = n->first_child; /* collections: position at their first leaf */
223
+ }
224
+ return UINT32_MAX;
225
+ }
226
+
227
+ /* ---- canonical key/value facts (shared with jsonschema.c) ---------------- */
228
+
229
+ /* The resolved tag id of a scalar node (explicit tag wins, then the
230
+ * plain verdict already on the node, then str for quoted/block). */
231
+ uint8_t yep_validate_scalar_tag(const yep_dnode* n) {
232
+ if (n->tag.len > 0) {
233
+ return n->tag_id;
234
+ }
235
+ /* implicit marks the resolver's verdict even at style ANY (the
236
+ * document-level empty scalar carries no token to style) */
237
+ if (n->style == YEP_STYLE_PLAIN || n->implicit) {
238
+ return n->tag_id;
239
+ }
240
+ return YEPTRIS_TAG_STR;
241
+ }
242
+
243
+ /* Canonical hash of a node (the key-uniqueness fingerprint): tags and
244
+ * canonical forms, numeric values for int/float. */
245
+ static uint64_t hash_mix(uint64_t h, uint64_t v) {
246
+ h ^= v + 0x9E3779B97F4A7C15ULL + (h << 6) + (h >> 2);
247
+ return h;
248
+ }
249
+
250
+ uint64_t yep_validate_node_hash(const yep_dom* d, uint32_t id, int depth) {
251
+ if (depth > 64) {
252
+ return 0xDA7A;
253
+ }
254
+ const yep_dnode* n = yep_dom_node(d, id);
255
+ if (n == NULL) {
256
+ return 0;
257
+ }
258
+ if (n->kind == YEP_DOM_ALIAS) {
259
+ return n->target != UINT32_MAX ? yep_validate_node_hash(d, n->target, depth + 1)
260
+ : hash_mix(0xA11A, n->value.len);
261
+ }
262
+ if (n->kind == YEP_DOM_SEQUENCE) {
263
+ uint64_t h = 0x5EED51ULL;
264
+ uint32_t ch = n->first_child;
265
+ while (ch != UINT32_MAX) {
266
+ h = hash_mix(h, yep_validate_node_hash(d, ch, depth + 1));
267
+ ch = yep_dom_node(d, ch) ? yep_dom_node(d, ch)->next_sibling : UINT32_MAX;
268
+ }
269
+ return h;
270
+ }
271
+ if (n->kind == YEP_DOM_MAPPING) {
272
+ uint64_t h = 0x5EED4A9ULL;
273
+ uint32_t k = n->first_child;
274
+ while (k != UINT32_MAX) {
275
+ uint32_t v = yep_dom_node(d, k) ? yep_dom_node(d, k)->next_sibling : UINT32_MAX;
276
+ h = hash_mix(h, yep_validate_node_hash(d, k, depth + 1));
277
+ h = hash_mix(h, yep_validate_node_hash(d, v, depth + 1));
278
+ k = v != UINT32_MAX && yep_dom_node(d, v) ? yep_dom_node(d, v)->next_sibling
279
+ : UINT32_MAX;
280
+ }
281
+ return h;
282
+ }
283
+ /* scalar: tag + canonical form */
284
+ uint8_t tag = yep_validate_scalar_tag(n);
285
+ yep_view v = yep_dom_view(d, n->value);
286
+ uint64_t h = 0x5CA1A + tag;
287
+ if (tag == YEPTRIS_TAG_INT || tag == YEPTRIS_TAG_FLOAT) {
288
+ /* canonical: the numeric value (0o13 == 0xB == 11; 1 == 1.0
289
+ * rides the same number bucket only when the tags match) */
290
+ int64_t iv = 0;
291
+ double dv = 0;
292
+ if (yep_num_i64(v.p, v.len, &iv) == 0) {
293
+ int is_neg = iv < 0;
294
+ uint64_t mag = is_neg ? (uint64_t)(-(iv + 1)) + 1 : (uint64_t)iv;
295
+ h = hash_mix(h, mag ^ (is_neg ? 0x8000000000000000ULL : 0));
296
+ } else if (yep_num_f64(v.p, v.len, &dv) == 0) {
297
+ uint64_t bits;
298
+ memcpy(&bits, &dv, sizeof(bits));
299
+ h = hash_mix(h, bits);
300
+ } else {
301
+ h = hash_mix(h, v.len);
302
+ }
303
+ return h;
304
+ }
305
+ for (uint32_t i = 0; i < v.len; i++) {
306
+ h = hash_mix(h, (unsigned char)v.p[i]);
307
+ }
308
+ return h;
309
+ }
310
+
311
+ /* Canonical equality (spec 3.2.1.3): same tag and canonical form.
312
+ * Collections compare structurally; numbers within one tag compare
313
+ * numerically. The two sides may live in different DOMs (the JSON
314
+ * Schema face compares instances against a schema's enum/const).
315
+ * Depth-bounded like the hashes. */
316
+ int yep_validate_node_equal(const yep_dom* da, uint32_t a, const yep_dom* db, uint32_t b,
317
+ int depth) {
318
+ if (depth > 64) {
319
+ return 0;
320
+ }
321
+ const yep_dnode* na = yep_dom_node(da, a);
322
+ const yep_dnode* nb = yep_dom_node(db, b);
323
+ if (na == NULL || nb == NULL) {
324
+ return 0;
325
+ }
326
+ if (na->kind == YEP_DOM_ALIAS) {
327
+ return na->target != UINT32_MAX &&
328
+ yep_validate_node_equal(da, na->target, db, b, depth + 1);
329
+ }
330
+ if (nb->kind == YEP_DOM_ALIAS) {
331
+ return nb->target != UINT32_MAX &&
332
+ yep_validate_node_equal(da, a, db, nb->target, depth + 1);
333
+ }
334
+ if (na->kind != nb->kind) {
335
+ return 0;
336
+ }
337
+ if (na->kind == YEP_DOM_SEQUENCE) {
338
+ if (na->count != nb->count) {
339
+ return 0;
340
+ }
341
+ uint32_t ca = na->first_child, cb = nb->first_child;
342
+ while (ca != UINT32_MAX && cb != UINT32_MAX) {
343
+ if (!yep_validate_node_equal(da, ca, db, cb, depth + 1)) {
344
+ return 0;
345
+ }
346
+ const yep_dnode* da2 = yep_dom_node(da, ca);
347
+ const yep_dnode* db2 = yep_dom_node(db, cb);
348
+ ca = da2 ? da2->next_sibling : UINT32_MAX;
349
+ cb = db2 ? db2->next_sibling : UINT32_MAX;
350
+ }
351
+ return ca == UINT32_MAX && cb == UINT32_MAX;
352
+ }
353
+ if (na->kind == YEP_DOM_MAPPING) {
354
+ if (na->count != nb->count) {
355
+ return 0;
356
+ }
357
+ uint32_t ka = na->first_child;
358
+ while (ka != UINT32_MAX) {
359
+ uint32_t va = yep_dom_node(da, ka) ? yep_dom_node(da, ka)->next_sibling : UINT32_MAX;
360
+ /* find ka's key in b */
361
+ uint32_t kb = nb->first_child;
362
+ int found = 0;
363
+ while (kb != UINT32_MAX && !found) {
364
+ uint32_t vb =
365
+ yep_dom_node(db, kb) ? yep_dom_node(db, kb)->next_sibling : UINT32_MAX;
366
+ if (yep_validate_node_equal(da, ka, db, kb, depth + 1)) {
367
+ if (!yep_validate_node_equal(da, va, db, vb, depth + 1)) {
368
+ return 0;
369
+ }
370
+ found = 1;
371
+ }
372
+ kb = vb != UINT32_MAX && yep_dom_node(db, vb) ? yep_dom_node(db, vb)->next_sibling
373
+ : UINT32_MAX;
374
+ }
375
+ if (!found) {
376
+ return 0;
377
+ }
378
+ ka = va != UINT32_MAX && yep_dom_node(da, va) ? yep_dom_node(da, va)->next_sibling
379
+ : UINT32_MAX;
380
+ }
381
+ return 1;
382
+ }
383
+ /* scalars */
384
+ uint8_t ta = yep_validate_scalar_tag(na), tb = yep_validate_scalar_tag(nb);
385
+ if (ta != tb) {
386
+ return 0;
387
+ }
388
+ yep_view va = yep_dom_view(da, na->value);
389
+ yep_view vb = yep_dom_view(db, nb->value);
390
+ if (ta == YEPTRIS_TAG_INT || ta == YEPTRIS_TAG_FLOAT) {
391
+ double fa, fb;
392
+ int64_t ia, ib;
393
+ if (yep_num_i64(va.p, va.len, &ia) == 0 && yep_num_i64(vb.p, vb.len, &ib) == 0) {
394
+ return ia == ib;
395
+ }
396
+ if (yep_num_f64(va.p, va.len, &fa) == 0 && yep_num_f64(vb.p, vb.len, &fb) == 0) {
397
+ return fa == fb;
398
+ }
399
+ return 0;
400
+ }
401
+ return va.len == vb.len && (va.len == 0 || memcmp(va.p, vb.p, va.len) == 0);
402
+ }
403
+
404
+ /* ---- the family walk ---------------------------------------------------- */
405
+
406
+ typedef struct {
407
+ uint32_t hash;
408
+ uint32_t node; /* 0 = empty slot (node 0 is the DOCUMENT root, never
409
+ * a key inside itself) */
410
+ } key_slot;
411
+
412
+ /* Duplicate-key detection over one mapping: open addressing, expected
413
+ * O(pairs). An allocation failure silently skips (the walk's other
414
+ * checks still run; oom flags the context). */
415
+ static void check_duplicates(yep_vctx* c, uint32_t map_id) {
416
+ const yep_dom* d = c->dom;
417
+ const yep_dnode* m = yep_dom_node(d, map_id);
418
+ uint32_t pairs = m->count / 2;
419
+ if (pairs < 2) {
420
+ return;
421
+ }
422
+ const yep_allocator* sys = yep_system_allocator();
423
+ uint32_t cap = 8;
424
+ while (cap < pairs * 4) {
425
+ cap *= 2;
426
+ }
427
+ key_slot* tbl = yep_alloc(sys, (size_t)cap * sizeof(key_slot));
428
+ if (tbl == NULL) {
429
+ c->oom = 1;
430
+ return;
431
+ }
432
+ memset(tbl, 0, (size_t)cap * sizeof(key_slot));
433
+ uint32_t k = m->first_child;
434
+ int reported = 0;
435
+ while (k != UINT32_MAX) {
436
+ uint32_t v = yep_dom_node(d, k) ? yep_dom_node(d, k)->next_sibling : UINT32_MAX;
437
+ uint64_t h64 = yep_validate_node_hash(d, k, 0);
438
+ uint32_t h = (uint32_t)(h64 ^ (h64 >> 32));
439
+ uint32_t i = h & (cap - 1);
440
+ while (tbl[i].node != 0) {
441
+ if (tbl[i].hash == h && yep_validate_node_equal(d, tbl[i].node - 1, d, k, 0)) {
442
+ if (!reported) {
443
+ reported = 1;
444
+ uint32_t off = yep_validate_node_off(d, k);
445
+ yep_view kv = yep_dom_view(d, yep_dom_node(d, k)->value);
446
+ char key[40];
447
+ size_t kl = kv.len < sizeof(key) - 8 ? kv.len : sizeof(key) - 8;
448
+ if (kl > 0 && kv.p != NULL) {
449
+ memcpy(key, kv.p, kl);
450
+ }
451
+ key[kl] = '\0';
452
+ yep_validate_emit(c, YEPTRIS_DIAG_DUPLICATE_KEY, off,
453
+ "duplicate mapping key (\"%s\")", key);
454
+ }
455
+ break;
456
+ }
457
+ i = (i + 1) & (cap - 1);
458
+ }
459
+ if (tbl[i].node == 0) {
460
+ tbl[i].hash = h;
461
+ tbl[i].node = k + 1; /* +1: node 0 means empty */
462
+ }
463
+ k = v != UINT32_MAX && yep_dom_node(d, v) ? yep_dom_node(d, v)->next_sibling : UINT32_MAX;
464
+ }
465
+ yep_free(sys, tbl);
466
+ }
467
+
468
+ static const char* tag_word(uint8_t id) {
469
+ switch (id) {
470
+ case YEPTRIS_TAG_STR:
471
+ return "string";
472
+ case YEPTRIS_TAG_INT:
473
+ return "int";
474
+ case YEPTRIS_TAG_FLOAT:
475
+ return "float";
476
+ case YEPTRIS_TAG_BOOL:
477
+ return "bool";
478
+ case YEPTRIS_TAG_NULL:
479
+ return "null";
480
+ case YEPTRIS_TAG_TIMESTAMP:
481
+ return "timestamp";
482
+ case YEPTRIS_TAG_SEQ:
483
+ return "sequence";
484
+ case YEPTRIS_TAG_MAP:
485
+ return "mapping";
486
+ case YEPTRIS_TAG_BINARY:
487
+ return "binary";
488
+ case YEPTRIS_TAG_MERGE:
489
+ return "merge";
490
+ case YEPTRIS_TAG_VALUE:
491
+ return "value";
492
+ default:
493
+ return "custom";
494
+ }
495
+ }
496
+
497
+ /* Explicit-tag consistency (both families): the tag asserts a type the
498
+ * content cannot carry. seq/map check the node KIND; the five core
499
+ * scalar tags re-resolve the content through the family resolver. */
500
+ static void check_tag(yep_vctx* c, uint32_t id, const yep_resolver* fam, int json_family) {
501
+ const yep_dom* d = c->dom;
502
+ const yep_dnode* n = yep_dom_node(d, id);
503
+ if (n->tag.len == 0) {
504
+ return; /* no explicit tag */
505
+ }
506
+ uint8_t want = n->tag_id;
507
+ const char* w = tag_word(want);
508
+ if (want == YEPTRIS_TAG_SEQ || want == YEPTRIS_TAG_MAP || want == YEPTRIS_TAG_STR) {
509
+ int ok = want == YEPTRIS_TAG_STR
510
+ ? n->kind == YEP_DOM_SCALAR
511
+ : (want == YEPTRIS_TAG_SEQ && n->kind == YEP_DOM_SEQUENCE) ||
512
+ (want == YEPTRIS_TAG_MAP && n->kind == YEP_DOM_MAPPING);
513
+ if (!ok) {
514
+ yep_validate_emit(c, YEPTRIS_DIAG_TAG_MISMATCH, yep_validate_node_off(d, id),
515
+ "explicit %s tag on a %s", w,
516
+ n->kind == YEP_DOM_SCALAR
517
+ ? "scalar"
518
+ : (n->kind == YEP_DOM_SEQUENCE ? "sequence" : "mapping"));
519
+ }
520
+ return;
521
+ }
522
+ if (want >= YEPTRIS_TAG_INT && want <= YEPTRIS_TAG_NULL) {
523
+ if (n->kind != YEP_DOM_SCALAR) {
524
+ yep_validate_emit(c, YEPTRIS_DIAG_TAG_MISMATCH, yep_validate_node_off(d, id),
525
+ "explicit %s tag on a %s", w,
526
+ n->kind == YEP_DOM_SEQUENCE ? "sequence" : "mapping");
527
+ return;
528
+ }
529
+ yep_view v = yep_dom_view(d, n->value);
530
+ uint8_t got = fam->resolve(fam->ctx, v.p != NULL ? v.p : "", v.len);
531
+ if (got != want) {
532
+ if (json_family) {
533
+ yep_validate_emit(c, YEPTRIS_DIAG_TAG_MISMATCH, yep_validate_node_off(d, id),
534
+ "explicit %s tag; content is not a JSON %s", w, w);
535
+ } else {
536
+ yep_validate_emit(c, YEPTRIS_DIAG_TAG_MISMATCH, yep_validate_node_off(d, id),
537
+ "explicit %s tag on %s content", w, tag_word(got));
538
+ }
539
+ }
540
+ return;
541
+ }
542
+ /* timestamp/binary/merge/value/custom: consumer-defined under core;
543
+ * the JSON family rejects them wholesale (checked by the caller). */
544
+ }
545
+
546
+ /* json12: a mapping key must be a string scalar. */
547
+ static void check_json_key(yep_vctx* c, uint32_t key_id) {
548
+ const yep_dom* d = c->dom;
549
+ const yep_dnode* n = yep_dom_node(d, key_id);
550
+ if (n->kind == YEP_DOM_ALIAS) {
551
+ if (n->target != UINT32_MAX) {
552
+ check_json_key(c, n->target);
553
+ }
554
+ return;
555
+ }
556
+ if (n->kind != YEP_DOM_SCALAR) {
557
+ uint32_t off = yep_validate_node_off(d, key_id);
558
+ yep_validate_emit(c, YEPTRIS_DIAG_KEY_NOT_STRING, off, "mapping key is a %s",
559
+ n->kind == YEP_DOM_SEQUENCE ? "sequence" : "mapping");
560
+ return;
561
+ }
562
+ uint32_t off = yep_validate_node_off(d, key_id);
563
+ if (n->tag.len > 0) {
564
+ uint8_t want = n->tag_id;
565
+ if (want == YEPTRIS_TAG_STR) {
566
+ return;
567
+ }
568
+ if (want >= YEPTRIS_TAG_INT && want <= YEPTRIS_TAG_NULL) {
569
+ yep_validate_emit(c, YEPTRIS_DIAG_KEY_NOT_STRING, off,
570
+ "mapping key is %s (expected a "
571
+ "string)",
572
+ tag_word(want));
573
+ } else {
574
+ yep_validate_emit(c, YEPTRIS_DIAG_NON_JSON_TYPE, off,
575
+ "mapping key carries a non-JSON "
576
+ "tag");
577
+ }
578
+ return;
579
+ }
580
+ if (!(n->implicit || n->style == YEP_STYLE_PLAIN)) {
581
+ return; /* quoted/block keys are strings */
582
+ }
583
+ yep_view v = yep_dom_view(d, n->value);
584
+ int cls = yep_json12_plain(v.p != NULL ? v.p : "", v.len);
585
+ if (cls == 0) {
586
+ yep_validate_emit(c, YEPTRIS_DIAG_NON_JSON_TYPE, off,
587
+ "plain scalar matches no JSON production");
588
+ return;
589
+ }
590
+ if (cls != (int)YEPTRIS_TAG_STR) {
591
+ yep_validate_emit(c, YEPTRIS_DIAG_KEY_NOT_STRING, off,
592
+ "mapping key is %s (expected a "
593
+ "string)",
594
+ tag_word((uint8_t)cls));
595
+ }
596
+ }
597
+
598
+ /* json12: plain scalars must match a JSON production; explicit
599
+ * non-JSON tags rejected (the caller runs check_tag separately). */
600
+ static void check_json_scalar(yep_vctx* c, uint32_t id) {
601
+ const yep_dom* d = c->dom;
602
+ const yep_dnode* n = yep_dom_node(d, id);
603
+ if (n->tag.len > 0) {
604
+ uint8_t want = n->tag_id;
605
+ if (want == YEPTRIS_TAG_TIMESTAMP || want == YEPTRIS_TAG_BINARY ||
606
+ want == YEPTRIS_TAG_MERGE || want == YEPTRIS_TAG_VALUE || want == YEPTRIS_TAG_CUSTOM) {
607
+ yep_validate_emit(c, YEPTRIS_DIAG_NON_JSON_TYPE, yep_validate_node_off(d, id),
608
+ "explicit %s tag is not a JSON type", tag_word(want));
609
+ }
610
+ return;
611
+ }
612
+ if (!(n->implicit || n->style == YEP_STYLE_PLAIN)) {
613
+ return; /* quoted/block scalars are strings */
614
+ }
615
+ yep_view v = yep_dom_view(d, n->value);
616
+ if (yep_json12_plain(v.p != NULL ? v.p : "", v.len) == 0) {
617
+ yep_validate_emit(c, YEPTRIS_DIAG_NON_JSON_TYPE, yep_validate_node_off(d, id),
618
+ "plain scalar matches no JSON production");
619
+ }
620
+ }
621
+
622
+ static void walk_family(yep_vctx* c, uint32_t id, const yep_resolver* fam, int json_family);
623
+
624
+ static void walk_children(yep_vctx* c, uint32_t first, const yep_resolver* fam, int json_family) {
625
+ uint32_t ch = first;
626
+ int guard = 0;
627
+ while (ch != UINT32_MAX && !c->oom) {
628
+ if (++guard > (int)c->dom->ncount) {
629
+ break; /* defensive: the sibling list is parse-built */
630
+ }
631
+ const yep_dnode* n = yep_dom_node(c->dom, ch);
632
+ uint32_t next = n ? n->next_sibling : UINT32_MAX;
633
+ walk_family(c, ch, fam, json_family);
634
+ ch = next;
635
+ }
636
+ }
637
+
638
+ static void walk_family(yep_vctx* c, uint32_t id, const yep_resolver* fam, int json_family) {
639
+ if (c->oom) {
640
+ return;
641
+ }
642
+ if (++c->depth > YEP_DOM_MAX_DEPTH) {
643
+ if (c->depth == YEP_DOM_MAX_DEPTH + 1) {
644
+ yep_validate_emit(c, YEPTRIS_DIAG_DEPTH, UINT32_MAX,
645
+ "validation walk exceeded the nesting limit");
646
+ }
647
+ c->depth--;
648
+ return;
649
+ }
650
+ const yep_dnode* n = yep_dom_node(c->dom, id);
651
+ if (n == NULL) {
652
+ c->depth--;
653
+ return;
654
+ }
655
+ switch (n->kind) {
656
+ case YEP_DOM_SCALAR:
657
+ check_tag(c, id, fam, json_family);
658
+ if (json_family) {
659
+ check_json_scalar(c, id);
660
+ }
661
+ break;
662
+ case YEP_DOM_ALIAS:
663
+ break; /* the target is validated at its own place */
664
+ case YEP_DOM_SEQUENCE: {
665
+ check_tag(c, id, fam, json_family);
666
+ size_t mark = c->path_len;
667
+ uint32_t idx = 0;
668
+ uint32_t ch = n->first_child;
669
+ while (ch != UINT32_MAX && !c->oom) {
670
+ const yep_dnode* cn = yep_dom_node(c->dom, ch);
671
+ uint32_t next = cn ? cn->next_sibling : UINT32_MAX;
672
+ if (yep_validate_path_push_index(c, idx++) == 0) {
673
+ walk_family(c, ch, fam, json_family);
674
+ yep_validate_path_pop(c, mark);
675
+ }
676
+ ch = next;
677
+ }
678
+ break;
679
+ }
680
+ case YEP_DOM_MAPPING: {
681
+ check_tag(c, id, fam, json_family);
682
+ size_t mark = c->path_len;
683
+ check_duplicates(c, id);
684
+ uint32_t k = n->first_child;
685
+ while (k != UINT32_MAX && !c->oom) {
686
+ uint32_t v =
687
+ yep_dom_node(c->dom, k) ? yep_dom_node(c->dom, k)->next_sibling : UINT32_MAX;
688
+ if (json_family) {
689
+ check_json_key(c, k);
690
+ }
691
+ /* keys: tag-checked like any scalar; collection keys walk
692
+ * under the mapping's path (their own path is undefined) */
693
+ const yep_dnode* kn = yep_dom_node(c->dom, k);
694
+ if (kn != NULL) {
695
+ if (kn->kind == YEP_DOM_SCALAR) {
696
+ check_tag(c, k, fam, json_family);
697
+ } else if (kn->kind != YEP_DOM_ALIAS) {
698
+ walk_children(c, kn->first_child, fam, json_family);
699
+ }
700
+ }
701
+ if (v != UINT32_MAX) {
702
+ yep_view kv = yep_dom_view(c->dom, kn ? kn->value : (yep_sview){0, 0});
703
+ if (yep_validate_path_push(c, kv.p != NULL ? kv.p : "", kv.len) == 0) {
704
+ walk_family(c, v, fam, json_family);
705
+ yep_validate_path_pop(c, mark);
706
+ }
707
+ }
708
+ k = v != UINT32_MAX && yep_dom_node(c->dom, v) ? yep_dom_node(c->dom, v)->next_sibling
709
+ : UINT32_MAX;
710
+ }
711
+ break;
712
+ }
713
+ default:
714
+ break;
715
+ }
716
+ c->depth--;
717
+ }
718
+
719
+ /* ---- the public entries -------------------------------------------------- */
720
+
721
+ YEPTRIS_API size_t yeptris_validate_report_count(const yeptris_validate_report* report) {
722
+ return report ? report->count : 0;
723
+ }
724
+
725
+ YEPTRIS_API const yeptris_diagnostic*
726
+ yeptris_validate_report_at(const yeptris_validate_report* report, size_t index) {
727
+ if (report == NULL || index >= report->count) {
728
+ return NULL;
729
+ }
730
+ return &report->diags[index];
731
+ }
732
+
733
+ YEPTRIS_API void yeptris_validate_report_free(yeptris_validate_report* report) {
734
+ if (report == NULL) {
735
+ return;
736
+ }
737
+ const yep_allocator* sys = yep_system_allocator();
738
+ yep_free(sys, report->diags);
739
+ yep_free(sys, report);
740
+ }
741
+
742
+ /* The shared tail: walk every document of the DOM into a fresh report. */
743
+ static YeptrisStatus validate_dom(yep_dom* dom, const yep_resolver* fam, int json_family,
744
+ yeptris_validate_report** out) {
745
+ const yep_allocator* sys = yep_system_allocator();
746
+ yeptris_validate_report* r = yep_alloc(sys, sizeof(*r));
747
+ if (r == NULL) {
748
+ return YEPTRIS_ERROR_MEMORY;
749
+ }
750
+ r->diags = yep_alloc(sys, YEP_VALIDATE_MAX_DIAGS * sizeof(yeptris_diagnostic));
751
+ if (r->diags == NULL) {
752
+ yep_free(sys, r);
753
+ return YEPTRIS_ERROR_MEMORY;
754
+ }
755
+ r->count = 0;
756
+ r->truncated = 0;
757
+
758
+ yep_vctx c;
759
+ memset(&c, 0, sizeof(c));
760
+ c.dom = dom;
761
+ c.rep = r;
762
+ c.input = dom->input_base;
763
+ c.input_len = dom->input_len;
764
+
765
+ for (uint32_t i = 0; i < dom->dcount && !c.oom; i++) {
766
+ c.path_len = 0; /* paths are root-relative per document */
767
+ walk_family(&c, dom->docs[i], fam, json_family);
768
+ }
769
+
770
+ yep_free(sys, c.line_off);
771
+ yep_free(sys, c.path);
772
+
773
+ YeptrisStatus st = YEPTRIS_OK;
774
+ if (c.oom) {
775
+ yeptris_validate_report_free(r);
776
+ return YEPTRIS_ERROR_MEMORY;
777
+ }
778
+ if (r->count > 0) {
779
+ st = YEPTRIS_ERROR_SCHEMA;
780
+ }
781
+ *out = r;
782
+ return st;
783
+ }
784
+
785
+ static int schema_is_family(YeptrisSchema schema, const yep_resolver** fam, int* json_family) {
786
+ switch (schema) {
787
+ case YEPTRIS_SCHEMA_12_CORE:
788
+ *fam = yep_resolver_core12();
789
+ *json_family = 0;
790
+ return 1;
791
+ case YEPTRIS_SCHEMA_12_JSON:
792
+ *fam = yep_resolver_json12();
793
+ *json_family = 1;
794
+ return 1;
795
+ default:
796
+ return 0;
797
+ }
798
+ }
799
+
800
+ YEPTRIS_API YeptrisStatus yeptris_validate_document(YeptrisDocument doc, YeptrisSchema schema,
801
+ yeptris_validate_report** out) {
802
+ if (out == NULL) {
803
+ return YEPTRIS_ERROR_ARG;
804
+ }
805
+ *out = NULL;
806
+ const yep_resolver* fam;
807
+ int json_family;
808
+ if (!schema_is_family(schema, &fam, &json_family)) {
809
+ yep_error_set(yep_error_tls(), YEP_ERR_UNEXPECTED, 0, 0, 0,
810
+ "schema %d is not a validation family", (int)schema);
811
+ return YEPTRIS_ERROR_ARG;
812
+ }
813
+ yep_dom* dom = doc ? yep_doc_dom((yeptris_document*)doc) : NULL;
814
+ if (doc != NULL && dom == NULL) {
815
+ return YEPTRIS_ERROR_MEMORY; /* a lazy tree that failed to build */
816
+ }
817
+ if (dom == NULL) {
818
+ /* an empty stream (or NULL doc): valid, empty report */
819
+ const yep_allocator* sys = yep_system_allocator();
820
+ yeptris_validate_report* r = yep_alloc(sys, sizeof(*r));
821
+ if (r == NULL) {
822
+ return YEPTRIS_ERROR_MEMORY;
823
+ }
824
+ r->diags = yep_alloc(sys, YEP_VALIDATE_MAX_DIAGS * sizeof(yeptris_diagnostic));
825
+ if (r->diags == NULL) {
826
+ yep_free(sys, r);
827
+ return YEPTRIS_ERROR_MEMORY;
828
+ }
829
+ r->count = 0;
830
+ r->truncated = 0;
831
+ *out = r;
832
+ return YEPTRIS_OK;
833
+ }
834
+ return validate_dom(dom, fam, json_family, out);
835
+ }
836
+
837
+ YEPTRIS_API YeptrisStatus yeptris_validate(const char* buf, size_t len, YeptrisSchema schema,
838
+ yeptris_validate_report** out) {
839
+ if (out == NULL || (buf == NULL && len != 0)) {
840
+ return YEPTRIS_ERROR_ARG;
841
+ }
842
+ *out = NULL;
843
+ const yep_resolver* fam;
844
+ int json_family;
845
+ if (!schema_is_family(schema, &fam, &json_family)) {
846
+ yep_error_set(yep_error_tls(), YEP_ERR_UNEXPECTED, 0, 0, 0,
847
+ "schema %d is not a validation family", (int)schema);
848
+ return YEPTRIS_ERROR_ARG;
849
+ }
850
+ YeptrisParseOptions opts;
851
+ memset(&opts, 0, sizeof(opts));
852
+ opts.schema = schema; /* the parse resolves implicit types per family */
853
+ YeptrisStatus st = YEPTRIS_OK;
854
+ YeptrisDocument doc = yeptris_parse_eager_ex(buf, len, &opts, &st);
855
+ if (doc == NULL) {
856
+ if (st == YEPTRIS_OK) {
857
+ /* an empty stream: valid, empty report */
858
+ return yeptris_validate_document(NULL, schema, out);
859
+ }
860
+ return st; /* the parse's own error */
861
+ }
862
+ st = yeptris_validate_document(doc, schema, out);
863
+ yeptris_document_free(doc);
864
+ return st;
865
+ }
866
+
867
+ YEPTRIS_API YeptrisStatus yeptris_json_schema_validate(const char* schema, size_t schema_len,
868
+ const char* buf, size_t len,
869
+ yeptris_validate_report** out) {
870
+ if (out == NULL || (buf == NULL && len != 0) || (schema == NULL && schema_len != 0)) {
871
+ return YEPTRIS_ERROR_ARG;
872
+ }
873
+ *out = NULL;
874
+ const yep_allocator* sys = yep_system_allocator();
875
+ YeptrisParseOptions opts;
876
+ memset(&opts, 0, sizeof(opts)); /* core12 both sides: the YAML->JSON data model */
877
+
878
+ /* the schema document (document 0 of its stream) */
879
+ YeptrisStatus st = YEPTRIS_OK;
880
+ YeptrisDocument sdoc = yeptris_parse_eager_ex(schema, schema_len, &opts, &st);
881
+ if (sdoc == NULL) {
882
+ if (st == YEPTRIS_OK) {
883
+ yep_error_set(yep_error_tls(), YEP_ERR_UNEXPECTED, 0, 0, 0,
884
+ "the schema bytes hold no document");
885
+ return YEPTRIS_ERROR_ARG;
886
+ }
887
+ return st; /* the schema did not parse */
888
+ }
889
+ yep_dom* sdom = yep_doc_dom((yeptris_document*)sdoc);
890
+ if (sdom == NULL || sdom->dcount == 0) {
891
+ yep_error_set(yep_error_tls(), YEP_ERR_UNEXPECTED, 0, 0, 0,
892
+ "the schema bytes hold no document");
893
+ yeptris_document_free(sdoc);
894
+ return YEPTRIS_ERROR_ARG;
895
+ }
896
+
897
+ /* the instance stream */
898
+ YeptrisDocument idoc = yeptris_parse_eager_ex(buf, len, &opts, &st);
899
+ if (idoc == NULL) {
900
+ yeptris_document_free(sdoc);
901
+ if (st == YEPTRIS_OK) {
902
+ /* an empty instance stream: nothing to invalidate */
903
+ return yeptris_validate_document(NULL, YEPTRIS_SCHEMA_12_CORE, out);
904
+ }
905
+ return st;
906
+ }
907
+ yep_dom* idom = yep_doc_dom((yeptris_document*)idoc);
908
+ if (idom == NULL) {
909
+ yeptris_document_free(idoc);
910
+ yeptris_document_free(sdoc);
911
+ return YEPTRIS_ERROR_MEMORY;
912
+ }
913
+
914
+ yeptris_validate_report* r = yep_alloc(sys, sizeof(*r));
915
+ if (r == NULL) {
916
+ yeptris_document_free(idoc);
917
+ yeptris_document_free(sdoc);
918
+ return YEPTRIS_ERROR_MEMORY;
919
+ }
920
+ r->diags = yep_alloc(sys, YEP_VALIDATE_MAX_DIAGS * sizeof(yeptris_diagnostic));
921
+ if (r->diags == NULL) {
922
+ yep_free(sys, r);
923
+ yeptris_document_free(idoc);
924
+ yeptris_document_free(sdoc);
925
+ return YEPTRIS_ERROR_MEMORY;
926
+ }
927
+ r->count = 0;
928
+ r->truncated = 0;
929
+
930
+ int rc = yep_jsonschema_run(sdom, sdom->docs[0], idom, idom->docs, idom->dcount, r);
931
+ yeptris_document_free(idoc);
932
+ yeptris_document_free(sdoc);
933
+ if (rc != 0) {
934
+ yeptris_validate_report_free(r);
935
+ return YEPTRIS_ERROR_MEMORY;
936
+ }
937
+ *out = r;
938
+ return r->count > 0 ? YEPTRIS_ERROR_SCHEMA : YEPTRIS_OK;
939
+ }