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.
- checksums.yaml +4 -4
- data/ext/yeptris_native/version.h +2 -2
- data/lib/yeptris/3.4/native.so +0 -0
- data/lib/yeptris.rb +1 -1
- data/libyeptris.so +0 -0
- data/vendor/libyeptris/CMakeLists.txt +1 -1
- data/vendor/libyeptris/src/CMakeLists.txt +5 -0
- data/vendor/libyeptris/src/include/yeptris/error.h +2 -1
- data/vendor/libyeptris/src/include/yeptris/resolve.h +3 -1
- data/vendor/libyeptris/src/include/yeptris/validate.h +157 -0
- data/vendor/libyeptris/src/include/yeptris.h +1 -0
- data/vendor/libyeptris/src/yeptris/parse.c +16 -7
- data/vendor/libyeptris/src/yeptris/resolve/json12.c +103 -0
- data/vendor/libyeptris/src/yeptris/resolve/resolver.h +9 -0
- data/vendor/libyeptris/src/yeptris/validate/internal.h +102 -0
- data/vendor/libyeptris/src/yeptris/validate/jsonschema.c +1022 -0
- data/vendor/libyeptris/src/yeptris/validate/regexp.c +972 -0
- data/vendor/libyeptris/src/yeptris/validate/validate.c +939 -0
- metadata +7 -1
|
@@ -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
|
+
}
|