ata-validator 1.0.2 → 1.1.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.
- package/CHANGELOG.md +14 -0
- package/README.md +19 -2
- package/index.js +18 -2
- package/lib/aot.js +1 -1
- package/lib/js-compiler.js +32 -0
- package/lib/native-load.browser.js +1 -1
- package/lib/native-load.js +83 -11
- package/lib/t.js +90 -0
- package/lib/version.js +3 -3
- package/package.json +14 -20
- package/t.d.ts +84 -0
- package/CMakeLists.txt +0 -150
- package/binding/ata_napi.cpp +0 -1574
- package/binding-options.js +0 -4
- package/deps/simdjson/simdjson.cpp +0 -56221
- package/deps/simdjson/simdjson.h +0 -122784
- package/include/ata.h +0 -111
- package/include/ata_c.h +0 -57
- package/prebuilds/ata-darwin-arm64/node-napi-v10.node +0 -0
- package/prebuilds/ata-linux-arm64/node-napi-v10.node +0 -0
- package/prebuilds/ata-linux-arm64-musl/node-napi-v10.node +0 -0
- package/prebuilds/ata-linux-x64/node-napi-v10.node +0 -0
- package/prebuilds/ata-linux-x64-musl/node-napi-v10.node +0 -0
- package/prebuilds/ata-win32-x64/node-napi-v10.node +0 -0
- package/scripts/check-doc-coverage.js +0 -34
- package/scripts/check-prebuilds.js +0 -54
- package/scripts/install.js +0 -23
- package/scripts/regen-error-codes-doc.js +0 -40
- package/scripts/regen-lock.js +0 -19
- package/scripts/regen-safe-regex-source.js +0 -33
- package/src/ata.cpp +0 -3146
- package/src/ata_c.cpp +0 -63
package/src/ata.cpp
DELETED
|
@@ -1,3146 +0,0 @@
|
|
|
1
|
-
#include "ata.h"
|
|
2
|
-
|
|
3
|
-
// mimalloc: faster new/delete for small allocations.
|
|
4
|
-
#if __has_include(<mimalloc-new-delete.h>)
|
|
5
|
-
#include <mimalloc-new-delete.h>
|
|
6
|
-
#endif
|
|
7
|
-
|
|
8
|
-
#include <algorithm>
|
|
9
|
-
#include <cmath>
|
|
10
|
-
#include <cstring>
|
|
11
|
-
#ifndef ATA_NO_RE2
|
|
12
|
-
#include <re2/re2.h>
|
|
13
|
-
#endif
|
|
14
|
-
#include <set>
|
|
15
|
-
#include <unordered_map>
|
|
16
|
-
|
|
17
|
-
#ifdef _WIN32
|
|
18
|
-
#include <windows.h>
|
|
19
|
-
#include <sysinfoapi.h>
|
|
20
|
-
#else
|
|
21
|
-
#include <unistd.h>
|
|
22
|
-
#endif
|
|
23
|
-
|
|
24
|
-
// MSVC implementation by Pavel P (https://gist.github.com/pps83/3210a2f980fd02bb2ba2e5a1fc4a2ef0)
|
|
25
|
-
#if defined(_MSC_VER) && !defined(__clang__)
|
|
26
|
-
#include <intrin.h>
|
|
27
|
-
#ifndef __builtin_popcount
|
|
28
|
-
#define __builtin_popcount __popcnt
|
|
29
|
-
#endif
|
|
30
|
-
#endif // defined(_MSC_VER) && !defined(__clang__)
|
|
31
|
-
|
|
32
|
-
#include "simdjson.h"
|
|
33
|
-
|
|
34
|
-
// --- Fast format validators (no std::regex) ---
|
|
35
|
-
|
|
36
|
-
static bool is_digit(char c) { return c >= '0' && c <= '9'; }
|
|
37
|
-
static bool is_alpha(char c) {
|
|
38
|
-
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
|
39
|
-
}
|
|
40
|
-
static bool is_alnum(char c) { return is_alpha(c) || is_digit(c); }
|
|
41
|
-
static bool is_hex(char c) {
|
|
42
|
-
return is_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
static bool fast_check_email(std::string_view s) {
|
|
46
|
-
auto at = s.find('@');
|
|
47
|
-
if (at == std::string_view::npos || at == 0 || at == s.size() - 1)
|
|
48
|
-
return false;
|
|
49
|
-
auto dot = s.find('.', at + 1);
|
|
50
|
-
if (dot == std::string_view::npos || dot == at + 1 ||
|
|
51
|
-
dot == s.size() - 1)
|
|
52
|
-
return false;
|
|
53
|
-
// Check TLD has at least 2 chars
|
|
54
|
-
return (s.size() - dot - 1) >= 2;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
static bool fast_check_date(std::string_view s) {
|
|
58
|
-
// YYYY-MM-DD with range validation
|
|
59
|
-
if (s.size() != 10 || !is_digit(s[0]) || !is_digit(s[1]) ||
|
|
60
|
-
!is_digit(s[2]) || !is_digit(s[3]) || s[4] != '-' ||
|
|
61
|
-
!is_digit(s[5]) || !is_digit(s[6]) || s[7] != '-' ||
|
|
62
|
-
!is_digit(s[8]) || !is_digit(s[9]))
|
|
63
|
-
return false;
|
|
64
|
-
int month = (s[5] - '0') * 10 + (s[6] - '0');
|
|
65
|
-
int day = (s[8] - '0') * 10 + (s[9] - '0');
|
|
66
|
-
return month >= 1 && month <= 12 && day >= 1 && day <= 31;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
static bool fast_check_time(std::string_view s) {
|
|
70
|
-
// HH:MM:SS[.frac][Z|+HH:MM]
|
|
71
|
-
if (s.size() < 8) return false;
|
|
72
|
-
if (!is_digit(s[0]) || !is_digit(s[1]) || s[2] != ':' ||
|
|
73
|
-
!is_digit(s[3]) || !is_digit(s[4]) || s[5] != ':' ||
|
|
74
|
-
!is_digit(s[6]) || !is_digit(s[7]))
|
|
75
|
-
return false;
|
|
76
|
-
return true;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
static bool fast_check_datetime(std::string_view s) {
|
|
80
|
-
if (s.size() < 19) return false;
|
|
81
|
-
if (!fast_check_date(s.substr(0, 10))) return false;
|
|
82
|
-
if (s[10] != 'T' && s[10] != 't' && s[10] != ' ') return false;
|
|
83
|
-
return fast_check_time(s.substr(11));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
static bool fast_check_ipv4(std::string_view s) {
|
|
87
|
-
int parts = 0, val = 0, digits = 0;
|
|
88
|
-
for (size_t i = 0; i <= s.size(); ++i) {
|
|
89
|
-
if (i == s.size() || s[i] == '.') {
|
|
90
|
-
if (digits == 0 || val > 255) return false;
|
|
91
|
-
++parts;
|
|
92
|
-
val = 0;
|
|
93
|
-
digits = 0;
|
|
94
|
-
} else if (is_digit(s[i])) {
|
|
95
|
-
val = val * 10 + (s[i] - '0');
|
|
96
|
-
++digits;
|
|
97
|
-
if (digits > 3) return false;
|
|
98
|
-
} else {
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return parts == 4;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
static bool fast_check_uri(std::string_view s) {
|
|
106
|
-
if (s.size() < 3) return false;
|
|
107
|
-
// Must start with alpha, then scheme chars, then ':'
|
|
108
|
-
if (!is_alpha(s[0])) return false;
|
|
109
|
-
size_t i = 1;
|
|
110
|
-
while (i < s.size() && (is_alnum(s[i]) || s[i] == '+' || s[i] == '-' ||
|
|
111
|
-
s[i] == '.'))
|
|
112
|
-
++i;
|
|
113
|
-
return i < s.size() && s[i] == ':' && i + 1 < s.size();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
static bool fast_check_uuid(std::string_view s) {
|
|
117
|
-
// 8-4-4-4-12
|
|
118
|
-
if (s.size() != 36) return false;
|
|
119
|
-
for (size_t i = 0; i < 36; ++i) {
|
|
120
|
-
if (i == 8 || i == 13 || i == 18 || i == 23) {
|
|
121
|
-
if (s[i] != '-') return false;
|
|
122
|
-
} else {
|
|
123
|
-
if (!is_hex(s[i])) return false;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return true;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
static bool fast_check_hostname(std::string_view s) {
|
|
130
|
-
if (s.empty() || s.size() > 253) return false;
|
|
131
|
-
size_t label_len = 0;
|
|
132
|
-
for (size_t i = 0; i < s.size(); ++i) {
|
|
133
|
-
if (s[i] == '.') {
|
|
134
|
-
if (label_len == 0) return false;
|
|
135
|
-
label_len = 0;
|
|
136
|
-
} else if (is_alnum(s[i]) || s[i] == '-') {
|
|
137
|
-
++label_len;
|
|
138
|
-
if (label_len > 63) return false;
|
|
139
|
-
} else {
|
|
140
|
-
return false;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return label_len > 0;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Check format by pre-resolved numeric ID — no string comparisons.
|
|
147
|
-
static bool check_format_by_id(std::string_view sv, uint8_t fid) {
|
|
148
|
-
switch (fid) {
|
|
149
|
-
case 0: return fast_check_email(sv);
|
|
150
|
-
case 1: return fast_check_date(sv);
|
|
151
|
-
case 2: return fast_check_datetime(sv);
|
|
152
|
-
case 3: return fast_check_time(sv);
|
|
153
|
-
case 4: return fast_check_ipv4(sv);
|
|
154
|
-
case 5: return sv.find(':') != std::string_view::npos;
|
|
155
|
-
case 6: return fast_check_uri(sv);
|
|
156
|
-
case 7: return fast_check_uuid(sv);
|
|
157
|
-
case 8: return fast_check_hostname(sv);
|
|
158
|
-
default: return true; // unknown formats pass
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
namespace ata {
|
|
163
|
-
|
|
164
|
-
using namespace simdjson;
|
|
165
|
-
|
|
166
|
-
// Canonical JSON: sort object keys for semantic equality comparison
|
|
167
|
-
static std::string canonical_json(dom::element el) {
|
|
168
|
-
switch (el.type()) {
|
|
169
|
-
case dom::element_type::OBJECT: {
|
|
170
|
-
dom::object obj; el.get(obj);
|
|
171
|
-
std::vector<std::pair<std::string_view, dom::element>> entries;
|
|
172
|
-
for (auto [k, v] : obj) entries.push_back({k, v});
|
|
173
|
-
std::sort(entries.begin(), entries.end(),
|
|
174
|
-
[](const auto& a, const auto& b) { return a.first < b.first; });
|
|
175
|
-
std::string r = "{";
|
|
176
|
-
for (size_t i = 0; i < entries.size(); ++i) {
|
|
177
|
-
if (i) r += ',';
|
|
178
|
-
r += '"';
|
|
179
|
-
r += entries[i].first;
|
|
180
|
-
r += "\":";
|
|
181
|
-
r += canonical_json(entries[i].second);
|
|
182
|
-
}
|
|
183
|
-
r += '}';
|
|
184
|
-
return r;
|
|
185
|
-
}
|
|
186
|
-
case dom::element_type::ARRAY: {
|
|
187
|
-
dom::array arr; el.get(arr);
|
|
188
|
-
std::string r = "[";
|
|
189
|
-
bool first = true;
|
|
190
|
-
for (auto v : arr) {
|
|
191
|
-
if (!first) r += ',';
|
|
192
|
-
first = false;
|
|
193
|
-
r += canonical_json(v);
|
|
194
|
-
}
|
|
195
|
-
r += ']';
|
|
196
|
-
return r;
|
|
197
|
-
}
|
|
198
|
-
default:
|
|
199
|
-
return std::string(minify(el));
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// JSON Schema type enum — avoids string comparisons on the hot path.
|
|
204
|
-
enum class json_type : uint8_t {
|
|
205
|
-
string, number, integer, boolean, null_value, object, array
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
static json_type json_type_from_sv(std::string_view s) {
|
|
209
|
-
if (s == "string") return json_type::string;
|
|
210
|
-
if (s == "number") return json_type::number;
|
|
211
|
-
if (s == "integer") return json_type::integer;
|
|
212
|
-
if (s == "boolean") return json_type::boolean;
|
|
213
|
-
if (s == "null") return json_type::null_value;
|
|
214
|
-
if (s == "object") return json_type::object;
|
|
215
|
-
if (s == "array") return json_type::array;
|
|
216
|
-
return json_type::string; // fallback
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
static const char* json_type_name(json_type t) {
|
|
220
|
-
switch (t) {
|
|
221
|
-
case json_type::string: return "string";
|
|
222
|
-
case json_type::number: return "number";
|
|
223
|
-
case json_type::integer: return "integer";
|
|
224
|
-
case json_type::boolean: return "boolean";
|
|
225
|
-
case json_type::null_value: return "null";
|
|
226
|
-
case json_type::object: return "object";
|
|
227
|
-
case json_type::array: return "array";
|
|
228
|
-
}
|
|
229
|
-
return "unknown";
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Bitmask for O(1) type checking: one bit per json_type value.
|
|
233
|
-
static uint8_t json_type_bit(json_type t) { return 1u << static_cast<uint8_t>(t); }
|
|
234
|
-
|
|
235
|
-
// Map dom::element_type to a json_type bitmask (number matches integer too).
|
|
236
|
-
static uint8_t element_type_mask(dom::element_type t) {
|
|
237
|
-
switch (t) {
|
|
238
|
-
case dom::element_type::STRING: return json_type_bit(json_type::string);
|
|
239
|
-
case dom::element_type::INT64:
|
|
240
|
-
case dom::element_type::UINT64: return json_type_bit(json_type::integer) | json_type_bit(json_type::number);
|
|
241
|
-
case dom::element_type::DOUBLE: return json_type_bit(json_type::number);
|
|
242
|
-
case dom::element_type::BOOL: return json_type_bit(json_type::boolean);
|
|
243
|
-
case dom::element_type::NULL_VALUE: return json_type_bit(json_type::null_value);
|
|
244
|
-
case dom::element_type::ARRAY: return json_type_bit(json_type::array);
|
|
245
|
-
case dom::element_type::OBJECT: return json_type_bit(json_type::object);
|
|
246
|
-
}
|
|
247
|
-
return 0;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Resolve format string to numeric ID at compile time.
|
|
251
|
-
static uint8_t format_id_from_string(const std::string& f) {
|
|
252
|
-
if (f == "email") return 0;
|
|
253
|
-
if (f == "date") return 1;
|
|
254
|
-
if (f == "date-time") return 2;
|
|
255
|
-
if (f == "time") return 3;
|
|
256
|
-
if (f == "ipv4") return 4;
|
|
257
|
-
if (f == "ipv6") return 5;
|
|
258
|
-
if (f == "uri" || f == "uri-reference") return 6;
|
|
259
|
-
if (f == "uuid") return 7;
|
|
260
|
-
if (f == "hostname") return 8;
|
|
261
|
-
return 255;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Forward declarations
|
|
265
|
-
struct schema_node;
|
|
266
|
-
using schema_node_ptr = std::shared_ptr<schema_node>;
|
|
267
|
-
|
|
268
|
-
struct schema_node {
|
|
269
|
-
// type constraint — bitmask for O(1) type checking
|
|
270
|
-
uint8_t type_mask = 0; // bit per json_type value
|
|
271
|
-
|
|
272
|
-
// numeric
|
|
273
|
-
std::optional<double> minimum;
|
|
274
|
-
std::optional<double> maximum;
|
|
275
|
-
std::optional<double> exclusive_minimum;
|
|
276
|
-
std::optional<double> exclusive_maximum;
|
|
277
|
-
std::optional<double> multiple_of;
|
|
278
|
-
|
|
279
|
-
// string
|
|
280
|
-
std::optional<uint64_t> min_length;
|
|
281
|
-
std::optional<uint64_t> max_length;
|
|
282
|
-
std::optional<std::string> pattern;
|
|
283
|
-
#ifndef ATA_NO_RE2
|
|
284
|
-
std::shared_ptr<re2::RE2> compiled_pattern; // cached compiled regex (RE2)
|
|
285
|
-
#endif
|
|
286
|
-
|
|
287
|
-
// array
|
|
288
|
-
std::optional<uint64_t> min_items;
|
|
289
|
-
std::optional<uint64_t> max_items;
|
|
290
|
-
bool unique_items = false;
|
|
291
|
-
schema_node_ptr items_schema;
|
|
292
|
-
std::vector<schema_node_ptr> prefix_items;
|
|
293
|
-
schema_node_ptr contains_schema;
|
|
294
|
-
std::optional<uint64_t> min_contains;
|
|
295
|
-
std::optional<uint64_t> max_contains;
|
|
296
|
-
|
|
297
|
-
// object
|
|
298
|
-
std::unordered_map<std::string, schema_node_ptr> properties;
|
|
299
|
-
std::vector<std::string> required;
|
|
300
|
-
std::optional<bool> additional_properties_bool;
|
|
301
|
-
schema_node_ptr additional_properties_schema;
|
|
302
|
-
std::optional<uint64_t> min_properties;
|
|
303
|
-
std::optional<uint64_t> max_properties;
|
|
304
|
-
schema_node_ptr property_names_schema;
|
|
305
|
-
std::unordered_map<std::string, std::vector<std::string>> dependent_required;
|
|
306
|
-
std::unordered_map<std::string, schema_node_ptr> dependent_schemas;
|
|
307
|
-
|
|
308
|
-
// patternProperties — each entry: (pattern_string, schema, compiled_regex)
|
|
309
|
-
struct pattern_prop {
|
|
310
|
-
std::string pattern;
|
|
311
|
-
schema_node_ptr schema;
|
|
312
|
-
#ifndef ATA_NO_RE2
|
|
313
|
-
std::shared_ptr<re2::RE2> compiled;
|
|
314
|
-
#endif
|
|
315
|
-
};
|
|
316
|
-
std::vector<pattern_prop> pattern_properties;
|
|
317
|
-
|
|
318
|
-
// enum / const
|
|
319
|
-
std::vector<std::string> enum_values_minified; // pre-minified enum values
|
|
320
|
-
std::optional<std::string> const_value_raw; // raw JSON value string
|
|
321
|
-
|
|
322
|
-
// format
|
|
323
|
-
std::optional<std::string> format;
|
|
324
|
-
uint8_t format_id = 255; // pre-resolved format ID (255 = unknown/pass)
|
|
325
|
-
|
|
326
|
-
// composition
|
|
327
|
-
std::vector<schema_node_ptr> all_of;
|
|
328
|
-
std::vector<schema_node_ptr> any_of;
|
|
329
|
-
std::vector<schema_node_ptr> one_of;
|
|
330
|
-
schema_node_ptr not_schema;
|
|
331
|
-
|
|
332
|
-
// conditional
|
|
333
|
-
schema_node_ptr if_schema;
|
|
334
|
-
schema_node_ptr then_schema;
|
|
335
|
-
schema_node_ptr else_schema;
|
|
336
|
-
|
|
337
|
-
// $ref
|
|
338
|
-
std::string ref;
|
|
339
|
-
std::string dynamic_ref; // $dynamicRef value (e.g. "#items")
|
|
340
|
-
std::string id; // $id — resource boundary marker
|
|
341
|
-
|
|
342
|
-
// $defs — stored on node for pointer navigation
|
|
343
|
-
std::unordered_map<std::string, schema_node_ptr> defs;
|
|
344
|
-
|
|
345
|
-
// boolean schema
|
|
346
|
-
std::optional<bool> boolean_schema;
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
// --- Codegen: flat bytecode plan ---
|
|
350
|
-
namespace cg {
|
|
351
|
-
enum class op : uint8_t {
|
|
352
|
-
END=0, EXPECT_OBJECT, EXPECT_ARRAY, EXPECT_STRING, EXPECT_NUMBER,
|
|
353
|
-
EXPECT_INTEGER, EXPECT_BOOLEAN, EXPECT_NULL, EXPECT_TYPE_MULTI,
|
|
354
|
-
CHECK_MINIMUM, CHECK_MAXIMUM, CHECK_EX_MINIMUM, CHECK_EX_MAXIMUM,
|
|
355
|
-
CHECK_MULTIPLE_OF, CHECK_MIN_LENGTH, CHECK_MAX_LENGTH, CHECK_PATTERN,
|
|
356
|
-
CHECK_FORMAT, CHECK_MIN_ITEMS, CHECK_MAX_ITEMS, CHECK_UNIQUE_ITEMS,
|
|
357
|
-
ARRAY_ITEMS, CHECK_REQUIRED, CHECK_MIN_PROPS, CHECK_MAX_PROPS,
|
|
358
|
-
OBJ_PROPS_START, OBJ_PROP, OBJ_PROPS_END, CHECK_NO_ADDITIONAL,
|
|
359
|
-
CHECK_ENUM_STR, CHECK_ENUM, CHECK_CONST, COMPOSITION,
|
|
360
|
-
};
|
|
361
|
-
struct ins { op o; uint32_t a=0, b=0; };
|
|
362
|
-
struct plan {
|
|
363
|
-
std::vector<ins> code;
|
|
364
|
-
std::vector<double> doubles;
|
|
365
|
-
std::vector<std::string> strings;
|
|
366
|
-
#ifndef ATA_NO_RE2
|
|
367
|
-
std::vector<std::shared_ptr<re2::RE2>> regexes;
|
|
368
|
-
#endif
|
|
369
|
-
std::vector<std::vector<std::string>> enum_sets;
|
|
370
|
-
std::vector<uint8_t> type_masks;
|
|
371
|
-
std::vector<uint8_t> format_ids;
|
|
372
|
-
std::vector<std::vector<ins>> subs;
|
|
373
|
-
};
|
|
374
|
-
} // namespace cg
|
|
375
|
-
|
|
376
|
-
// --- On-Demand validation plan ---
|
|
377
|
-
// Grouped checks per value type. Each value consumed exactly once.
|
|
378
|
-
// Built from schema_node at compile time, used by od_exec_plan at runtime.
|
|
379
|
-
struct od_plan {
|
|
380
|
-
uint8_t type_mask = 0;
|
|
381
|
-
|
|
382
|
-
// Numeric — bitmask for which checks to run + flat array of bounds
|
|
383
|
-
enum num_flag : uint8_t {
|
|
384
|
-
HAS_MIN = 1, HAS_MAX = 2, HAS_EX_MIN = 4, HAS_EX_MAX = 8, HAS_MUL = 16
|
|
385
|
-
};
|
|
386
|
-
uint8_t num_flags = 0;
|
|
387
|
-
double num_min = 0, num_max = 0, num_ex_min = 0, num_ex_max = 0, num_mul = 0;
|
|
388
|
-
|
|
389
|
-
// String — single value.get(sv) then all checks
|
|
390
|
-
std::optional<uint64_t> min_length, max_length;
|
|
391
|
-
#ifndef ATA_NO_RE2
|
|
392
|
-
re2::RE2* pattern = nullptr; // borrowed pointer from schema_node
|
|
393
|
-
#endif
|
|
394
|
-
// Inline digit pattern: ^[0-9]+$ / ^[0-9]{N}$ / ^[0-9]{N,M}$ / ^\d+$ etc.
|
|
395
|
-
// When set, supersedes the RE2 pattern call.
|
|
396
|
-
struct digit_pattern_t { uint32_t min_len; uint32_t max_len; };
|
|
397
|
-
std::optional<digit_pattern_t> digit_pattern;
|
|
398
|
-
uint8_t format_id = 255; // 255 = no format check
|
|
399
|
-
|
|
400
|
-
// Object — single iterate with merged required+property lookup
|
|
401
|
-
// fast_kind allows the obj iterator to skip the recursive od_exec_plan call
|
|
402
|
-
// (and its inner type().get() + switch) for primitive sub-plans, dispatching
|
|
403
|
-
// straight to inline constraint code.
|
|
404
|
-
enum class fast_kind : uint8_t {
|
|
405
|
-
OTHER = 0, // recurse via od_exec_plan
|
|
406
|
-
INTEGER, // value.get(int64) + numeric range/multipleOf
|
|
407
|
-
STRING, // value.get(string) + length/pattern/format/enum
|
|
408
|
-
BOOLEAN, // value.get(bool)
|
|
409
|
-
OBJECT, // value.get(object) + nested obj iteration (skips type detect)
|
|
410
|
-
ARRAY, // value.get(array) + element iteration (skips type detect)
|
|
411
|
-
};
|
|
412
|
-
struct prop_entry {
|
|
413
|
-
std::string key;
|
|
414
|
-
int required_idx = -1; // bit index for required tracking, or -1
|
|
415
|
-
std::shared_ptr<od_plan> sub; // property sub-plan, or nullptr
|
|
416
|
-
fast_kind fk = fast_kind::OTHER; // inline dispatch hint, set at compile time
|
|
417
|
-
};
|
|
418
|
-
struct obj_plan {
|
|
419
|
-
std::vector<prop_entry> entries; // merged required + properties — single scan
|
|
420
|
-
size_t required_count = 0;
|
|
421
|
-
bool no_additional = false;
|
|
422
|
-
std::optional<uint64_t> min_props, max_props;
|
|
423
|
-
};
|
|
424
|
-
std::shared_ptr<obj_plan> object;
|
|
425
|
-
|
|
426
|
-
// Array — single iterate: items + count
|
|
427
|
-
struct arr_plan {
|
|
428
|
-
std::shared_ptr<od_plan> items;
|
|
429
|
-
std::optional<uint64_t> min_items, max_items;
|
|
430
|
-
};
|
|
431
|
-
std::shared_ptr<arr_plan> array;
|
|
432
|
-
|
|
433
|
-
// Enum: primitive-only set membership. Complex (object/array) enums fall back.
|
|
434
|
-
struct enum_constraint {
|
|
435
|
-
std::vector<std::string> strings; // unescaped string values
|
|
436
|
-
std::vector<int64_t> integers;
|
|
437
|
-
std::vector<double> doubles;
|
|
438
|
-
bool has_null = false;
|
|
439
|
-
bool has_true = false;
|
|
440
|
-
bool has_false = false;
|
|
441
|
-
};
|
|
442
|
-
std::shared_ptr<enum_constraint> enum_check;
|
|
443
|
-
|
|
444
|
-
// If false, schema uses unsupported features — must fall back to DOM path.
|
|
445
|
-
bool supported = true;
|
|
446
|
-
};
|
|
447
|
-
|
|
448
|
-
using od_plan_ptr = std::shared_ptr<od_plan>;
|
|
449
|
-
|
|
450
|
-
struct compiled_schema {
|
|
451
|
-
schema_node_ptr root;
|
|
452
|
-
std::unordered_map<std::string, schema_node_ptr> defs;
|
|
453
|
-
std::string raw_schema;
|
|
454
|
-
std::string compile_error; // non-empty if compilation failed
|
|
455
|
-
dom::parser parser; // used only at compile time
|
|
456
|
-
cg::plan gen_plan; // codegen validation plan
|
|
457
|
-
bool use_ondemand = false; // true if codegen plan supports On Demand
|
|
458
|
-
od_plan_ptr od; // On-Demand execution plan
|
|
459
|
-
|
|
460
|
-
// anchor resolution
|
|
461
|
-
std::unordered_map<std::string, schema_node_ptr> anchors;
|
|
462
|
-
std::unordered_map<std::string,
|
|
463
|
-
std::unordered_map<std::string, schema_node_ptr>> resource_dynamic_anchors;
|
|
464
|
-
bool has_dynamic_refs = false;
|
|
465
|
-
std::string current_resource_id; // compile-time only
|
|
466
|
-
|
|
467
|
-
// compile-time warnings (misplaced keywords, etc.)
|
|
468
|
-
std::vector<schema_warning> warnings;
|
|
469
|
-
std::string compile_path; // current JSON pointer during compilation
|
|
470
|
-
};
|
|
471
|
-
|
|
472
|
-
// Thread-local persistent parsers — reused across all validate calls on the
|
|
473
|
-
// same thread. Keeps internal buffers hot in cache and avoids re-allocation.
|
|
474
|
-
static dom::parser& tl_dom_parser() {
|
|
475
|
-
thread_local dom::parser p;
|
|
476
|
-
return p;
|
|
477
|
-
}
|
|
478
|
-
static dom::parser& tl_dom_key_parser() {
|
|
479
|
-
thread_local dom::parser p;
|
|
480
|
-
return p;
|
|
481
|
-
}
|
|
482
|
-
static simdjson::ondemand::parser& tl_od_parser() {
|
|
483
|
-
thread_local simdjson::ondemand::parser p;
|
|
484
|
-
return p;
|
|
485
|
-
};
|
|
486
|
-
|
|
487
|
-
// --- Schema compilation ---
|
|
488
|
-
|
|
489
|
-
static schema_node_ptr compile_node(dom::element el,
|
|
490
|
-
compiled_schema& ctx);
|
|
491
|
-
|
|
492
|
-
static schema_node_ptr compile_node(dom::element el,
|
|
493
|
-
compiled_schema& ctx) {
|
|
494
|
-
auto node = std::make_shared<schema_node>();
|
|
495
|
-
|
|
496
|
-
// Boolean schema
|
|
497
|
-
if (el.is<bool>()) {
|
|
498
|
-
bool bval;
|
|
499
|
-
el.get(bval);
|
|
500
|
-
node->boolean_schema = bval;
|
|
501
|
-
return node;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
if (!el.is<dom::object>()) {
|
|
505
|
-
return node;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
dom::object obj;
|
|
509
|
-
el.get(obj);
|
|
510
|
-
|
|
511
|
-
// $ref
|
|
512
|
-
dom::element ref_el;
|
|
513
|
-
if (obj["$ref"].get(ref_el) == SUCCESS) {
|
|
514
|
-
std::string_view ref_sv;
|
|
515
|
-
if (ref_el.get(ref_sv) == SUCCESS) {
|
|
516
|
-
node->ref = std::string(ref_sv);
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
// $id — must come before $anchor/$dynamicAnchor so current_resource_id is set
|
|
521
|
-
std::string prev_resource = ctx.current_resource_id;
|
|
522
|
-
{
|
|
523
|
-
dom::element id_el;
|
|
524
|
-
if (obj["$id"].get(id_el) == SUCCESS) {
|
|
525
|
-
std::string_view sv;
|
|
526
|
-
if (id_el.get(sv) == SUCCESS) {
|
|
527
|
-
node->id = std::string(sv);
|
|
528
|
-
ctx.current_resource_id = node->id;
|
|
529
|
-
ctx.defs[node->id] = node;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
// $anchor — register in flat anchor map
|
|
535
|
-
{
|
|
536
|
-
dom::element anchor_el;
|
|
537
|
-
if (obj["$anchor"].get(anchor_el) == SUCCESS) {
|
|
538
|
-
std::string_view sv;
|
|
539
|
-
if (anchor_el.get(sv) == SUCCESS) {
|
|
540
|
-
ctx.anchors[std::string(sv)] = node;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
// $dynamicAnchor — register in both flat anchors and per-resource map
|
|
546
|
-
{
|
|
547
|
-
dom::element da_el;
|
|
548
|
-
if (obj["$dynamicAnchor"].get(da_el) == SUCCESS) {
|
|
549
|
-
std::string_view sv;
|
|
550
|
-
if (da_el.get(sv) == SUCCESS) {
|
|
551
|
-
std::string name(sv);
|
|
552
|
-
ctx.anchors[name] = node;
|
|
553
|
-
ctx.resource_dynamic_anchors[ctx.current_resource_id][name] = node;
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// $dynamicRef
|
|
559
|
-
{
|
|
560
|
-
dom::element dr_el;
|
|
561
|
-
if (obj["$dynamicRef"].get(dr_el) == SUCCESS) {
|
|
562
|
-
std::string_view sv;
|
|
563
|
-
if (dr_el.get(sv) == SUCCESS) {
|
|
564
|
-
std::string dr_val(sv);
|
|
565
|
-
// If the $dynamicRef starts with "#" (fragment-only) and we're inside
|
|
566
|
-
// a non-root resource, qualify it with the current resource ID so
|
|
567
|
-
// validation can resolve it correctly.
|
|
568
|
-
if (!dr_val.empty() && dr_val[0] == '#' &&
|
|
569
|
-
!ctx.current_resource_id.empty()) {
|
|
570
|
-
dr_val = ctx.current_resource_id + dr_val;
|
|
571
|
-
}
|
|
572
|
-
node->dynamic_ref = dr_val;
|
|
573
|
-
ctx.has_dynamic_refs = true;
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
// type
|
|
579
|
-
dom::element type_el;
|
|
580
|
-
if (obj["type"].get(type_el) == SUCCESS) {
|
|
581
|
-
if (type_el.is<std::string_view>()) {
|
|
582
|
-
std::string_view sv;
|
|
583
|
-
type_el.get(sv);
|
|
584
|
-
node->type_mask |= json_type_bit(json_type_from_sv(sv));
|
|
585
|
-
} else if (type_el.is<dom::array>()) {
|
|
586
|
-
dom::array type_arr; type_el.get(type_arr); for (auto t : type_arr) {
|
|
587
|
-
std::string_view sv;
|
|
588
|
-
if (t.get(sv) == SUCCESS) {
|
|
589
|
-
node->type_mask |= json_type_bit(json_type_from_sv(sv));
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
// numeric constraints
|
|
596
|
-
dom::element num_el;
|
|
597
|
-
if (obj["minimum"].get(num_el) == SUCCESS) {
|
|
598
|
-
double v;
|
|
599
|
-
if (num_el.get(v) == SUCCESS) node->minimum = v;
|
|
600
|
-
}
|
|
601
|
-
if (obj["maximum"].get(num_el) == SUCCESS) {
|
|
602
|
-
double v;
|
|
603
|
-
if (num_el.get(v) == SUCCESS) node->maximum = v;
|
|
604
|
-
}
|
|
605
|
-
if (obj["exclusiveMinimum"].get(num_el) == SUCCESS) {
|
|
606
|
-
double v;
|
|
607
|
-
if (num_el.get(v) == SUCCESS) node->exclusive_minimum = v;
|
|
608
|
-
}
|
|
609
|
-
if (obj["exclusiveMaximum"].get(num_el) == SUCCESS) {
|
|
610
|
-
double v;
|
|
611
|
-
if (num_el.get(v) == SUCCESS) node->exclusive_maximum = v;
|
|
612
|
-
}
|
|
613
|
-
if (obj["multipleOf"].get(num_el) == SUCCESS) {
|
|
614
|
-
double v;
|
|
615
|
-
if (num_el.get(v) == SUCCESS) node->multiple_of = v;
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
// string constraints
|
|
619
|
-
dom::element str_el;
|
|
620
|
-
if (obj["minLength"].get(str_el) == SUCCESS) {
|
|
621
|
-
uint64_t v;
|
|
622
|
-
if (str_el.get(v) == SUCCESS) node->min_length = v;
|
|
623
|
-
}
|
|
624
|
-
if (obj["maxLength"].get(str_el) == SUCCESS) {
|
|
625
|
-
uint64_t v;
|
|
626
|
-
if (str_el.get(v) == SUCCESS) node->max_length = v;
|
|
627
|
-
}
|
|
628
|
-
if (obj["pattern"].get(str_el) == SUCCESS) {
|
|
629
|
-
std::string_view sv;
|
|
630
|
-
if (str_el.get(sv) == SUCCESS) {
|
|
631
|
-
node->pattern = std::string(sv);
|
|
632
|
-
#ifdef ATA_NO_RE2
|
|
633
|
-
ctx.compile_error = "pattern keyword requires RE2 support (built with ATA_NO_RE2)";
|
|
634
|
-
return node;
|
|
635
|
-
#else
|
|
636
|
-
auto re = std::make_shared<re2::RE2>(node->pattern.value());
|
|
637
|
-
if (re->ok()) {
|
|
638
|
-
node->compiled_pattern = std::move(re);
|
|
639
|
-
}
|
|
640
|
-
#endif
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
// array constraints
|
|
645
|
-
if (obj["minItems"].get(str_el) == SUCCESS) {
|
|
646
|
-
uint64_t v;
|
|
647
|
-
if (str_el.get(v) == SUCCESS) node->min_items = v;
|
|
648
|
-
}
|
|
649
|
-
if (obj["maxItems"].get(str_el) == SUCCESS) {
|
|
650
|
-
uint64_t v;
|
|
651
|
-
if (str_el.get(v) == SUCCESS) node->max_items = v;
|
|
652
|
-
}
|
|
653
|
-
dom::element ui_el;
|
|
654
|
-
if (obj["uniqueItems"].get(ui_el) == SUCCESS) {
|
|
655
|
-
bool v;
|
|
656
|
-
if (ui_el.get(v) == SUCCESS) node->unique_items = v;
|
|
657
|
-
}
|
|
658
|
-
// prefixItems (Draft 2020-12)
|
|
659
|
-
dom::element pi_el;
|
|
660
|
-
if (obj["prefixItems"].get(pi_el) == SUCCESS && pi_el.is<dom::array>()) {
|
|
661
|
-
dom::array pi_arr; pi_el.get(pi_arr); for (auto item : pi_arr) {
|
|
662
|
-
node->prefix_items.push_back(compile_node(item, ctx));
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
dom::element items_el;
|
|
667
|
-
if (obj["items"].get(items_el) == SUCCESS) {
|
|
668
|
-
node->items_schema = compile_node(items_el, ctx);
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
// contains
|
|
672
|
-
dom::element contains_el;
|
|
673
|
-
if (obj["contains"].get(contains_el) == SUCCESS) {
|
|
674
|
-
node->contains_schema = compile_node(contains_el, ctx);
|
|
675
|
-
}
|
|
676
|
-
dom::element mc_el;
|
|
677
|
-
if (obj["minContains"].get(mc_el) == SUCCESS) {
|
|
678
|
-
uint64_t v;
|
|
679
|
-
if (mc_el.get(v) == SUCCESS) node->min_contains = v;
|
|
680
|
-
}
|
|
681
|
-
if (obj["maxContains"].get(mc_el) == SUCCESS) {
|
|
682
|
-
uint64_t v;
|
|
683
|
-
if (mc_el.get(v) == SUCCESS) node->max_contains = v;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
// object constraints
|
|
687
|
-
dom::element props_el;
|
|
688
|
-
if (obj["properties"].get(props_el) == SUCCESS && props_el.is<dom::object>()) {
|
|
689
|
-
dom::object props_obj; props_el.get(props_obj); for (auto [key, val] : props_obj) {
|
|
690
|
-
node->properties[std::string(key)] = compile_node(val, ctx);
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
dom::element req_el;
|
|
695
|
-
if (obj["required"].get(req_el) == SUCCESS && req_el.is<dom::array>()) {
|
|
696
|
-
dom::array req_arr; req_el.get(req_arr); for (auto r : req_arr) {
|
|
697
|
-
std::string_view sv;
|
|
698
|
-
if (r.get(sv) == SUCCESS) {
|
|
699
|
-
node->required.emplace_back(sv);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
dom::element ap_el;
|
|
705
|
-
if (obj["additionalProperties"].get(ap_el) == SUCCESS) {
|
|
706
|
-
if (ap_el.is<bool>()) {
|
|
707
|
-
bool ap_bool; ap_el.get(ap_bool); node->additional_properties_bool = ap_bool;
|
|
708
|
-
} else {
|
|
709
|
-
node->additional_properties_schema = compile_node(ap_el, ctx);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
if (obj["minProperties"].get(str_el) == SUCCESS) {
|
|
714
|
-
uint64_t v;
|
|
715
|
-
if (str_el.get(v) == SUCCESS) node->min_properties = v;
|
|
716
|
-
}
|
|
717
|
-
if (obj["maxProperties"].get(str_el) == SUCCESS) {
|
|
718
|
-
uint64_t v;
|
|
719
|
-
if (str_el.get(v) == SUCCESS) node->max_properties = v;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
// propertyNames
|
|
723
|
-
dom::element pn_el;
|
|
724
|
-
if (obj["propertyNames"].get(pn_el) == SUCCESS) {
|
|
725
|
-
node->property_names_schema = compile_node(pn_el, ctx);
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
// dependentRequired
|
|
729
|
-
dom::element dr_el;
|
|
730
|
-
if (obj["dependentRequired"].get(dr_el) == SUCCESS &&
|
|
731
|
-
dr_el.is<dom::object>()) {
|
|
732
|
-
dom::object dr_obj; dr_el.get(dr_obj); for (auto [key, val] : dr_obj) {
|
|
733
|
-
std::vector<std::string> deps;
|
|
734
|
-
if (val.is<dom::array>()) {
|
|
735
|
-
dom::array val_arr; val.get(val_arr); for (auto d : val_arr) {
|
|
736
|
-
std::string_view sv;
|
|
737
|
-
if (d.get(sv) == SUCCESS) deps.emplace_back(sv);
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
node->dependent_required[std::string(key)] = std::move(deps);
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
// dependentSchemas
|
|
745
|
-
dom::element ds_el;
|
|
746
|
-
if (obj["dependentSchemas"].get(ds_el) == SUCCESS &&
|
|
747
|
-
ds_el.is<dom::object>()) {
|
|
748
|
-
dom::object ds_obj; ds_el.get(ds_obj); for (auto [key, val] : ds_obj) {
|
|
749
|
-
node->dependent_schemas[std::string(key)] = compile_node(val, ctx);
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
// patternProperties — compile regex at schema compile time
|
|
754
|
-
dom::element pp_el;
|
|
755
|
-
if (obj["patternProperties"].get(pp_el) == SUCCESS &&
|
|
756
|
-
pp_el.is<dom::object>()) {
|
|
757
|
-
#ifdef ATA_NO_RE2
|
|
758
|
-
ctx.compile_error = "patternProperties keyword requires RE2 support (built with ATA_NO_RE2)";
|
|
759
|
-
return node;
|
|
760
|
-
#else
|
|
761
|
-
dom::object pp_obj; pp_el.get(pp_obj);
|
|
762
|
-
for (auto [key, val] : pp_obj) {
|
|
763
|
-
schema_node::pattern_prop pp;
|
|
764
|
-
pp.pattern = std::string(key);
|
|
765
|
-
pp.schema = compile_node(val, ctx);
|
|
766
|
-
auto re = std::make_shared<re2::RE2>(pp.pattern);
|
|
767
|
-
if (re->ok()) {
|
|
768
|
-
pp.compiled = std::move(re);
|
|
769
|
-
}
|
|
770
|
-
node->pattern_properties.push_back(std::move(pp));
|
|
771
|
-
}
|
|
772
|
-
#endif
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
// format
|
|
776
|
-
dom::element fmt_el;
|
|
777
|
-
if (obj["format"].get(fmt_el) == SUCCESS) {
|
|
778
|
-
std::string_view sv;
|
|
779
|
-
if (fmt_el.get(sv) == SUCCESS) {
|
|
780
|
-
node->format = std::string(sv);
|
|
781
|
-
node->format_id = format_id_from_string(node->format.value());
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
// enum — pre-minify each value at compile time
|
|
786
|
-
dom::element enum_el;
|
|
787
|
-
if (obj["enum"].get(enum_el) == SUCCESS) {
|
|
788
|
-
if (enum_el.is<dom::array>()) {
|
|
789
|
-
dom::array enum_arr; enum_el.get(enum_arr); for (auto e : enum_arr) {
|
|
790
|
-
node->enum_values_minified.push_back(canonical_json(e));
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
// const
|
|
796
|
-
dom::element const_el;
|
|
797
|
-
if (obj["const"].get(const_el) == SUCCESS) {
|
|
798
|
-
node->const_value_raw = canonical_json(const_el);
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
// composition
|
|
802
|
-
dom::element comp_el;
|
|
803
|
-
if (obj["allOf"].get(comp_el) == SUCCESS && comp_el.is<dom::array>()) {
|
|
804
|
-
dom::array comp_arr; comp_el.get(comp_arr);
|
|
805
|
-
for (auto s : comp_arr) {
|
|
806
|
-
node->all_of.push_back(compile_node(s, ctx));
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
if (obj["anyOf"].get(comp_el) == SUCCESS && comp_el.is<dom::array>()) {
|
|
810
|
-
dom::array comp_arr2; comp_el.get(comp_arr2);
|
|
811
|
-
for (auto s : comp_arr2) {
|
|
812
|
-
node->any_of.push_back(compile_node(s, ctx));
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
if (obj["oneOf"].get(comp_el) == SUCCESS && comp_el.is<dom::array>()) {
|
|
816
|
-
dom::array comp_arr3; comp_el.get(comp_arr3);
|
|
817
|
-
for (auto s : comp_arr3) {
|
|
818
|
-
node->one_of.push_back(compile_node(s, ctx));
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
dom::element not_el;
|
|
822
|
-
if (obj["not"].get(not_el) == SUCCESS) {
|
|
823
|
-
node->not_schema = compile_node(not_el, ctx);
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
// conditional
|
|
827
|
-
dom::element if_el;
|
|
828
|
-
if (obj["if"].get(if_el) == SUCCESS) {
|
|
829
|
-
node->if_schema = compile_node(if_el, ctx);
|
|
830
|
-
}
|
|
831
|
-
dom::element then_el;
|
|
832
|
-
if (obj["then"].get(then_el) == SUCCESS) {
|
|
833
|
-
node->then_schema = compile_node(then_el, ctx);
|
|
834
|
-
}
|
|
835
|
-
dom::element else_el;
|
|
836
|
-
if (obj["else"].get(else_el) == SUCCESS) {
|
|
837
|
-
node->else_schema = compile_node(else_el, ctx);
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
// $defs / definitions
|
|
841
|
-
dom::element defs_el;
|
|
842
|
-
if (obj["$defs"].get(defs_el) == SUCCESS && defs_el.is<dom::object>()) {
|
|
843
|
-
dom::object defs_obj; defs_el.get(defs_obj); for (auto [key, val] : defs_obj) {
|
|
844
|
-
std::string def_path = "#/$defs/" + std::string(key);
|
|
845
|
-
auto compiled = compile_node(val, ctx);
|
|
846
|
-
ctx.defs[def_path] = compiled;
|
|
847
|
-
node->defs[std::string(key)] = compiled;
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
if (obj["definitions"].get(defs_el) == SUCCESS &&
|
|
851
|
-
defs_el.is<dom::object>()) {
|
|
852
|
-
dom::object defs_obj; defs_el.get(defs_obj); for (auto [key, val] : defs_obj) {
|
|
853
|
-
std::string def_path = "#/definitions/" + std::string(key);
|
|
854
|
-
auto compiled = compile_node(val, ctx);
|
|
855
|
-
ctx.defs[def_path] = compiled;
|
|
856
|
-
node->defs[std::string(key)] = compiled;
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
// Warn about keywords used at the wrong type level.
|
|
861
|
-
// Only check when an explicit "type" is declared (type_mask != 0).
|
|
862
|
-
if (node->type_mask != 0) {
|
|
863
|
-
const uint8_t array_bit = json_type_bit(json_type::array);
|
|
864
|
-
const uint8_t string_bit = json_type_bit(json_type::string);
|
|
865
|
-
const uint8_t number_bits = json_type_bit(json_type::number) |
|
|
866
|
-
json_type_bit(json_type::integer);
|
|
867
|
-
const uint8_t object_bit = json_type_bit(json_type::object);
|
|
868
|
-
|
|
869
|
-
auto warn = [&](const char* keyword, const char* expected_type) {
|
|
870
|
-
ctx.warnings.push_back({
|
|
871
|
-
ctx.compile_path,
|
|
872
|
-
std::string(keyword) + " has no effect on type \"" +
|
|
873
|
-
(node->type_mask & json_type_bit(json_type::string) ? "string" :
|
|
874
|
-
node->type_mask & json_type_bit(json_type::boolean) ? "boolean" :
|
|
875
|
-
node->type_mask & json_type_bit(json_type::number) ? "number" :
|
|
876
|
-
node->type_mask & object_bit ? "object" :
|
|
877
|
-
node->type_mask & array_bit ? "array" : "unknown") +
|
|
878
|
-
"\", only applies to " + expected_type
|
|
879
|
-
});
|
|
880
|
-
};
|
|
881
|
-
|
|
882
|
-
// Array keywords on non-array type
|
|
883
|
-
if (!(node->type_mask & array_bit)) {
|
|
884
|
-
if (node->min_items.has_value()) warn("minItems", "array");
|
|
885
|
-
if (node->max_items.has_value()) warn("maxItems", "array");
|
|
886
|
-
if (node->unique_items) warn("uniqueItems", "array");
|
|
887
|
-
if (!node->prefix_items.empty()) warn("prefixItems", "array");
|
|
888
|
-
if (node->items_schema) warn("items", "array");
|
|
889
|
-
if (node->contains_schema) warn("contains", "array");
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
// String keywords on non-string type
|
|
893
|
-
if (!(node->type_mask & string_bit)) {
|
|
894
|
-
if (node->min_length.has_value()) warn("minLength", "string");
|
|
895
|
-
if (node->max_length.has_value()) warn("maxLength", "string");
|
|
896
|
-
if (node->pattern.has_value()) warn("pattern", "string");
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
// Numeric keywords on non-numeric type
|
|
900
|
-
if (!(node->type_mask & number_bits)) {
|
|
901
|
-
if (node->minimum.has_value()) warn("minimum", "number");
|
|
902
|
-
if (node->maximum.has_value()) warn("maximum", "number");
|
|
903
|
-
if (node->exclusive_minimum.has_value()) warn("exclusiveMinimum", "number");
|
|
904
|
-
if (node->exclusive_maximum.has_value()) warn("exclusiveMaximum", "number");
|
|
905
|
-
if (node->multiple_of.has_value()) warn("multipleOf", "number");
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
// Object keywords on non-object type
|
|
909
|
-
if (!(node->type_mask & object_bit)) {
|
|
910
|
-
if (!node->properties.empty()) warn("properties", "object");
|
|
911
|
-
if (!node->required.empty()) warn("required", "object");
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
ctx.current_resource_id = prev_resource;
|
|
916
|
-
return node;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
// --- Validation ---
|
|
920
|
-
|
|
921
|
-
using dynamic_scope_t = std::vector<const std::unordered_map<std::string, schema_node_ptr>*>;
|
|
922
|
-
|
|
923
|
-
// Decode a single JSON Pointer segment (percent-decode, then ~1->/, ~0->~)
|
|
924
|
-
static std::string decode_pointer_segment(const std::string& seg) {
|
|
925
|
-
std::string pct;
|
|
926
|
-
for (size_t i = 0; i < seg.size(); ++i) {
|
|
927
|
-
if (seg[i] == '%' && i + 2 < seg.size()) {
|
|
928
|
-
auto hex = [](char c) -> int {
|
|
929
|
-
if (c >= '0' && c <= '9') return c - '0';
|
|
930
|
-
if (c >= 'a' && c <= 'f') return 10 + c - 'a';
|
|
931
|
-
if (c >= 'A' && c <= 'F') return 10 + c - 'A';
|
|
932
|
-
return -1;
|
|
933
|
-
};
|
|
934
|
-
int hv = hex(seg[i+1]), lv = hex(seg[i+2]);
|
|
935
|
-
if (hv >= 0 && lv >= 0) {
|
|
936
|
-
pct += static_cast<char>(hv * 16 + lv);
|
|
937
|
-
i += 2;
|
|
938
|
-
} else {
|
|
939
|
-
pct += seg[i];
|
|
940
|
-
}
|
|
941
|
-
} else {
|
|
942
|
-
pct += seg[i];
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
std::string out;
|
|
946
|
-
for (size_t i = 0; i < pct.size(); ++i) {
|
|
947
|
-
if (pct[i] == '~' && i + 1 < pct.size()) {
|
|
948
|
-
if (pct[i + 1] == '1') { out += '/'; ++i; }
|
|
949
|
-
else if (pct[i + 1] == '0') { out += '~'; ++i; }
|
|
950
|
-
else out += pct[i];
|
|
951
|
-
} else {
|
|
952
|
-
out += pct[i];
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
return out;
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
// Walk a JSON Pointer (without leading #) within a given schema node.
|
|
959
|
-
// Returns the resolved node, or nullptr if not found.
|
|
960
|
-
static schema_node_ptr walk_json_pointer(const schema_node_ptr& root_node,
|
|
961
|
-
const std::string& pointer) {
|
|
962
|
-
if (pointer.empty()) return root_node;
|
|
963
|
-
|
|
964
|
-
std::vector<std::string> segments;
|
|
965
|
-
size_t spos = 0;
|
|
966
|
-
// pointer starts with "/" — skip leading slash
|
|
967
|
-
if (!pointer.empty() && pointer[0] == '/') spos = 1;
|
|
968
|
-
while (spos <= pointer.size()) {
|
|
969
|
-
size_t snext = pointer.find('/', spos);
|
|
970
|
-
segments.push_back(decode_pointer_segment(
|
|
971
|
-
pointer.substr(spos, snext == std::string::npos ? snext : snext - spos)));
|
|
972
|
-
spos = (snext == std::string::npos) ? pointer.size() + 1 : snext + 1;
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
schema_node_ptr current = root_node;
|
|
976
|
-
for (size_t si = 0; si < segments.size() && current; ++si) {
|
|
977
|
-
const auto& key = segments[si];
|
|
978
|
-
if (key == "properties" && si + 1 < segments.size()) {
|
|
979
|
-
const auto& prop_name = segments[++si];
|
|
980
|
-
auto pit = current->properties.find(prop_name);
|
|
981
|
-
if (pit != current->properties.end()) { current = pit->second; }
|
|
982
|
-
else { return nullptr; }
|
|
983
|
-
} else if (key == "items" && current->items_schema) {
|
|
984
|
-
current = current->items_schema;
|
|
985
|
-
} else if (key == "$defs" || key == "definitions") {
|
|
986
|
-
if (si + 1 < segments.size()) {
|
|
987
|
-
const auto& def_name = segments[++si];
|
|
988
|
-
auto dit = current->defs.find(def_name);
|
|
989
|
-
if (dit != current->defs.end()) { current = dit->second; }
|
|
990
|
-
else { return nullptr; }
|
|
991
|
-
} else { return nullptr; }
|
|
992
|
-
} else if (key == "allOf" || key == "anyOf" || key == "oneOf") {
|
|
993
|
-
if (si + 1 < segments.size()) {
|
|
994
|
-
size_t idx = std::stoul(segments[++si]);
|
|
995
|
-
auto& vec = (key == "allOf") ? current->all_of
|
|
996
|
-
: (key == "anyOf") ? current->any_of
|
|
997
|
-
: current->one_of;
|
|
998
|
-
if (idx < vec.size()) { current = vec[idx]; }
|
|
999
|
-
else { return nullptr; }
|
|
1000
|
-
} else { return nullptr; }
|
|
1001
|
-
} else if (key == "not" && current->not_schema) {
|
|
1002
|
-
current = current->not_schema;
|
|
1003
|
-
} else if (key == "if" && current->if_schema) {
|
|
1004
|
-
current = current->if_schema;
|
|
1005
|
-
} else if (key == "then" && current->then_schema) {
|
|
1006
|
-
current = current->then_schema;
|
|
1007
|
-
} else if (key == "else" && current->else_schema) {
|
|
1008
|
-
current = current->else_schema;
|
|
1009
|
-
} else if (key == "additionalProperties" &&
|
|
1010
|
-
current->additional_properties_schema) {
|
|
1011
|
-
current = current->additional_properties_schema;
|
|
1012
|
-
} else if (key == "prefixItems") {
|
|
1013
|
-
if (si + 1 < segments.size()) {
|
|
1014
|
-
size_t idx = std::stoul(segments[++si]);
|
|
1015
|
-
if (idx < current->prefix_items.size()) { current = current->prefix_items[idx]; }
|
|
1016
|
-
else { return nullptr; }
|
|
1017
|
-
} else { return nullptr; }
|
|
1018
|
-
} else if (key == "contains" && current->contains_schema) {
|
|
1019
|
-
current = current->contains_schema;
|
|
1020
|
-
} else if (key == "propertyNames" && current->property_names_schema) {
|
|
1021
|
-
current = current->property_names_schema;
|
|
1022
|
-
} else {
|
|
1023
|
-
return nullptr;
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
return current;
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
// Find an anchor (non-pointer fragment) within a specific resource node by
|
|
1030
|
-
// searching its sub-tree. Used for resolving "base#anchor" references.
|
|
1031
|
-
static schema_node_ptr find_anchor_in_resource(const compiled_schema& ctx,
|
|
1032
|
-
const std::string& resource_id,
|
|
1033
|
-
const std::string& anchor_name) {
|
|
1034
|
-
// Look up in per-resource dynamic anchors first
|
|
1035
|
-
auto rit = ctx.resource_dynamic_anchors.find(resource_id);
|
|
1036
|
-
if (rit != ctx.resource_dynamic_anchors.end()) {
|
|
1037
|
-
auto ait = rit->second.find(anchor_name);
|
|
1038
|
-
if (ait != rit->second.end()) return ait->second;
|
|
1039
|
-
}
|
|
1040
|
-
// Fallback to flat anchors (which includes $anchor entries)
|
|
1041
|
-
auto ait = ctx.anchors.find(anchor_name);
|
|
1042
|
-
if (ait != ctx.anchors.end()) return ait->second;
|
|
1043
|
-
return nullptr;
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
static void validate_node(const schema_node_ptr& node,
|
|
1047
|
-
dom::element value,
|
|
1048
|
-
const std::string& path,
|
|
1049
|
-
const compiled_schema& ctx,
|
|
1050
|
-
std::vector<validation_error>& errors,
|
|
1051
|
-
bool all_errors = true,
|
|
1052
|
-
dynamic_scope_t* dynamic_scope = nullptr);
|
|
1053
|
-
|
|
1054
|
-
// Fast boolean-only tree walker — no error collection, no string allocation.
|
|
1055
|
-
// Uses [[likely]]/[[unlikely]] hints. Returns true if valid.
|
|
1056
|
-
static bool validate_fast(const schema_node_ptr& node,
|
|
1057
|
-
dom::element value,
|
|
1058
|
-
const compiled_schema& ctx);
|
|
1059
|
-
|
|
1060
|
-
// Macro for early termination
|
|
1061
|
-
#define ATA_CHECK_EARLY() if (!all_errors && !errors.empty()) return
|
|
1062
|
-
|
|
1063
|
-
using et = dom::element_type;
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
// Use string_view to avoid allocations in hot path
|
|
1067
|
-
static std::string_view type_of_sv(dom::element el) {
|
|
1068
|
-
switch (el.type()) {
|
|
1069
|
-
case et::STRING: return "string";
|
|
1070
|
-
case et::INT64:
|
|
1071
|
-
case et::UINT64: return "integer";
|
|
1072
|
-
case et::DOUBLE: return "number";
|
|
1073
|
-
case et::BOOL: return "boolean";
|
|
1074
|
-
case et::NULL_VALUE:return "null";
|
|
1075
|
-
case et::ARRAY: return "array";
|
|
1076
|
-
case et::OBJECT: return "object";
|
|
1077
|
-
}
|
|
1078
|
-
return "unknown";
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
// O(1) type check: test element's type bits against the schema's type_mask.
|
|
1083
|
-
static bool type_matches_mask(dom::element el, uint8_t type_mask) {
|
|
1084
|
-
return (element_type_mask(el.type()) & type_mask) != 0;
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
static double to_double(dom::element el) {
|
|
1088
|
-
switch (el.type()) {
|
|
1089
|
-
case et::DOUBLE: { double v; el.get(v); return v; }
|
|
1090
|
-
case et::INT64: { int64_t v; el.get(v); return static_cast<double>(v); }
|
|
1091
|
-
case et::UINT64: { uint64_t v; el.get(v); return static_cast<double>(v); }
|
|
1092
|
-
default: return 0;
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
// Count UTF-8 codepoints — branchless: count non-continuation bytes
|
|
1097
|
-
static uint64_t utf8_length(std::string_view s) {
|
|
1098
|
-
uint64_t count = 0;
|
|
1099
|
-
for (size_t i = 0; i < s.size(); ++i) {
|
|
1100
|
-
// Continuation bytes are 10xxxxxx (0x80-0xBF)
|
|
1101
|
-
// Non-continuation bytes start codepoints
|
|
1102
|
-
count += ((static_cast<unsigned char>(s[i]) & 0xC0) != 0x80);
|
|
1103
|
-
}
|
|
1104
|
-
return count;
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
// Recursion depth guard — prevents stack overflow on self-referencing schemas
|
|
1108
|
-
struct DepthGuard {
|
|
1109
|
-
static thread_local int depth;
|
|
1110
|
-
bool overflow;
|
|
1111
|
-
DepthGuard() : overflow(++depth > 100) {}
|
|
1112
|
-
~DepthGuard() { --depth; }
|
|
1113
|
-
};
|
|
1114
|
-
thread_local int DepthGuard::depth = 0;
|
|
1115
|
-
|
|
1116
|
-
static void validate_node(const schema_node_ptr& node,
|
|
1117
|
-
dom::element value,
|
|
1118
|
-
const std::string& path,
|
|
1119
|
-
const compiled_schema& ctx,
|
|
1120
|
-
std::vector<validation_error>& errors,
|
|
1121
|
-
bool all_errors,
|
|
1122
|
-
dynamic_scope_t* dynamic_scope) {
|
|
1123
|
-
if (!node) return;
|
|
1124
|
-
|
|
1125
|
-
DepthGuard guard;
|
|
1126
|
-
if (guard.overflow) return;
|
|
1127
|
-
|
|
1128
|
-
// Boolean schema
|
|
1129
|
-
if (node->boolean_schema.has_value()) {
|
|
1130
|
-
if (!node->boolean_schema.value()) {
|
|
1131
|
-
errors.push_back({error_code::type_mismatch, path,
|
|
1132
|
-
"schema is false, no value is valid"});
|
|
1133
|
-
}
|
|
1134
|
-
return;
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
// Dynamic scope tracking: push this resource's dynamic anchors
|
|
1138
|
-
bool pushed_scope = false;
|
|
1139
|
-
if (dynamic_scope && !node->id.empty()) {
|
|
1140
|
-
auto it = ctx.resource_dynamic_anchors.find(node->id);
|
|
1141
|
-
if (it != ctx.resource_dynamic_anchors.end()) {
|
|
1142
|
-
dynamic_scope->push_back(&it->second);
|
|
1143
|
-
pushed_scope = true;
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
// $ref — Draft 2020-12: $ref is not a short-circuit, sibling keywords still apply
|
|
1148
|
-
bool ref_resolved = false;
|
|
1149
|
-
if (!node->ref.empty()) {
|
|
1150
|
-
// Self-reference: "#"
|
|
1151
|
-
if (node->ref == "#" && ctx.root) {
|
|
1152
|
-
validate_node(ctx.root, value, path, ctx, errors, all_errors, dynamic_scope);
|
|
1153
|
-
ref_resolved = true;
|
|
1154
|
-
}
|
|
1155
|
-
// Check for "base#fragment" pattern (e.g. "first#/$defs/stuff", "tree.json")
|
|
1156
|
-
if (!ref_resolved) {
|
|
1157
|
-
std::string base_uri;
|
|
1158
|
-
std::string fragment;
|
|
1159
|
-
size_t hash_pos = node->ref.find('#');
|
|
1160
|
-
if (hash_pos != std::string::npos) {
|
|
1161
|
-
base_uri = node->ref.substr(0, hash_pos);
|
|
1162
|
-
fragment = node->ref.substr(hash_pos + 1);
|
|
1163
|
-
} else {
|
|
1164
|
-
base_uri = node->ref;
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
// Helper: push base resource's dynamic anchors to scope, validate, pop
|
|
1168
|
-
auto validate_with_resource_scope = [&](const schema_node_ptr& target,
|
|
1169
|
-
const std::string& resource_id) {
|
|
1170
|
-
bool scope_pushed = false;
|
|
1171
|
-
if (dynamic_scope && !resource_id.empty()) {
|
|
1172
|
-
auto rit = ctx.resource_dynamic_anchors.find(resource_id);
|
|
1173
|
-
if (rit != ctx.resource_dynamic_anchors.end()) {
|
|
1174
|
-
dynamic_scope->push_back(&rit->second);
|
|
1175
|
-
scope_pushed = true;
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
validate_node(target, value, path, ctx, errors, all_errors, dynamic_scope);
|
|
1179
|
-
if (scope_pushed) dynamic_scope->pop_back();
|
|
1180
|
-
};
|
|
1181
|
-
|
|
1182
|
-
if (!base_uri.empty()) {
|
|
1183
|
-
// Resolve base URI to a resource via defs
|
|
1184
|
-
auto it = ctx.defs.find(base_uri);
|
|
1185
|
-
if (it != ctx.defs.end()) {
|
|
1186
|
-
schema_node_ptr target = it->second;
|
|
1187
|
-
if (!fragment.empty()) {
|
|
1188
|
-
if (fragment[0] == '/') {
|
|
1189
|
-
// JSON Pointer within the resource
|
|
1190
|
-
auto resolved = walk_json_pointer(target, fragment);
|
|
1191
|
-
if (resolved) {
|
|
1192
|
-
validate_with_resource_scope(resolved, base_uri);
|
|
1193
|
-
ref_resolved = true;
|
|
1194
|
-
}
|
|
1195
|
-
} else {
|
|
1196
|
-
// Anchor lookup within the resource
|
|
1197
|
-
auto resolved = find_anchor_in_resource(ctx, base_uri, fragment);
|
|
1198
|
-
if (resolved) {
|
|
1199
|
-
validate_with_resource_scope(resolved, base_uri);
|
|
1200
|
-
ref_resolved = true;
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
} else {
|
|
1204
|
-
// No fragment, just the base resource (it pushes its own scope)
|
|
1205
|
-
validate_node(target, value, path, ctx, errors, all_errors, dynamic_scope);
|
|
1206
|
-
ref_resolved = true;
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
} else if (!fragment.empty()) {
|
|
1210
|
-
// "#fragment" — no base URI
|
|
1211
|
-
if (fragment[0] == '/') {
|
|
1212
|
-
// JSON Pointer from root
|
|
1213
|
-
auto resolved = walk_json_pointer(ctx.root, fragment);
|
|
1214
|
-
if (resolved) {
|
|
1215
|
-
validate_node(resolved, value, path, ctx, errors, all_errors, dynamic_scope);
|
|
1216
|
-
ref_resolved = true;
|
|
1217
|
-
}
|
|
1218
|
-
} else {
|
|
1219
|
-
// Anchor lookup
|
|
1220
|
-
auto ait = ctx.anchors.find(fragment);
|
|
1221
|
-
if (ait != ctx.anchors.end()) {
|
|
1222
|
-
validate_node(ait->second, value, path, ctx, errors, all_errors, dynamic_scope);
|
|
1223
|
-
ref_resolved = true;
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
// Fallback: try defs map directly (handles bare $id references like "list")
|
|
1229
|
-
if (!ref_resolved) {
|
|
1230
|
-
auto it = ctx.defs.find(node->ref);
|
|
1231
|
-
if (it != ctx.defs.end()) {
|
|
1232
|
-
validate_node(it->second, value, path, ctx, errors, all_errors, dynamic_scope);
|
|
1233
|
-
ref_resolved = true;
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
// Fallback: relative URI resolution — match ref against defs keys by suffix
|
|
1237
|
-
if (!ref_resolved && !node->ref.empty() && node->ref[0] != '#') {
|
|
1238
|
-
std::string suffix = "/" + node->ref;
|
|
1239
|
-
for (const auto& [key, def_node] : ctx.defs) {
|
|
1240
|
-
if (key.size() >= suffix.size() &&
|
|
1241
|
-
key.compare(key.size() - suffix.size(), suffix.size(), suffix) == 0) {
|
|
1242
|
-
validate_node(def_node, value, path, ctx, errors, all_errors, dynamic_scope);
|
|
1243
|
-
ref_resolved = true;
|
|
1244
|
-
break;
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
if (!ref_resolved) {
|
|
1249
|
-
errors.push_back({error_code::ref_not_found, path,
|
|
1250
|
-
"cannot resolve $ref: " + node->ref});
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
// $dynamicRef — Draft 2020-12 dynamic scope resolution
|
|
1255
|
-
if (!node->dynamic_ref.empty()) {
|
|
1256
|
-
bool dref_resolved = false;
|
|
1257
|
-
|
|
1258
|
-
// Parse the $dynamicRef value into base URI and fragment
|
|
1259
|
-
std::string dr_base;
|
|
1260
|
-
std::string dr_fragment;
|
|
1261
|
-
{
|
|
1262
|
-
size_t hash_pos = node->dynamic_ref.find('#');
|
|
1263
|
-
if (hash_pos != std::string::npos) {
|
|
1264
|
-
dr_base = node->dynamic_ref.substr(0, hash_pos);
|
|
1265
|
-
dr_fragment = node->dynamic_ref.substr(hash_pos + 1);
|
|
1266
|
-
} else {
|
|
1267
|
-
dr_base = node->dynamic_ref;
|
|
1268
|
-
}
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
// Helper: push base resource's dynamic anchors to scope temporarily
|
|
1272
|
-
auto push_resource_scope = [&](const std::string& resource_id) -> bool {
|
|
1273
|
-
if (dynamic_scope && !resource_id.empty()) {
|
|
1274
|
-
auto rit = ctx.resource_dynamic_anchors.find(resource_id);
|
|
1275
|
-
if (rit != ctx.resource_dynamic_anchors.end()) {
|
|
1276
|
-
dynamic_scope->push_back(&rit->second);
|
|
1277
|
-
return true;
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
|
-
return false;
|
|
1281
|
-
};
|
|
1282
|
-
|
|
1283
|
-
// If fragment is a JSON pointer (starts with /), resolve like $ref
|
|
1284
|
-
if (!dr_fragment.empty() && dr_fragment[0] == '/') {
|
|
1285
|
-
schema_node_ptr base_node = dr_base.empty() ? ctx.root : nullptr;
|
|
1286
|
-
if (!dr_base.empty()) {
|
|
1287
|
-
auto it = ctx.defs.find(dr_base);
|
|
1288
|
-
if (it != ctx.defs.end()) base_node = it->second;
|
|
1289
|
-
}
|
|
1290
|
-
if (base_node) {
|
|
1291
|
-
auto resolved = walk_json_pointer(base_node, dr_fragment);
|
|
1292
|
-
if (resolved) {
|
|
1293
|
-
bool dr_scope_pushed = push_resource_scope(dr_base);
|
|
1294
|
-
validate_node(resolved, value, path, ctx, errors, all_errors, dynamic_scope);
|
|
1295
|
-
if (dr_scope_pushed) dynamic_scope->pop_back();
|
|
1296
|
-
dref_resolved = true;
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
// If fragment is an anchor name (not a JSON pointer)
|
|
1302
|
-
if (!dref_resolved && !dr_fragment.empty() && dr_fragment[0] != '/') {
|
|
1303
|
-
std::string anchor_name = dr_fragment;
|
|
1304
|
-
|
|
1305
|
-
// Initial resolution: find the anchor
|
|
1306
|
-
schema_node_ptr target = nullptr;
|
|
1307
|
-
|
|
1308
|
-
if (!dr_base.empty()) {
|
|
1309
|
-
// Resolve base URI first, then find anchor in that resource
|
|
1310
|
-
auto it = ctx.defs.find(dr_base);
|
|
1311
|
-
if (it != ctx.defs.end()) {
|
|
1312
|
-
target = find_anchor_in_resource(ctx, dr_base, anchor_name);
|
|
1313
|
-
}
|
|
1314
|
-
} else {
|
|
1315
|
-
// No base URI — look up in flat anchors map
|
|
1316
|
-
auto ait = ctx.anchors.find(anchor_name);
|
|
1317
|
-
if (ait != ctx.anchors.end()) {
|
|
1318
|
-
target = ait->second;
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
if (target) {
|
|
1323
|
-
// Check if the initially resolved target is itself a $dynamicAnchor
|
|
1324
|
-
// (the "bookend" requirement). Only do dynamic scope walk if the
|
|
1325
|
-
// initial target's resource has a $dynamicAnchor with this name.
|
|
1326
|
-
bool is_dynamic_at_initial = false;
|
|
1327
|
-
if (!dr_base.empty()) {
|
|
1328
|
-
// We resolved via a specific base URI
|
|
1329
|
-
auto rit = ctx.resource_dynamic_anchors.find(dr_base);
|
|
1330
|
-
if (rit != ctx.resource_dynamic_anchors.end() &&
|
|
1331
|
-
rit->second.count(anchor_name)) {
|
|
1332
|
-
is_dynamic_at_initial = true;
|
|
1333
|
-
}
|
|
1334
|
-
} else {
|
|
1335
|
-
// No base URI — check if ANY resource has this as $dynamicAnchor
|
|
1336
|
-
// and the target matches (i.e., the initially resolved node IS a
|
|
1337
|
-
// $dynamicAnchor node)
|
|
1338
|
-
for (const auto& [rid, rmap] : ctx.resource_dynamic_anchors) {
|
|
1339
|
-
auto ait2 = rmap.find(anchor_name);
|
|
1340
|
-
if (ait2 != rmap.end() && ait2->second == target) {
|
|
1341
|
-
is_dynamic_at_initial = true;
|
|
1342
|
-
break;
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
// Dynamic scope walk: find first override in dynamic scope
|
|
1348
|
-
if (is_dynamic_at_initial && dynamic_scope) {
|
|
1349
|
-
for (size_t i = 0; i < dynamic_scope->size(); ++i) {
|
|
1350
|
-
auto dit = (*dynamic_scope)[i]->find(anchor_name);
|
|
1351
|
-
if (dit != (*dynamic_scope)[i]->end()) {
|
|
1352
|
-
target = dit->second;
|
|
1353
|
-
break;
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
|
-
bool dr_scope_pushed = push_resource_scope(dr_base);
|
|
1359
|
-
validate_node(target, value, path, ctx, errors, all_errors, dynamic_scope);
|
|
1360
|
-
if (dr_scope_pushed) dynamic_scope->pop_back();
|
|
1361
|
-
dref_resolved = true;
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
// Bare $dynamicRef without fragment (unusual, but handle it)
|
|
1366
|
-
if (!dref_resolved && dr_fragment.empty() && !dr_base.empty()) {
|
|
1367
|
-
auto it = ctx.defs.find(dr_base);
|
|
1368
|
-
if (it != ctx.defs.end()) {
|
|
1369
|
-
validate_node(it->second, value, path, ctx, errors, all_errors, dynamic_scope);
|
|
1370
|
-
dref_resolved = true;
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
if (!dref_resolved) {
|
|
1375
|
-
errors.push_back({error_code::ref_not_found, path,
|
|
1376
|
-
"cannot resolve $dynamicRef: " + node->dynamic_ref});
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
// type
|
|
1381
|
-
if (node->type_mask) {
|
|
1382
|
-
if (!type_matches_mask(value, node->type_mask)) {
|
|
1383
|
-
std::string expected;
|
|
1384
|
-
for (int b = 0; b < 7; ++b) {
|
|
1385
|
-
if (node->type_mask & (1u << b)) {
|
|
1386
|
-
if (!expected.empty()) expected += ", ";
|
|
1387
|
-
expected += json_type_name(static_cast<json_type>(b));
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
std::string actual = std::string(type_of_sv(value));
|
|
1391
|
-
errors.push_back({error_code::type_mismatch, path,
|
|
1392
|
-
"expected type " + expected + ", got " + actual,
|
|
1393
|
-
expected, actual});
|
|
1394
|
-
ATA_CHECK_EARLY();
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
// enum — use pre-minified values (no re-parsing)
|
|
1399
|
-
if (!node->enum_values_minified.empty()) {
|
|
1400
|
-
std::string val_str = canonical_json(value);
|
|
1401
|
-
bool found = false;
|
|
1402
|
-
for (const auto& ev : node->enum_values_minified) {
|
|
1403
|
-
if (ev == val_str) {
|
|
1404
|
-
found = true;
|
|
1405
|
-
break;
|
|
1406
|
-
}
|
|
1407
|
-
}
|
|
1408
|
-
if (!found) {
|
|
1409
|
-
errors.push_back({error_code::enum_mismatch, path,
|
|
1410
|
-
"value not in enum"});
|
|
1411
|
-
}
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1414
|
-
// const
|
|
1415
|
-
if (node->const_value_raw.has_value()) {
|
|
1416
|
-
std::string val_str = canonical_json(value);
|
|
1417
|
-
if (val_str != node->const_value_raw.value()) {
|
|
1418
|
-
errors.push_back({error_code::const_mismatch, path,
|
|
1419
|
-
"value does not match const"});
|
|
1420
|
-
ATA_CHECK_EARLY();
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
ATA_CHECK_EARLY();
|
|
1425
|
-
// Numeric validations
|
|
1426
|
-
auto vtype = value.type();
|
|
1427
|
-
if (vtype == et::INT64 || vtype == et::UINT64 || vtype == et::DOUBLE) {
|
|
1428
|
-
double v = to_double(value);
|
|
1429
|
-
if (node->minimum.has_value() && v < node->minimum.value()) {
|
|
1430
|
-
errors.push_back({error_code::minimum_violation, path,
|
|
1431
|
-
"value " + std::to_string(v) + " < minimum " +
|
|
1432
|
-
std::to_string(node->minimum.value())});
|
|
1433
|
-
}
|
|
1434
|
-
if (node->maximum.has_value() && v > node->maximum.value()) {
|
|
1435
|
-
errors.push_back({error_code::maximum_violation, path,
|
|
1436
|
-
"value " + std::to_string(v) + " > maximum " +
|
|
1437
|
-
std::to_string(node->maximum.value())});
|
|
1438
|
-
}
|
|
1439
|
-
if (node->exclusive_minimum.has_value() &&
|
|
1440
|
-
v <= node->exclusive_minimum.value()) {
|
|
1441
|
-
errors.push_back({error_code::exclusive_minimum_violation, path,
|
|
1442
|
-
"value must be > " +
|
|
1443
|
-
std::to_string(node->exclusive_minimum.value())});
|
|
1444
|
-
}
|
|
1445
|
-
if (node->exclusive_maximum.has_value() &&
|
|
1446
|
-
v >= node->exclusive_maximum.value()) {
|
|
1447
|
-
errors.push_back({error_code::exclusive_maximum_violation, path,
|
|
1448
|
-
"value must be < " +
|
|
1449
|
-
std::to_string(node->exclusive_maximum.value())});
|
|
1450
|
-
}
|
|
1451
|
-
if (node->multiple_of.has_value()) {
|
|
1452
|
-
double divisor = node->multiple_of.value();
|
|
1453
|
-
double rem = std::fmod(v, divisor);
|
|
1454
|
-
// Use relative tolerance for floating point comparison
|
|
1455
|
-
if (std::abs(rem) > 1e-8 && std::abs(rem - divisor) > 1e-8) {
|
|
1456
|
-
errors.push_back({error_code::multiple_of_violation, path,
|
|
1457
|
-
"value not a multiple of " +
|
|
1458
|
-
std::to_string(node->multiple_of.value())});
|
|
1459
|
-
}
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
// String validations
|
|
1464
|
-
if (vtype == et::STRING) {
|
|
1465
|
-
std::string_view sv;
|
|
1466
|
-
value.get(sv);
|
|
1467
|
-
uint64_t len = utf8_length(sv);
|
|
1468
|
-
|
|
1469
|
-
if (node->min_length.has_value() && len < node->min_length.value()) {
|
|
1470
|
-
errors.push_back({error_code::min_length_violation, path,
|
|
1471
|
-
"string length " + std::to_string(len) +
|
|
1472
|
-
" < minLength " +
|
|
1473
|
-
std::to_string(node->min_length.value())});
|
|
1474
|
-
}
|
|
1475
|
-
if (node->max_length.has_value() && len > node->max_length.value()) {
|
|
1476
|
-
errors.push_back({error_code::max_length_violation, path,
|
|
1477
|
-
"string length " + std::to_string(len) +
|
|
1478
|
-
" > maxLength " +
|
|
1479
|
-
std::to_string(node->max_length.value())});
|
|
1480
|
-
}
|
|
1481
|
-
#ifndef ATA_NO_RE2
|
|
1482
|
-
if (node->compiled_pattern) {
|
|
1483
|
-
if (!re2::RE2::PartialMatch(re2::StringPiece(sv.data(), sv.size()), *node->compiled_pattern)) {
|
|
1484
|
-
errors.push_back({error_code::pattern_mismatch, path,
|
|
1485
|
-
"string does not match pattern: " +
|
|
1486
|
-
node->pattern.value()});
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
#endif
|
|
1490
|
-
|
|
1491
|
-
if (node->format.has_value()) {
|
|
1492
|
-
if (!check_format_by_id(sv, node->format_id)) {
|
|
1493
|
-
errors.push_back({error_code::format_mismatch, path,
|
|
1494
|
-
"string does not match format: " +
|
|
1495
|
-
node->format.value()});
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
// Array validations
|
|
1501
|
-
if (vtype == et::ARRAY) {
|
|
1502
|
-
dom::array arr; value.get(arr);
|
|
1503
|
-
uint64_t arr_size = arr.size();
|
|
1504
|
-
if(arr_size == 0xFFFFFF) [[unlikely]] {
|
|
1505
|
-
// Fallback for large arrays where size() saturates — count manually to avoid overflow
|
|
1506
|
-
arr_size = 0;
|
|
1507
|
-
for ([[maybe_unused]] auto _ : arr) ++arr_size;
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
if (node->min_items.has_value() && arr_size < node->min_items.value()) {
|
|
1511
|
-
errors.push_back({error_code::min_items_violation, path,
|
|
1512
|
-
"array has " + std::to_string(arr_size) +
|
|
1513
|
-
" items, minimum " +
|
|
1514
|
-
std::to_string(node->min_items.value())});
|
|
1515
|
-
}
|
|
1516
|
-
if (node->max_items.has_value() && arr_size > node->max_items.value()) {
|
|
1517
|
-
errors.push_back({error_code::max_items_violation, path,
|
|
1518
|
-
"array has " + std::to_string(arr_size) +
|
|
1519
|
-
" items, maximum " +
|
|
1520
|
-
std::to_string(node->max_items.value())});
|
|
1521
|
-
}
|
|
1522
|
-
|
|
1523
|
-
if (node->unique_items) {
|
|
1524
|
-
bool has_dup = false;
|
|
1525
|
-
// Fast path: check if all items are the same simple type
|
|
1526
|
-
auto first_it = arr.begin();
|
|
1527
|
-
if (first_it != arr.end()) {
|
|
1528
|
-
auto first_type = (*first_it).type();
|
|
1529
|
-
bool all_same = true;
|
|
1530
|
-
for (auto item : arr) { if (item.type() != first_type) { all_same = false; break; } }
|
|
1531
|
-
if (all_same && first_type == et::STRING) {
|
|
1532
|
-
std::set<std::string_view> seen;
|
|
1533
|
-
for (auto item : arr) {
|
|
1534
|
-
std::string_view sv; item.get(sv);
|
|
1535
|
-
if (!seen.insert(sv).second) { has_dup = true; break; }
|
|
1536
|
-
}
|
|
1537
|
-
} else if (all_same && (first_type == et::INT64 || first_type == et::UINT64 || first_type == et::DOUBLE)) {
|
|
1538
|
-
std::set<double> seen;
|
|
1539
|
-
for (auto item : arr) {
|
|
1540
|
-
if (!seen.insert(to_double(item)).second) { has_dup = true; break; }
|
|
1541
|
-
}
|
|
1542
|
-
} else {
|
|
1543
|
-
std::set<std::string> seen;
|
|
1544
|
-
for (auto item : arr) {
|
|
1545
|
-
if (!seen.insert(canonical_json(item)).second) { has_dup = true; break; }
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
if (has_dup) {
|
|
1550
|
-
errors.push_back({error_code::unique_items_violation, path,
|
|
1551
|
-
"array contains duplicate items"});
|
|
1552
|
-
}
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
// prefixItems + items (Draft 2020-12 semantics)
|
|
1556
|
-
{
|
|
1557
|
-
uint64_t idx = 0;
|
|
1558
|
-
for (auto item : arr) {
|
|
1559
|
-
if (idx < node->prefix_items.size()) {
|
|
1560
|
-
validate_node(node->prefix_items[idx], item,
|
|
1561
|
-
path + "/" + std::to_string(idx), ctx, errors, all_errors, dynamic_scope);
|
|
1562
|
-
} else if (node->items_schema) {
|
|
1563
|
-
validate_node(node->items_schema, item,
|
|
1564
|
-
path + "/" + std::to_string(idx), ctx, errors, all_errors, dynamic_scope);
|
|
1565
|
-
}
|
|
1566
|
-
++idx;
|
|
1567
|
-
}
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
// contains / minContains / maxContains
|
|
1571
|
-
if (node->contains_schema) {
|
|
1572
|
-
uint64_t match_count = 0;
|
|
1573
|
-
for (auto item : arr) {
|
|
1574
|
-
if (validate_fast(node->contains_schema, item, ctx)) ++match_count;
|
|
1575
|
-
}
|
|
1576
|
-
uint64_t min_c = node->min_contains.value_or(1);
|
|
1577
|
-
uint64_t max_c = node->max_contains.value_or(arr_size);
|
|
1578
|
-
if (match_count < min_c) {
|
|
1579
|
-
errors.push_back({error_code::min_items_violation, path,
|
|
1580
|
-
"contains: " + std::to_string(match_count) +
|
|
1581
|
-
" matches, minimum " + std::to_string(min_c)});
|
|
1582
|
-
}
|
|
1583
|
-
if (match_count > max_c) {
|
|
1584
|
-
errors.push_back({error_code::max_items_violation, path,
|
|
1585
|
-
"contains: " + std::to_string(match_count) +
|
|
1586
|
-
" matches, maximum " + std::to_string(max_c)});
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
|
-
}
|
|
1590
|
-
|
|
1591
|
-
// Object validations
|
|
1592
|
-
if (vtype == et::OBJECT) {
|
|
1593
|
-
dom::object obj; value.get(obj);
|
|
1594
|
-
|
|
1595
|
-
if (node->min_properties.has_value() || node->max_properties.has_value()) {
|
|
1596
|
-
uint64_t prop_count = 0;
|
|
1597
|
-
for ([[maybe_unused]] auto _ : obj) ++prop_count;
|
|
1598
|
-
if (node->min_properties.has_value() &&
|
|
1599
|
-
prop_count < node->min_properties.value()) {
|
|
1600
|
-
errors.push_back({error_code::min_properties_violation, path,
|
|
1601
|
-
"object has " + std::to_string(prop_count) +
|
|
1602
|
-
" properties, minimum " +
|
|
1603
|
-
std::to_string(node->min_properties.value())});
|
|
1604
|
-
}
|
|
1605
|
-
if (node->max_properties.has_value() &&
|
|
1606
|
-
prop_count > node->max_properties.value()) {
|
|
1607
|
-
errors.push_back({error_code::max_properties_violation, path,
|
|
1608
|
-
"object has " + std::to_string(prop_count) +
|
|
1609
|
-
" properties, maximum " +
|
|
1610
|
-
std::to_string(node->max_properties.value())});
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
// required
|
|
1615
|
-
for (const auto& req : node->required) {
|
|
1616
|
-
dom::element dummy;
|
|
1617
|
-
if (obj[req].get(dummy) != SUCCESS) {
|
|
1618
|
-
errors.push_back({error_code::required_property_missing, path,
|
|
1619
|
-
"missing required property: " + req});
|
|
1620
|
-
}
|
|
1621
|
-
}
|
|
1622
|
-
|
|
1623
|
-
// properties + patternProperties + additionalProperties
|
|
1624
|
-
for (auto [key, val] : obj) {
|
|
1625
|
-
std::string key_str(key);
|
|
1626
|
-
bool matched = false;
|
|
1627
|
-
|
|
1628
|
-
// Check properties
|
|
1629
|
-
auto it = node->properties.find(key_str);
|
|
1630
|
-
if (it != node->properties.end()) {
|
|
1631
|
-
validate_node(it->second, val, path + "/" + key_str, ctx, errors, all_errors, dynamic_scope);
|
|
1632
|
-
matched = true;
|
|
1633
|
-
}
|
|
1634
|
-
|
|
1635
|
-
// Check patternProperties (use cached compiled regex)
|
|
1636
|
-
for (const auto& pp : node->pattern_properties) {
|
|
1637
|
-
#ifndef ATA_NO_RE2
|
|
1638
|
-
if (pp.compiled && re2::RE2::PartialMatch(key_str, *pp.compiled)) {
|
|
1639
|
-
validate_node(pp.schema, val, path + "/" + key_str, ctx, errors, all_errors, dynamic_scope);
|
|
1640
|
-
matched = true;
|
|
1641
|
-
}
|
|
1642
|
-
#endif
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
// additionalProperties (only if not matched by properties or patternProperties)
|
|
1646
|
-
if (!matched) {
|
|
1647
|
-
if (node->additional_properties_bool.has_value() &&
|
|
1648
|
-
!node->additional_properties_bool.value()) {
|
|
1649
|
-
errors.push_back(
|
|
1650
|
-
{error_code::additional_property_not_allowed, path,
|
|
1651
|
-
"additional property not allowed: " + key_str});
|
|
1652
|
-
} else if (node->additional_properties_schema) {
|
|
1653
|
-
validate_node(node->additional_properties_schema, val,
|
|
1654
|
-
path + "/" + key_str, ctx, errors, all_errors, dynamic_scope);
|
|
1655
|
-
}
|
|
1656
|
-
}
|
|
1657
|
-
}
|
|
1658
|
-
// propertyNames — validate key as string directly when possible
|
|
1659
|
-
if (node->property_names_schema) {
|
|
1660
|
-
auto pn = node->property_names_schema;
|
|
1661
|
-
bool string_only = pn->ref.empty() && pn->all_of.empty() &&
|
|
1662
|
-
pn->any_of.empty() && pn->one_of.empty() && !pn->not_schema &&
|
|
1663
|
-
!pn->if_schema && pn->enum_values_minified.empty() &&
|
|
1664
|
-
!pn->const_value_raw.has_value();
|
|
1665
|
-
if (string_only) {
|
|
1666
|
-
// Fast path: validate string constraints on key directly
|
|
1667
|
-
for (auto [key, val] : obj) {
|
|
1668
|
-
std::string_view key_sv(key);
|
|
1669
|
-
if (pn->type_mask && !(pn->type_mask & json_type_bit(json_type::string))) {
|
|
1670
|
-
errors.push_back({error_code::type_mismatch, path,
|
|
1671
|
-
"propertyNames: key is string but schema requires different type"});
|
|
1672
|
-
continue;
|
|
1673
|
-
}
|
|
1674
|
-
uint64_t len = utf8_length(key_sv);
|
|
1675
|
-
if (pn->min_length.has_value() && len < pn->min_length.value()) {
|
|
1676
|
-
errors.push_back({error_code::min_length_violation, path,
|
|
1677
|
-
"propertyNames: key too short: " + std::string(key_sv)});
|
|
1678
|
-
}
|
|
1679
|
-
if (pn->max_length.has_value() && len > pn->max_length.value()) {
|
|
1680
|
-
errors.push_back({error_code::max_length_violation, path,
|
|
1681
|
-
"propertyNames: key too long: " + std::string(key_sv)});
|
|
1682
|
-
}
|
|
1683
|
-
#ifndef ATA_NO_RE2
|
|
1684
|
-
if (pn->compiled_pattern) {
|
|
1685
|
-
if (!re2::RE2::PartialMatch(re2::StringPiece(key_sv.data(), key_sv.size()), *pn->compiled_pattern)) {
|
|
1686
|
-
errors.push_back({error_code::pattern_mismatch, path,
|
|
1687
|
-
"propertyNames: key does not match pattern: " + std::string(key_sv)});
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
#endif
|
|
1691
|
-
if (pn->format.has_value() && !check_format_by_id(key_sv, pn->format_id)) {
|
|
1692
|
-
errors.push_back({error_code::format_mismatch, path,
|
|
1693
|
-
"propertyNames: key does not match format: " + std::string(key_sv)});
|
|
1694
|
-
}
|
|
1695
|
-
}
|
|
1696
|
-
} else {
|
|
1697
|
-
// Fallback: parse key as JSON string element
|
|
1698
|
-
for (auto [key, val] : obj) {
|
|
1699
|
-
std::string key_json = "\"" + std::string(key) + "\"";
|
|
1700
|
-
auto key_result = tl_dom_key_parser().parse(key_json);
|
|
1701
|
-
if (!key_result.error()) {
|
|
1702
|
-
validate_node(pn, key_result.value_unsafe(), path, ctx, errors, all_errors, dynamic_scope);
|
|
1703
|
-
}
|
|
1704
|
-
}
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
|
-
// dependentRequired
|
|
1709
|
-
for (const auto& [prop, deps] : node->dependent_required) {
|
|
1710
|
-
dom::element dummy;
|
|
1711
|
-
if (obj[prop].get(dummy) == SUCCESS) {
|
|
1712
|
-
for (const auto& dep : deps) {
|
|
1713
|
-
dom::element dep_dummy;
|
|
1714
|
-
if (obj[dep].get(dep_dummy) != SUCCESS) {
|
|
1715
|
-
errors.push_back({error_code::required_property_missing, path,
|
|
1716
|
-
"property '" + prop + "' requires '" + dep +
|
|
1717
|
-
"' to be present"});
|
|
1718
|
-
}
|
|
1719
|
-
}
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
|
-
// dependentSchemas
|
|
1724
|
-
for (const auto& [prop, schema] : node->dependent_schemas) {
|
|
1725
|
-
dom::element dummy;
|
|
1726
|
-
if (obj[prop].get(dummy) == SUCCESS) {
|
|
1727
|
-
validate_node(schema, value, path, ctx, errors, all_errors, dynamic_scope);
|
|
1728
|
-
}
|
|
1729
|
-
}
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
// allOf
|
|
1733
|
-
if (!node->all_of.empty()) {
|
|
1734
|
-
for (const auto& sub : node->all_of) {
|
|
1735
|
-
std::vector<validation_error> sub_errors;
|
|
1736
|
-
validate_node(sub, value, path, ctx, sub_errors, all_errors, dynamic_scope);
|
|
1737
|
-
if (!sub_errors.empty()) {
|
|
1738
|
-
errors.push_back({error_code::all_of_failed, path,
|
|
1739
|
-
"allOf subschema failed"});
|
|
1740
|
-
errors.insert(errors.end(), sub_errors.begin(), sub_errors.end());
|
|
1741
|
-
}
|
|
1742
|
-
}
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
// anyOf
|
|
1746
|
-
if (!node->any_of.empty()) {
|
|
1747
|
-
bool any_valid = false;
|
|
1748
|
-
for (const auto& sub : node->any_of) {
|
|
1749
|
-
std::vector<validation_error> sub_errors;
|
|
1750
|
-
validate_node(sub, value, path, ctx, sub_errors, all_errors, dynamic_scope);
|
|
1751
|
-
if (sub_errors.empty()) {
|
|
1752
|
-
any_valid = true;
|
|
1753
|
-
break;
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1756
|
-
if (!any_valid) {
|
|
1757
|
-
errors.push_back({error_code::any_of_failed, path,
|
|
1758
|
-
"no anyOf subschema matched"});
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
// oneOf
|
|
1763
|
-
if (!node->one_of.empty()) {
|
|
1764
|
-
int match_count = 0;
|
|
1765
|
-
for (const auto& sub : node->one_of) {
|
|
1766
|
-
std::vector<validation_error> sub_errors;
|
|
1767
|
-
validate_node(sub, value, path, ctx, sub_errors, all_errors, dynamic_scope);
|
|
1768
|
-
if (sub_errors.empty()) ++match_count;
|
|
1769
|
-
}
|
|
1770
|
-
if (match_count != 1) {
|
|
1771
|
-
errors.push_back({error_code::one_of_failed, path,
|
|
1772
|
-
"expected exactly one oneOf match, got " +
|
|
1773
|
-
std::to_string(match_count)});
|
|
1774
|
-
}
|
|
1775
|
-
}
|
|
1776
|
-
|
|
1777
|
-
// not
|
|
1778
|
-
if (node->not_schema) {
|
|
1779
|
-
std::vector<validation_error> sub_errors;
|
|
1780
|
-
validate_node(node->not_schema, value, path, ctx, sub_errors, all_errors, dynamic_scope);
|
|
1781
|
-
if (sub_errors.empty()) {
|
|
1782
|
-
errors.push_back({error_code::not_failed, path,
|
|
1783
|
-
"value should not match 'not' schema"});
|
|
1784
|
-
}
|
|
1785
|
-
}
|
|
1786
|
-
|
|
1787
|
-
// if/then/else
|
|
1788
|
-
if (node->if_schema) {
|
|
1789
|
-
std::vector<validation_error> if_errors;
|
|
1790
|
-
validate_node(node->if_schema, value, path, ctx, if_errors, all_errors, dynamic_scope);
|
|
1791
|
-
if (if_errors.empty()) {
|
|
1792
|
-
// if passed → validate then
|
|
1793
|
-
if (node->then_schema) {
|
|
1794
|
-
validate_node(node->then_schema, value, path, ctx, errors, all_errors, dynamic_scope);
|
|
1795
|
-
}
|
|
1796
|
-
} else {
|
|
1797
|
-
// if failed → validate else
|
|
1798
|
-
if (node->else_schema) {
|
|
1799
|
-
validate_node(node->else_schema, value, path, ctx, errors, all_errors, dynamic_scope);
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
}
|
|
1803
|
-
|
|
1804
|
-
if (pushed_scope) dynamic_scope->pop_back();
|
|
1805
|
-
}
|
|
1806
|
-
|
|
1807
|
-
// Fast boolean-only tree walker — stripped of all error collection.
|
|
1808
|
-
// No std::string allocation, no path tracking, no error messages.
|
|
1809
|
-
// Returns true if valid. Uses [[likely]]/[[unlikely]] branch hints.
|
|
1810
|
-
static bool validate_fast(const schema_node_ptr& node,
|
|
1811
|
-
dom::element value,
|
|
1812
|
-
const compiled_schema& ctx) {
|
|
1813
|
-
if (!node) [[unlikely]] return true;
|
|
1814
|
-
|
|
1815
|
-
DepthGuard guard;
|
|
1816
|
-
if (guard.overflow) [[unlikely]] return true;
|
|
1817
|
-
|
|
1818
|
-
if (node->boolean_schema.has_value()) [[unlikely]]
|
|
1819
|
-
return node->boolean_schema.value();
|
|
1820
|
-
|
|
1821
|
-
// $dynamicRef — bail to tree walker
|
|
1822
|
-
if (!node->dynamic_ref.empty()) [[unlikely]] return false;
|
|
1823
|
-
|
|
1824
|
-
// $ref
|
|
1825
|
-
if (!node->ref.empty()) [[unlikely]] {
|
|
1826
|
-
auto it = ctx.defs.find(node->ref);
|
|
1827
|
-
if (it != ctx.defs.end()) {
|
|
1828
|
-
if (!validate_fast(it->second, value, ctx)) return false;
|
|
1829
|
-
} else if (node->ref.size() > 1 && node->ref[0] == '#' && node->ref[1] != '/') {
|
|
1830
|
-
auto ait = ctx.anchors.find(node->ref.substr(1));
|
|
1831
|
-
if (ait != ctx.anchors.end()) {
|
|
1832
|
-
if (!validate_fast(ait->second, value, ctx)) return false;
|
|
1833
|
-
} else {
|
|
1834
|
-
return false;
|
|
1835
|
-
}
|
|
1836
|
-
} else if (node->ref == "#" && ctx.root) {
|
|
1837
|
-
if (!validate_fast(ctx.root, value, ctx)) return false;
|
|
1838
|
-
} else {
|
|
1839
|
-
return false;
|
|
1840
|
-
}
|
|
1841
|
-
}
|
|
1842
|
-
|
|
1843
|
-
// type
|
|
1844
|
-
if (node->type_mask) {
|
|
1845
|
-
if (!type_matches_mask(value, node->type_mask)) [[unlikely]] return false;
|
|
1846
|
-
}
|
|
1847
|
-
|
|
1848
|
-
// enum
|
|
1849
|
-
if (!node->enum_values_minified.empty()) {
|
|
1850
|
-
auto val_str = canonical_json(value);
|
|
1851
|
-
bool found = false;
|
|
1852
|
-
for (const auto& ev : node->enum_values_minified) {
|
|
1853
|
-
if (ev == val_str) { found = true; break; }
|
|
1854
|
-
}
|
|
1855
|
-
if (!found) [[unlikely]] return false;
|
|
1856
|
-
}
|
|
1857
|
-
|
|
1858
|
-
// const
|
|
1859
|
-
if (node->const_value_raw.has_value()) {
|
|
1860
|
-
if (canonical_json(value) != node->const_value_raw.value()) [[unlikely]] return false;
|
|
1861
|
-
}
|
|
1862
|
-
|
|
1863
|
-
auto vtype = value.type();
|
|
1864
|
-
|
|
1865
|
-
// Numeric
|
|
1866
|
-
if (vtype == et::INT64 || vtype == et::UINT64 || vtype == et::DOUBLE) {
|
|
1867
|
-
double v = to_double(value);
|
|
1868
|
-
if (node->minimum.has_value() && v < node->minimum.value()) return false;
|
|
1869
|
-
if (node->maximum.has_value() && v > node->maximum.value()) return false;
|
|
1870
|
-
if (node->exclusive_minimum.has_value() && v <= node->exclusive_minimum.value()) return false;
|
|
1871
|
-
if (node->exclusive_maximum.has_value() && v >= node->exclusive_maximum.value()) return false;
|
|
1872
|
-
if (node->multiple_of.has_value()) {
|
|
1873
|
-
double rem = std::fmod(v, node->multiple_of.value());
|
|
1874
|
-
if (std::abs(rem) > 1e-8 && std::abs(rem - node->multiple_of.value()) > 1e-8) return false;
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
|
|
1878
|
-
// String
|
|
1879
|
-
if (vtype == et::STRING) {
|
|
1880
|
-
std::string_view sv;
|
|
1881
|
-
value.get(sv);
|
|
1882
|
-
uint64_t len = utf8_length(sv);
|
|
1883
|
-
if (node->min_length.has_value() && len < node->min_length.value()) return false;
|
|
1884
|
-
if (node->max_length.has_value() && len > node->max_length.value()) return false;
|
|
1885
|
-
#ifndef ATA_NO_RE2
|
|
1886
|
-
if (node->compiled_pattern) {
|
|
1887
|
-
if (!re2::RE2::PartialMatch(re2::StringPiece(sv.data(), sv.size()), *node->compiled_pattern))
|
|
1888
|
-
return false;
|
|
1889
|
-
}
|
|
1890
|
-
#endif
|
|
1891
|
-
if (node->format.has_value() && !check_format_by_id(sv, node->format_id)) return false;
|
|
1892
|
-
}
|
|
1893
|
-
|
|
1894
|
-
// Array
|
|
1895
|
-
if (vtype == et::ARRAY) {
|
|
1896
|
-
dom::array arr; value.get(arr);
|
|
1897
|
-
uint64_t arr_size = arr.size();
|
|
1898
|
-
if(arr_size == 0xFFFFFF) [[unlikely]] {
|
|
1899
|
-
// Fallback for large arrays where size() saturates — count manually to avoid overflow
|
|
1900
|
-
arr_size = 0;
|
|
1901
|
-
for ([[maybe_unused]] auto _ : arr) ++arr_size;
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1904
|
-
if (node->min_items.has_value() && arr_size < node->min_items.value()) return false;
|
|
1905
|
-
if (node->max_items.has_value() && arr_size > node->max_items.value()) return false;
|
|
1906
|
-
|
|
1907
|
-
if (node->unique_items) {
|
|
1908
|
-
auto first_it = arr.begin();
|
|
1909
|
-
if (first_it != arr.end()) {
|
|
1910
|
-
auto first_type = (*first_it).type();
|
|
1911
|
-
bool all_same = true;
|
|
1912
|
-
for (auto item : arr) { if (item.type() != first_type) { all_same = false; break; } }
|
|
1913
|
-
if (all_same && first_type == et::STRING) {
|
|
1914
|
-
std::set<std::string_view> seen;
|
|
1915
|
-
for (auto item : arr) { std::string_view sv; item.get(sv); if (!seen.insert(sv).second) return false; }
|
|
1916
|
-
} else if (all_same && (first_type == et::INT64 || first_type == et::UINT64 || first_type == et::DOUBLE)) {
|
|
1917
|
-
std::set<double> seen;
|
|
1918
|
-
for (auto item : arr) { if (!seen.insert(to_double(item)).second) return false; }
|
|
1919
|
-
} else {
|
|
1920
|
-
std::set<std::string> seen;
|
|
1921
|
-
for (auto item : arr) { if (!seen.insert(canonical_json(item)).second) return false; }
|
|
1922
|
-
}
|
|
1923
|
-
}
|
|
1924
|
-
}
|
|
1925
|
-
|
|
1926
|
-
{ uint64_t idx = 0;
|
|
1927
|
-
for (auto item : arr) {
|
|
1928
|
-
if (idx < node->prefix_items.size()) {
|
|
1929
|
-
if (!validate_fast(node->prefix_items[idx], item, ctx)) return false;
|
|
1930
|
-
} else if (node->items_schema) {
|
|
1931
|
-
if (!validate_fast(node->items_schema, item, ctx)) return false;
|
|
1932
|
-
}
|
|
1933
|
-
++idx;
|
|
1934
|
-
}
|
|
1935
|
-
}
|
|
1936
|
-
|
|
1937
|
-
if (node->contains_schema) {
|
|
1938
|
-
uint64_t match_count = 0;
|
|
1939
|
-
for (auto item : arr) {
|
|
1940
|
-
if (validate_fast(node->contains_schema, item, ctx)) ++match_count;
|
|
1941
|
-
}
|
|
1942
|
-
uint64_t min_c = node->min_contains.value_or(1);
|
|
1943
|
-
uint64_t max_c = node->max_contains.value_or(arr_size);
|
|
1944
|
-
if (match_count < min_c || match_count > max_c) return false;
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
|
-
|
|
1948
|
-
// Object
|
|
1949
|
-
if (vtype == et::OBJECT) {
|
|
1950
|
-
dom::object obj; value.get(obj);
|
|
1951
|
-
|
|
1952
|
-
if (node->min_properties.has_value() || node->max_properties.has_value()) {
|
|
1953
|
-
uint64_t n = 0;
|
|
1954
|
-
for ([[maybe_unused]] auto _ : obj) ++n;
|
|
1955
|
-
if (node->min_properties.has_value() && n < node->min_properties.value()) return false;
|
|
1956
|
-
if (node->max_properties.has_value() && n > node->max_properties.value()) return false;
|
|
1957
|
-
}
|
|
1958
|
-
|
|
1959
|
-
for (const auto& req : node->required) {
|
|
1960
|
-
dom::element d;
|
|
1961
|
-
if (obj[req].get(d) != SUCCESS) [[unlikely]] return false;
|
|
1962
|
-
}
|
|
1963
|
-
|
|
1964
|
-
for (auto [key, val] : obj) {
|
|
1965
|
-
std::string_view key_sv(key);
|
|
1966
|
-
bool matched = false;
|
|
1967
|
-
|
|
1968
|
-
auto it = node->properties.find(std::string(key_sv));
|
|
1969
|
-
if (it != node->properties.end()) {
|
|
1970
|
-
if (!validate_fast(it->second, val, ctx)) return false;
|
|
1971
|
-
matched = true;
|
|
1972
|
-
}
|
|
1973
|
-
|
|
1974
|
-
for (const auto& pp : node->pattern_properties) {
|
|
1975
|
-
#ifndef ATA_NO_RE2
|
|
1976
|
-
if (pp.compiled && re2::RE2::PartialMatch(
|
|
1977
|
-
re2::StringPiece(key_sv.data(), key_sv.size()), *pp.compiled)) {
|
|
1978
|
-
if (!validate_fast(pp.schema, val, ctx)) return false;
|
|
1979
|
-
matched = true;
|
|
1980
|
-
}
|
|
1981
|
-
#endif
|
|
1982
|
-
}
|
|
1983
|
-
|
|
1984
|
-
if (!matched) {
|
|
1985
|
-
if (node->additional_properties_bool.has_value() &&
|
|
1986
|
-
!node->additional_properties_bool.value()) return false;
|
|
1987
|
-
if (node->additional_properties_schema &&
|
|
1988
|
-
!validate_fast(node->additional_properties_schema, val, ctx)) return false;
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
|
|
1992
|
-
for (const auto& [prop, deps] : node->dependent_required) {
|
|
1993
|
-
dom::element d;
|
|
1994
|
-
if (obj[prop].get(d) == SUCCESS) {
|
|
1995
|
-
for (const auto& dep : deps) {
|
|
1996
|
-
dom::element dd;
|
|
1997
|
-
if (obj[dep].get(dd) != SUCCESS) return false;
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
}
|
|
2001
|
-
|
|
2002
|
-
for (const auto& [prop, schema] : node->dependent_schemas) {
|
|
2003
|
-
dom::element d;
|
|
2004
|
-
if (obj[prop].get(d) == SUCCESS) {
|
|
2005
|
-
if (!validate_fast(schema, value, ctx)) return false;
|
|
2006
|
-
}
|
|
2007
|
-
}
|
|
2008
|
-
}
|
|
2009
|
-
|
|
2010
|
-
// allOf
|
|
2011
|
-
for (const auto& sub : node->all_of) {
|
|
2012
|
-
if (!validate_fast(sub, value, ctx)) return false;
|
|
2013
|
-
}
|
|
2014
|
-
|
|
2015
|
-
// anyOf
|
|
2016
|
-
if (!node->any_of.empty()) {
|
|
2017
|
-
bool any = false;
|
|
2018
|
-
for (const auto& sub : node->any_of) {
|
|
2019
|
-
if (validate_fast(sub, value, ctx)) { any = true; break; }
|
|
2020
|
-
}
|
|
2021
|
-
if (!any) return false;
|
|
2022
|
-
}
|
|
2023
|
-
|
|
2024
|
-
// oneOf
|
|
2025
|
-
if (!node->one_of.empty()) {
|
|
2026
|
-
int n = 0;
|
|
2027
|
-
for (const auto& sub : node->one_of) {
|
|
2028
|
-
if (validate_fast(sub, value, ctx)) ++n;
|
|
2029
|
-
if (n > 1) return false;
|
|
2030
|
-
}
|
|
2031
|
-
if (n != 1) return false;
|
|
2032
|
-
}
|
|
2033
|
-
|
|
2034
|
-
// not
|
|
2035
|
-
if (node->not_schema) {
|
|
2036
|
-
if (validate_fast(node->not_schema, value, ctx)) return false;
|
|
2037
|
-
}
|
|
2038
|
-
|
|
2039
|
-
// if/then/else
|
|
2040
|
-
if (node->if_schema) {
|
|
2041
|
-
if (validate_fast(node->if_schema, value, ctx)) {
|
|
2042
|
-
if (node->then_schema && !validate_fast(node->then_schema, value, ctx)) return false;
|
|
2043
|
-
} else {
|
|
2044
|
-
if (node->else_schema && !validate_fast(node->else_schema, value, ctx)) return false;
|
|
2045
|
-
}
|
|
2046
|
-
}
|
|
2047
|
-
|
|
2048
|
-
return true;
|
|
2049
|
-
}
|
|
2050
|
-
|
|
2051
|
-
// --- Codegen compiler ---
|
|
2052
|
-
static void cg_compile(const schema_node* n, cg::plan& p,
|
|
2053
|
-
std::vector<cg::ins>& out) {
|
|
2054
|
-
if (!n) return;
|
|
2055
|
-
if (n->boolean_schema.has_value()) {
|
|
2056
|
-
if (!*n->boolean_schema) out.push_back({cg::op::EXPECT_NULL});
|
|
2057
|
-
return;
|
|
2058
|
-
}
|
|
2059
|
-
// Composition fallback
|
|
2060
|
-
if (!n->ref.empty() || !n->dynamic_ref.empty() || !n->all_of.empty() ||
|
|
2061
|
-
!n->any_of.empty() || !n->one_of.empty() || n->not_schema ||
|
|
2062
|
-
n->if_schema) {
|
|
2063
|
-
uintptr_t ptr = reinterpret_cast<uintptr_t>(n);
|
|
2064
|
-
out.push_back({cg::op::COMPOSITION, (uint32_t)(ptr & 0xFFFFFFFF),
|
|
2065
|
-
(uint32_t)((ptr >> 32) & 0xFFFFFFFF)});
|
|
2066
|
-
return;
|
|
2067
|
-
}
|
|
2068
|
-
// Type
|
|
2069
|
-
if (n->type_mask) {
|
|
2070
|
-
int popcount = __builtin_popcount(n->type_mask);
|
|
2071
|
-
if (popcount == 1) {
|
|
2072
|
-
// Single type — emit specific opcode
|
|
2073
|
-
for (int b = 0; b < 7; ++b) {
|
|
2074
|
-
if (n->type_mask & (1u << b)) {
|
|
2075
|
-
switch (static_cast<json_type>(b)) {
|
|
2076
|
-
case json_type::object: out.push_back({cg::op::EXPECT_OBJECT}); break;
|
|
2077
|
-
case json_type::array: out.push_back({cg::op::EXPECT_ARRAY}); break;
|
|
2078
|
-
case json_type::string: out.push_back({cg::op::EXPECT_STRING}); break;
|
|
2079
|
-
case json_type::number: out.push_back({cg::op::EXPECT_NUMBER}); break;
|
|
2080
|
-
case json_type::integer: out.push_back({cg::op::EXPECT_INTEGER}); break;
|
|
2081
|
-
case json_type::boolean: out.push_back({cg::op::EXPECT_BOOLEAN}); break;
|
|
2082
|
-
case json_type::null_value: out.push_back({cg::op::EXPECT_NULL}); break;
|
|
2083
|
-
}
|
|
2084
|
-
break;
|
|
2085
|
-
}
|
|
2086
|
-
}
|
|
2087
|
-
} else {
|
|
2088
|
-
uint32_t i = (uint32_t)p.type_masks.size();
|
|
2089
|
-
p.type_masks.push_back(n->type_mask);
|
|
2090
|
-
out.push_back({cg::op::EXPECT_TYPE_MULTI, i});
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2093
|
-
// Enum
|
|
2094
|
-
if (!n->enum_values_minified.empty()) {
|
|
2095
|
-
bool all_str = true;
|
|
2096
|
-
for (auto& e : n->enum_values_minified)
|
|
2097
|
-
if (e.empty() || e[0]!='"') { all_str=false; break; }
|
|
2098
|
-
uint32_t i = (uint32_t)p.enum_sets.size();
|
|
2099
|
-
p.enum_sets.push_back(n->enum_values_minified);
|
|
2100
|
-
out.push_back({all_str ? cg::op::CHECK_ENUM_STR : cg::op::CHECK_ENUM, i});
|
|
2101
|
-
}
|
|
2102
|
-
if (n->const_value_raw.has_value()) {
|
|
2103
|
-
uint32_t i=(uint32_t)p.strings.size();
|
|
2104
|
-
p.strings.push_back(*n->const_value_raw);
|
|
2105
|
-
out.push_back({cg::op::CHECK_CONST, i});
|
|
2106
|
-
}
|
|
2107
|
-
// Numeric
|
|
2108
|
-
if (n->minimum.has_value()) { uint32_t i=(uint32_t)p.doubles.size(); p.doubles.push_back(*n->minimum); out.push_back({cg::op::CHECK_MINIMUM,i}); }
|
|
2109
|
-
if (n->maximum.has_value()) { uint32_t i=(uint32_t)p.doubles.size(); p.doubles.push_back(*n->maximum); out.push_back({cg::op::CHECK_MAXIMUM,i}); }
|
|
2110
|
-
if (n->exclusive_minimum.has_value()) { uint32_t i=(uint32_t)p.doubles.size(); p.doubles.push_back(*n->exclusive_minimum); out.push_back({cg::op::CHECK_EX_MINIMUM,i}); }
|
|
2111
|
-
if (n->exclusive_maximum.has_value()) { uint32_t i=(uint32_t)p.doubles.size(); p.doubles.push_back(*n->exclusive_maximum); out.push_back({cg::op::CHECK_EX_MAXIMUM,i}); }
|
|
2112
|
-
if (n->multiple_of.has_value()) { uint32_t i=(uint32_t)p.doubles.size(); p.doubles.push_back(*n->multiple_of); out.push_back({cg::op::CHECK_MULTIPLE_OF,i}); }
|
|
2113
|
-
// String
|
|
2114
|
-
if (n->min_length.has_value()) out.push_back({cg::op::CHECK_MIN_LENGTH,(uint32_t)*n->min_length});
|
|
2115
|
-
if (n->max_length.has_value()) out.push_back({cg::op::CHECK_MAX_LENGTH,(uint32_t)*n->max_length});
|
|
2116
|
-
#ifndef ATA_NO_RE2
|
|
2117
|
-
if (n->compiled_pattern) { uint32_t i=(uint32_t)p.regexes.size(); p.regexes.push_back(n->compiled_pattern); out.push_back({cg::op::CHECK_PATTERN,i}); }
|
|
2118
|
-
#endif
|
|
2119
|
-
if (n->format.has_value()) {
|
|
2120
|
-
uint32_t i=(uint32_t)p.format_ids.size();
|
|
2121
|
-
p.format_ids.push_back(n->format_id);
|
|
2122
|
-
out.push_back({cg::op::CHECK_FORMAT,i});
|
|
2123
|
-
}
|
|
2124
|
-
// Array
|
|
2125
|
-
if (n->min_items.has_value()) out.push_back({cg::op::CHECK_MIN_ITEMS,(uint32_t)*n->min_items});
|
|
2126
|
-
if (n->max_items.has_value()) out.push_back({cg::op::CHECK_MAX_ITEMS,(uint32_t)*n->max_items});
|
|
2127
|
-
if (n->unique_items) out.push_back({cg::op::CHECK_UNIQUE_ITEMS});
|
|
2128
|
-
if (n->items_schema) {
|
|
2129
|
-
uint32_t si=(uint32_t)p.subs.size();
|
|
2130
|
-
p.subs.emplace_back();
|
|
2131
|
-
std::vector<cg::ins> sub_code;
|
|
2132
|
-
cg_compile(n->items_schema.get(), p, sub_code);
|
|
2133
|
-
sub_code.push_back({cg::op::END});
|
|
2134
|
-
p.subs[si] = std::move(sub_code);
|
|
2135
|
-
out.push_back({cg::op::ARRAY_ITEMS, si});
|
|
2136
|
-
}
|
|
2137
|
-
// Object
|
|
2138
|
-
for (auto& r : n->required) { uint32_t i=(uint32_t)p.strings.size(); p.strings.push_back(r); out.push_back({cg::op::CHECK_REQUIRED,i}); }
|
|
2139
|
-
if (n->min_properties.has_value()) out.push_back({cg::op::CHECK_MIN_PROPS,(uint32_t)*n->min_properties});
|
|
2140
|
-
if (n->max_properties.has_value()) out.push_back({cg::op::CHECK_MAX_PROPS,(uint32_t)*n->max_properties});
|
|
2141
|
-
// additional_properties_schema requires tree walker — bail out to COMPOSITION
|
|
2142
|
-
if (n->additional_properties_schema) {
|
|
2143
|
-
out.push_back({cg::op::COMPOSITION, 0, 0});
|
|
2144
|
-
return;
|
|
2145
|
-
}
|
|
2146
|
-
if (!n->properties.empty() || (n->additional_properties_bool.has_value() && !*n->additional_properties_bool)) {
|
|
2147
|
-
out.push_back({cg::op::OBJ_PROPS_START});
|
|
2148
|
-
if (n->additional_properties_bool.has_value() && !*n->additional_properties_bool)
|
|
2149
|
-
out.push_back({cg::op::CHECK_NO_ADDITIONAL});
|
|
2150
|
-
for (auto& [name, schema] : n->properties) {
|
|
2151
|
-
uint32_t ni=(uint32_t)p.strings.size(); p.strings.push_back(name);
|
|
2152
|
-
uint32_t si=(uint32_t)p.subs.size();
|
|
2153
|
-
p.subs.emplace_back();
|
|
2154
|
-
std::vector<cg::ins> sub_code;
|
|
2155
|
-
cg_compile(schema.get(), p, sub_code);
|
|
2156
|
-
sub_code.push_back({cg::op::END});
|
|
2157
|
-
p.subs[si] = std::move(sub_code);
|
|
2158
|
-
out.push_back({cg::op::OBJ_PROP, ni, si});
|
|
2159
|
-
}
|
|
2160
|
-
out.push_back({cg::op::OBJ_PROPS_END});
|
|
2161
|
-
}
|
|
2162
|
-
}
|
|
2163
|
-
|
|
2164
|
-
// --- Codegen executor ---
|
|
2165
|
-
|
|
2166
|
-
static bool cg_exec(const cg::plan& p, const std::vector<cg::ins>& code,
|
|
2167
|
-
dom::element value) {
|
|
2168
|
-
auto t = value.type();
|
|
2169
|
-
bool t_numeric = (t == et::INT64 || t == et::UINT64 || t == et::DOUBLE);
|
|
2170
|
-
double t_dval = t_numeric ? to_double(value) : 0.0;
|
|
2171
|
-
for (size_t i=0; i<code.size(); ++i) {
|
|
2172
|
-
auto& c = code[i];
|
|
2173
|
-
switch(c.o) {
|
|
2174
|
-
case cg::op::END: return true;
|
|
2175
|
-
case cg::op::EXPECT_OBJECT: if(t!=et::OBJECT) return false; break;
|
|
2176
|
-
case cg::op::EXPECT_ARRAY: if(t!=et::ARRAY) return false; break;
|
|
2177
|
-
case cg::op::EXPECT_STRING: if(t!=et::STRING) return false; break;
|
|
2178
|
-
case cg::op::EXPECT_NUMBER: if(!t_numeric) return false; break;
|
|
2179
|
-
case cg::op::EXPECT_INTEGER: if(t!=et::INT64&&t!=et::UINT64) return false; break;
|
|
2180
|
-
case cg::op::EXPECT_BOOLEAN: if(t!=et::BOOL) return false; break;
|
|
2181
|
-
case cg::op::EXPECT_NULL: if(t!=et::NULL_VALUE) return false; break;
|
|
2182
|
-
case cg::op::EXPECT_TYPE_MULTI: {
|
|
2183
|
-
if(!(element_type_mask(t) & p.type_masks[c.a])) return false; break;
|
|
2184
|
-
}
|
|
2185
|
-
case cg::op::CHECK_MINIMUM: if(t_numeric&&t_dval<p.doubles[c.a])return false; break;
|
|
2186
|
-
case cg::op::CHECK_MAXIMUM: if(t_numeric&&t_dval>p.doubles[c.a])return false; break;
|
|
2187
|
-
case cg::op::CHECK_EX_MINIMUM: if(t_numeric&&t_dval<=p.doubles[c.a])return false; break;
|
|
2188
|
-
case cg::op::CHECK_EX_MAXIMUM: if(t_numeric&&t_dval>=p.doubles[c.a])return false; break;
|
|
2189
|
-
case cg::op::CHECK_MULTIPLE_OF: if(t_numeric){double d=p.doubles[c.a],r=std::fmod(t_dval,d);if(std::abs(r)>1e-8&&std::abs(r-d)>1e-8)return false;} break;
|
|
2190
|
-
case cg::op::CHECK_MIN_LENGTH: if(t==et::STRING){std::string_view sv;value.get(sv);if(utf8_length(sv)<c.a)return false;} break;
|
|
2191
|
-
case cg::op::CHECK_MAX_LENGTH: if(t==et::STRING){std::string_view sv;value.get(sv);if(utf8_length(sv)>c.a)return false;} break;
|
|
2192
|
-
#ifndef ATA_NO_RE2
|
|
2193
|
-
case cg::op::CHECK_PATTERN: if(t==et::STRING){std::string_view sv;value.get(sv);if(!re2::RE2::PartialMatch(re2::StringPiece(sv.data(),sv.size()),*p.regexes[c.a]))return false;} break;
|
|
2194
|
-
#else
|
|
2195
|
-
case cg::op::CHECK_PATTERN: break;
|
|
2196
|
-
#endif
|
|
2197
|
-
case cg::op::CHECK_FORMAT: if(t==et::STRING){std::string_view sv;value.get(sv);if(!check_format_by_id(sv,p.format_ids[c.a]))return false;} break;
|
|
2198
|
-
case cg::op::CHECK_MIN_ITEMS: if(t==et::ARRAY){dom::array a;value.get(a);uint64_t s=0;for([[maybe_unused]]auto _:a)++s;if(s<c.a)return false;} break;
|
|
2199
|
-
case cg::op::CHECK_MAX_ITEMS: if(t==et::ARRAY){dom::array a;value.get(a);uint64_t s=0;for([[maybe_unused]]auto _:a)++s;if(s>c.a)return false;} break;
|
|
2200
|
-
case cg::op::CHECK_UNIQUE_ITEMS: if(t==et::ARRAY){dom::array a;value.get(a);std::set<std::string> seen;for(auto x:a)if(!seen.insert(canonical_json(x)).second)return false;} break;
|
|
2201
|
-
case cg::op::ARRAY_ITEMS: if(t==et::ARRAY){dom::array a;value.get(a);for(auto x:a)if(!cg_exec(p,p.subs[c.a],x))return false;} break;
|
|
2202
|
-
case cg::op::CHECK_REQUIRED: if(t==et::OBJECT){dom::object o;value.get(o);dom::element d;if(o[p.strings[c.a]].get(d)!=SUCCESS)return false;} break;
|
|
2203
|
-
case cg::op::CHECK_MIN_PROPS: if(t==et::OBJECT){dom::object o;value.get(o);uint64_t n=0;for([[maybe_unused]]auto _:o)++n;if(n<c.a)return false;} break;
|
|
2204
|
-
case cg::op::CHECK_MAX_PROPS: if(t==et::OBJECT){dom::object o;value.get(o);uint64_t n=0;for([[maybe_unused]]auto _:o)++n;if(n>c.a)return false;} break;
|
|
2205
|
-
case cg::op::OBJ_PROPS_START: if(t==et::OBJECT){
|
|
2206
|
-
dom::object o; value.get(o);
|
|
2207
|
-
// collect prop defs
|
|
2208
|
-
struct pd{std::string_view nm;uint32_t si;};
|
|
2209
|
-
std::vector<pd> props; bool no_add=false;
|
|
2210
|
-
size_t j=i+1;
|
|
2211
|
-
for(;j<code.size()&&code[j].o!=cg::op::OBJ_PROPS_END;++j){
|
|
2212
|
-
if(code[j].o==cg::op::OBJ_PROP) props.push_back({p.strings[code[j].a],code[j].b});
|
|
2213
|
-
else if(code[j].o==cg::op::CHECK_NO_ADDITIONAL) no_add=true;
|
|
2214
|
-
}
|
|
2215
|
-
for(auto [key,val]:o){
|
|
2216
|
-
bool matched=false;
|
|
2217
|
-
for(auto& pp:props){if(key==pp.nm){if(!cg_exec(p,p.subs[pp.si],val))return false;matched=true;break;}}
|
|
2218
|
-
if(!matched&&no_add)return false;
|
|
2219
|
-
}
|
|
2220
|
-
i=j; break;
|
|
2221
|
-
} else { /* skip to OBJ_PROPS_END */ size_t j=i+1; for(;j<code.size()&&code[j].o!=cg::op::OBJ_PROPS_END;++j); i=j; } break;
|
|
2222
|
-
case cg::op::OBJ_PROP: case cg::op::OBJ_PROPS_END: case cg::op::CHECK_NO_ADDITIONAL: break;
|
|
2223
|
-
case cg::op::CHECK_ENUM_STR: {
|
|
2224
|
-
auto& es=p.enum_sets[c.a]; bool f=false;
|
|
2225
|
-
if(t==et::STRING){std::string_view sv;value.get(sv);for(auto& e:es)if(e.size()==sv.size()+2&&e[0]=='"'&&e.back()=='"'&&e.compare(1,sv.size(),sv)==0){f=true;break;}}
|
|
2226
|
-
if(!f){std::string v=canonical_json(value);for(auto& e:es)if(e==v){f=true;break;}}
|
|
2227
|
-
if(!f)return false; break;
|
|
2228
|
-
}
|
|
2229
|
-
case cg::op::CHECK_ENUM: {
|
|
2230
|
-
auto& es=p.enum_sets[c.a]; bool f=false;
|
|
2231
|
-
if(t==et::STRING){std::string_view sv;value.get(sv);for(auto& e:es)if(e.size()==sv.size()+2&&e[0]=='"'&&e.back()=='"'&&e.compare(1,sv.size(),sv)==0){f=true;break;}}
|
|
2232
|
-
if(!f&&value.is<int64_t>()){int64_t v;value.get(v);auto s=std::to_string(v);for(auto& e:es)if(e==s){f=true;break;}}
|
|
2233
|
-
if(!f){std::string v=canonical_json(value);for(auto& e:es)if(e==v){f=true;break;}}
|
|
2234
|
-
if(!f)return false; break;
|
|
2235
|
-
}
|
|
2236
|
-
case cg::op::CHECK_CONST: if(canonical_json(value)!=p.strings[c.a])return false; break;
|
|
2237
|
-
case cg::op::COMPOSITION: return false; // fallback to tree walker
|
|
2238
|
-
}
|
|
2239
|
-
}
|
|
2240
|
-
return true;
|
|
2241
|
-
}
|
|
2242
|
-
|
|
2243
|
-
// --- On Demand fast path executor ---
|
|
2244
|
-
// Uses simdjson On Demand API to avoid materializing the full DOM tree.
|
|
2245
|
-
// Returns: true = valid, false = invalid OR unsupported (fallback to DOM).
|
|
2246
|
-
|
|
2247
|
-
static json_type od_type(simdjson::ondemand::value& v) {
|
|
2248
|
-
simdjson::ondemand::json_type jt;
|
|
2249
|
-
if (v.type().get(jt)) return json_type::null_value;
|
|
2250
|
-
switch (jt) {
|
|
2251
|
-
case simdjson::ondemand::json_type::object: return json_type::object;
|
|
2252
|
-
case simdjson::ondemand::json_type::array: return json_type::array;
|
|
2253
|
-
case simdjson::ondemand::json_type::string: return json_type::string;
|
|
2254
|
-
case simdjson::ondemand::json_type::boolean: return json_type::boolean;
|
|
2255
|
-
case simdjson::ondemand::json_type::null: return json_type::null_value;
|
|
2256
|
-
case simdjson::ondemand::json_type::number: {
|
|
2257
|
-
simdjson::ondemand::number_type nt;
|
|
2258
|
-
if (v.get_number_type().get(nt) == SUCCESS &&
|
|
2259
|
-
nt == simdjson::ondemand::number_type::floating_point_number)
|
|
2260
|
-
return json_type::number;
|
|
2261
|
-
return json_type::integer;
|
|
2262
|
-
}
|
|
2263
|
-
}
|
|
2264
|
-
return json_type::string;
|
|
2265
|
-
}
|
|
2266
|
-
|
|
2267
|
-
static bool od_exec(const cg::plan& p, const std::vector<cg::ins>& code,
|
|
2268
|
-
simdjson::ondemand::value value) {
|
|
2269
|
-
auto t = od_type(value);
|
|
2270
|
-
bool t_numeric = (t == json_type::integer || t == json_type::number);
|
|
2271
|
-
for (size_t i = 0; i < code.size(); ++i) {
|
|
2272
|
-
auto& c = code[i];
|
|
2273
|
-
switch (c.o) {
|
|
2274
|
-
case cg::op::END: return true;
|
|
2275
|
-
case cg::op::EXPECT_OBJECT: if(t!=json_type::object) return false; break;
|
|
2276
|
-
case cg::op::EXPECT_ARRAY: if(t!=json_type::array) return false; break;
|
|
2277
|
-
case cg::op::EXPECT_STRING: if(t!=json_type::string) return false; break;
|
|
2278
|
-
case cg::op::EXPECT_NUMBER: if(!t_numeric) return false; break;
|
|
2279
|
-
case cg::op::EXPECT_INTEGER: if(t!=json_type::integer) return false; break;
|
|
2280
|
-
case cg::op::EXPECT_BOOLEAN: if(t!=json_type::boolean) return false; break;
|
|
2281
|
-
case cg::op::EXPECT_NULL: if(t!=json_type::null_value) return false; break;
|
|
2282
|
-
case cg::op::EXPECT_TYPE_MULTI: {
|
|
2283
|
-
// integer matches both "integer" and "number" type constraints
|
|
2284
|
-
uint8_t tbits = json_type_bit(t);
|
|
2285
|
-
if (t == json_type::integer) tbits |= json_type_bit(json_type::number);
|
|
2286
|
-
if(!(tbits & p.type_masks[c.a])) return false; break;
|
|
2287
|
-
}
|
|
2288
|
-
case cg::op::CHECK_MINIMUM:
|
|
2289
|
-
case cg::op::CHECK_MAXIMUM:
|
|
2290
|
-
case cg::op::CHECK_EX_MINIMUM:
|
|
2291
|
-
case cg::op::CHECK_EX_MAXIMUM:
|
|
2292
|
-
case cg::op::CHECK_MULTIPLE_OF: {
|
|
2293
|
-
if (t_numeric) {
|
|
2294
|
-
double v;
|
|
2295
|
-
if (t==json_type::integer) { int64_t iv; if(value.get(iv)!=SUCCESS) return false; v=(double)iv; }
|
|
2296
|
-
else { if(value.get(v)!=SUCCESS) return false; }
|
|
2297
|
-
double d=p.doubles[c.a];
|
|
2298
|
-
if(c.o==cg::op::CHECK_MINIMUM && v<d) return false;
|
|
2299
|
-
if(c.o==cg::op::CHECK_MAXIMUM && v>d) return false;
|
|
2300
|
-
if(c.o==cg::op::CHECK_EX_MINIMUM && v<=d) return false;
|
|
2301
|
-
if(c.o==cg::op::CHECK_EX_MAXIMUM && v>=d) return false;
|
|
2302
|
-
if(c.o==cg::op::CHECK_MULTIPLE_OF){double r=std::fmod(v,d);if(std::abs(r)>1e-8&&std::abs(r-d)>1e-8)return false;}
|
|
2303
|
-
}
|
|
2304
|
-
break;
|
|
2305
|
-
}
|
|
2306
|
-
case cg::op::CHECK_MIN_LENGTH: if(t==json_type::string){std::string_view sv; if(value.get(sv)!=SUCCESS) return false; if(utf8_length(sv)<c.a) return false;} break;
|
|
2307
|
-
case cg::op::CHECK_MAX_LENGTH: if(t==json_type::string){std::string_view sv; if(value.get(sv)!=SUCCESS) return false; if(utf8_length(sv)>c.a) return false;} break;
|
|
2308
|
-
#ifndef ATA_NO_RE2
|
|
2309
|
-
case cg::op::CHECK_PATTERN: if(t==json_type::string){std::string_view sv; if(value.get(sv)!=SUCCESS) return false; if(!re2::RE2::PartialMatch(re2::StringPiece(sv.data(),sv.size()),*p.regexes[c.a]))return false;} break;
|
|
2310
|
-
#else
|
|
2311
|
-
case cg::op::CHECK_PATTERN: break;
|
|
2312
|
-
#endif
|
|
2313
|
-
case cg::op::CHECK_FORMAT: if(t==json_type::string){std::string_view sv; if(value.get(sv)!=SUCCESS) return false; if(!check_format_by_id(sv,p.format_ids[c.a]))return false;} break;
|
|
2314
|
-
case cg::op::CHECK_MIN_ITEMS: if(t==json_type::array){
|
|
2315
|
-
simdjson::ondemand::array a; if(value.get(a)!=SUCCESS) return false;
|
|
2316
|
-
uint64_t s=0; for(auto x:a){(void)x;++s;} if(s<c.a) return false;
|
|
2317
|
-
} break;
|
|
2318
|
-
case cg::op::CHECK_MAX_ITEMS: if(t==json_type::array){
|
|
2319
|
-
simdjson::ondemand::array a; if(value.get(a)!=SUCCESS) return false;
|
|
2320
|
-
uint64_t s=0; for(auto x:a){(void)x;++s;} if(s>c.a) return false;
|
|
2321
|
-
} break;
|
|
2322
|
-
case cg::op::ARRAY_ITEMS: if(t==json_type::array){
|
|
2323
|
-
simdjson::ondemand::array a; if(value.get(a)!=SUCCESS) return false;
|
|
2324
|
-
for(auto elem:a){
|
|
2325
|
-
simdjson::ondemand::value v; if(elem.get(v)!=SUCCESS) return false;
|
|
2326
|
-
if(!od_exec(p,p.subs[c.a],v)) return false;
|
|
2327
|
-
}
|
|
2328
|
-
} break;
|
|
2329
|
-
case cg::op::CHECK_REQUIRED: if(t==json_type::object){
|
|
2330
|
-
simdjson::ondemand::object o; if(value.get(o)!=SUCCESS) return false;
|
|
2331
|
-
auto f = o.find_field_unordered(p.strings[c.a]);
|
|
2332
|
-
if(f.error()) return false;
|
|
2333
|
-
} break;
|
|
2334
|
-
case cg::op::CHECK_MIN_PROPS: if(t==json_type::object){
|
|
2335
|
-
simdjson::ondemand::object o; if(value.get(o)!=SUCCESS) return false;
|
|
2336
|
-
uint64_t n=0; for(auto f:o){(void)f;++n;} if(n<c.a) return false;
|
|
2337
|
-
} break;
|
|
2338
|
-
case cg::op::CHECK_MAX_PROPS: if(t==json_type::object){
|
|
2339
|
-
simdjson::ondemand::object o; if(value.get(o)!=SUCCESS) return false;
|
|
2340
|
-
uint64_t n=0; for(auto f:o){(void)f;++n;} if(n>c.a) return false;
|
|
2341
|
-
} break;
|
|
2342
|
-
case cg::op::OBJ_PROPS_START: if(t==json_type::object){
|
|
2343
|
-
simdjson::ondemand::object o; if(value.get(o)!=SUCCESS) return false;
|
|
2344
|
-
struct pd{std::string_view nm;uint32_t si;};
|
|
2345
|
-
std::vector<pd> props; bool no_add=false;
|
|
2346
|
-
size_t j=i+1;
|
|
2347
|
-
for(;j<code.size()&&code[j].o!=cg::op::OBJ_PROPS_END;++j){
|
|
2348
|
-
if(code[j].o==cg::op::OBJ_PROP) props.push_back({p.strings[code[j].a],code[j].b});
|
|
2349
|
-
else if(code[j].o==cg::op::CHECK_NO_ADDITIONAL) no_add=true;
|
|
2350
|
-
}
|
|
2351
|
-
for(auto field:o){
|
|
2352
|
-
simdjson::ondemand::raw_json_string rk; if(field.key().get(rk)!=SUCCESS) return false;
|
|
2353
|
-
std::string_view key;
|
|
2354
|
-
if (field.unescaped_key().get(key)) continue;
|
|
2355
|
-
bool matched=false;
|
|
2356
|
-
for(auto& pp:props){
|
|
2357
|
-
if(key==pp.nm){
|
|
2358
|
-
simdjson::ondemand::value fv; if(field.value().get(fv)!=SUCCESS) return false;
|
|
2359
|
-
if(!od_exec(p,p.subs[pp.si],fv)) return false;
|
|
2360
|
-
matched=true; break;
|
|
2361
|
-
}
|
|
2362
|
-
}
|
|
2363
|
-
if(!matched&&no_add) return false;
|
|
2364
|
-
}
|
|
2365
|
-
i=j; break;
|
|
2366
|
-
} else { size_t j=i+1; for(;j<code.size()&&code[j].o!=cg::op::OBJ_PROPS_END;++j); i=j; } break;
|
|
2367
|
-
case cg::op::OBJ_PROP: case cg::op::OBJ_PROPS_END: case cg::op::CHECK_NO_ADDITIONAL: break;
|
|
2368
|
-
|
|
2369
|
-
// These require full materialization — bail to DOM path
|
|
2370
|
-
case cg::op::CHECK_UNIQUE_ITEMS:
|
|
2371
|
-
case cg::op::CHECK_ENUM_STR:
|
|
2372
|
-
case cg::op::CHECK_ENUM:
|
|
2373
|
-
case cg::op::CHECK_CONST:
|
|
2374
|
-
case cg::op::COMPOSITION:
|
|
2375
|
-
return false;
|
|
2376
|
-
}
|
|
2377
|
-
}
|
|
2378
|
-
return true;
|
|
2379
|
-
}
|
|
2380
|
-
|
|
2381
|
-
// Determine if a codegen plan can use On Demand (no enum/const/uniqueItems)
|
|
2382
|
-
static bool plan_supports_ondemand(const cg::plan& p) {
|
|
2383
|
-
for (auto& c : p.code) {
|
|
2384
|
-
if (c.o == cg::op::CHECK_UNIQUE_ITEMS || c.o == cg::op::CHECK_ENUM_STR ||
|
|
2385
|
-
c.o == cg::op::CHECK_ENUM || c.o == cg::op::CHECK_CONST ||
|
|
2386
|
-
c.o == cg::op::COMPOSITION)
|
|
2387
|
-
return false;
|
|
2388
|
-
}
|
|
2389
|
-
// Also check sub-plans
|
|
2390
|
-
for (auto& sub : p.subs) {
|
|
2391
|
-
for (auto& c : sub) {
|
|
2392
|
-
if (c.o == cg::op::CHECK_UNIQUE_ITEMS || c.o == cg::op::CHECK_ENUM_STR ||
|
|
2393
|
-
c.o == cg::op::CHECK_ENUM || c.o == cg::op::CHECK_CONST ||
|
|
2394
|
-
c.o == cg::op::COMPOSITION)
|
|
2395
|
-
return false;
|
|
2396
|
-
}
|
|
2397
|
-
}
|
|
2398
|
-
return true;
|
|
2399
|
-
}
|
|
2400
|
-
|
|
2401
|
-
// Free padding: check if buffer is near a page boundary
|
|
2402
|
-
// On modern systems, pages are at least 4096 bytes. If we're far enough
|
|
2403
|
-
// from the end of a page, we can read 64 bytes beyond without a fault.
|
|
2404
|
-
static long get_page_size() {
|
|
2405
|
-
#ifdef _WIN32
|
|
2406
|
-
SYSTEM_INFO si; GetSystemInfo(&si); return si.dwPageSize;
|
|
2407
|
-
#else
|
|
2408
|
-
static long ps = sysconf(_SC_PAGESIZE);
|
|
2409
|
-
return ps;
|
|
2410
|
-
#endif
|
|
2411
|
-
}
|
|
2412
|
-
|
|
2413
|
-
static bool near_page_boundary(const char* buf, size_t len) {
|
|
2414
|
-
return ((reinterpret_cast<uintptr_t>(buf + len - 1) % get_page_size())
|
|
2415
|
-
+ REQUIRED_PADDING >= static_cast<uintptr_t>(get_page_size()));
|
|
2416
|
-
}
|
|
2417
|
-
|
|
2418
|
-
// Zero-copy validate with free padding (Lemire's trick).
|
|
2419
|
-
// Almost never allocates — only if buffer is near a page boundary.
|
|
2420
|
-
static simdjson::padded_string_view get_free_padded_view(
|
|
2421
|
-
const char* data, size_t length, simdjson::padded_string& fallback) {
|
|
2422
|
-
if (near_page_boundary(data, length)) {
|
|
2423
|
-
// Rare: near page boundary, must copy
|
|
2424
|
-
fallback = simdjson::padded_string(data, length);
|
|
2425
|
-
return fallback;
|
|
2426
|
-
}
|
|
2427
|
-
// Common: free padding available, zero-copy
|
|
2428
|
-
return simdjson::padded_string_view(data, length, length + REQUIRED_PADDING);
|
|
2429
|
-
}
|
|
2430
|
-
|
|
2431
|
-
// Recognize common digit-only regex patterns: ^[0-9]+$, ^[0-9]{N}$, ^[0-9]{N,M}$
|
|
2432
|
-
// (and \d variants). Returns true if recognized, fills min_n/max_n.
|
|
2433
|
-
static bool parse_digit_pattern(const std::string& p,
|
|
2434
|
-
uint32_t& min_n, uint32_t& max_n) {
|
|
2435
|
-
if (p.size() < 6) return false;
|
|
2436
|
-
if (p.front() != '^' || p.back() != '$') return false;
|
|
2437
|
-
std::string_view body(p.data() + 1, p.size() - 2);
|
|
2438
|
-
size_t pos = 0;
|
|
2439
|
-
if (body.size() >= 5 && body.compare(0, 5, "[0-9]") == 0) {
|
|
2440
|
-
pos = 5;
|
|
2441
|
-
} else if (body.size() >= 2 && body[0] == '\\' && body[1] == 'd') {
|
|
2442
|
-
pos = 2;
|
|
2443
|
-
} else {
|
|
2444
|
-
return false;
|
|
2445
|
-
}
|
|
2446
|
-
if (pos >= body.size()) return false;
|
|
2447
|
-
char q = body[pos];
|
|
2448
|
-
if (q == '+' && pos + 1 == body.size()) {
|
|
2449
|
-
min_n = 1; max_n = UINT32_MAX; return true;
|
|
2450
|
-
}
|
|
2451
|
-
if (q == '*' && pos + 1 == body.size()) {
|
|
2452
|
-
min_n = 0; max_n = UINT32_MAX; return true;
|
|
2453
|
-
}
|
|
2454
|
-
if (q != '{') return false;
|
|
2455
|
-
size_t close = body.find('}', pos);
|
|
2456
|
-
if (close == std::string_view::npos || close + 1 != body.size()) return false;
|
|
2457
|
-
auto inner = body.substr(pos + 1, close - pos - 1);
|
|
2458
|
-
if (inner.empty()) return false;
|
|
2459
|
-
size_t comma = inner.find(',');
|
|
2460
|
-
auto parse_num = [](std::string_view s, uint32_t& out) -> bool {
|
|
2461
|
-
if (s.empty()) return false;
|
|
2462
|
-
uint64_t v = 0;
|
|
2463
|
-
for (char c : s) {
|
|
2464
|
-
if (c < '0' || c > '9') return false;
|
|
2465
|
-
v = v * 10 + (c - '0');
|
|
2466
|
-
if (v > UINT32_MAX) return false;
|
|
2467
|
-
}
|
|
2468
|
-
out = static_cast<uint32_t>(v);
|
|
2469
|
-
return true;
|
|
2470
|
-
};
|
|
2471
|
-
if (comma == std::string_view::npos) {
|
|
2472
|
-
if (!parse_num(inner, min_n)) return false;
|
|
2473
|
-
max_n = min_n;
|
|
2474
|
-
} else {
|
|
2475
|
-
auto a = inner.substr(0, comma);
|
|
2476
|
-
auto b = inner.substr(comma + 1);
|
|
2477
|
-
if (!parse_num(a, min_n)) return false;
|
|
2478
|
-
if (b.empty()) max_n = UINT32_MAX;
|
|
2479
|
-
else if (!parse_num(b, max_n)) return false;
|
|
2480
|
-
}
|
|
2481
|
-
return true;
|
|
2482
|
-
}
|
|
2483
|
-
|
|
2484
|
-
// Build an od_plan from a schema_node tree.
|
|
2485
|
-
static od_plan_ptr compile_od_plan(const schema_node_ptr& node) {
|
|
2486
|
-
if (!node) return nullptr;
|
|
2487
|
-
|
|
2488
|
-
auto plan = std::make_shared<od_plan>();
|
|
2489
|
-
|
|
2490
|
-
if (node->boolean_schema.has_value()) {
|
|
2491
|
-
if (!node->boolean_schema.value()) plan->supported = false;
|
|
2492
|
-
return plan;
|
|
2493
|
-
}
|
|
2494
|
-
|
|
2495
|
-
// Unsupported features → fall back to DOM
|
|
2496
|
-
if (!node->ref.empty() ||
|
|
2497
|
-
node->const_value_raw.has_value() ||
|
|
2498
|
-
node->unique_items ||
|
|
2499
|
-
!node->all_of.empty() ||
|
|
2500
|
-
!node->any_of.empty() ||
|
|
2501
|
-
!node->one_of.empty() ||
|
|
2502
|
-
node->not_schema ||
|
|
2503
|
-
node->if_schema ||
|
|
2504
|
-
node->contains_schema ||
|
|
2505
|
-
!node->prefix_items.empty() ||
|
|
2506
|
-
!node->pattern_properties.empty() ||
|
|
2507
|
-
!node->dependent_required.empty() ||
|
|
2508
|
-
!node->dependent_schemas.empty() ||
|
|
2509
|
-
node->property_names_schema ||
|
|
2510
|
-
node->additional_properties_schema) {
|
|
2511
|
-
plan->supported = false;
|
|
2512
|
-
return plan;
|
|
2513
|
-
}
|
|
2514
|
-
|
|
2515
|
-
// Enum: handle primitive enums on the on-demand path. Complex enum values
|
|
2516
|
-
// (objects, arrays, escaped strings) fall back to DOM.
|
|
2517
|
-
if (!node->enum_values_minified.empty()) {
|
|
2518
|
-
auto ec = std::make_shared<od_plan::enum_constraint>();
|
|
2519
|
-
for (const auto& ev : node->enum_values_minified) {
|
|
2520
|
-
if (ev.empty()) { plan->supported = false; return plan; }
|
|
2521
|
-
char c = ev[0];
|
|
2522
|
-
if (c == '"') {
|
|
2523
|
-
if (ev.size() < 2 || ev.back() != '"') { plan->supported = false; return plan; }
|
|
2524
|
-
bool has_escape = false;
|
|
2525
|
-
for (size_t i = 1; i + 1 < ev.size(); i++) {
|
|
2526
|
-
if (ev[i] == '\\') { has_escape = true; break; }
|
|
2527
|
-
}
|
|
2528
|
-
if (has_escape) { plan->supported = false; return plan; }
|
|
2529
|
-
ec->strings.push_back(ev.substr(1, ev.size() - 2));
|
|
2530
|
-
} else if (c == '-' || (c >= '0' && c <= '9')) {
|
|
2531
|
-
bool is_int = true;
|
|
2532
|
-
for (size_t i = (c == '-' ? 1 : 0); i < ev.size(); i++) {
|
|
2533
|
-
if (ev[i] < '0' || ev[i] > '9') { is_int = false; break; }
|
|
2534
|
-
}
|
|
2535
|
-
if (is_int) {
|
|
2536
|
-
try { ec->integers.push_back(std::stoll(ev)); }
|
|
2537
|
-
catch (...) { plan->supported = false; return plan; }
|
|
2538
|
-
} else {
|
|
2539
|
-
try { ec->doubles.push_back(std::stod(ev)); }
|
|
2540
|
-
catch (...) { plan->supported = false; return plan; }
|
|
2541
|
-
}
|
|
2542
|
-
} else if (ev == "true") {
|
|
2543
|
-
ec->has_true = true;
|
|
2544
|
-
} else if (ev == "false") {
|
|
2545
|
-
ec->has_false = true;
|
|
2546
|
-
} else if (ev == "null") {
|
|
2547
|
-
ec->has_null = true;
|
|
2548
|
-
} else {
|
|
2549
|
-
plan->supported = false;
|
|
2550
|
-
return plan;
|
|
2551
|
-
}
|
|
2552
|
-
}
|
|
2553
|
-
plan->enum_check = std::move(ec);
|
|
2554
|
-
}
|
|
2555
|
-
|
|
2556
|
-
plan->type_mask = node->type_mask;
|
|
2557
|
-
if (node->minimum) { plan->num_flags |= od_plan::HAS_MIN; plan->num_min = *node->minimum; }
|
|
2558
|
-
if (node->maximum) { plan->num_flags |= od_plan::HAS_MAX; plan->num_max = *node->maximum; }
|
|
2559
|
-
if (node->exclusive_minimum) { plan->num_flags |= od_plan::HAS_EX_MIN; plan->num_ex_min = *node->exclusive_minimum; }
|
|
2560
|
-
if (node->exclusive_maximum) { plan->num_flags |= od_plan::HAS_EX_MAX; plan->num_ex_max = *node->exclusive_maximum; }
|
|
2561
|
-
if (node->multiple_of) { plan->num_flags |= od_plan::HAS_MUL; plan->num_mul = *node->multiple_of; }
|
|
2562
|
-
plan->min_length = node->min_length;
|
|
2563
|
-
plan->max_length = node->max_length;
|
|
2564
|
-
#ifndef ATA_NO_RE2
|
|
2565
|
-
plan->pattern = node->compiled_pattern.get();
|
|
2566
|
-
#endif
|
|
2567
|
-
// Try to recognize the pattern as an inlined digit check; if so, skip RE2.
|
|
2568
|
-
if (node->pattern.has_value()) {
|
|
2569
|
-
uint32_t min_n, max_n;
|
|
2570
|
-
if (parse_digit_pattern(*node->pattern, min_n, max_n)) {
|
|
2571
|
-
plan->digit_pattern = od_plan::digit_pattern_t{min_n, max_n};
|
|
2572
|
-
#ifndef ATA_NO_RE2
|
|
2573
|
-
plan->pattern = nullptr;
|
|
2574
|
-
#endif
|
|
2575
|
-
}
|
|
2576
|
-
}
|
|
2577
|
-
plan->format_id = node->format_id;
|
|
2578
|
-
|
|
2579
|
-
// Object plan — build hash lookup for O(1) per-field dispatch
|
|
2580
|
-
if (!node->properties.empty() || !node->required.empty() ||
|
|
2581
|
-
node->additional_properties_bool.has_value() ||
|
|
2582
|
-
node->min_properties.has_value() || node->max_properties.has_value()) {
|
|
2583
|
-
auto op = std::make_shared<od_plan::obj_plan>();
|
|
2584
|
-
op->required_count = node->required.size();
|
|
2585
|
-
op->min_props = node->min_properties;
|
|
2586
|
-
op->max_props = node->max_properties;
|
|
2587
|
-
if (node->additional_properties_bool.has_value() &&
|
|
2588
|
-
!node->additional_properties_bool.value()) {
|
|
2589
|
-
op->no_additional = true;
|
|
2590
|
-
}
|
|
2591
|
-
// Build merged entries: each key appears once with required_idx + sub_plan
|
|
2592
|
-
std::unordered_map<std::string, size_t> key_to_idx;
|
|
2593
|
-
// Register required keys
|
|
2594
|
-
for (size_t i = 0; i < node->required.size() && i < 64; i++) {
|
|
2595
|
-
auto& rk = node->required[i];
|
|
2596
|
-
if (key_to_idx.find(rk) == key_to_idx.end()) {
|
|
2597
|
-
key_to_idx[rk] = op->entries.size();
|
|
2598
|
-
op->entries.push_back({rk, static_cast<int>(i), nullptr});
|
|
2599
|
-
} else {
|
|
2600
|
-
op->entries[key_to_idx[rk]].required_idx = static_cast<int>(i);
|
|
2601
|
-
}
|
|
2602
|
-
}
|
|
2603
|
-
// Register properties + compile sub-plans
|
|
2604
|
-
for (auto& [key, sub_node] : node->properties) {
|
|
2605
|
-
auto sub = compile_od_plan(sub_node);
|
|
2606
|
-
if (!sub || !sub->supported) { plan->supported = false; return plan; }
|
|
2607
|
-
auto it = key_to_idx.find(key);
|
|
2608
|
-
if (it != key_to_idx.end()) {
|
|
2609
|
-
op->entries[it->second].sub = std::move(sub);
|
|
2610
|
-
} else {
|
|
2611
|
-
key_to_idx[key] = op->entries.size();
|
|
2612
|
-
op->entries.push_back({key, -1, std::move(sub), od_plan::fast_kind::OTHER});
|
|
2613
|
-
}
|
|
2614
|
-
}
|
|
2615
|
-
// Compute fast_kind for each entry post-hoc — lets the obj iterator
|
|
2616
|
-
// skip the recursive od_exec_plan call (and its type().get + switch)
|
|
2617
|
-
// for primitive properties, and also skip type detection for nested
|
|
2618
|
-
// objects / primitive arrays.
|
|
2619
|
-
for (auto& e : op->entries) {
|
|
2620
|
-
if (!e.sub) continue;
|
|
2621
|
-
// Nested object: dispatch directly to value.get(obj) + iteration helper.
|
|
2622
|
-
if (e.sub->object && !e.sub->array) {
|
|
2623
|
-
e.fk = od_plan::fast_kind::OBJECT;
|
|
2624
|
-
continue;
|
|
2625
|
-
}
|
|
2626
|
-
// Array: dispatch directly to value.get(arr) + element iteration helper.
|
|
2627
|
-
if (e.sub->array && !e.sub->object) {
|
|
2628
|
-
e.fk = od_plan::fast_kind::ARRAY;
|
|
2629
|
-
continue;
|
|
2630
|
-
}
|
|
2631
|
-
if (e.sub->object || e.sub->array) continue; // unusual: both, skip
|
|
2632
|
-
uint8_t m = e.sub->type_mask;
|
|
2633
|
-
uint8_t s_bit = json_type_bit(json_type::string);
|
|
2634
|
-
uint8_t i_bit = json_type_bit(json_type::integer);
|
|
2635
|
-
uint8_t n_bit = json_type_bit(json_type::number);
|
|
2636
|
-
uint8_t b_bit = json_type_bit(json_type::boolean);
|
|
2637
|
-
if (m == s_bit) e.fk = od_plan::fast_kind::STRING;
|
|
2638
|
-
else if (m == i_bit || m == (i_bit | n_bit)) e.fk = od_plan::fast_kind::INTEGER;
|
|
2639
|
-
else if (m == b_bit) e.fk = od_plan::fast_kind::BOOLEAN;
|
|
2640
|
-
else if (m == 0 && e.sub->enum_check) {
|
|
2641
|
-
// Untyped enum: infer from enum value shapes
|
|
2642
|
-
auto& ec = *e.sub->enum_check;
|
|
2643
|
-
bool only_str = !ec.strings.empty() && ec.integers.empty() && ec.doubles.empty()
|
|
2644
|
-
&& !ec.has_null && !ec.has_true && !ec.has_false;
|
|
2645
|
-
bool only_int = ec.strings.empty() && !ec.integers.empty() && ec.doubles.empty()
|
|
2646
|
-
&& !ec.has_null && !ec.has_true && !ec.has_false;
|
|
2647
|
-
bool only_bool = ec.strings.empty() && ec.integers.empty() && ec.doubles.empty()
|
|
2648
|
-
&& !ec.has_null && (ec.has_true || ec.has_false);
|
|
2649
|
-
if (only_str) e.fk = od_plan::fast_kind::STRING;
|
|
2650
|
-
else if (only_int) e.fk = od_plan::fast_kind::INTEGER;
|
|
2651
|
-
else if (only_bool) e.fk = od_plan::fast_kind::BOOLEAN;
|
|
2652
|
-
}
|
|
2653
|
-
}
|
|
2654
|
-
plan->object = std::move(op);
|
|
2655
|
-
}
|
|
2656
|
-
|
|
2657
|
-
// Array plan
|
|
2658
|
-
if (node->items_schema || node->min_items.has_value() || node->max_items.has_value()) {
|
|
2659
|
-
auto ap = std::make_shared<od_plan::arr_plan>();
|
|
2660
|
-
ap->min_items = node->min_items;
|
|
2661
|
-
ap->max_items = node->max_items;
|
|
2662
|
-
if (node->items_schema) {
|
|
2663
|
-
ap->items = compile_od_plan(node->items_schema);
|
|
2664
|
-
if (!ap->items || !ap->items->supported) { plan->supported = false; return plan; }
|
|
2665
|
-
}
|
|
2666
|
-
plan->array = std::move(ap);
|
|
2667
|
-
}
|
|
2668
|
-
|
|
2669
|
-
return plan;
|
|
2670
|
-
}
|
|
2671
|
-
|
|
2672
|
-
// Fast ASCII check: if all bytes < 0x80, byte length == codepoint length
|
|
2673
|
-
static inline uint64_t utf8_length_fast(std::string_view s) {
|
|
2674
|
-
// Check 8 bytes at a time for non-ASCII
|
|
2675
|
-
const uint8_t* p = reinterpret_cast<const uint8_t*>(s.data());
|
|
2676
|
-
size_t n = s.size();
|
|
2677
|
-
size_t i = 0;
|
|
2678
|
-
uint64_t has_high = 0;
|
|
2679
|
-
for (; i + 8 <= n; i += 8) {
|
|
2680
|
-
uint64_t block;
|
|
2681
|
-
std::memcpy(&block, p + i, 8);
|
|
2682
|
-
has_high |= block & 0x8080808080808080ULL;
|
|
2683
|
-
}
|
|
2684
|
-
for (; i < n; i++) has_high |= p[i] & 0x80;
|
|
2685
|
-
if (has_high == 0) return n; // Pure ASCII — byte count == codepoint count
|
|
2686
|
-
return utf8_length(s); // Fallback to full counting
|
|
2687
|
-
}
|
|
2688
|
-
|
|
2689
|
-
// Execute an od_plan against a simdjson On-Demand value.
|
|
2690
|
-
// Each value consumed exactly once. Uses simdjson types directly — no od_type() overhead.
|
|
2691
|
-
// `pre_type` is an optional caller-supplied type hint; when set, the value.type().get
|
|
2692
|
-
// simdjson call is skipped (saves ~3-5 ns per recursion). Used by fast_kind::OBJECT
|
|
2693
|
-
// / ARRAY dispatch where the type is already established at compile time.
|
|
2694
|
-
static bool od_exec_plan(const od_plan& plan, simdjson::ondemand::value value,
|
|
2695
|
-
std::optional<simdjson::ondemand::json_type> pre_type = std::nullopt) {
|
|
2696
|
-
using sjt = simdjson::ondemand::json_type;
|
|
2697
|
-
sjt st;
|
|
2698
|
-
if (pre_type) {
|
|
2699
|
-
st = *pre_type;
|
|
2700
|
-
} else {
|
|
2701
|
-
if (value.type().get(st) != SUCCESS) return false;
|
|
2702
|
-
}
|
|
2703
|
-
|
|
2704
|
-
// Type check using simdjson type directly
|
|
2705
|
-
if (plan.type_mask) {
|
|
2706
|
-
uint8_t tbits;
|
|
2707
|
-
switch (st) {
|
|
2708
|
-
case sjt::string: tbits = json_type_bit(json_type::string); break;
|
|
2709
|
-
case sjt::boolean: tbits = json_type_bit(json_type::boolean); break;
|
|
2710
|
-
case sjt::null: tbits = json_type_bit(json_type::null_value); break;
|
|
2711
|
-
case sjt::object: tbits = json_type_bit(json_type::object); break;
|
|
2712
|
-
case sjt::array: tbits = json_type_bit(json_type::array); break;
|
|
2713
|
-
case sjt::number:
|
|
2714
|
-
// Only call get_number_type when schema has type constraint that distinguishes int/number
|
|
2715
|
-
tbits = json_type_bit(json_type::number) | json_type_bit(json_type::integer);
|
|
2716
|
-
if ((plan.type_mask & tbits) != tbits) {
|
|
2717
|
-
// Schema distinguishes — need to check actual number type
|
|
2718
|
-
simdjson::ondemand::number_type nt;
|
|
2719
|
-
if (value.get_number_type().get(nt) == SUCCESS &&
|
|
2720
|
-
nt != simdjson::ondemand::number_type::floating_point_number)
|
|
2721
|
-
tbits = json_type_bit(json_type::integer) | json_type_bit(json_type::number);
|
|
2722
|
-
else
|
|
2723
|
-
tbits = json_type_bit(json_type::number);
|
|
2724
|
-
}
|
|
2725
|
-
break;
|
|
2726
|
-
default: tbits = 0;
|
|
2727
|
-
}
|
|
2728
|
-
if (!(tbits & plan.type_mask)) return false;
|
|
2729
|
-
}
|
|
2730
|
-
|
|
2731
|
-
switch (st) {
|
|
2732
|
-
case sjt::number: {
|
|
2733
|
-
bool need_value = plan.num_flags || plan.enum_check;
|
|
2734
|
-
if (!need_value) break;
|
|
2735
|
-
double v;
|
|
2736
|
-
int64_t iv;
|
|
2737
|
-
bool got_int = (value.get(iv) == SUCCESS);
|
|
2738
|
-
if (got_int) {
|
|
2739
|
-
v = static_cast<double>(iv);
|
|
2740
|
-
} else if (value.get(v) != SUCCESS) {
|
|
2741
|
-
return false;
|
|
2742
|
-
}
|
|
2743
|
-
uint8_t f = plan.num_flags;
|
|
2744
|
-
if ((f & od_plan::HAS_MIN) && v < plan.num_min) return false;
|
|
2745
|
-
if ((f & od_plan::HAS_MAX) && v > plan.num_max) return false;
|
|
2746
|
-
if ((f & od_plan::HAS_EX_MIN) && v <= plan.num_ex_min) return false;
|
|
2747
|
-
if ((f & od_plan::HAS_EX_MAX) && v >= plan.num_ex_max) return false;
|
|
2748
|
-
if (f & od_plan::HAS_MUL) {
|
|
2749
|
-
double r = std::fmod(v, plan.num_mul);
|
|
2750
|
-
if (std::abs(r) > 1e-8 && std::abs(r - plan.num_mul) > 1e-8) return false;
|
|
2751
|
-
}
|
|
2752
|
-
if (plan.enum_check) {
|
|
2753
|
-
auto& ec = *plan.enum_check;
|
|
2754
|
-
bool match = false;
|
|
2755
|
-
if (got_int) {
|
|
2756
|
-
for (auto i : ec.integers) if (i == iv) { match = true; break; }
|
|
2757
|
-
}
|
|
2758
|
-
if (!match) {
|
|
2759
|
-
for (auto d : ec.doubles) if (d == v) { match = true; break; }
|
|
2760
|
-
}
|
|
2761
|
-
if (!match && got_int) {
|
|
2762
|
-
for (auto d : ec.doubles) if (d == v) { match = true; break; }
|
|
2763
|
-
}
|
|
2764
|
-
if (!match) return false;
|
|
2765
|
-
}
|
|
2766
|
-
break;
|
|
2767
|
-
}
|
|
2768
|
-
case sjt::string: {
|
|
2769
|
-
std::string_view sv;
|
|
2770
|
-
if (value.get(sv) != SUCCESS) return false;
|
|
2771
|
-
if (plan.min_length || plan.max_length) {
|
|
2772
|
-
uint64_t len = utf8_length_fast(sv);
|
|
2773
|
-
if (plan.min_length && len < *plan.min_length) return false;
|
|
2774
|
-
if (plan.max_length && len > *plan.max_length) return false;
|
|
2775
|
-
}
|
|
2776
|
-
if (plan.digit_pattern) {
|
|
2777
|
-
auto& dp = *plan.digit_pattern;
|
|
2778
|
-
if (sv.size() < dp.min_len || sv.size() > dp.max_len) return false;
|
|
2779
|
-
const uint8_t* p = reinterpret_cast<const uint8_t*>(sv.data());
|
|
2780
|
-
for (size_t i = 0, n = sv.size(); i < n; i++) {
|
|
2781
|
-
if (p[i] < '0' || p[i] > '9') return false;
|
|
2782
|
-
}
|
|
2783
|
-
}
|
|
2784
|
-
#ifndef ATA_NO_RE2
|
|
2785
|
-
else if (plan.pattern) {
|
|
2786
|
-
if (!re2::RE2::PartialMatch(re2::StringPiece(sv.data(), sv.size()), *plan.pattern))
|
|
2787
|
-
return false;
|
|
2788
|
-
}
|
|
2789
|
-
#endif
|
|
2790
|
-
if (plan.format_id != 255) {
|
|
2791
|
-
if (!check_format_by_id(sv, plan.format_id)) return false;
|
|
2792
|
-
}
|
|
2793
|
-
if (plan.enum_check) {
|
|
2794
|
-
auto& ec = *plan.enum_check;
|
|
2795
|
-
bool match = false;
|
|
2796
|
-
for (auto& s : ec.strings) {
|
|
2797
|
-
if (sv.size() == s.size() && std::memcmp(sv.data(), s.data(), s.size()) == 0) {
|
|
2798
|
-
match = true;
|
|
2799
|
-
break;
|
|
2800
|
-
}
|
|
2801
|
-
}
|
|
2802
|
-
if (!match) return false;
|
|
2803
|
-
}
|
|
2804
|
-
break;
|
|
2805
|
-
}
|
|
2806
|
-
case sjt::object: {
|
|
2807
|
-
if (!plan.object) break;
|
|
2808
|
-
auto& op = *plan.object;
|
|
2809
|
-
simdjson::ondemand::object obj;
|
|
2810
|
-
if (value.get(obj) != SUCCESS) return false;
|
|
2811
|
-
|
|
2812
|
-
uint64_t required_found = 0;
|
|
2813
|
-
uint64_t prop_count = 0;
|
|
2814
|
-
|
|
2815
|
-
for (auto field : obj) {
|
|
2816
|
-
// Fast key compare: use raw_json_string against schema keys directly,
|
|
2817
|
-
// skipping the unescape pass. Schema keys are assumed to have no
|
|
2818
|
-
// unescaped quotes (true for any well-formed JSON Schema).
|
|
2819
|
-
simdjson::ondemand::raw_json_string raw_key;
|
|
2820
|
-
if (field.key().get(raw_key) != SUCCESS) continue;
|
|
2821
|
-
prop_count++;
|
|
2822
|
-
|
|
2823
|
-
bool matched = false;
|
|
2824
|
-
for (auto& e : op.entries) {
|
|
2825
|
-
if (raw_key.unsafe_is_equal(e.key)) {
|
|
2826
|
-
if (e.required_idx >= 0)
|
|
2827
|
-
required_found |= (1ULL << e.required_idx);
|
|
2828
|
-
if (e.sub) {
|
|
2829
|
-
// Fast dispatch: skip recursive od_exec_plan + type().get + switch
|
|
2830
|
-
// for primitive sub-plans. Mirrors the constraint code in od_exec_plan.
|
|
2831
|
-
switch (e.fk) {
|
|
2832
|
-
case od_plan::fast_kind::INTEGER: {
|
|
2833
|
-
int64_t iv;
|
|
2834
|
-
if (field.value().get(iv) != SUCCESS) return false;
|
|
2835
|
-
auto& sub = *e.sub;
|
|
2836
|
-
uint8_t f = sub.num_flags;
|
|
2837
|
-
double v = static_cast<double>(iv);
|
|
2838
|
-
if ((f & od_plan::HAS_MIN) && v < sub.num_min) return false;
|
|
2839
|
-
if ((f & od_plan::HAS_MAX) && v > sub.num_max) return false;
|
|
2840
|
-
if ((f & od_plan::HAS_EX_MIN) && v <= sub.num_ex_min) return false;
|
|
2841
|
-
if ((f & od_plan::HAS_EX_MAX) && v >= sub.num_ex_max) return false;
|
|
2842
|
-
if (f & od_plan::HAS_MUL) {
|
|
2843
|
-
double r = std::fmod(v, sub.num_mul);
|
|
2844
|
-
if (std::abs(r) > 1e-8 && std::abs(r - sub.num_mul) > 1e-8) return false;
|
|
2845
|
-
}
|
|
2846
|
-
if (sub.enum_check) {
|
|
2847
|
-
bool em = false;
|
|
2848
|
-
for (auto i2 : sub.enum_check->integers) if (i2 == iv) { em = true; break; }
|
|
2849
|
-
if (!em) for (auto d : sub.enum_check->doubles) if (d == v) { em = true; break; }
|
|
2850
|
-
if (!em) return false;
|
|
2851
|
-
}
|
|
2852
|
-
break;
|
|
2853
|
-
}
|
|
2854
|
-
case od_plan::fast_kind::STRING: {
|
|
2855
|
-
std::string_view sv;
|
|
2856
|
-
if (field.value().get(sv) != SUCCESS) return false;
|
|
2857
|
-
auto& sub = *e.sub;
|
|
2858
|
-
if (sub.min_length || sub.max_length) {
|
|
2859
|
-
uint64_t len = utf8_length_fast(sv);
|
|
2860
|
-
if (sub.min_length && len < *sub.min_length) return false;
|
|
2861
|
-
if (sub.max_length && len > *sub.max_length) return false;
|
|
2862
|
-
}
|
|
2863
|
-
if (sub.digit_pattern) {
|
|
2864
|
-
auto& dp = *sub.digit_pattern;
|
|
2865
|
-
if (sv.size() < dp.min_len || sv.size() > dp.max_len) return false;
|
|
2866
|
-
const uint8_t* sp = reinterpret_cast<const uint8_t*>(sv.data());
|
|
2867
|
-
for (size_t i = 0, n = sv.size(); i < n; i++) {
|
|
2868
|
-
if (sp[i] < '0' || sp[i] > '9') return false;
|
|
2869
|
-
}
|
|
2870
|
-
}
|
|
2871
|
-
#ifndef ATA_NO_RE2
|
|
2872
|
-
else if (sub.pattern) {
|
|
2873
|
-
if (!re2::RE2::PartialMatch(re2::StringPiece(sv.data(), sv.size()), *sub.pattern))
|
|
2874
|
-
return false;
|
|
2875
|
-
}
|
|
2876
|
-
#endif
|
|
2877
|
-
if (sub.format_id != 255) {
|
|
2878
|
-
if (!check_format_by_id(sv, sub.format_id)) return false;
|
|
2879
|
-
}
|
|
2880
|
-
if (sub.enum_check) {
|
|
2881
|
-
bool em = false;
|
|
2882
|
-
for (auto& s : sub.enum_check->strings) {
|
|
2883
|
-
if (sv.size() == s.size() && std::memcmp(sv.data(), s.data(), s.size()) == 0) {
|
|
2884
|
-
em = true;
|
|
2885
|
-
break;
|
|
2886
|
-
}
|
|
2887
|
-
}
|
|
2888
|
-
if (!em) return false;
|
|
2889
|
-
}
|
|
2890
|
-
break;
|
|
2891
|
-
}
|
|
2892
|
-
case od_plan::fast_kind::BOOLEAN: {
|
|
2893
|
-
bool bv;
|
|
2894
|
-
if (field.value().get(bv) != SUCCESS) return false;
|
|
2895
|
-
if (e.sub->enum_check) {
|
|
2896
|
-
if (bv ? !e.sub->enum_check->has_true : !e.sub->enum_check->has_false) return false;
|
|
2897
|
-
}
|
|
2898
|
-
break;
|
|
2899
|
-
}
|
|
2900
|
-
case od_plan::fast_kind::OBJECT: {
|
|
2901
|
-
simdjson::ondemand::value fv;
|
|
2902
|
-
if (field.value().get(fv) != SUCCESS) return false;
|
|
2903
|
-
if (!od_exec_plan(*e.sub, fv,
|
|
2904
|
-
simdjson::ondemand::json_type::object)) return false;
|
|
2905
|
-
break;
|
|
2906
|
-
}
|
|
2907
|
-
case od_plan::fast_kind::ARRAY: {
|
|
2908
|
-
simdjson::ondemand::value fv;
|
|
2909
|
-
if (field.value().get(fv) != SUCCESS) return false;
|
|
2910
|
-
if (!od_exec_plan(*e.sub, fv,
|
|
2911
|
-
simdjson::ondemand::json_type::array)) return false;
|
|
2912
|
-
break;
|
|
2913
|
-
}
|
|
2914
|
-
case od_plan::fast_kind::OTHER:
|
|
2915
|
-
default: {
|
|
2916
|
-
simdjson::ondemand::value fv;
|
|
2917
|
-
if (field.value().get(fv) != SUCCESS) return false;
|
|
2918
|
-
if (!od_exec_plan(*e.sub, fv)) return false;
|
|
2919
|
-
}
|
|
2920
|
-
}
|
|
2921
|
-
}
|
|
2922
|
-
matched = true;
|
|
2923
|
-
break;
|
|
2924
|
-
}
|
|
2925
|
-
}
|
|
2926
|
-
// Safety net: if no match via raw byte compare, the JSON key may be
|
|
2927
|
-
// escaped (e.g., "aname"). Fall back to a properly-unescaped
|
|
2928
|
-
// compare so escaped keys still match the unescaped schema keys.
|
|
2929
|
-
if (!matched) {
|
|
2930
|
-
std::string_view raw_view = field.escaped_key();
|
|
2931
|
-
if (raw_view.find('\\') != std::string_view::npos) {
|
|
2932
|
-
std::string_view ukey;
|
|
2933
|
-
if (field.unescaped_key().get(ukey) == SUCCESS) {
|
|
2934
|
-
for (auto& e : op.entries) {
|
|
2935
|
-
if (ukey == e.key) {
|
|
2936
|
-
if (e.required_idx >= 0)
|
|
2937
|
-
required_found |= (1ULL << e.required_idx);
|
|
2938
|
-
if (e.sub) {
|
|
2939
|
-
simdjson::ondemand::value fv;
|
|
2940
|
-
if (field.value().get(fv) != SUCCESS) return false;
|
|
2941
|
-
if (!od_exec_plan(*e.sub, fv)) return false;
|
|
2942
|
-
}
|
|
2943
|
-
matched = true;
|
|
2944
|
-
break;
|
|
2945
|
-
}
|
|
2946
|
-
}
|
|
2947
|
-
}
|
|
2948
|
-
}
|
|
2949
|
-
}
|
|
2950
|
-
if (!matched && op.no_additional) return false;
|
|
2951
|
-
}
|
|
2952
|
-
|
|
2953
|
-
uint64_t required_mask = (op.required_count >= 64)
|
|
2954
|
-
? ~0ULL : ((1ULL << op.required_count) - 1);
|
|
2955
|
-
if ((required_found & required_mask) != required_mask) return false;
|
|
2956
|
-
if (op.min_props && prop_count < *op.min_props) return false;
|
|
2957
|
-
if (op.max_props && prop_count > *op.max_props) return false;
|
|
2958
|
-
break;
|
|
2959
|
-
}
|
|
2960
|
-
case sjt::array: {
|
|
2961
|
-
if (!plan.array) break;
|
|
2962
|
-
auto& ap = *plan.array;
|
|
2963
|
-
simdjson::ondemand::array arr;
|
|
2964
|
-
if (value.get(arr) != SUCCESS) return false;
|
|
2965
|
-
|
|
2966
|
-
uint64_t count = 0;
|
|
2967
|
-
for (auto elem : arr) {
|
|
2968
|
-
simdjson::ondemand::value v;
|
|
2969
|
-
if (elem.get(v) != SUCCESS) return false;
|
|
2970
|
-
if (ap.items && !od_exec_plan(*ap.items, v)) return false;
|
|
2971
|
-
count++;
|
|
2972
|
-
}
|
|
2973
|
-
if (ap.min_items && count < *ap.min_items) return false;
|
|
2974
|
-
if (ap.max_items && count > *ap.max_items) return false;
|
|
2975
|
-
break;
|
|
2976
|
-
}
|
|
2977
|
-
case sjt::boolean: {
|
|
2978
|
-
if (plan.enum_check) {
|
|
2979
|
-
bool b;
|
|
2980
|
-
if (value.get(b) != SUCCESS) return false;
|
|
2981
|
-
if (b ? !plan.enum_check->has_true : !plan.enum_check->has_false) return false;
|
|
2982
|
-
}
|
|
2983
|
-
break;
|
|
2984
|
-
}
|
|
2985
|
-
case sjt::null: {
|
|
2986
|
-
if (plan.enum_check && !plan.enum_check->has_null) return false;
|
|
2987
|
-
break;
|
|
2988
|
-
}
|
|
2989
|
-
default:
|
|
2990
|
-
break;
|
|
2991
|
-
}
|
|
2992
|
-
|
|
2993
|
-
return true;
|
|
2994
|
-
}
|
|
2995
|
-
|
|
2996
|
-
schema_ref compile(std::string_view schema_json) {
|
|
2997
|
-
auto ctx = std::make_shared<compiled_schema>();
|
|
2998
|
-
ctx->raw_schema = std::string(schema_json);
|
|
2999
|
-
|
|
3000
|
-
dom::element doc;
|
|
3001
|
-
auto result = ctx->parser.parse(ctx->raw_schema);
|
|
3002
|
-
if (result.error()) {
|
|
3003
|
-
return schema_ref{nullptr};
|
|
3004
|
-
}
|
|
3005
|
-
doc = result.value_unsafe();
|
|
3006
|
-
|
|
3007
|
-
ctx->root = compile_node(doc, *ctx);
|
|
3008
|
-
|
|
3009
|
-
if (!ctx->compile_error.empty()) {
|
|
3010
|
-
return schema_ref{nullptr};
|
|
3011
|
-
}
|
|
3012
|
-
|
|
3013
|
-
// Generate codegen plan
|
|
3014
|
-
cg_compile(ctx->root.get(), ctx->gen_plan, ctx->gen_plan.code);
|
|
3015
|
-
ctx->gen_plan.code.push_back({cg::op::END});
|
|
3016
|
-
ctx->use_ondemand = plan_supports_ondemand(ctx->gen_plan);
|
|
3017
|
-
ctx->od = compile_od_plan(ctx->root);
|
|
3018
|
-
|
|
3019
|
-
schema_ref ref;
|
|
3020
|
-
ref.impl = ctx;
|
|
3021
|
-
ref.warnings = std::move(ctx->warnings);
|
|
3022
|
-
return ref;
|
|
3023
|
-
}
|
|
3024
|
-
|
|
3025
|
-
validation_result validate(const schema_ref& schema, std::string_view json,
|
|
3026
|
-
const validate_options& opts) {
|
|
3027
|
-
if (!schema.impl || !schema.impl->root) {
|
|
3028
|
-
return {false, {{error_code::invalid_schema, "", "schema not compiled"}}};
|
|
3029
|
-
}
|
|
3030
|
-
|
|
3031
|
-
// Free padding trick: avoid padded_string copy when possible
|
|
3032
|
-
simdjson::padded_string fallback;
|
|
3033
|
-
auto psv = get_free_padded_view(json.data(), json.size(), fallback);
|
|
3034
|
-
|
|
3035
|
-
// Ultra-fast path: On Demand (no DOM materialization)
|
|
3036
|
-
static constexpr size_t OD_THRESHOLD = 32;
|
|
3037
|
-
if (schema.impl->use_ondemand && !schema.impl->gen_plan.code.empty() &&
|
|
3038
|
-
json.size() >= OD_THRESHOLD) {
|
|
3039
|
-
auto od_result = tl_od_parser().iterate(psv);
|
|
3040
|
-
if (!od_result.error()) {
|
|
3041
|
-
simdjson::ondemand::value root_val;
|
|
3042
|
-
if (od_result.get_value().get(root_val) == SUCCESS) {
|
|
3043
|
-
if (od_exec(schema.impl->gen_plan, schema.impl->gen_plan.code, root_val)) {
|
|
3044
|
-
return {true, {}};
|
|
3045
|
-
}
|
|
3046
|
-
}
|
|
3047
|
-
}
|
|
3048
|
-
// Need fresh view for DOM parse (On Demand consumed it)
|
|
3049
|
-
psv = get_free_padded_view(json.data(), json.size(), fallback);
|
|
3050
|
-
}
|
|
3051
|
-
|
|
3052
|
-
auto& dom_p = tl_dom_parser();
|
|
3053
|
-
auto result = dom_p.parse(psv);
|
|
3054
|
-
if (result.error()) {
|
|
3055
|
-
return {false, {{error_code::invalid_json, "", "invalid JSON document"}}};
|
|
3056
|
-
}
|
|
3057
|
-
|
|
3058
|
-
// Fast path: codegen bytecode execution (DOM)
|
|
3059
|
-
if (!schema.impl->use_ondemand && !schema.impl->gen_plan.code.empty()) {
|
|
3060
|
-
if (cg_exec(schema.impl->gen_plan, schema.impl->gen_plan.code,
|
|
3061
|
-
result.value_unsafe())) {
|
|
3062
|
-
return {true, {}};
|
|
3063
|
-
}
|
|
3064
|
-
// Codegen said invalid OR hit COMPOSITION — fall through to tree walker
|
|
3065
|
-
}
|
|
3066
|
-
|
|
3067
|
-
// Slow path: tree walker with error details (reuse already-parsed DOM)
|
|
3068
|
-
std::vector<validation_error> errors;
|
|
3069
|
-
if (schema.impl->has_dynamic_refs) {
|
|
3070
|
-
dynamic_scope_t scope;
|
|
3071
|
-
auto rit = schema.impl->resource_dynamic_anchors.find("");
|
|
3072
|
-
if (rit != schema.impl->resource_dynamic_anchors.end()) {
|
|
3073
|
-
scope.push_back(&rit->second);
|
|
3074
|
-
}
|
|
3075
|
-
if (!schema.impl->root->id.empty()) {
|
|
3076
|
-
auto iit = schema.impl->resource_dynamic_anchors.find(schema.impl->root->id);
|
|
3077
|
-
if (iit != schema.impl->resource_dynamic_anchors.end()) {
|
|
3078
|
-
scope.push_back(&iit->second);
|
|
3079
|
-
}
|
|
3080
|
-
}
|
|
3081
|
-
validate_node(schema.impl->root, result.value_unsafe(), "", *schema.impl, errors,
|
|
3082
|
-
opts.all_errors, &scope);
|
|
3083
|
-
} else {
|
|
3084
|
-
validate_node(schema.impl->root, result.value_unsafe(), "", *schema.impl, errors,
|
|
3085
|
-
opts.all_errors);
|
|
3086
|
-
}
|
|
3087
|
-
|
|
3088
|
-
return {errors.empty(), std::move(errors)};
|
|
3089
|
-
}
|
|
3090
|
-
|
|
3091
|
-
validation_result validate(std::string_view schema_json,
|
|
3092
|
-
std::string_view json,
|
|
3093
|
-
const validate_options& opts) {
|
|
3094
|
-
auto s = compile(schema_json);
|
|
3095
|
-
if (!s) {
|
|
3096
|
-
return {false, {{error_code::invalid_schema, "", "failed to compile schema"}}};
|
|
3097
|
-
}
|
|
3098
|
-
return validate(s, json, opts);
|
|
3099
|
-
}
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
bool is_valid_prepadded(const schema_ref& schema, const char* data, size_t length) {
|
|
3103
|
-
if (!schema.impl || !schema.impl->root) return false;
|
|
3104
|
-
|
|
3105
|
-
simdjson::padded_string fallback;
|
|
3106
|
-
auto psv = get_free_padded_view(data, length, fallback);
|
|
3107
|
-
|
|
3108
|
-
// On-Demand fast path: skip DOM parse entirely
|
|
3109
|
-
// Minimum 32 bytes — On-Demand doesn't fully validate small malformed docs
|
|
3110
|
-
if (schema.impl->od && schema.impl->od->supported && length >= 32) {
|
|
3111
|
-
auto od_result = tl_od_parser().iterate(psv);
|
|
3112
|
-
if (!od_result.error()) {
|
|
3113
|
-
simdjson::ondemand::value root_val;
|
|
3114
|
-
if (od_result.get_value().get(root_val) == SUCCESS) {
|
|
3115
|
-
if (od_exec_plan(*schema.impl->od, root_val)) {
|
|
3116
|
-
return true;
|
|
3117
|
-
}
|
|
3118
|
-
}
|
|
3119
|
-
}
|
|
3120
|
-
psv = get_free_padded_view(data, length, fallback);
|
|
3121
|
-
}
|
|
3122
|
-
|
|
3123
|
-
auto result = tl_dom_parser().parse(psv);
|
|
3124
|
-
if (result.error()) return false;
|
|
3125
|
-
|
|
3126
|
-
if (!schema.impl->gen_plan.code.empty()) {
|
|
3127
|
-
return cg_exec(schema.impl->gen_plan, schema.impl->gen_plan.code, result.value_unsafe());
|
|
3128
|
-
}
|
|
3129
|
-
|
|
3130
|
-
return validate_fast(schema.impl->root, result.value_unsafe(), *schema.impl);
|
|
3131
|
-
}
|
|
3132
|
-
|
|
3133
|
-
bool is_valid_buf(const schema_ref& schema, const uint8_t* data, size_t length) {
|
|
3134
|
-
if (!schema.impl || !schema.impl->root || !data || length == 0) return false;
|
|
3135
|
-
|
|
3136
|
-
// Thread-local buffer with simdjson padding — reused across calls
|
|
3137
|
-
thread_local std::string tl_buf;
|
|
3138
|
-
const size_t needed = length + REQUIRED_PADDING;
|
|
3139
|
-
if (tl_buf.size() < needed) tl_buf.resize(needed);
|
|
3140
|
-
std::memcpy(tl_buf.data(), data, length);
|
|
3141
|
-
std::memset(tl_buf.data() + length, 0, REQUIRED_PADDING);
|
|
3142
|
-
|
|
3143
|
-
return is_valid_prepadded(schema, tl_buf.data(), length);
|
|
3144
|
-
}
|
|
3145
|
-
|
|
3146
|
-
} // namespace ata
|