teptris 0.3.22.0 → 0.3.23.0
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/teptris_ext/engine/README.md +1 -1
- data/ext/teptris_ext/engine/src/include/teptris/schema.h +7 -0
- data/ext/teptris_ext/engine/src/include/teptris/version.h +2 -2
- data/ext/teptris_ext/engine/src/teptris/schema/metaschemas.c +243 -0
- data/ext/teptris_ext/engine/src/teptris/schema/metaschemas.h +15 -0
- data/ext/teptris_ext/engine/src/teptris/schema/refs.c +243 -0
- data/ext/teptris_ext/engine/src/teptris/schema/refs.h +24 -0
- data/ext/teptris_ext/engine/src/teptris/schema/schema.c +779 -40
- data/ext/teptris_ext/engine/src/teptris/schema/schema_internal.h +76 -6
- data/ext/teptris_ext/engine/src/teptris/schema/validate.c +87 -11
- data/lib/teptris/version.rb +1 -1
- metadata +5 -1
|
@@ -19,9 +19,10 @@ enum {
|
|
|
19
19
|
ST_NUMBER = 1 << 2, /* float or integer */
|
|
20
20
|
ST_BOOLEAN = 1 << 3,
|
|
21
21
|
ST_OBJECT = 1 << 4,
|
|
22
|
-
ST_ARRAY = 1 << 5
|
|
22
|
+
ST_ARRAY = 1 << 5,
|
|
23
|
+
ST_NULL = 1 << 6 /* the JSON null type (policy 1: no TOML value) */
|
|
23
24
|
};
|
|
24
|
-
#define ST_ANY
|
|
25
|
+
#define ST_ANY 0x7Fu
|
|
25
26
|
|
|
26
27
|
enum {
|
|
27
28
|
FMT_NONE = 0,
|
|
@@ -109,11 +110,25 @@ struct sch_node {
|
|
|
109
110
|
sch_node *uneval_props; /* unevaluatedProperties: frame-local */
|
|
110
111
|
sch_node *uneval_items; /* unevaluatedItems: frame-local */
|
|
111
112
|
sch_node *content_schema; /* contentSchema: asserts decoded content */
|
|
113
|
+
/* the dynamic anchors inside this node's own subtree (self
|
|
114
|
+
* included, document order): pushed as one scope frame whenever
|
|
115
|
+
* this node is applied — the dynamic scope grows per entered
|
|
116
|
+
* subschema, not per traversed anchor */
|
|
117
|
+
struct sch_scope_entry {
|
|
118
|
+
const char *name;
|
|
119
|
+
struct sch_node *node;
|
|
120
|
+
const jv *res; /* the anchor's own resource (nearest $id) */
|
|
121
|
+
} *self_scope;
|
|
122
|
+
size_t n_self_scope;
|
|
123
|
+
const jv *res_id; /* the resource root this node sits in */
|
|
124
|
+
bool dynamic_recursive; /* the ref site was a $recursiveRef */
|
|
112
125
|
const char *dynamic_anchor; /* $dynamicAnchor name carried here */
|
|
113
|
-
const char *recursive_anchor; /* "
|
|
126
|
+
const char *recursive_anchor; /* "" when $recursiveAnchor is true */
|
|
114
127
|
const char *dynamic_ref; /* "#name" fragment for
|
|
115
128
|
* $dynamicRef / $recursiveRef */
|
|
116
129
|
sch_node *dynamic_ref_target; /* compile-resolved static fallback */
|
|
130
|
+
bool ref_only; /* draft-07-and-older: this $ref ignores siblings */
|
|
131
|
+
bool enum_empty; /* "enum": [] matches nothing */
|
|
117
132
|
sch_node *contains;
|
|
118
133
|
long min_contains, max_contains;
|
|
119
134
|
bool has_contains, has_min_contains, has_max_contains;
|
|
@@ -158,6 +173,8 @@ struct teptris_schema {
|
|
|
158
173
|
char err[256];
|
|
159
174
|
};
|
|
160
175
|
|
|
176
|
+
typedef struct teptris_resource teptris_resource;
|
|
177
|
+
|
|
161
178
|
typedef struct {
|
|
162
179
|
teptris_arena *a;
|
|
163
180
|
jv *root;
|
|
@@ -168,26 +185,76 @@ typedef struct {
|
|
|
168
185
|
bool in_progress;
|
|
169
186
|
} *memo;
|
|
170
187
|
size_t n_memo, cap_memo;
|
|
188
|
+
/* pointer-keyed hash over the memo (compile is O(n), not O(n^2)):
|
|
189
|
+
* open addressing, slot = entry index + 1, 0 = empty */
|
|
190
|
+
uint32_t *memo_hash;
|
|
191
|
+
size_t memo_hash_cap; /* power of two, 0 = absent */
|
|
192
|
+
/* the same treatment for the per-node base table */
|
|
193
|
+
uint32_t *node_base_hash;
|
|
194
|
+
size_t node_base_hash_cap;
|
|
171
195
|
/* dependency-list capacities (grow-in-place across keywords) */
|
|
172
196
|
size_t cap_dep_req, cap_dep_schemas;
|
|
173
|
-
/* anchors: "$anchor": name -> subtree
|
|
197
|
+
/* anchors: "$anchor": name -> subtree (doc-wide; the $anchor
|
|
198
|
+
* space of every loaded resource joins it) */
|
|
174
199
|
struct {
|
|
175
200
|
const char *name;
|
|
176
201
|
jv *target;
|
|
177
202
|
} *anchors;
|
|
178
203
|
size_t n_anchors, cap_anchors;
|
|
179
|
-
/* $dynamicAnchor
|
|
180
|
-
* fallback for $dynamicRef / $recursiveRef) */
|
|
204
|
+
/* $dynamicAnchor targets (the static fallback for $dynamicRef) */
|
|
181
205
|
struct {
|
|
182
206
|
const char *name;
|
|
183
207
|
jv *target;
|
|
184
208
|
} *dyn_anchors;
|
|
185
209
|
size_t n_dyn_anchors, cap_dyn_anchors;
|
|
186
210
|
jv *rec_anchor_target;
|
|
211
|
+
/* the resource model: every base URI this compile has materialized */
|
|
212
|
+
teptris_resource *resources;
|
|
213
|
+
size_t n_resources, cap_resources;
|
|
214
|
+
/* the resource the walk is currently inside */
|
|
215
|
+
jv *cur_root; /* its root jv ("#" resolves here) */
|
|
216
|
+
const char *cur_abs; /* its base URI (NULL in local-only mode) */
|
|
217
|
+
const char *cur_dir; /* its fs dir (relative refs join against it) */
|
|
218
|
+
bool ref_exclusive; /* draft-07-and-older: $ref ignores siblings */
|
|
219
|
+
char base_dir[1024]; /* fs dir of the main schema ("" = none) */
|
|
220
|
+
/* the base every node was pre-passed with: compile looks each
|
|
221
|
+
* node's base up here (pointers reach subtrees whose $id chain
|
|
222
|
+
* the document walk would otherwise miss) */
|
|
223
|
+
/* scratch accumulator for attach_self_scope */
|
|
224
|
+
struct sch_scope_entry *entry_acc;
|
|
225
|
+
size_t n_entry_acc, cap_entry_acc;
|
|
226
|
+
/* which standard vocabularies the dialect keeps (8.1.2: a custom
|
|
227
|
+
* metaschema's $vocabulary decides; absent = all of them) */
|
|
228
|
+
unsigned vocab_mask;
|
|
229
|
+
/* standard vocabulary gates (the core vocabulary is mandatory and
|
|
230
|
+
* never gated) */
|
|
231
|
+
#define TEPTRIS_VOC_APPLICATOR 1u
|
|
232
|
+
#define TEPTRIS_VOC_UNEVALUATED 2u
|
|
233
|
+
#define TEPTRIS_VOC_VALIDATION 4u
|
|
234
|
+
#define TEPTRIS_VOC_FORMAT 8u
|
|
235
|
+
#define TEPTRIS_VOC_CONTENT 16u
|
|
236
|
+
#define TEPTRIS_VOC_ALL 31u
|
|
237
|
+
|
|
238
|
+
struct node_base {
|
|
239
|
+
const jv *node;
|
|
240
|
+
const char *abs;
|
|
241
|
+
const char *dir;
|
|
242
|
+
jv *root;
|
|
243
|
+
bool recursive;
|
|
244
|
+
} *node_bases;
|
|
245
|
+
size_t n_node_bases, cap_node_bases;
|
|
187
246
|
char *err;
|
|
188
247
|
size_t err_cap;
|
|
189
248
|
} sctx;
|
|
190
249
|
|
|
250
|
+
/* a materialized resource: a base URI naming a jv subtree */
|
|
251
|
+
struct teptris_resource {
|
|
252
|
+
const char *abs; /* base URI (file://... or empty for local-only) */
|
|
253
|
+
jv *root; /* the resource's root jv ("#" resolves here) */
|
|
254
|
+
sch_node *node; /* compiled root (memo-resolved) */
|
|
255
|
+
bool recursive_anchor; /* $recursiveAnchor: true on the root */
|
|
256
|
+
};
|
|
257
|
+
|
|
191
258
|
/* the tagged value the validator walks: converted from a document's
|
|
192
259
|
* DOM once per validate call, or synthesized (schema-side enum/const
|
|
193
260
|
* values, propertyNames strings) */
|
|
@@ -231,6 +298,9 @@ sch_node *sch_resolve_pointer(sctx *c, const char *pointer);
|
|
|
231
298
|
* with |mant| < 10^18; false for non-finite d */
|
|
232
299
|
bool sch_decimal_of_double(double d, int64_t *mant, int *exp);
|
|
233
300
|
|
|
301
|
+
/* refs.c: the resource loader used by $ref resolution */
|
|
302
|
+
jv *sch_load_resource(sctx *c, const char *abs_uri);
|
|
303
|
+
|
|
234
304
|
/* formats.c */
|
|
235
305
|
/* the string-side format battery (the taplo parity set); datetimes
|
|
236
306
|
* reach it through their canonical text like any other string */
|
|
@@ -43,11 +43,17 @@ typedef struct {
|
|
|
43
43
|
size_t n_idx, cap_idx;
|
|
44
44
|
} *frames;
|
|
45
45
|
int n_frames, cap_frames;
|
|
46
|
-
/* dynamic-scope stack for $dynamicRef / $recursiveRef:
|
|
47
|
-
*
|
|
46
|
+
/* dynamic-scope stack for $dynamicRef / $recursiveRef: one frame
|
|
47
|
+
* per applied schema object, in evaluation order. Each entry
|
|
48
|
+
* names its anchor and the frame owner (the applied object), so
|
|
49
|
+
* resolution walks RESOURCES outermost-first (2020-12 8.2.3.2:
|
|
50
|
+
* the outermost resource in the dynamic scope that defines the
|
|
51
|
+
* fragment wins, its first anchor in document order) */
|
|
48
52
|
struct dyn_scope {
|
|
49
53
|
const char *name;
|
|
50
54
|
sch_node *node;
|
|
55
|
+
const jv *res; /* the anchor's resource (nearest $id) */
|
|
56
|
+
sch_node *owner; /* the applied object that entered it */
|
|
51
57
|
} *scopes;
|
|
52
58
|
int n_scopes, cap_scopes;
|
|
53
59
|
} vctx;
|
|
@@ -313,8 +319,9 @@ static void frames_free(vctx *vc) {
|
|
|
313
319
|
|
|
314
320
|
/* ------------------------------------------------- dynamic scopes -- */
|
|
315
321
|
|
|
316
|
-
static bool scope_push(vctx *vc, const char *name, sch_node *node
|
|
317
|
-
|
|
322
|
+
static bool scope_push(vctx *vc, const char *name, sch_node *node,
|
|
323
|
+
const jv *res, sch_node *owner) {
|
|
324
|
+
if (name == NULL || node == NULL || owner == NULL) return true;
|
|
318
325
|
if (vc->n_scopes == vc->cap_scopes) {
|
|
319
326
|
vc->cap_scopes = vc->cap_scopes == 0 ? 16 : vc->cap_scopes * 2;
|
|
320
327
|
void *ns = realloc(vc->scopes, vc->cap_scopes * sizeof(*vc->scopes));
|
|
@@ -323,14 +330,47 @@ static bool scope_push(vctx *vc, const char *name, sch_node *node) {
|
|
|
323
330
|
}
|
|
324
331
|
vc->scopes[vc->n_scopes].name = name;
|
|
325
332
|
vc->scopes[vc->n_scopes].node = node;
|
|
333
|
+
vc->scopes[vc->n_scopes].res = res;
|
|
334
|
+
vc->scopes[vc->n_scopes].owner = owner;
|
|
326
335
|
vc->n_scopes++;
|
|
327
336
|
return true;
|
|
328
337
|
}
|
|
329
338
|
|
|
330
|
-
/*
|
|
339
|
+
/* 2020-12 $dynamicRef: walk the RESOURCES of the evaluation path
|
|
340
|
+
* outermost-first (the resource of each applied object, in frame
|
|
341
|
+
* order); the first resource that DEFINES an identically-named
|
|
342
|
+
* $dynamicAnchor — the anchor's nearest-$id resource anywhere in the
|
|
343
|
+
* stack — wins, and its first matching anchor in document order is
|
|
344
|
+
* the target (2020-12 8.2.3.2 + appendix C) */
|
|
331
345
|
static sch_node *scope_resolve(vctx *vc, const char *name) {
|
|
332
|
-
for (int i =
|
|
333
|
-
|
|
346
|
+
for (int i = 0; i < vc->n_scopes; i++) {
|
|
347
|
+
const jv *res = vc->scopes[i].owner->res_id;
|
|
348
|
+
bool seen = false;
|
|
349
|
+
for (int k = 0; k < i; k++) {
|
|
350
|
+
if (vc->scopes[k].owner->res_id == res) {
|
|
351
|
+
seen = true;
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (seen) continue;
|
|
356
|
+
for (int j = 0; j < vc->n_scopes; j++) {
|
|
357
|
+
if (vc->scopes[j].res != res) continue;
|
|
358
|
+
if (strcmp(vc->scopes[j].name, name) == 0) {
|
|
359
|
+
return vc->scopes[j].node;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return NULL;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/* 2019-09 $recursiveRef: the first resource on the evaluation path
|
|
367
|
+
* (outermost first) whose ROOT carries $recursiveAnchor: true; the
|
|
368
|
+
* target is that resource's root */
|
|
369
|
+
static sch_node *scope_resolve_recursive(vctx *vc) {
|
|
370
|
+
for (int i = 0; i < vc->n_scopes; i++) {
|
|
371
|
+
if (vc->scopes[i].owner->recursive_anchor != NULL) {
|
|
372
|
+
return vc->scopes[i].owner;
|
|
373
|
+
}
|
|
334
374
|
}
|
|
335
375
|
return NULL;
|
|
336
376
|
}
|
|
@@ -346,6 +386,15 @@ static bool sv_equal(const sch_svalue *a, const sch_svalue *b) {
|
|
|
346
386
|
* equals a datetime text */
|
|
347
387
|
int ad = a->kind >= SV_DATETIME;
|
|
348
388
|
int bd = b->kind >= SV_DATETIME;
|
|
389
|
+
/* numeric identity per JSON Schema: an integer and a float
|
|
390
|
+
* with the same value are equal (bools stay distinct - their
|
|
391
|
+
* kinds differ, so they never reach this branch) */
|
|
392
|
+
if ((a->kind == SV_INT || a->kind == SV_FLOAT) &&
|
|
393
|
+
(b->kind == SV_INT || b->kind == SV_FLOAT)) {
|
|
394
|
+
long double av = a->kind == SV_INT ? (long double)a->i : (long double)a->f;
|
|
395
|
+
long double bv = b->kind == SV_INT ? (long double)b->i : (long double)b->f;
|
|
396
|
+
return av == bv;
|
|
397
|
+
}
|
|
349
398
|
if ((ad || bd) && (a->kind == SV_STRING || b->kind == SV_STRING)) {
|
|
350
399
|
const sch_svalue *dt = ad ? a : b;
|
|
351
400
|
const sch_svalue *st = ad ? b : a;
|
|
@@ -676,15 +725,31 @@ static int sv_to_st(const sch_svalue *v) {
|
|
|
676
725
|
switch (v->kind) {
|
|
677
726
|
case SV_STRING: return ST_STRING;
|
|
678
727
|
case SV_INT: return ST_INTEGER | ST_NUMBER;
|
|
679
|
-
case SV_FLOAT:
|
|
728
|
+
case SV_FLOAT:
|
|
729
|
+
/* a float with a zero fractional part is an integer (the
|
|
730
|
+
* 2020-12 rule the suite tests) */
|
|
731
|
+
return (v->f == v->f && v->f != INFINITY && v->f != -INFINITY &&
|
|
732
|
+
v->f == (double)(int64_t)v->f)
|
|
733
|
+
? ST_INTEGER | ST_NUMBER
|
|
734
|
+
: ST_NUMBER;
|
|
680
735
|
case SV_BOOL: return ST_BOOLEAN;
|
|
681
736
|
case SV_TABLE: return ST_OBJECT;
|
|
682
737
|
case SV_ARRAY: return ST_ARRAY;
|
|
683
738
|
case SV_DATETIME: case SV_DATE: case SV_TIME: return ST_STRING;
|
|
739
|
+
case SV_NULL: return ST_NULL;
|
|
684
740
|
default: return 0;
|
|
685
741
|
}
|
|
686
742
|
}
|
|
687
743
|
|
|
744
|
+
/* one application = one scope frame: every anchor inside the applied
|
|
745
|
+
* schema's own subtree is in scope below it, owned by this object */
|
|
746
|
+
static void scope_frame_push(vctx *vc, sch_node *n) {
|
|
747
|
+
for (size_t i = 0; i < n->n_self_scope; i++) {
|
|
748
|
+
scope_push(vc, n->self_scope[i].name, n->self_scope[i].node,
|
|
749
|
+
n->self_scope[i].res, n);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
688
753
|
static void val(vctx *vc, sch_node *n, const sch_svalue *v) {
|
|
689
754
|
if (n == NULL || v == NULL) return;
|
|
690
755
|
if (vc->depth >= MAX_DEPTH) {
|
|
@@ -697,8 +762,7 @@ static void val(vctx *vc, sch_node *n, const sch_svalue *v) {
|
|
|
697
762
|
* scope for the instance path below it */
|
|
698
763
|
frame_push(vc, v);
|
|
699
764
|
int scopes_base = vc->n_scopes;
|
|
700
|
-
|
|
701
|
-
scope_push(vc, n->recursive_anchor, n);
|
|
765
|
+
scope_frame_push(vc, n);
|
|
702
766
|
|
|
703
767
|
/* a dynamic $ref resolves through the innermost scope entered on
|
|
704
768
|
* the evaluation path; the compile-resolved target is the static
|
|
@@ -706,7 +770,8 @@ static void val(vctx *vc, sch_node *n, const sch_svalue *v) {
|
|
|
706
770
|
if (n->dynamic_ref != NULL) {
|
|
707
771
|
const char *name = n->dynamic_ref[0] == '#' ? n->dynamic_ref + 1
|
|
708
772
|
: n->dynamic_ref;
|
|
709
|
-
sch_node *target =
|
|
773
|
+
sch_node *target = n->dynamic_recursive ? scope_resolve_recursive(vc)
|
|
774
|
+
: scope_resolve(vc, name);
|
|
710
775
|
if (target == NULL) target = n->dynamic_ref_target;
|
|
711
776
|
if (target != NULL) {
|
|
712
777
|
if (vc->ref_depth >= MAX_DEPTH) {
|
|
@@ -715,6 +780,7 @@ static void val(vctx *vc, sch_node *n, const sch_svalue *v) {
|
|
|
715
780
|
vc->ref_depth++;
|
|
716
781
|
spush(vc, "$dynamicRef");
|
|
717
782
|
val(vc, target, v);
|
|
783
|
+
vc->n_scopes = scopes_base;
|
|
718
784
|
spop(vc);
|
|
719
785
|
vc->ref_depth--;
|
|
720
786
|
}
|
|
@@ -731,9 +797,16 @@ static void val(vctx *vc, sch_node *n, const sch_svalue *v) {
|
|
|
731
797
|
vc->ref_depth++;
|
|
732
798
|
spush(vc, "$ref");
|
|
733
799
|
val(vc, n->ref, v);
|
|
800
|
+
vc->n_scopes = scopes_base;
|
|
734
801
|
spop(vc);
|
|
735
802
|
vc->ref_depth--;
|
|
736
803
|
}
|
|
804
|
+
/* draft 07 and older: the $ref replaces every sibling */
|
|
805
|
+
if (n->ref_only) {
|
|
806
|
+
vc->n_scopes = scopes_base;
|
|
807
|
+
frame_pop(vc);
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
737
810
|
}
|
|
738
811
|
|
|
739
812
|
if (n->is_false) {
|
|
@@ -767,6 +840,9 @@ static void val(vctx *vc, sch_node *n, const sch_svalue *v) {
|
|
|
767
840
|
verr(vc, "value does not equal the constant");
|
|
768
841
|
}
|
|
769
842
|
}
|
|
843
|
+
if (n->enum_empty) {
|
|
844
|
+
verr(vc, "value does not match the empty enum (nothing matches)");
|
|
845
|
+
}
|
|
770
846
|
if (n->n_enum > 0) {
|
|
771
847
|
bool hit = false;
|
|
772
848
|
for (size_t i = 0; i < n->n_enum && !hit; i++) {
|
data/lib/teptris/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: teptris
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.23.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- teptris contributors
|
|
@@ -56,6 +56,10 @@ files:
|
|
|
56
56
|
- ext/teptris_ext/engine/src/teptris/schema/formats.c
|
|
57
57
|
- ext/teptris_ext/engine/src/teptris/schema/json_read.c
|
|
58
58
|
- ext/teptris_ext/engine/src/teptris/schema/json_read.h
|
|
59
|
+
- ext/teptris_ext/engine/src/teptris/schema/metaschemas.c
|
|
60
|
+
- ext/teptris_ext/engine/src/teptris/schema/metaschemas.h
|
|
61
|
+
- ext/teptris_ext/engine/src/teptris/schema/refs.c
|
|
62
|
+
- ext/teptris_ext/engine/src/teptris/schema/refs.h
|
|
59
63
|
- ext/teptris_ext/engine/src/teptris/schema/regex.c
|
|
60
64
|
- ext/teptris_ext/engine/src/teptris/schema/regex.h
|
|
61
65
|
- ext/teptris_ext/engine/src/teptris/schema/schema.c
|