@altopelago/aeos-core 0.9.6 → 0.11.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/diag/codes.d.ts +30 -30
- package/dist/diag/codes.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.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 +1 -0
- package/dist/rules/stringForm.js.map +1 -1
- package/dist/rules/typeCheck.d.ts.map +1 -1
- package/dist/rules/typeCheck.js +1 -0
- 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 +291 -0
- package/dist/schema-codec.js.map +1 -0
- package/dist/types/envelope.js.map +1 -1
- package/dist/types/schema.d.ts +3 -3
- package/dist/types/schema.d.ts.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +117 -161
- package/dist/validate.js.map +1 -1
- package/package.json +8 -3
package/dist/validate.js
CHANGED
|
@@ -13,6 +13,7 @@ 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 { parseAddress, resolveAddress, } from '@altopelago/sansa';
|
|
16
17
|
const TYPE_ALIASES = {
|
|
17
18
|
NumberLiteral: ['NumberLiteral'],
|
|
18
19
|
StringLiteral: ['StringLiteral'],
|
|
@@ -29,6 +30,7 @@ const TYPE_ALIASES = {
|
|
|
29
30
|
RadixLiteral: ['RadixLiteral'],
|
|
30
31
|
EncodingLiteral: ['EncodingLiteral'],
|
|
31
32
|
SeparatorLiteral: ['SeparatorLiteral'],
|
|
33
|
+
SansaAddressLiteral: ['SansaAddressLiteral'],
|
|
32
34
|
DateLiteral: ['DateLiteral'],
|
|
33
35
|
TimeLiteral: ['TimeLiteral'],
|
|
34
36
|
DateTimeLiteral: ['DateTimeLiteral'],
|
|
@@ -40,6 +42,15 @@ const TYPE_ALIASES = {
|
|
|
40
42
|
function formatQuotedMemberSegment(key) {
|
|
41
43
|
return `.[${JSON.stringify(String(key))}]`;
|
|
42
44
|
}
|
|
45
|
+
function formatMemberSelector(key) {
|
|
46
|
+
const value = String(key);
|
|
47
|
+
return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)
|
|
48
|
+
? `.${value}`
|
|
49
|
+
: formatQuotedMemberSegment(value);
|
|
50
|
+
}
|
|
51
|
+
function appendAttributePath(basePath, key) {
|
|
52
|
+
return `${basePath}.@${formatMemberSelector(key)}`;
|
|
53
|
+
}
|
|
43
54
|
const DEFAULT_RESOURCE_POLICY = {
|
|
44
55
|
max_events: 100_000,
|
|
45
56
|
max_rules: 10_000,
|
|
@@ -89,6 +100,7 @@ const STRING_LIKE_VALUE_TYPES = new Set([
|
|
|
89
100
|
'StringLiteral',
|
|
90
101
|
'TrimtickLiteral',
|
|
91
102
|
'SeparatorLiteral',
|
|
103
|
+
'SansaAddressLiteral',
|
|
92
104
|
'HexLiteral',
|
|
93
105
|
'EncodingLiteral',
|
|
94
106
|
'NullLiteral',
|
|
@@ -109,7 +121,7 @@ function enforceStringLengthResourceBudget(info, path, policy, ctx) {
|
|
|
109
121
|
emitResourceError(ctx, path, `String-like payload length ${payloadLength} exceeds max_string_length_default ${policy.max_string_length_default}`, info.span);
|
|
110
122
|
}
|
|
111
123
|
for (const [key, attribute] of info.attributes ?? []) {
|
|
112
|
-
enforceStringLengthResourceBudget(attribute,
|
|
124
|
+
enforceStringLengthResourceBudget(attribute, appendAttributePath(path, key), policy, ctx);
|
|
113
125
|
}
|
|
114
126
|
}
|
|
115
127
|
function inspectSchemaResourceShape(schema, policy, ctx) {
|
|
@@ -143,7 +155,7 @@ function inspectConstraintResourceShape(constraints, path, depth, policy, ctx) {
|
|
|
143
155
|
}
|
|
144
156
|
if (constraints.attributes !== undefined) {
|
|
145
157
|
for (const [key, child] of Object.entries(constraints.attributes)) {
|
|
146
|
-
inspectConstraintResourceShape(child,
|
|
158
|
+
inspectConstraintResourceShape(child, appendAttributePath(path, key), depth + 1, policy, ctx);
|
|
147
159
|
}
|
|
148
160
|
}
|
|
149
161
|
}
|
|
@@ -274,6 +286,7 @@ export function validate(aes, schema, options = {}) {
|
|
|
274
286
|
...(attributes ? { attributes } : {}),
|
|
275
287
|
};
|
|
276
288
|
eventsByPath.set(elementPath, info);
|
|
289
|
+
addAttributeEvents(elementPath, attributes);
|
|
277
290
|
}
|
|
278
291
|
}
|
|
279
292
|
function buildAttributeInfoMap(attributes) {
|
|
@@ -300,6 +313,22 @@ export function validate(aes, schema, options = {}) {
|
|
|
300
313
|
}
|
|
301
314
|
return mapped;
|
|
302
315
|
}
|
|
316
|
+
function addAttributeEvents(basePath, attributes) {
|
|
317
|
+
if (!attributes)
|
|
318
|
+
return;
|
|
319
|
+
for (const [key, attribute] of attributes.entries()) {
|
|
320
|
+
const attributePath = appendAttributePath(basePath, key);
|
|
321
|
+
eventsByPath.set(attributePath, {
|
|
322
|
+
type: attribute.type,
|
|
323
|
+
raw: attribute.raw,
|
|
324
|
+
value: attribute.value,
|
|
325
|
+
...(attribute.datatype !== undefined ? { datatype: attribute.datatype } : {}),
|
|
326
|
+
span: attribute.span,
|
|
327
|
+
...(attribute.attributes ? { attributes: attribute.attributes } : {}),
|
|
328
|
+
});
|
|
329
|
+
addAttributeEvents(attributePath, attribute.attributes);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
303
332
|
for (let i = 0; i < aes.length; i++) {
|
|
304
333
|
const event = aes[i];
|
|
305
334
|
const pathStr = formatCanonicalPath(event.path);
|
|
@@ -338,6 +367,7 @@ export function validate(aes, schema, options = {}) {
|
|
|
338
367
|
...(attributes ? { attributes } : {}),
|
|
339
368
|
};
|
|
340
369
|
eventsByPath.set(pathStr, info);
|
|
370
|
+
addAttributeEvents(pathStr, attributes);
|
|
341
371
|
if ((event.value.type === 'TupleLiteral' || event.value.type === 'ListLiteral' || event.value.type === 'ListNode')
|
|
342
372
|
&& Array.isArray(event.value.elements)) {
|
|
343
373
|
containerArity.set(pathStr, event.value.elements.length);
|
|
@@ -391,12 +421,12 @@ export function validate(aes, schema, options = {}) {
|
|
|
391
421
|
// Phase 3: Build rule index from schema (run after baseline invariants)
|
|
392
422
|
const ruleIndex = buildRuleIndex(schema, ctx);
|
|
393
423
|
const selectorExpansionBudget = { count: 0 };
|
|
394
|
-
const expandedRuleIndex =
|
|
424
|
+
const expandedRuleIndex = expandSelectorRules(ruleIndex, schema, eventsByPath, ctx, resourcePolicy, selectorExpansionBudget);
|
|
395
425
|
const effectiveRuleIndex = mergeDatatypeRules(expandedRuleIndex, schema.datatype_rules, eventsByPath);
|
|
396
426
|
// Phase 4: Presence checks (required fields)
|
|
397
|
-
const boundPaths = new Set(
|
|
427
|
+
const boundPaths = new Set(eventsByPath.keys());
|
|
398
428
|
checkPresence(effectiveRuleIndex, boundPaths, ctx);
|
|
399
|
-
checkWorldPolicy(schema, aes, boundPaths, ctx);
|
|
429
|
+
checkWorldPolicy(schema, aes, boundPaths, eventsByPath, ctx);
|
|
400
430
|
// Phase 5: Type checks (literal kind)
|
|
401
431
|
checkReferenceForms(schema, effectiveRuleIndex, eventsByPath, ctx);
|
|
402
432
|
const effectiveEventsByPath = resolveReferenceFormEvents(effectiveRuleIndex, eventsByPath, resourcePolicy, ctx);
|
|
@@ -494,7 +524,7 @@ export function validate(aes, schema, options = {}) {
|
|
|
494
524
|
}
|
|
495
525
|
return createPassingEnvelope(guarantees, ctx.warnings);
|
|
496
526
|
}
|
|
497
|
-
function checkWorldPolicy(schema, aes, boundPaths, ctx) {
|
|
527
|
+
function checkWorldPolicy(schema, aes, boundPaths, eventsByPath, ctx) {
|
|
498
528
|
if ((schema.world ?? 'open') !== 'closed')
|
|
499
529
|
return;
|
|
500
530
|
const allowedRules = schema.rules
|
|
@@ -504,6 +534,12 @@ function checkWorldPolicy(schema, aes, boundPaths, ctx) {
|
|
|
504
534
|
? { kind: 'selector', value: rule.selector }
|
|
505
535
|
: null)
|
|
506
536
|
.filter((rule) => rule !== null);
|
|
537
|
+
const selectorMatches = new Map();
|
|
538
|
+
for (const rule of allowedRules) {
|
|
539
|
+
if (rule.kind !== 'selector' || selectorMatches.has(rule.value))
|
|
540
|
+
continue;
|
|
541
|
+
selectorMatches.set(rule.value, resolveSansaSelectorPathSet(rule.value, eventsByPath, ctx));
|
|
542
|
+
}
|
|
507
543
|
for (const event of aes) {
|
|
508
544
|
const key = typeof event.key === 'string' ? event.key : '';
|
|
509
545
|
if (key.startsWith('aeon:'))
|
|
@@ -512,7 +548,7 @@ function checkWorldPolicy(schema, aes, boundPaths, ctx) {
|
|
|
512
548
|
if (!boundPaths.has(path))
|
|
513
549
|
continue;
|
|
514
550
|
if (allowedRules.some((rule) => rule.kind === 'selector'
|
|
515
|
-
?
|
|
551
|
+
? selectorMatches.get(rule.value)?.has(path) === true
|
|
516
552
|
: matchesAllowedPath(path, rule.value)))
|
|
517
553
|
continue;
|
|
518
554
|
emitError(ctx, createDiag(path, toTupleLocal(event.span), `Binding '${path}' is not allowed by closed-world schema`, ErrorCodes.UNEXPECTED_BINDING));
|
|
@@ -549,10 +585,11 @@ function expandSelectorRules(ruleIndex, schema, eventsByPath, ctx, resourcePolic
|
|
|
549
585
|
continue;
|
|
550
586
|
if (typeof rule.path === 'string' && rule.path.length > 0)
|
|
551
587
|
continue;
|
|
588
|
+
const resolvedPaths = resolveSansaSelectorPathSet(rule.selector, eventsByPath, ctx);
|
|
589
|
+
if (resolvedPaths === null)
|
|
590
|
+
continue;
|
|
552
591
|
let matched = false;
|
|
553
|
-
for (const actualPath of
|
|
554
|
-
if (!matchesSelectorPath(actualPath, rule.selector))
|
|
555
|
-
continue;
|
|
592
|
+
for (const actualPath of resolvedPaths) {
|
|
556
593
|
matched = true;
|
|
557
594
|
expansionBudget.count += 1;
|
|
558
595
|
if (expansionBudget.count > resourcePolicy.max_selector_expansions) {
|
|
@@ -569,25 +606,6 @@ function expandSelectorRules(ruleIndex, schema, eventsByPath, ctx, resourcePolic
|
|
|
569
606
|
}
|
|
570
607
|
return expanded;
|
|
571
608
|
}
|
|
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
609
|
function selectAnyOfRules(ruleIndex, eventsByPath, ctx) {
|
|
592
610
|
const selected = new Map(ruleIndex);
|
|
593
611
|
for (const [path, rule] of ruleIndex.entries()) {
|
|
@@ -701,156 +719,93 @@ function formatReferenceLookupPath(segments) {
|
|
|
701
719
|
: formatQuotedMemberSegment(segment);
|
|
702
720
|
continue;
|
|
703
721
|
}
|
|
704
|
-
out
|
|
705
|
-
? `@${segment.key}`
|
|
706
|
-
: `@[${JSON.stringify(segment.key)}]`;
|
|
722
|
+
out = appendAttributePath(out, segment.key);
|
|
707
723
|
}
|
|
708
724
|
return out;
|
|
709
725
|
}
|
|
710
726
|
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);
|
|
727
|
+
return actualPath === allowedPath;
|
|
723
728
|
}
|
|
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
|
-
}
|
|
729
|
+
function resolveSansaSelectorPathSet(selector, eventsByPath, ctx) {
|
|
730
|
+
const result = resolveAddress(selector, createAeosSansaResolveNamespace(eventsByPath));
|
|
731
|
+
if (!result.ok) {
|
|
732
|
+
const first = result.errors[0];
|
|
733
|
+
emitError(ctx, createDiag(selector, null, first ? `Invalid or unsupported SANSA selector: ${first.message}` : `Invalid or unsupported SANSA selector: ${selector}`, ErrorCodes.INVALID_SCHEMA_POLICY));
|
|
777
734
|
return null;
|
|
778
735
|
}
|
|
779
|
-
return
|
|
736
|
+
return new Set(result.bindings.map((binding) => binding.path).filter((path) => eventsByPath.has(path)));
|
|
780
737
|
}
|
|
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;
|
|
738
|
+
function createAeosSansaResolveNamespace(eventsByPath) {
|
|
739
|
+
const root = buildAeosSansaResolveTree(eventsByPath);
|
|
740
|
+
return {
|
|
741
|
+
root,
|
|
742
|
+
children: (binding) => binding.children,
|
|
743
|
+
member: (binding, name) => binding.children.find((child) => child.name === name),
|
|
744
|
+
position: (binding, index) => binding.children.find((child) => child.index === index),
|
|
745
|
+
attributeSpace: (binding) => binding.attributeSpace,
|
|
746
|
+
name: (binding) => binding.name,
|
|
747
|
+
index: (binding) => binding.index,
|
|
748
|
+
semanticType: (binding) => binding.info?.datatype,
|
|
749
|
+
representationKind: (binding) => binding.info?.type,
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
function buildAeosSansaResolveTree(eventsByPath) {
|
|
753
|
+
const root = { path: '$', children: [] };
|
|
754
|
+
for (const [path, info] of [...eventsByPath.entries()].sort((a, b) => a[0].length - b[0].length)) {
|
|
755
|
+
insertAeosSansaResolvePath(root, path, info);
|
|
805
756
|
}
|
|
806
|
-
return
|
|
757
|
+
return root;
|
|
807
758
|
}
|
|
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);
|
|
759
|
+
function insertAeosSansaResolvePath(root, path, info) {
|
|
760
|
+
const parsed = parseAddress(path);
|
|
761
|
+
if (!parsed.ok)
|
|
762
|
+
return;
|
|
763
|
+
if (parsed.address.root.kind !== 'absolute')
|
|
764
|
+
return;
|
|
765
|
+
let current = root;
|
|
766
|
+
let currentPath = '$';
|
|
767
|
+
for (const selector of parsed.address.selectors) {
|
|
768
|
+
switch (selector.type) {
|
|
769
|
+
case 'member':
|
|
770
|
+
currentPath += formatMemberSelector(selector.name);
|
|
771
|
+
current = getOrCreateChildBinding(current, currentPath, { name: selector.name });
|
|
772
|
+
break;
|
|
773
|
+
case 'position':
|
|
774
|
+
currentPath += `[${selector.index}]`;
|
|
775
|
+
current = getOrCreateChildBinding(current, currentPath, { index: selector.index });
|
|
776
|
+
break;
|
|
777
|
+
case 'attributeSpace':
|
|
778
|
+
currentPath += '.@';
|
|
779
|
+
current.attributeSpace ??= { path: currentPath, children: [] };
|
|
780
|
+
current = current.attributeSpace;
|
|
781
|
+
break;
|
|
782
|
+
default:
|
|
783
|
+
return;
|
|
837
784
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
785
|
+
}
|
|
786
|
+
current.info = info;
|
|
787
|
+
}
|
|
788
|
+
function getOrCreateChildBinding(parent, path, identity) {
|
|
789
|
+
const existing = parent.children.find((child) => identity.name !== undefined
|
|
790
|
+
? child.name === identity.name
|
|
791
|
+
: child.index === identity.index);
|
|
792
|
+
if (existing)
|
|
793
|
+
return existing;
|
|
794
|
+
const child = { path, children: [], ...identity };
|
|
795
|
+
parent.children.push(child);
|
|
796
|
+
return child;
|
|
842
797
|
}
|
|
843
798
|
function collectAllowedAttributePaths(ruleIndex) {
|
|
844
799
|
const allowed = [];
|
|
845
800
|
function visit(basePath, constraints) {
|
|
846
|
-
if (basePath.includes('
|
|
801
|
+
if (basePath.includes('.@.')) {
|
|
847
802
|
allowed.push(basePath);
|
|
848
803
|
}
|
|
849
804
|
const attributes = constraints.attributes;
|
|
850
805
|
if (!attributes)
|
|
851
806
|
return;
|
|
852
807
|
for (const [key, childConstraints] of Object.entries(attributes)) {
|
|
853
|
-
visit(
|
|
808
|
+
visit(appendAttributePath(basePath, key), childConstraints);
|
|
854
809
|
}
|
|
855
810
|
}
|
|
856
811
|
for (const [path, rule] of ruleIndex) {
|
|
@@ -864,7 +819,7 @@ function collectAttributeEntries(eventsByPath) {
|
|
|
864
819
|
if (!attributes)
|
|
865
820
|
return;
|
|
866
821
|
for (const [key, entry] of attributes.entries()) {
|
|
867
|
-
const path =
|
|
822
|
+
const path = appendAttributePath(basePath, key);
|
|
868
823
|
entries.push({ path, span: entry.span });
|
|
869
824
|
visit(path, entry.attributes);
|
|
870
825
|
}
|
|
@@ -974,7 +929,7 @@ function checkAttributeConstraints(ruleIndex, eventsByPath, datatypeRules, ctx)
|
|
|
974
929
|
function validateAttributeMap(basePath, attributes, constraints, datatypeRules, ctx) {
|
|
975
930
|
const requiredAttributes = constraints.attributes ?? {};
|
|
976
931
|
for (const [key, childConstraints] of Object.entries(requiredAttributes)) {
|
|
977
|
-
const childPath =
|
|
932
|
+
const childPath = appendAttributePath(basePath, key);
|
|
978
933
|
const entry = attributes?.get(key);
|
|
979
934
|
if (childConstraints.required === true && !entry) {
|
|
980
935
|
emitError(ctx, createDiag(childPath, null, `Missing required field: ${childPath}`, ErrorCodes.MISSING_REQUIRED_FIELD));
|
|
@@ -989,7 +944,7 @@ function validateAttributeMap(basePath, attributes, constraints, datatypeRules,
|
|
|
989
944
|
for (const key of attributes.keys()) {
|
|
990
945
|
if (allowed.has(key))
|
|
991
946
|
continue;
|
|
992
|
-
const childPath =
|
|
947
|
+
const childPath = appendAttributePath(basePath, key);
|
|
993
948
|
emitError(ctx, createDiag(childPath, attributes.get(key)?.span ?? null, `Attribute '${childPath}' is not allowed by closed attribute constraints`, ErrorCodes.UNEXPECTED_ATTRIBUTE_ENTRY));
|
|
994
949
|
}
|
|
995
950
|
}
|
|
@@ -1217,6 +1172,7 @@ function isStringType(type) {
|
|
|
1217
1172
|
|| type === 'TrimtickLiteral'
|
|
1218
1173
|
|| type === 'TrimtickStringLiteral'
|
|
1219
1174
|
|| type === 'SeparatorLiteral'
|
|
1175
|
+
|| type === 'SansaAddressLiteral'
|
|
1220
1176
|
|| type === 'NullLiteral'
|
|
1221
1177
|
|| type === 'EncodingLiteral'
|
|
1222
1178
|
|| type === 'DateLiteral'
|