@altopelago/aeos-core 0.10.0 → 0.12.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/README.md +36 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/rules/numericForm.d.ts.map +1 -1
- package/dist/rules/numericForm.js +1 -9
- package/dist/rules/numericForm.js.map +1 -1
- package/dist/rules/presence.d.ts.map +1 -1
- package/dist/rules/presence.js +0 -2
- package/dist/rules/presence.js.map +1 -1
- package/dist/rules/referenceForm.d.ts.map +1 -1
- package/dist/rules/referenceForm.js +9 -3
- package/dist/rules/referenceForm.js.map +1 -1
- package/dist/rules/schemaIndex.d.ts.map +1 -1
- package/dist/rules/schemaIndex.js +27 -2
- package/dist/rules/schemaIndex.js.map +1 -1
- package/dist/rules/stringForm.js +2 -1
- package/dist/rules/stringForm.js.map +1 -1
- package/dist/rules/typeCheck.d.ts +1 -0
- package/dist/rules/typeCheck.d.ts.map +1 -1
- package/dist/rules/typeCheck.js +7 -2
- package/dist/rules/typeCheck.js.map +1 -1
- package/dist/schema-codec.d.ts +19 -0
- package/dist/schema-codec.d.ts.map +1 -0
- package/dist/schema-codec.js +424 -0
- package/dist/schema-codec.js.map +1 -0
- package/dist/schema-tools.d.ts +90 -0
- package/dist/schema-tools.d.ts.map +1 -0
- package/dist/schema-tools.js +465 -0
- package/dist/schema-tools.js.map +1 -0
- package/dist/types/schema.d.ts +25 -4
- package/dist/types/schema.d.ts.map +1 -1
- package/dist/types/schema.js.map +1 -1
- package/dist/util/datatypes.d.ts +13 -0
- package/dist/util/datatypes.d.ts.map +1 -0
- package/dist/util/datatypes.js +50 -0
- package/dist/util/datatypes.js.map +1 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +123 -198
- package/dist/validate.js.map +1 -1
- package/package.json +8 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"datatypes.js","sourceRoot":"","sources":["../../src/util/datatypes.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB;IACzC,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,CAAC,UAAU,EAAE,YAAY,CAAC;SACpC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;SACzB,MAAM,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC/D,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;AACrC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAgB;IACjD,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,KAAK,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IACpD,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9C,IAAI,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,OAAO,GAAG,CAAY,CAAC;QACrD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,OAAO,EAAE,CAAC;QACtC,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAA4B,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC;IACtH,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,EAAE,CAAC;IACd,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,yBAAyB,CAAC,QAA4B;IAClE,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACxC,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;IAChC,IAAI,OAAO,CAAC,WAAW,EAAE,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACnD,MAAM,KAAK,GAAG,oBAAoB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACjD,IAAI,KAAK;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,IAAI,YAAY,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IACjE,MAAM,MAAM,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC7C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,MAAM,CAAC,CAAC,CAAC,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACtE,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACxB,OAAO,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAC/E,CAAC"}
|
package/dist/validate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,KAAK,EAAc,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,KAAK,EAAc,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAY1D,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AA4EzE;;GAEG;AACH,MAAM,WAAW,eAAe;IAC5B;;OAEG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,gCAAgC,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;IAErE,+DAA+D;IAC/D,QAAQ,CAAC,cAAc,CAAC,EAAE,gBAAgB,CAAC;CAE9C;AAiJD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,QAAQ,CACpB,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,QAAQ,EAChB,OAAO,GAAE,eAAoB,GAC9B,cAAc,CAqWhB"}
|
package/dist/validate.js
CHANGED
|
@@ -13,6 +13,8 @@ import { checkTypes } from './rules/typeCheck.js';
|
|
|
13
13
|
import { checkReferenceForms } from './rules/referenceForm.js';
|
|
14
14
|
import { checkNumericForm } from './rules/numericForm.js';
|
|
15
15
|
import { checkStringForm, checkPatterns, matchesPortablePattern } from './rules/stringForm.js';
|
|
16
|
+
import { datatypeBase, declaredRadixFromDatatype, parseClarifierValues } from './util/datatypes.js';
|
|
17
|
+
import { parseAddress, resolveAddress, } from '@altopelago/sansa';
|
|
16
18
|
const TYPE_ALIASES = {
|
|
17
19
|
NumberLiteral: ['NumberLiteral'],
|
|
18
20
|
StringLiteral: ['StringLiteral'],
|
|
@@ -29,10 +31,11 @@ const TYPE_ALIASES = {
|
|
|
29
31
|
RadixLiteral: ['RadixLiteral'],
|
|
30
32
|
EncodingLiteral: ['EncodingLiteral'],
|
|
31
33
|
SeparatorLiteral: ['SeparatorLiteral'],
|
|
34
|
+
SansaAddressLiteral: ['SansaAddressLiteral'],
|
|
32
35
|
DateLiteral: ['DateLiteral'],
|
|
33
36
|
TimeLiteral: ['TimeLiteral'],
|
|
34
37
|
DateTimeLiteral: ['DateTimeLiteral'],
|
|
35
|
-
|
|
38
|
+
WTCDateTimeLiteral: ['WTCDateTimeLiteral'],
|
|
36
39
|
CloneReference: ['CloneReference'],
|
|
37
40
|
PointerReference: ['PointerReference'],
|
|
38
41
|
NodeLiteral: ['NodeLiteral'],
|
|
@@ -40,6 +43,15 @@ const TYPE_ALIASES = {
|
|
|
40
43
|
function formatQuotedMemberSegment(key) {
|
|
41
44
|
return `.[${JSON.stringify(String(key))}]`;
|
|
42
45
|
}
|
|
46
|
+
function formatMemberSelector(key) {
|
|
47
|
+
const value = String(key);
|
|
48
|
+
return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)
|
|
49
|
+
? `.${value}`
|
|
50
|
+
: formatQuotedMemberSegment(value);
|
|
51
|
+
}
|
|
52
|
+
function appendAttributePath(basePath, key) {
|
|
53
|
+
return `${basePath}.@${formatMemberSelector(key)}`;
|
|
54
|
+
}
|
|
43
55
|
const DEFAULT_RESOURCE_POLICY = {
|
|
44
56
|
max_events: 100_000,
|
|
45
57
|
max_rules: 10_000,
|
|
@@ -89,13 +101,14 @@ const STRING_LIKE_VALUE_TYPES = new Set([
|
|
|
89
101
|
'StringLiteral',
|
|
90
102
|
'TrimtickLiteral',
|
|
91
103
|
'SeparatorLiteral',
|
|
104
|
+
'SansaAddressLiteral',
|
|
92
105
|
'HexLiteral',
|
|
93
106
|
'EncodingLiteral',
|
|
94
107
|
'NullLiteral',
|
|
95
108
|
'DateLiteral',
|
|
96
109
|
'TimeLiteral',
|
|
97
110
|
'DateTimeLiteral',
|
|
98
|
-
'
|
|
111
|
+
'WTCDateTimeLiteral',
|
|
99
112
|
]);
|
|
100
113
|
function stringLikePayloadLength(event) {
|
|
101
114
|
if (!STRING_LIKE_VALUE_TYPES.has(event.type))
|
|
@@ -109,7 +122,7 @@ function enforceStringLengthResourceBudget(info, path, policy, ctx) {
|
|
|
109
122
|
emitResourceError(ctx, path, `String-like payload length ${payloadLength} exceeds max_string_length_default ${policy.max_string_length_default}`, info.span);
|
|
110
123
|
}
|
|
111
124
|
for (const [key, attribute] of info.attributes ?? []) {
|
|
112
|
-
enforceStringLengthResourceBudget(attribute,
|
|
125
|
+
enforceStringLengthResourceBudget(attribute, appendAttributePath(path, key), policy, ctx);
|
|
113
126
|
}
|
|
114
127
|
}
|
|
115
128
|
function inspectSchemaResourceShape(schema, policy, ctx) {
|
|
@@ -143,7 +156,7 @@ function inspectConstraintResourceShape(constraints, path, depth, policy, ctx) {
|
|
|
143
156
|
}
|
|
144
157
|
if (constraints.attributes !== undefined) {
|
|
145
158
|
for (const [key, child] of Object.entries(constraints.attributes)) {
|
|
146
|
-
inspectConstraintResourceShape(child,
|
|
159
|
+
inspectConstraintResourceShape(child, appendAttributePath(path, key), depth + 1, policy, ctx);
|
|
147
160
|
}
|
|
148
161
|
}
|
|
149
162
|
}
|
|
@@ -231,24 +244,9 @@ export function validate(aes, schema, options = {}) {
|
|
|
231
244
|
function decodeSeparatorChars(datatype) {
|
|
232
245
|
if (!datatype)
|
|
233
246
|
return [];
|
|
234
|
-
|
|
235
|
-
if (!match)
|
|
236
|
-
return [];
|
|
237
|
-
const payload = match[1] ?? '';
|
|
238
|
-
if (payload.length === 0)
|
|
247
|
+
if (datatypeBase(datatype).toLowerCase() !== 'sep')
|
|
239
248
|
return [];
|
|
240
|
-
|
|
241
|
-
let i = 0;
|
|
242
|
-
while (i < payload.length) {
|
|
243
|
-
separators.push(payload[i]);
|
|
244
|
-
i += 1;
|
|
245
|
-
if (i < payload.length) {
|
|
246
|
-
if (payload[i] !== ',')
|
|
247
|
-
return [];
|
|
248
|
-
i += 1;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
return separators;
|
|
249
|
+
return parseClarifierValues(datatype).filter((value) => typeof value === 'string');
|
|
252
250
|
}
|
|
253
251
|
// Phase 2 — Baseline invariants
|
|
254
252
|
const seen = new Map();
|
|
@@ -274,6 +272,7 @@ export function validate(aes, schema, options = {}) {
|
|
|
274
272
|
...(attributes ? { attributes } : {}),
|
|
275
273
|
};
|
|
276
274
|
eventsByPath.set(elementPath, info);
|
|
275
|
+
addAttributeEvents(elementPath, attributes);
|
|
277
276
|
}
|
|
278
277
|
}
|
|
279
278
|
function buildAttributeInfoMap(attributes) {
|
|
@@ -300,6 +299,22 @@ export function validate(aes, schema, options = {}) {
|
|
|
300
299
|
}
|
|
301
300
|
return mapped;
|
|
302
301
|
}
|
|
302
|
+
function addAttributeEvents(basePath, attributes) {
|
|
303
|
+
if (!attributes)
|
|
304
|
+
return;
|
|
305
|
+
for (const [key, attribute] of attributes.entries()) {
|
|
306
|
+
const attributePath = appendAttributePath(basePath, key);
|
|
307
|
+
eventsByPath.set(attributePath, {
|
|
308
|
+
type: attribute.type,
|
|
309
|
+
raw: attribute.raw,
|
|
310
|
+
value: attribute.value,
|
|
311
|
+
...(attribute.datatype !== undefined ? { datatype: attribute.datatype } : {}),
|
|
312
|
+
span: attribute.span,
|
|
313
|
+
...(attribute.attributes ? { attributes: attribute.attributes } : {}),
|
|
314
|
+
});
|
|
315
|
+
addAttributeEvents(attributePath, attribute.attributes);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
303
318
|
for (let i = 0; i < aes.length; i++) {
|
|
304
319
|
const event = aes[i];
|
|
305
320
|
const pathStr = formatCanonicalPath(event.path);
|
|
@@ -338,6 +353,7 @@ export function validate(aes, schema, options = {}) {
|
|
|
338
353
|
...(attributes ? { attributes } : {}),
|
|
339
354
|
};
|
|
340
355
|
eventsByPath.set(pathStr, info);
|
|
356
|
+
addAttributeEvents(pathStr, attributes);
|
|
341
357
|
if ((event.value.type === 'TupleLiteral' || event.value.type === 'ListLiteral' || event.value.type === 'ListNode')
|
|
342
358
|
&& Array.isArray(event.value.elements)) {
|
|
343
359
|
containerArity.set(pathStr, event.value.elements.length);
|
|
@@ -391,12 +407,12 @@ export function validate(aes, schema, options = {}) {
|
|
|
391
407
|
// Phase 3: Build rule index from schema (run after baseline invariants)
|
|
392
408
|
const ruleIndex = buildRuleIndex(schema, ctx);
|
|
393
409
|
const selectorExpansionBudget = { count: 0 };
|
|
394
|
-
const expandedRuleIndex =
|
|
410
|
+
const expandedRuleIndex = expandSelectorRules(ruleIndex, schema, eventsByPath, ctx, resourcePolicy, selectorExpansionBudget);
|
|
395
411
|
const effectiveRuleIndex = mergeDatatypeRules(expandedRuleIndex, schema.datatype_rules, eventsByPath);
|
|
396
412
|
// Phase 4: Presence checks (required fields)
|
|
397
|
-
const boundPaths = new Set(
|
|
413
|
+
const boundPaths = new Set(eventsByPath.keys());
|
|
398
414
|
checkPresence(effectiveRuleIndex, boundPaths, ctx);
|
|
399
|
-
checkWorldPolicy(schema, aes, boundPaths, ctx);
|
|
415
|
+
checkWorldPolicy(schema, aes, boundPaths, eventsByPath, ctx);
|
|
400
416
|
// Phase 5: Type checks (literal kind)
|
|
401
417
|
checkReferenceForms(schema, effectiveRuleIndex, eventsByPath, ctx);
|
|
402
418
|
const effectiveEventsByPath = resolveReferenceFormEvents(effectiveRuleIndex, eventsByPath, resourcePolicy, ctx);
|
|
@@ -494,7 +510,7 @@ export function validate(aes, schema, options = {}) {
|
|
|
494
510
|
}
|
|
495
511
|
return createPassingEnvelope(guarantees, ctx.warnings);
|
|
496
512
|
}
|
|
497
|
-
function checkWorldPolicy(schema, aes, boundPaths, ctx) {
|
|
513
|
+
function checkWorldPolicy(schema, aes, boundPaths, eventsByPath, ctx) {
|
|
498
514
|
if ((schema.world ?? 'open') !== 'closed')
|
|
499
515
|
return;
|
|
500
516
|
const allowedRules = schema.rules
|
|
@@ -504,6 +520,12 @@ function checkWorldPolicy(schema, aes, boundPaths, ctx) {
|
|
|
504
520
|
? { kind: 'selector', value: rule.selector }
|
|
505
521
|
: null)
|
|
506
522
|
.filter((rule) => rule !== null);
|
|
523
|
+
const selectorMatches = new Map();
|
|
524
|
+
for (const rule of allowedRules) {
|
|
525
|
+
if (rule.kind !== 'selector' || selectorMatches.has(rule.value))
|
|
526
|
+
continue;
|
|
527
|
+
selectorMatches.set(rule.value, resolveSansaSelectorPathSet(rule.value, eventsByPath, ctx));
|
|
528
|
+
}
|
|
507
529
|
for (const event of aes) {
|
|
508
530
|
const key = typeof event.key === 'string' ? event.key : '';
|
|
509
531
|
if (key.startsWith('aeon:'))
|
|
@@ -512,7 +534,7 @@ function checkWorldPolicy(schema, aes, boundPaths, ctx) {
|
|
|
512
534
|
if (!boundPaths.has(path))
|
|
513
535
|
continue;
|
|
514
536
|
if (allowedRules.some((rule) => rule.kind === 'selector'
|
|
515
|
-
?
|
|
537
|
+
? selectorMatches.get(rule.value)?.has(path) === true
|
|
516
538
|
: matchesAllowedPath(path, rule.value)))
|
|
517
539
|
continue;
|
|
518
540
|
emitError(ctx, createDiag(path, toTupleLocal(event.span), `Binding '${path}' is not allowed by closed-world schema`, ErrorCodes.UNEXPECTED_BINDING));
|
|
@@ -549,10 +571,11 @@ function expandSelectorRules(ruleIndex, schema, eventsByPath, ctx, resourcePolic
|
|
|
549
571
|
continue;
|
|
550
572
|
if (typeof rule.path === 'string' && rule.path.length > 0)
|
|
551
573
|
continue;
|
|
574
|
+
const resolvedPaths = resolveSansaSelectorPathSet(rule.selector, eventsByPath, ctx);
|
|
575
|
+
if (resolvedPaths === null)
|
|
576
|
+
continue;
|
|
552
577
|
let matched = false;
|
|
553
|
-
for (const actualPath of
|
|
554
|
-
if (!matchesSelectorPath(actualPath, rule.selector))
|
|
555
|
-
continue;
|
|
578
|
+
for (const actualPath of resolvedPaths) {
|
|
556
579
|
matched = true;
|
|
557
580
|
expansionBudget.count += 1;
|
|
558
581
|
if (expansionBudget.count > resourcePolicy.max_selector_expansions) {
|
|
@@ -569,25 +592,6 @@ function expandSelectorRules(ruleIndex, schema, eventsByPath, ctx, resourcePolic
|
|
|
569
592
|
}
|
|
570
593
|
return expanded;
|
|
571
594
|
}
|
|
572
|
-
function expandWildcardRules(ruleIndex, eventsByPath, ctx, resourcePolicy, expansionBudget) {
|
|
573
|
-
const expanded = new Map(ruleIndex);
|
|
574
|
-
for (const [path, rule] of ruleIndex.entries()) {
|
|
575
|
-
if (!path.includes('[*]'))
|
|
576
|
-
continue;
|
|
577
|
-
expanded.delete(path);
|
|
578
|
-
for (const actualPath of eventsByPath.keys()) {
|
|
579
|
-
if (matchesAllowedPath(actualPath, path)) {
|
|
580
|
-
expansionBudget.count += 1;
|
|
581
|
-
if (expansionBudget.count > resourcePolicy.max_selector_expansions) {
|
|
582
|
-
emitResourceError(ctx, path, `Wildcard expansion count exceeds max_selector_expansions ${resourcePolicy.max_selector_expansions}`);
|
|
583
|
-
return expanded;
|
|
584
|
-
}
|
|
585
|
-
expanded.set(actualPath, { ...rule, path: actualPath });
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
return expanded;
|
|
590
|
-
}
|
|
591
595
|
function selectAnyOfRules(ruleIndex, eventsByPath, ctx) {
|
|
592
596
|
const selected = new Map(ruleIndex);
|
|
593
597
|
for (const [path, rule] of ruleIndex.entries()) {
|
|
@@ -701,156 +705,93 @@ function formatReferenceLookupPath(segments) {
|
|
|
701
705
|
: formatQuotedMemberSegment(segment);
|
|
702
706
|
continue;
|
|
703
707
|
}
|
|
704
|
-
out
|
|
705
|
-
? `@${segment.key}`
|
|
706
|
-
: `@[${JSON.stringify(segment.key)}]`;
|
|
708
|
+
out = appendAttributePath(out, segment.key);
|
|
707
709
|
}
|
|
708
710
|
return out;
|
|
709
711
|
}
|
|
710
712
|
function matchesAllowedPath(actualPath, allowedPath) {
|
|
711
|
-
|
|
712
|
-
return true;
|
|
713
|
-
// Closed-world schemas may allow list descendants via canonical wildcard paths
|
|
714
|
-
// such as `$.items[*]` or `$.items[*].x`.
|
|
715
|
-
if (!allowedPath.includes('[*]'))
|
|
716
|
-
return false;
|
|
717
|
-
const escaped = allowedPath
|
|
718
|
-
.split('[*]')
|
|
719
|
-
.map((part) => part.replace(/[|\\{}()[\]^$+?.]/g, '\\$&'))
|
|
720
|
-
.join('\\[\\d+\\]');
|
|
721
|
-
const pattern = `^${escaped}$`;
|
|
722
|
-
return new RegExp(pattern).test(actualPath);
|
|
713
|
+
return actualPath === allowedPath;
|
|
723
714
|
}
|
|
724
|
-
function
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
while (index < path.length) {
|
|
730
|
-
const marker = path[index];
|
|
731
|
-
if (marker === '.') {
|
|
732
|
-
index += 1;
|
|
733
|
-
if (path[index] === '[') {
|
|
734
|
-
const end = findBracketEnd(path, index);
|
|
735
|
-
if (end < 0)
|
|
736
|
-
return null;
|
|
737
|
-
segments.push(path.slice(index, end + 1));
|
|
738
|
-
index = end + 1;
|
|
739
|
-
continue;
|
|
740
|
-
}
|
|
741
|
-
const start = index;
|
|
742
|
-
while (index < path.length && !['.', '[', '@'].includes(path[index])) {
|
|
743
|
-
index += 1;
|
|
744
|
-
}
|
|
745
|
-
if (start === index)
|
|
746
|
-
return null;
|
|
747
|
-
segments.push(path.slice(start, index));
|
|
748
|
-
continue;
|
|
749
|
-
}
|
|
750
|
-
if (marker === '[') {
|
|
751
|
-
const end = findBracketEnd(path, index);
|
|
752
|
-
if (end < 0)
|
|
753
|
-
return null;
|
|
754
|
-
segments.push(path.slice(index, end + 1));
|
|
755
|
-
index = end + 1;
|
|
756
|
-
continue;
|
|
757
|
-
}
|
|
758
|
-
if (marker === '@') {
|
|
759
|
-
index += 1;
|
|
760
|
-
if (path[index] === '[') {
|
|
761
|
-
const end = findBracketEnd(path, index);
|
|
762
|
-
if (end < 0)
|
|
763
|
-
return null;
|
|
764
|
-
segments.push(`@${path.slice(index, end + 1)}`);
|
|
765
|
-
index = end + 1;
|
|
766
|
-
continue;
|
|
767
|
-
}
|
|
768
|
-
const start = index;
|
|
769
|
-
while (index < path.length && !['.', '[', '@'].includes(path[index])) {
|
|
770
|
-
index += 1;
|
|
771
|
-
}
|
|
772
|
-
if (start === index)
|
|
773
|
-
return null;
|
|
774
|
-
segments.push(`@${path.slice(start, index)}`);
|
|
775
|
-
continue;
|
|
776
|
-
}
|
|
715
|
+
function resolveSansaSelectorPathSet(selector, eventsByPath, ctx) {
|
|
716
|
+
const result = resolveAddress(selector, createAeosSansaResolveNamespace(eventsByPath));
|
|
717
|
+
if (!result.ok) {
|
|
718
|
+
const first = result.errors[0];
|
|
719
|
+
emitError(ctx, createDiag(selector, null, first ? `Invalid or unsupported SANSA selector: ${first.message}` : `Invalid or unsupported SANSA selector: ${selector}`, ErrorCodes.INVALID_SCHEMA_POLICY));
|
|
777
720
|
return null;
|
|
778
721
|
}
|
|
779
|
-
return
|
|
722
|
+
return new Set(result.bindings.map((binding) => binding.path).filter((path) => eventsByPath.has(path)));
|
|
780
723
|
}
|
|
781
|
-
function
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
if (ch === '"' || ch === "'") {
|
|
800
|
-
quote = ch;
|
|
801
|
-
continue;
|
|
802
|
-
}
|
|
803
|
-
if (ch === ']')
|
|
804
|
-
return index;
|
|
724
|
+
function createAeosSansaResolveNamespace(eventsByPath) {
|
|
725
|
+
const root = buildAeosSansaResolveTree(eventsByPath);
|
|
726
|
+
return {
|
|
727
|
+
root,
|
|
728
|
+
children: (binding) => binding.children,
|
|
729
|
+
member: (binding, name) => binding.children.find((child) => child.name === name),
|
|
730
|
+
position: (binding, index) => binding.children.find((child) => child.index === index),
|
|
731
|
+
attributeSpace: (binding) => binding.attributeSpace,
|
|
732
|
+
name: (binding) => binding.name,
|
|
733
|
+
index: (binding) => binding.index,
|
|
734
|
+
semanticType: (binding) => binding.info?.datatype,
|
|
735
|
+
representationKind: (binding) => binding.info?.type,
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function buildAeosSansaResolveTree(eventsByPath) {
|
|
739
|
+
const root = { path: '$', children: [] };
|
|
740
|
+
for (const [path, info] of [...eventsByPath.entries()].sort((a, b) => a[0].length - b[0].length)) {
|
|
741
|
+
insertAeosSansaResolvePath(root, path, info);
|
|
805
742
|
}
|
|
806
|
-
return
|
|
743
|
+
return root;
|
|
807
744
|
}
|
|
808
|
-
function
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
}
|
|
834
|
-
if (selectorSegment === '[*]') {
|
|
835
|
-
return /^\[\d+\]$/.test(actualSegments[actualIndex])
|
|
836
|
-
&& matchFrom(actualIndex + 1, selectorIndex + 1);
|
|
745
|
+
function insertAeosSansaResolvePath(root, path, info) {
|
|
746
|
+
const parsed = parseAddress(path);
|
|
747
|
+
if (!parsed.ok)
|
|
748
|
+
return;
|
|
749
|
+
if (parsed.address.root.kind !== 'absolute')
|
|
750
|
+
return;
|
|
751
|
+
let current = root;
|
|
752
|
+
let currentPath = '$';
|
|
753
|
+
for (const selector of parsed.address.selectors) {
|
|
754
|
+
switch (selector.type) {
|
|
755
|
+
case 'member':
|
|
756
|
+
currentPath += formatMemberSelector(selector.name);
|
|
757
|
+
current = getOrCreateChildBinding(current, currentPath, { name: selector.name });
|
|
758
|
+
break;
|
|
759
|
+
case 'position':
|
|
760
|
+
currentPath += `[${selector.index}]`;
|
|
761
|
+
current = getOrCreateChildBinding(current, currentPath, { index: selector.index });
|
|
762
|
+
break;
|
|
763
|
+
case 'attributeSpace':
|
|
764
|
+
currentPath += '.@';
|
|
765
|
+
current.attributeSpace ??= { path: currentPath, children: [] };
|
|
766
|
+
current = current.attributeSpace;
|
|
767
|
+
break;
|
|
768
|
+
default:
|
|
769
|
+
return;
|
|
837
770
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
771
|
+
}
|
|
772
|
+
current.info = info;
|
|
773
|
+
}
|
|
774
|
+
function getOrCreateChildBinding(parent, path, identity) {
|
|
775
|
+
const existing = parent.children.find((child) => identity.name !== undefined
|
|
776
|
+
? child.name === identity.name
|
|
777
|
+
: child.index === identity.index);
|
|
778
|
+
if (existing)
|
|
779
|
+
return existing;
|
|
780
|
+
const child = { path, children: [], ...identity };
|
|
781
|
+
parent.children.push(child);
|
|
782
|
+
return child;
|
|
842
783
|
}
|
|
843
784
|
function collectAllowedAttributePaths(ruleIndex) {
|
|
844
785
|
const allowed = [];
|
|
845
786
|
function visit(basePath, constraints) {
|
|
846
|
-
if (basePath.includes('
|
|
787
|
+
if (basePath.includes('.@.')) {
|
|
847
788
|
allowed.push(basePath);
|
|
848
789
|
}
|
|
849
790
|
const attributes = constraints.attributes;
|
|
850
791
|
if (!attributes)
|
|
851
792
|
return;
|
|
852
793
|
for (const [key, childConstraints] of Object.entries(attributes)) {
|
|
853
|
-
visit(
|
|
794
|
+
visit(appendAttributePath(basePath, key), childConstraints);
|
|
854
795
|
}
|
|
855
796
|
}
|
|
856
797
|
for (const [path, rule] of ruleIndex) {
|
|
@@ -864,7 +805,7 @@ function collectAttributeEntries(eventsByPath) {
|
|
|
864
805
|
if (!attributes)
|
|
865
806
|
return;
|
|
866
807
|
for (const [key, entry] of attributes.entries()) {
|
|
867
|
-
const path =
|
|
808
|
+
const path = appendAttributePath(basePath, key);
|
|
868
809
|
entries.push({ path, span: entry.span });
|
|
869
810
|
visit(path, entry.attributes);
|
|
870
811
|
}
|
|
@@ -974,7 +915,7 @@ function checkAttributeConstraints(ruleIndex, eventsByPath, datatypeRules, ctx)
|
|
|
974
915
|
function validateAttributeMap(basePath, attributes, constraints, datatypeRules, ctx) {
|
|
975
916
|
const requiredAttributes = constraints.attributes ?? {};
|
|
976
917
|
for (const [key, childConstraints] of Object.entries(requiredAttributes)) {
|
|
977
|
-
const childPath =
|
|
918
|
+
const childPath = appendAttributePath(basePath, key);
|
|
978
919
|
const entry = attributes?.get(key);
|
|
979
920
|
if (childConstraints.required === true && !entry) {
|
|
980
921
|
emitError(ctx, createDiag(childPath, null, `Missing required field: ${childPath}`, ErrorCodes.MISSING_REQUIRED_FIELD));
|
|
@@ -989,7 +930,7 @@ function validateAttributeMap(basePath, attributes, constraints, datatypeRules,
|
|
|
989
930
|
for (const key of attributes.keys()) {
|
|
990
931
|
if (allowed.has(key))
|
|
991
932
|
continue;
|
|
992
|
-
const childPath =
|
|
933
|
+
const childPath = appendAttributePath(basePath, key);
|
|
993
934
|
emitError(ctx, createDiag(childPath, attributes.get(key)?.span ?? null, `Attribute '${childPath}' is not allowed by closed attribute constraints`, ErrorCodes.UNEXPECTED_ATTRIBUTE_ENTRY));
|
|
994
935
|
}
|
|
995
936
|
}
|
|
@@ -1062,14 +1003,6 @@ function validateAttributeEntry(path, entry, constraints, datatypeRules, ctx) {
|
|
|
1062
1003
|
validateAttributeMap(path, entry.attributes, effectiveConstraints, datatypeRules, ctx);
|
|
1063
1004
|
}
|
|
1064
1005
|
}
|
|
1065
|
-
function datatypeBase(datatype) {
|
|
1066
|
-
const genericIdx = datatype.indexOf('<');
|
|
1067
|
-
const separatorIdx = datatype.indexOf('[');
|
|
1068
|
-
const endIdx = [genericIdx, separatorIdx]
|
|
1069
|
-
.filter((idx) => idx >= 0)
|
|
1070
|
-
.reduce((min, idx) => Math.min(min, idx), datatype.length);
|
|
1071
|
-
return datatype.slice(0, endIdx);
|
|
1072
|
-
}
|
|
1073
1006
|
function mergeDatatypeRuleConstraints(constraints, datatype, datatypeRules) {
|
|
1074
1007
|
if (!datatype || !datatypeRules)
|
|
1075
1008
|
return constraints;
|
|
@@ -1203,26 +1136,18 @@ function radixConstraintMatches(datatype, raw, constraints) {
|
|
|
1203
1136
|
return false;
|
|
1204
1137
|
return firstInvalidRadixDigit(raw, constraints.radix) === null;
|
|
1205
1138
|
}
|
|
1206
|
-
function declaredRadixFromDatatype(datatype) {
|
|
1207
|
-
if (datatype === undefined)
|
|
1208
|
-
return null;
|
|
1209
|
-
const match = /^radix(?:\[(\d+)\]|(\d+))$/i.exec(datatype.trim());
|
|
1210
|
-
if (!match)
|
|
1211
|
-
return null;
|
|
1212
|
-
const value = Number(match[1] ?? match[2]);
|
|
1213
|
-
return Number.isInteger(value) && value >= 0 ? value : null;
|
|
1214
|
-
}
|
|
1215
1139
|
function isStringType(type) {
|
|
1216
1140
|
return type === 'StringLiteral'
|
|
1217
1141
|
|| type === 'TrimtickLiteral'
|
|
1218
1142
|
|| type === 'TrimtickStringLiteral'
|
|
1219
1143
|
|| type === 'SeparatorLiteral'
|
|
1144
|
+
|| type === 'SansaAddressLiteral'
|
|
1220
1145
|
|| type === 'NullLiteral'
|
|
1221
1146
|
|| type === 'EncodingLiteral'
|
|
1222
1147
|
|| type === 'DateLiteral'
|
|
1223
1148
|
|| type === 'TimeLiteral'
|
|
1224
1149
|
|| type === 'DateTimeLiteral'
|
|
1225
|
-
|| type === '
|
|
1150
|
+
|| type === 'WTCDateTimeLiteral';
|
|
1226
1151
|
}
|
|
1227
1152
|
function countFormDigits(type, raw) {
|
|
1228
1153
|
if (type === 'NumberLiteral')
|