@malloydata/motly-ts-parser 0.2.1 → 0.3.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/build/interface/src/types.d.ts +17 -13
- package/build/interface/src/types.js +3 -3
- package/build/parser/src/clone.d.ts +7 -3
- package/build/parser/src/clone.js +16 -8
- package/build/parser/src/index.d.ts +1 -1
- package/build/parser/src/interpreter.d.ts +3 -3
- package/build/parser/src/interpreter.js +135 -43
- package/build/parser/src/session.d.ts +2 -2
- package/build/parser/src/session.js +1 -1
- package/build/parser/src/validate.d.ts +3 -3
- package/build/parser/src/validate.js +158 -158
- package/package.json +1 -1
|
@@ -8,24 +8,28 @@ export interface MOTLYRef {
|
|
|
8
8
|
export interface MOTLYEnvRef {
|
|
9
9
|
env: string;
|
|
10
10
|
}
|
|
11
|
+
/** What goes to the right of = (the eq slot). */
|
|
12
|
+
export type MOTLYValue = MOTLYScalar | MOTLYEnvRef | MOTLYPropertyValue[];
|
|
11
13
|
/**
|
|
12
|
-
* A
|
|
14
|
+
* A node in the MOTLY tree.
|
|
13
15
|
*
|
|
14
|
-
* - `eq` — the node's assigned value: a scalar,
|
|
15
|
-
* or an array of
|
|
16
|
-
* - `properties` — named child
|
|
16
|
+
* - `eq` — the node's assigned value: a scalar, an env ref ({@link MOTLYEnvRef}),
|
|
17
|
+
* or an array of property values
|
|
18
|
+
* - `properties` — named child property values
|
|
17
19
|
* - `deleted` — true if this node was explicitly deleted with `-name`
|
|
18
20
|
*/
|
|
19
|
-
export interface
|
|
20
|
-
eq?:
|
|
21
|
-
properties?: Record<string,
|
|
21
|
+
export interface MOTLYNode {
|
|
22
|
+
eq?: MOTLYValue;
|
|
23
|
+
properties?: Record<string, MOTLYPropertyValue>;
|
|
22
24
|
deleted?: boolean;
|
|
23
25
|
}
|
|
24
26
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
+
* What a property or array element leads to: either a node or a link reference.
|
|
28
|
+
*
|
|
29
|
+
* A `MOTLYRef` means "this IS that other node" — no own value, no own properties.
|
|
30
|
+
* A `MOTLYNode` is a full node with optional eq, properties, and deleted flag.
|
|
27
31
|
*/
|
|
28
|
-
export type
|
|
32
|
+
export type MOTLYPropertyValue = MOTLYNode | MOTLYRef;
|
|
29
33
|
/** A parse error with source location span. */
|
|
30
34
|
export interface MOTLYError {
|
|
31
35
|
/** Machine-readable error code (e.g. `"tag-parse-syntax-error"`). */
|
|
@@ -54,10 +58,10 @@ export interface MOTLYSchemaError {
|
|
|
54
58
|
/** Path to the offending node (e.g. `["metadata", "name"]`). */
|
|
55
59
|
path: string[];
|
|
56
60
|
}
|
|
57
|
-
/** Type guard: is this
|
|
58
|
-
export declare function isRef(
|
|
61
|
+
/** Type guard: is this property value a link reference? */
|
|
62
|
+
export declare function isRef(pv: MOTLYPropertyValue | undefined): pv is MOTLYRef;
|
|
59
63
|
/** Type guard: is this eq value an env reference? */
|
|
60
|
-
export declare function isEnvRef(eq:
|
|
64
|
+
export declare function isEnvRef(eq: MOTLYNode["eq"]): eq is MOTLYEnvRef;
|
|
61
65
|
/** An error from reference validation. */
|
|
62
66
|
export interface MOTLYValidationError {
|
|
63
67
|
/** Machine-readable error code (e.g. `"unresolved-reference"`). */
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.isRef = isRef;
|
|
4
4
|
exports.isEnvRef = isEnvRef;
|
|
5
|
-
/** Type guard: is this
|
|
6
|
-
function isRef(
|
|
7
|
-
return typeof
|
|
5
|
+
/** Type guard: is this property value a link reference? */
|
|
6
|
+
function isRef(pv) {
|
|
7
|
+
return typeof pv === "object" && pv !== null && "linkTo" in pv && !Array.isArray(pv) && !(pv instanceof Date);
|
|
8
8
|
}
|
|
9
9
|
/** Type guard: is this eq value an env reference? */
|
|
10
10
|
function isEnvRef(eq) {
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
/** Deep clone a
|
|
3
|
-
export declare function
|
|
1
|
+
import { MOTLYNode, MOTLYPropertyValue } from "../../interface/src/types";
|
|
2
|
+
/** Deep clone a MOTLYNode. */
|
|
3
|
+
export declare function cloneNode(value: MOTLYNode): MOTLYNode;
|
|
4
|
+
/** Deep clone a MOTLYPropertyValue (either a node or a ref). */
|
|
5
|
+
export declare function clonePropertyValue(pv: MOTLYPropertyValue): MOTLYPropertyValue;
|
|
6
|
+
/** @deprecated Use cloneNode instead. */
|
|
7
|
+
export declare const cloneValue: typeof cloneNode;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.cloneValue =
|
|
3
|
+
exports.cloneValue = void 0;
|
|
4
|
+
exports.cloneNode = cloneNode;
|
|
5
|
+
exports.clonePropertyValue = clonePropertyValue;
|
|
4
6
|
const types_1 = require("../../interface/src/types");
|
|
5
|
-
/** Deep clone a
|
|
6
|
-
function
|
|
7
|
+
/** Deep clone a MOTLYNode. */
|
|
8
|
+
function cloneNode(value) {
|
|
7
9
|
const result = {};
|
|
8
10
|
if (value.deleted)
|
|
9
11
|
result.deleted = true;
|
|
@@ -12,10 +14,7 @@ function cloneValue(value) {
|
|
|
12
14
|
result.eq = new Date(value.eq.getTime());
|
|
13
15
|
}
|
|
14
16
|
else if (Array.isArray(value.eq)) {
|
|
15
|
-
result.eq = value.eq.map(
|
|
16
|
-
}
|
|
17
|
-
else if ((0, types_1.isRef)(value.eq)) {
|
|
18
|
-
result.eq = { linkTo: value.eq.linkTo };
|
|
17
|
+
result.eq = value.eq.map(clonePropertyValue);
|
|
19
18
|
}
|
|
20
19
|
else if ((0, types_1.isEnvRef)(value.eq)) {
|
|
21
20
|
result.eq = { env: value.eq.env };
|
|
@@ -27,9 +26,18 @@ function cloneValue(value) {
|
|
|
27
26
|
if (value.properties) {
|
|
28
27
|
const props = {};
|
|
29
28
|
for (const key of Object.keys(value.properties)) {
|
|
30
|
-
props[key] =
|
|
29
|
+
props[key] = clonePropertyValue(value.properties[key]);
|
|
31
30
|
}
|
|
32
31
|
result.properties = props;
|
|
33
32
|
}
|
|
34
33
|
return result;
|
|
35
34
|
}
|
|
35
|
+
/** Deep clone a MOTLYPropertyValue (either a node or a ref). */
|
|
36
|
+
function clonePropertyValue(pv) {
|
|
37
|
+
if ((0, types_1.isRef)(pv)) {
|
|
38
|
+
return { linkTo: pv.linkTo };
|
|
39
|
+
}
|
|
40
|
+
return cloneNode(pv);
|
|
41
|
+
}
|
|
42
|
+
/** @deprecated Use cloneNode instead. */
|
|
43
|
+
exports.cloneValue = cloneNode;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export type { MOTLYScalar, MOTLYRef, MOTLYEnvRef, MOTLYValue, MOTLYNode, MOTLYError, MOTLYSchemaError, MOTLYValidationError, } from "../../interface/src/types";
|
|
1
|
+
export type { MOTLYScalar, MOTLYRef, MOTLYEnvRef, MOTLYValue, MOTLYNode, MOTLYPropertyValue, MOTLYError, MOTLYSchemaError, MOTLYValidationError, } from "../../interface/src/types";
|
|
2
2
|
export { isRef, isEnvRef } from "../../interface/src/types";
|
|
3
3
|
export { MOTLYSession } from "./session";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { Statement } from "./ast";
|
|
2
|
-
import {
|
|
3
|
-
/** Execute a list of parsed statements against an existing
|
|
4
|
-
export declare function execute(statements: Statement[], root:
|
|
2
|
+
import { MOTLYNode, MOTLYError } from "../../interface/src/types";
|
|
3
|
+
/** Execute a list of parsed statements against an existing MOTLYNode. */
|
|
4
|
+
export declare function execute(statements: Statement[], root: MOTLYNode): MOTLYError[];
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.execute = execute;
|
|
4
4
|
const types_1 = require("../../interface/src/types");
|
|
5
5
|
const clone_1 = require("./clone");
|
|
6
|
-
/** Execute a list of parsed statements against an existing
|
|
6
|
+
/** Execute a list of parsed statements against an existing MOTLYNode. */
|
|
7
7
|
function execute(statements, root) {
|
|
8
8
|
const errors = [];
|
|
9
9
|
for (const stmt of statements) {
|
|
@@ -37,16 +37,37 @@ function executeStatement(stmt, node, errors) {
|
|
|
37
37
|
/**
|
|
38
38
|
* `name = value` — set eq, preserve existing properties.
|
|
39
39
|
* `name = value { props }` — set eq, then merge properties.
|
|
40
|
+
*
|
|
41
|
+
* Special case: `name = $ref` inserts a MOTLYRef directly.
|
|
42
|
+
* `name = $ref { props }` produces a non-fatal error (ref created, props ignored).
|
|
40
43
|
*/
|
|
41
44
|
function executeSetEq(node, path, value, properties, errors) {
|
|
45
|
+
// Special case: reference value → insert as MOTLYRef
|
|
46
|
+
if (value.kind === "scalar" && value.value.kind === "reference") {
|
|
47
|
+
const refStr = formatRefString(value.value.ups, value.value.path);
|
|
48
|
+
if (properties !== null) {
|
|
49
|
+
const zero = { line: 0, column: 0, offset: 0 };
|
|
50
|
+
errors.push({
|
|
51
|
+
code: "ref-with-properties",
|
|
52
|
+
message: "Cannot add properties to a reference. Did you mean := (clone)?",
|
|
53
|
+
begin: zero,
|
|
54
|
+
end: zero,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const [writeKey, parent] = buildAccessPath(node, path);
|
|
58
|
+
getOrCreateProperties(parent)[writeKey] = { linkTo: refStr };
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
42
61
|
const [writeKey, parent] = buildAccessPath(node, path);
|
|
43
62
|
const props = getOrCreateProperties(parent);
|
|
44
63
|
// Get or create target (preserves existing node and its properties)
|
|
45
|
-
let
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
props[writeKey] =
|
|
64
|
+
let targetPv = props[writeKey];
|
|
65
|
+
if (targetPv === undefined) {
|
|
66
|
+
targetPv = {};
|
|
67
|
+
props[writeKey] = targetPv;
|
|
49
68
|
}
|
|
69
|
+
// If it was a ref, convert to empty node
|
|
70
|
+
const target = ensureNode(props, writeKey);
|
|
50
71
|
// Set the value slot
|
|
51
72
|
setEqSlot(target, value);
|
|
52
73
|
// If properties block present, MERGE them
|
|
@@ -106,10 +127,10 @@ function executeAssignBoth(node, path, value, properties, errors) {
|
|
|
106
127
|
function executeReplaceProperties(node, path, properties, errors) {
|
|
107
128
|
const [writeKey, parent] = buildAccessPath(node, path);
|
|
108
129
|
const result = {};
|
|
109
|
-
// Always preserve the existing value
|
|
130
|
+
// Always preserve the existing value (if it's a node, not a ref)
|
|
110
131
|
const parentProps = getOrCreateProperties(parent);
|
|
111
132
|
const existing = parentProps[writeKey];
|
|
112
|
-
if (existing !== undefined) {
|
|
133
|
+
if (existing !== undefined && !(0, types_1.isRef)(existing)) {
|
|
113
134
|
result.eq = existing.eq;
|
|
114
135
|
}
|
|
115
136
|
for (const stmt of properties) {
|
|
@@ -120,11 +141,11 @@ function executeReplaceProperties(node, path, properties, errors) {
|
|
|
120
141
|
function executeUpdateProperties(node, path, properties, errors) {
|
|
121
142
|
const [writeKey, parent] = buildAccessPath(node, path);
|
|
122
143
|
const props = getOrCreateProperties(parent);
|
|
123
|
-
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
props[writeKey] = target;
|
|
144
|
+
// Get or create the target node (merging semantics - preserves existing)
|
|
145
|
+
if (props[writeKey] === undefined) {
|
|
146
|
+
props[writeKey] = {};
|
|
127
147
|
}
|
|
148
|
+
const target = ensureNode(props, writeKey);
|
|
128
149
|
for (const stmt of properties) {
|
|
129
150
|
executeStatement(stmt, target, errors);
|
|
130
151
|
}
|
|
@@ -148,19 +169,17 @@ function buildAccessPath(node, path) {
|
|
|
148
169
|
for (let i = 0; i < path.length - 1; i++) {
|
|
149
170
|
const segment = path[i];
|
|
150
171
|
const props = getOrCreateProperties(current);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
entry = {};
|
|
154
|
-
props[segment] = entry;
|
|
172
|
+
if (props[segment] === undefined) {
|
|
173
|
+
props[segment] = {};
|
|
155
174
|
}
|
|
156
|
-
current =
|
|
175
|
+
current = ensureNode(props, segment);
|
|
157
176
|
}
|
|
158
177
|
return [path[path.length - 1], current];
|
|
159
178
|
}
|
|
160
179
|
/** Set the eq slot on a target node from a TagValue. */
|
|
161
|
-
function setEqSlot(target, value
|
|
180
|
+
function setEqSlot(target, value) {
|
|
162
181
|
if (value.kind === "array") {
|
|
163
|
-
target.eq = resolveArray(value.elements,
|
|
182
|
+
target.eq = resolveArray(value.elements, []);
|
|
164
183
|
}
|
|
165
184
|
else {
|
|
166
185
|
const sv = value.value;
|
|
@@ -178,8 +197,8 @@ function setEqSlot(target, value, errors) {
|
|
|
178
197
|
target.eq = new Date(sv.value);
|
|
179
198
|
break;
|
|
180
199
|
case "reference":
|
|
181
|
-
|
|
182
|
-
|
|
200
|
+
// References are handled by the caller — should not reach here
|
|
201
|
+
throw new Error("References should be handled before calling setEqSlot");
|
|
183
202
|
case "env":
|
|
184
203
|
target.eq = { env: sv.name };
|
|
185
204
|
break;
|
|
@@ -189,11 +208,25 @@ function setEqSlot(target, value, errors) {
|
|
|
189
208
|
}
|
|
190
209
|
}
|
|
191
210
|
}
|
|
192
|
-
/** Resolve an array of AST elements to
|
|
211
|
+
/** Resolve an array of AST elements to MOTLYPropertyValues. */
|
|
193
212
|
function resolveArray(elements, errors) {
|
|
194
213
|
return elements.map((el) => resolveArrayElement(el, errors));
|
|
195
214
|
}
|
|
196
215
|
function resolveArrayElement(el, errors) {
|
|
216
|
+
// Check if the element value is a reference → becomes MOTLYRef
|
|
217
|
+
if (el.value !== null && el.value.kind === "scalar" && el.value.value.kind === "reference") {
|
|
218
|
+
const refStr = formatRefString(el.value.value.ups, el.value.value.path);
|
|
219
|
+
if (el.properties !== null) {
|
|
220
|
+
const zero = { line: 0, column: 0, offset: 0 };
|
|
221
|
+
errors.push({
|
|
222
|
+
code: "ref-with-properties",
|
|
223
|
+
message: "Cannot add properties to a reference. Did you mean := (clone)?",
|
|
224
|
+
begin: zero,
|
|
225
|
+
end: zero,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
return { linkTo: refStr };
|
|
229
|
+
}
|
|
197
230
|
const node = {};
|
|
198
231
|
if (el.value !== null) {
|
|
199
232
|
setEqSlot(node, el.value);
|
|
@@ -233,30 +266,40 @@ function resolveAndClone(root, stmtPath, ups, refPath) {
|
|
|
233
266
|
start = root;
|
|
234
267
|
}
|
|
235
268
|
else {
|
|
236
|
-
// Relative reference: go up from the current context.
|
|
237
|
-
// stmtPath is the full write path (including the key being assigned to).
|
|
238
|
-
// Current context = parent of write target = stmtPath[0..len-2].
|
|
239
|
-
// Going up `ups` levels: stmtPath[0..len-2-ups].
|
|
240
269
|
const contextLen = stmtPath.length - 1 - ups;
|
|
241
270
|
if (contextLen < 0) {
|
|
242
271
|
throw cloneError(`Clone reference ${refStr} goes ${ups} level(s) up but only ${stmtPath.length - 1} ancestor(s) available`);
|
|
243
272
|
}
|
|
244
273
|
start = root;
|
|
245
274
|
for (let i = 0; i < contextLen; i++) {
|
|
246
|
-
if (!start.properties
|
|
275
|
+
if (!start.properties) {
|
|
247
276
|
throw cloneError(`Clone reference ${refStr} could not be resolved: path segment "${stmtPath[i]}" not found`);
|
|
248
277
|
}
|
|
249
|
-
|
|
278
|
+
const pv = start.properties[stmtPath[i]];
|
|
279
|
+
if (pv === undefined) {
|
|
280
|
+
throw cloneError(`Clone reference ${refStr} could not be resolved: path segment "${stmtPath[i]}" not found`);
|
|
281
|
+
}
|
|
282
|
+
if ((0, types_1.isRef)(pv)) {
|
|
283
|
+
throw cloneError(`Clone reference ${refStr} could not be resolved: path segment "${stmtPath[i]}" is a link reference`);
|
|
284
|
+
}
|
|
285
|
+
start = pv;
|
|
250
286
|
}
|
|
251
287
|
}
|
|
252
288
|
// Follow refPath segments
|
|
253
289
|
let current = start;
|
|
254
290
|
for (const seg of refPath) {
|
|
255
291
|
if (seg.kind === "name") {
|
|
256
|
-
if (!current.properties
|
|
292
|
+
if (!current.properties) {
|
|
257
293
|
throw cloneError(`Clone reference ${refStr} could not be resolved: property "${seg.name}" not found`);
|
|
258
294
|
}
|
|
259
|
-
|
|
295
|
+
const pv = current.properties[seg.name];
|
|
296
|
+
if (pv === undefined) {
|
|
297
|
+
throw cloneError(`Clone reference ${refStr} could not be resolved: property "${seg.name}" not found`);
|
|
298
|
+
}
|
|
299
|
+
if ((0, types_1.isRef)(pv)) {
|
|
300
|
+
throw cloneError(`Clone reference ${refStr} could not be resolved: property "${seg.name}" is a link reference`);
|
|
301
|
+
}
|
|
302
|
+
current = pv;
|
|
260
303
|
}
|
|
261
304
|
else {
|
|
262
305
|
if (!current.eq || !Array.isArray(current.eq)) {
|
|
@@ -265,10 +308,14 @@ function resolveAndClone(root, stmtPath, ups, refPath) {
|
|
|
265
308
|
if (seg.index >= current.eq.length) {
|
|
266
309
|
throw cloneError(`Clone reference ${refStr} could not be resolved: index [${seg.index}] out of bounds (array length ${current.eq.length})`);
|
|
267
310
|
}
|
|
268
|
-
|
|
311
|
+
const elemPv = current.eq[seg.index];
|
|
312
|
+
if ((0, types_1.isRef)(elemPv)) {
|
|
313
|
+
throw cloneError(`Clone reference ${refStr} could not be resolved: index [${seg.index}] is a link reference`);
|
|
314
|
+
}
|
|
315
|
+
current = elemPv;
|
|
269
316
|
}
|
|
270
317
|
}
|
|
271
|
-
return (0, clone_1.
|
|
318
|
+
return (0, clone_1.cloneNode)(current);
|
|
272
319
|
}
|
|
273
320
|
function cloneError(message) {
|
|
274
321
|
const zero = { line: 0, column: 0, offset: 0 };
|
|
@@ -280,29 +327,60 @@ function cloneError(message) {
|
|
|
280
327
|
* if N > D. Absolute references (ups=0) are left alone.
|
|
281
328
|
*/
|
|
282
329
|
function sanitizeClonedRefs(node, depth, errors) {
|
|
283
|
-
|
|
284
|
-
|
|
330
|
+
// Check array elements
|
|
331
|
+
if (node.eq !== undefined && Array.isArray(node.eq)) {
|
|
332
|
+
for (let i = 0; i < node.eq.length; i++) {
|
|
333
|
+
sanitizeClonedPv(node.eq, i, depth + 1, errors);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// Check properties
|
|
337
|
+
if (node.properties) {
|
|
338
|
+
for (const key of Object.keys(node.properties)) {
|
|
339
|
+
sanitizeClonedPvInProps(node.properties, key, depth + 1, errors);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/** Sanitize a single property value within a cloned subtree (in an array context). */
|
|
344
|
+
function sanitizeClonedPv(arr, index, depth, errors) {
|
|
345
|
+
const pv = arr[index];
|
|
346
|
+
if ((0, types_1.isRef)(pv)) {
|
|
347
|
+
const parsed = parseRefUps(pv.linkTo);
|
|
285
348
|
if (parsed.ups > 0 && parsed.ups > depth) {
|
|
286
349
|
const zero = { line: 0, column: 0, offset: 0 };
|
|
287
350
|
errors.push({
|
|
288
351
|
code: "clone-reference-out-of-scope",
|
|
289
|
-
message: `Cloned reference "${
|
|
352
|
+
message: `Cloned reference "${pv.linkTo}" escapes the clone boundary (${parsed.ups} level(s) up from depth ${depth})`,
|
|
290
353
|
begin: zero,
|
|
291
354
|
end: zero,
|
|
292
355
|
});
|
|
293
|
-
|
|
356
|
+
// Convert to empty node
|
|
357
|
+
arr[index] = {};
|
|
294
358
|
}
|
|
295
359
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
sanitizeClonedRefs(elem, depth + 1, errors);
|
|
299
|
-
}
|
|
360
|
+
else {
|
|
361
|
+
sanitizeClonedRefs(pv, depth, errors);
|
|
300
362
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
363
|
+
}
|
|
364
|
+
/** Sanitize a single property value within a cloned subtree (in a properties context). */
|
|
365
|
+
function sanitizeClonedPvInProps(props, key, depth, errors) {
|
|
366
|
+
const pv = props[key];
|
|
367
|
+
if ((0, types_1.isRef)(pv)) {
|
|
368
|
+
const parsed = parseRefUps(pv.linkTo);
|
|
369
|
+
if (parsed.ups > 0 && parsed.ups > depth) {
|
|
370
|
+
const zero = { line: 0, column: 0, offset: 0 };
|
|
371
|
+
errors.push({
|
|
372
|
+
code: "clone-reference-out-of-scope",
|
|
373
|
+
message: `Cloned reference "${pv.linkTo}" escapes the clone boundary (${parsed.ups} level(s) up from depth ${depth})`,
|
|
374
|
+
begin: zero,
|
|
375
|
+
end: zero,
|
|
376
|
+
});
|
|
377
|
+
// Convert to empty node
|
|
378
|
+
props[key] = {};
|
|
304
379
|
}
|
|
305
380
|
}
|
|
381
|
+
else {
|
|
382
|
+
sanitizeClonedRefs(pv, depth, errors);
|
|
383
|
+
}
|
|
306
384
|
}
|
|
307
385
|
/** Extract the ups count from a linkTo string like "$^^name". */
|
|
308
386
|
function parseRefUps(linkTo) {
|
|
@@ -316,10 +394,24 @@ function parseRefUps(linkTo) {
|
|
|
316
394
|
}
|
|
317
395
|
return { ups };
|
|
318
396
|
}
|
|
319
|
-
/** Get or create the properties object on a
|
|
397
|
+
/** Get or create the properties object on a MOTLYNode. */
|
|
320
398
|
function getOrCreateProperties(node) {
|
|
321
399
|
if (!node.properties) {
|
|
322
400
|
node.properties = {};
|
|
323
401
|
}
|
|
324
402
|
return node.properties;
|
|
325
403
|
}
|
|
404
|
+
/**
|
|
405
|
+
* Ensure the property value at props[key] is a MOTLYNode (not a MOTLYRef).
|
|
406
|
+
* If it's a ref, replace it with an empty node.
|
|
407
|
+
* Returns a mutable reference to the node.
|
|
408
|
+
*/
|
|
409
|
+
function ensureNode(props, key) {
|
|
410
|
+
const pv = props[key];
|
|
411
|
+
if ((0, types_1.isRef)(pv)) {
|
|
412
|
+
const node = {};
|
|
413
|
+
props[key] = node;
|
|
414
|
+
return node;
|
|
415
|
+
}
|
|
416
|
+
return pv;
|
|
417
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MOTLYNode, MOTLYError, MOTLYSchemaError, MOTLYValidationError } from "../../interface/src/types";
|
|
2
2
|
/**
|
|
3
3
|
* A stateful MOTLY parsing session.
|
|
4
4
|
*
|
|
@@ -26,7 +26,7 @@ export declare class MOTLYSession {
|
|
|
26
26
|
/**
|
|
27
27
|
* Return a deep clone of the session's current value.
|
|
28
28
|
*/
|
|
29
|
-
getValue():
|
|
29
|
+
getValue(): MOTLYNode;
|
|
30
30
|
/**
|
|
31
31
|
* Validate the session's value against its stored schema.
|
|
32
32
|
* Returns an empty array if no schema has been set.
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function validateReferences(root:
|
|
3
|
-
export declare function validateSchema(tag:
|
|
1
|
+
import { MOTLYNode, MOTLYSchemaError, MOTLYValidationError } from "../../interface/src/types";
|
|
2
|
+
export declare function validateReferences(root: MOTLYNode): MOTLYValidationError[];
|
|
3
|
+
export declare function validateSchema(tag: MOTLYNode, schema: MOTLYNode): MOTLYSchemaError[];
|
|
@@ -6,18 +6,18 @@ const types_1 = require("../../interface/src/types");
|
|
|
6
6
|
function getEqString(node) {
|
|
7
7
|
return typeof node.eq === "string" ? node.eq : undefined;
|
|
8
8
|
}
|
|
9
|
-
function
|
|
10
|
-
if ((0, types_1.isRef)(
|
|
9
|
+
function pvEqString(pv) {
|
|
10
|
+
if ((0, types_1.isRef)(pv))
|
|
11
11
|
return undefined;
|
|
12
|
-
return getEqString(
|
|
12
|
+
return getEqString(pv);
|
|
13
13
|
}
|
|
14
14
|
function extractSection(node, name) {
|
|
15
15
|
if (!node.properties)
|
|
16
16
|
return undefined;
|
|
17
|
-
const
|
|
18
|
-
if (
|
|
17
|
+
const pv = node.properties[name];
|
|
18
|
+
if (pv === undefined || (0, types_1.isRef)(pv))
|
|
19
19
|
return undefined;
|
|
20
|
-
return
|
|
20
|
+
return pv.properties;
|
|
21
21
|
}
|
|
22
22
|
// ── Reference Validation ────────────────────────────────────────
|
|
23
23
|
function validateReferences(root) {
|
|
@@ -34,12 +34,11 @@ function walkRefs(node, path, ancestors, root, errors) {
|
|
|
34
34
|
}
|
|
35
35
|
if (node.properties) {
|
|
36
36
|
for (const key of Object.keys(node.properties)) {
|
|
37
|
-
const
|
|
37
|
+
const childPv = node.properties[key];
|
|
38
38
|
path.push(key);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const errMsg = checkLink(child.eq, ancestors, root);
|
|
39
|
+
if ((0, types_1.isRef)(childPv)) {
|
|
40
|
+
// This property is a reference — check it
|
|
41
|
+
const errMsg = checkLink(childPv.linkTo, ancestors, root);
|
|
43
42
|
if (errMsg !== null) {
|
|
44
43
|
errors.push({
|
|
45
44
|
message: errMsg,
|
|
@@ -48,22 +47,23 @@ function walkRefs(node, path, ancestors, root, errors) {
|
|
|
48
47
|
});
|
|
49
48
|
}
|
|
50
49
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
else {
|
|
51
|
+
// Recurse into child node
|
|
52
|
+
ancestors.push(node);
|
|
53
|
+
walkRefs(childPv, path, ancestors, root, errors);
|
|
54
|
+
ancestors.pop();
|
|
55
|
+
}
|
|
55
56
|
path.pop();
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
function walkArrayRefs(arr, path, ancestors, parentNode, root, errors) {
|
|
60
61
|
for (let i = 0; i < arr.length; i++) {
|
|
61
|
-
const
|
|
62
|
+
const elemPv = arr[i];
|
|
62
63
|
const idxKey = `[${i}]`;
|
|
63
64
|
path.push(idxKey);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const errMsg = checkLink(elem.eq, ancestors, root);
|
|
65
|
+
if ((0, types_1.isRef)(elemPv)) {
|
|
66
|
+
const errMsg = checkLink(elemPv.linkTo, ancestors, root);
|
|
67
67
|
if (errMsg !== null) {
|
|
68
68
|
errors.push({
|
|
69
69
|
message: errMsg,
|
|
@@ -72,15 +72,17 @@ function walkArrayRefs(arr, path, ancestors, parentNode, root, errors) {
|
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
else {
|
|
76
|
+
// Recurse into element node
|
|
77
|
+
ancestors.push(parentNode);
|
|
78
|
+
walkRefs(elemPv, path, ancestors, root, errors);
|
|
79
|
+
ancestors.pop();
|
|
80
|
+
}
|
|
79
81
|
path.pop();
|
|
80
82
|
}
|
|
81
83
|
}
|
|
82
|
-
function checkLink(
|
|
83
|
-
const { ups, segments, error } = parseLinkString(
|
|
84
|
+
function checkLink(linkTo, ancestors, root) {
|
|
85
|
+
const { ups, segments, error } = parseLinkString(linkTo);
|
|
84
86
|
if (error !== null)
|
|
85
87
|
return error;
|
|
86
88
|
let start;
|
|
@@ -90,11 +92,11 @@ function checkLink(link, ancestors, root) {
|
|
|
90
92
|
else {
|
|
91
93
|
const idx = ancestors.length - ups;
|
|
92
94
|
if (idx < 0 || idx >= ancestors.length) {
|
|
93
|
-
return `Reference "${
|
|
95
|
+
return `Reference "${linkTo}" goes ${ups} level(s) up but only ${ancestors.length} ancestor(s) available`;
|
|
94
96
|
}
|
|
95
97
|
start = ancestors[idx];
|
|
96
98
|
}
|
|
97
|
-
return resolvePath(start, segments,
|
|
99
|
+
return resolvePath(start, segments, linkTo);
|
|
98
100
|
}
|
|
99
101
|
function parseLinkString(s) {
|
|
100
102
|
let i = 0;
|
|
@@ -148,15 +150,23 @@ function parseLinkString(s) {
|
|
|
148
150
|
function resolvePath(start, segments, linkStr) {
|
|
149
151
|
let current = start;
|
|
150
152
|
for (const seg of segments) {
|
|
153
|
+
if (current === "terminal") {
|
|
154
|
+
return `Reference "${linkStr}" could not be resolved: cannot follow path through a link`;
|
|
155
|
+
}
|
|
151
156
|
if (seg.kind === "name") {
|
|
152
157
|
if (!current.properties) {
|
|
153
158
|
return `Reference "${linkStr}" could not be resolved: property "${seg.name}" not found (node has no properties)`;
|
|
154
159
|
}
|
|
155
|
-
const
|
|
156
|
-
if (
|
|
160
|
+
const childPv = current.properties[seg.name];
|
|
161
|
+
if (childPv === undefined) {
|
|
157
162
|
return `Reference "${linkStr}" could not be resolved: property "${seg.name}" not found`;
|
|
158
163
|
}
|
|
159
|
-
|
|
164
|
+
if ((0, types_1.isRef)(childPv)) {
|
|
165
|
+
current = "terminal";
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
current = childPv;
|
|
169
|
+
}
|
|
160
170
|
}
|
|
161
171
|
else {
|
|
162
172
|
if (current.eq === undefined || !Array.isArray(current.eq)) {
|
|
@@ -165,7 +175,13 @@ function resolvePath(start, segments, linkStr) {
|
|
|
165
175
|
if (seg.index >= current.eq.length) {
|
|
166
176
|
return `Reference "${linkStr}" could not be resolved: index [${seg.index}] out of bounds (array length ${current.eq.length})`;
|
|
167
177
|
}
|
|
168
|
-
|
|
178
|
+
const elemPv = current.eq[seg.index];
|
|
179
|
+
if ((0, types_1.isRef)(elemPv)) {
|
|
180
|
+
current = "terminal";
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
current = elemPv;
|
|
184
|
+
}
|
|
169
185
|
}
|
|
170
186
|
}
|
|
171
187
|
return null;
|
|
@@ -180,12 +196,10 @@ function validateSchema(tag, schema) {
|
|
|
180
196
|
function getAdditionalPolicy(schema) {
|
|
181
197
|
if (!schema.properties)
|
|
182
198
|
return { kind: "reject" };
|
|
183
|
-
const
|
|
184
|
-
if (
|
|
185
|
-
return { kind: "reject" };
|
|
186
|
-
if ((0, types_1.isRef)(additional.eq))
|
|
199
|
+
const additionalPv = schema.properties["Additional"];
|
|
200
|
+
if (additionalPv === undefined || (0, types_1.isRef)(additionalPv))
|
|
187
201
|
return { kind: "reject" };
|
|
188
|
-
const eqStr = getEqString(
|
|
202
|
+
const eqStr = getEqString(additionalPv);
|
|
189
203
|
if (eqStr !== undefined) {
|
|
190
204
|
if (eqStr === "allow")
|
|
191
205
|
return { kind: "allow" };
|
|
@@ -204,8 +218,8 @@ function validateNodeAgainstSchema(tag, schema, types, path, errors) {
|
|
|
204
218
|
if (required) {
|
|
205
219
|
for (const key of Object.keys(required)) {
|
|
206
220
|
const propPath = [...path, key];
|
|
207
|
-
const
|
|
208
|
-
if (
|
|
221
|
+
const tagValuePv = tagProps ? tagProps[key] : undefined;
|
|
222
|
+
if (tagValuePv === undefined) {
|
|
209
223
|
errors.push({
|
|
210
224
|
message: `Missing required property "${key}"`,
|
|
211
225
|
path: propPath,
|
|
@@ -213,16 +227,16 @@ function validateNodeAgainstSchema(tag, schema, types, path, errors) {
|
|
|
213
227
|
});
|
|
214
228
|
}
|
|
215
229
|
else {
|
|
216
|
-
validateValueType(
|
|
230
|
+
validateValueType(tagValuePv, required[key], types, propPath, errors);
|
|
217
231
|
}
|
|
218
232
|
}
|
|
219
233
|
}
|
|
220
234
|
// Check optional properties that exist
|
|
221
235
|
if (optional && tagProps) {
|
|
222
236
|
for (const key of Object.keys(optional)) {
|
|
223
|
-
const
|
|
224
|
-
if (
|
|
225
|
-
validateValueType(
|
|
237
|
+
const tagValuePv = tagProps[key];
|
|
238
|
+
if (tagValuePv !== undefined) {
|
|
239
|
+
validateValueType(tagValuePv, optional[key], types, [...path, key], errors);
|
|
226
240
|
}
|
|
227
241
|
}
|
|
228
242
|
}
|
|
@@ -261,29 +275,83 @@ function validateNodeAgainstSchema(tag, schema, types, path, errors) {
|
|
|
261
275
|
function makeTypeSpecNode(typeName) {
|
|
262
276
|
return { eq: typeName };
|
|
263
277
|
}
|
|
264
|
-
function validateValueType(
|
|
265
|
-
|
|
278
|
+
function validateValueType(valuePv, typeSpecPv, types, path, errors) {
|
|
279
|
+
// Skip ref type specs in schema
|
|
280
|
+
if ((0, types_1.isRef)(typeSpecPv))
|
|
266
281
|
return;
|
|
282
|
+
const specNode = typeSpecPv;
|
|
283
|
+
// If value is a ref, generate appropriate "found a link" error
|
|
284
|
+
if ((0, types_1.isRef)(valuePv)) {
|
|
285
|
+
pushRefTypeError(specNode, path, errors);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
// Value is a node
|
|
289
|
+
const value = valuePv;
|
|
290
|
+
validateNodeAgainstTypeSpec(value, specNode, types, path, errors);
|
|
291
|
+
}
|
|
292
|
+
function pushRefTypeError(specNode, path, errors) {
|
|
293
|
+
// Check for enum
|
|
294
|
+
if (specNode.properties) {
|
|
295
|
+
const eqProp = specNode.properties["eq"];
|
|
296
|
+
if (eqProp !== undefined && !(0, types_1.isRef)(eqProp)) {
|
|
297
|
+
if (Array.isArray(eqProp.eq)) {
|
|
298
|
+
errors.push({
|
|
299
|
+
message: "Expected an enum value but found a link",
|
|
300
|
+
path: [...path],
|
|
301
|
+
code: "wrong-type",
|
|
302
|
+
});
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (specNode.properties["matches"] !== undefined) {
|
|
307
|
+
errors.push({
|
|
308
|
+
message: "Expected a value matching a pattern but found a link",
|
|
309
|
+
path: [...path],
|
|
310
|
+
code: "wrong-type",
|
|
311
|
+
});
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const typeName = getEqString(specNode);
|
|
316
|
+
if (typeName !== undefined) {
|
|
317
|
+
errors.push({
|
|
318
|
+
message: `Expected type "${typeName}" but found a link`,
|
|
319
|
+
path: [...path],
|
|
320
|
+
code: "wrong-type",
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
else if (specNode.properties &&
|
|
324
|
+
("Required" in specNode.properties ||
|
|
325
|
+
"Optional" in specNode.properties ||
|
|
326
|
+
"Additional" in specNode.properties)) {
|
|
327
|
+
errors.push({
|
|
328
|
+
message: "Expected a tag but found a link",
|
|
329
|
+
path: [...path],
|
|
330
|
+
code: "wrong-type",
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function validateNodeAgainstTypeSpec(value, specNode, types, path, errors) {
|
|
267
335
|
// Check for union type (oneOf)
|
|
268
|
-
if (
|
|
269
|
-
const
|
|
270
|
-
if (
|
|
271
|
-
validateUnion(value,
|
|
336
|
+
if (specNode.properties) {
|
|
337
|
+
const oneOfPv = specNode.properties["oneOf"];
|
|
338
|
+
if (oneOfPv !== undefined && !(0, types_1.isRef)(oneOfPv)) {
|
|
339
|
+
validateUnion(value, oneOfPv, types, path, errors);
|
|
272
340
|
return;
|
|
273
341
|
}
|
|
274
342
|
}
|
|
275
343
|
// Check for enum (eq) or pattern (matches)
|
|
276
|
-
if (
|
|
277
|
-
const eqProp =
|
|
278
|
-
if (eqProp !== undefined && !(0, types_1.isRef)(eqProp
|
|
344
|
+
if (specNode.properties) {
|
|
345
|
+
const eqProp = specNode.properties["eq"];
|
|
346
|
+
if (eqProp !== undefined && !(0, types_1.isRef)(eqProp)) {
|
|
279
347
|
if (Array.isArray(eqProp.eq)) {
|
|
280
348
|
validateEnum(value, eqProp.eq, path, errors);
|
|
281
349
|
return;
|
|
282
350
|
}
|
|
283
351
|
}
|
|
284
|
-
const matchesProp =
|
|
285
|
-
if (matchesProp !== undefined && !(0, types_1.isRef)(matchesProp
|
|
286
|
-
const baseType = getEqString(
|
|
352
|
+
const matchesProp = specNode.properties["matches"];
|
|
353
|
+
if (matchesProp !== undefined && !(0, types_1.isRef)(matchesProp)) {
|
|
354
|
+
const baseType = getEqString(specNode);
|
|
287
355
|
if (baseType !== undefined) {
|
|
288
356
|
validateBaseType(value, baseType, types, path, errors);
|
|
289
357
|
}
|
|
@@ -292,23 +360,14 @@ function validateValueType(value, typeSpec, types, path, errors) {
|
|
|
292
360
|
}
|
|
293
361
|
}
|
|
294
362
|
// Get the type name from the spec's eq value
|
|
295
|
-
const typeName = getEqString(
|
|
363
|
+
const typeName = getEqString(specNode);
|
|
296
364
|
if (typeName === undefined) {
|
|
297
365
|
// Nested schema (has Required/Optional/Additional)
|
|
298
|
-
if (
|
|
299
|
-
("Required" in
|
|
300
|
-
"Optional" in
|
|
301
|
-
"Additional" in
|
|
302
|
-
|
|
303
|
-
errors.push({
|
|
304
|
-
message: "Expected a tag but found a link",
|
|
305
|
-
path: [...path],
|
|
306
|
-
code: "wrong-type",
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
else {
|
|
310
|
-
validateNodeAgainstSchema(value, typeSpec, types, path, errors);
|
|
311
|
-
}
|
|
366
|
+
if (specNode.properties &&
|
|
367
|
+
("Required" in specNode.properties ||
|
|
368
|
+
"Optional" in specNode.properties ||
|
|
369
|
+
"Additional" in specNode.properties)) {
|
|
370
|
+
validateNodeAgainstSchema(value, specNode, types, path, errors);
|
|
312
371
|
}
|
|
313
372
|
return;
|
|
314
373
|
}
|
|
@@ -335,19 +394,22 @@ function validateBaseType(value, typeName, types, path, errors) {
|
|
|
335
394
|
validateTypeDate(value, path, errors);
|
|
336
395
|
break;
|
|
337
396
|
case "tag":
|
|
338
|
-
|
|
339
|
-
break;
|
|
397
|
+
break; // tag — node exists, always valid for a non-ref
|
|
340
398
|
case "flag":
|
|
341
|
-
|
|
342
|
-
break;
|
|
399
|
+
break; // flag — presence-only, always valid for a non-ref
|
|
343
400
|
case "any":
|
|
344
|
-
break;
|
|
401
|
+
break; // any — always valid
|
|
345
402
|
default: {
|
|
346
403
|
// Custom type
|
|
347
404
|
if (types) {
|
|
348
|
-
const
|
|
349
|
-
if (
|
|
350
|
-
|
|
405
|
+
const typeDefPv = types[typeName];
|
|
406
|
+
if (typeDefPv !== undefined) {
|
|
407
|
+
if ((0, types_1.isRef)(typeDefPv)) {
|
|
408
|
+
// Type definition is a ref — skip
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
validateNodeAgainstTypeSpec(value, typeDefPv, types, path, errors);
|
|
412
|
+
}
|
|
351
413
|
}
|
|
352
414
|
else {
|
|
353
415
|
errors.push({
|
|
@@ -368,14 +430,6 @@ function validateBaseType(value, typeName, types, path, errors) {
|
|
|
368
430
|
}
|
|
369
431
|
}
|
|
370
432
|
function validateTypeString(value, path, errors) {
|
|
371
|
-
if ((0, types_1.isRef)(value.eq)) {
|
|
372
|
-
errors.push({
|
|
373
|
-
message: 'Expected type "string" but found a link',
|
|
374
|
-
path: [...path],
|
|
375
|
-
code: "wrong-type",
|
|
376
|
-
});
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
433
|
if (typeof value.eq !== "string") {
|
|
380
434
|
errors.push({
|
|
381
435
|
message: 'Expected type "string"',
|
|
@@ -385,14 +439,6 @@ function validateTypeString(value, path, errors) {
|
|
|
385
439
|
}
|
|
386
440
|
}
|
|
387
441
|
function validateTypeNumber(value, path, errors) {
|
|
388
|
-
if ((0, types_1.isRef)(value.eq)) {
|
|
389
|
-
errors.push({
|
|
390
|
-
message: 'Expected type "number" but found a link',
|
|
391
|
-
path: [...path],
|
|
392
|
-
code: "wrong-type",
|
|
393
|
-
});
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
442
|
if (typeof value.eq !== "number") {
|
|
397
443
|
errors.push({
|
|
398
444
|
message: 'Expected type "number"',
|
|
@@ -402,14 +448,6 @@ function validateTypeNumber(value, path, errors) {
|
|
|
402
448
|
}
|
|
403
449
|
}
|
|
404
450
|
function validateTypeBoolean(value, path, errors) {
|
|
405
|
-
if ((0, types_1.isRef)(value.eq)) {
|
|
406
|
-
errors.push({
|
|
407
|
-
message: 'Expected type "boolean" but found a link',
|
|
408
|
-
path: [...path],
|
|
409
|
-
code: "wrong-type",
|
|
410
|
-
});
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
451
|
if (typeof value.eq !== "boolean") {
|
|
414
452
|
errors.push({
|
|
415
453
|
message: 'Expected type "boolean"',
|
|
@@ -419,14 +457,6 @@ function validateTypeBoolean(value, path, errors) {
|
|
|
419
457
|
}
|
|
420
458
|
}
|
|
421
459
|
function validateTypeDate(value, path, errors) {
|
|
422
|
-
if ((0, types_1.isRef)(value.eq)) {
|
|
423
|
-
errors.push({
|
|
424
|
-
message: 'Expected type "date" but found a link',
|
|
425
|
-
path: [...path],
|
|
426
|
-
code: "wrong-type",
|
|
427
|
-
});
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
460
|
if (!(value.eq instanceof Date)) {
|
|
431
461
|
errors.push({
|
|
432
462
|
message: 'Expected type "date"',
|
|
@@ -435,33 +465,7 @@ function validateTypeDate(value, path, errors) {
|
|
|
435
465
|
});
|
|
436
466
|
}
|
|
437
467
|
}
|
|
438
|
-
function validateTypeTag(value, path, errors) {
|
|
439
|
-
if ((0, types_1.isRef)(value.eq)) {
|
|
440
|
-
errors.push({
|
|
441
|
-
message: 'Expected type "tag" but found a link',
|
|
442
|
-
path: [...path],
|
|
443
|
-
code: "wrong-type",
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
function validateTypeFlag(value, path, errors) {
|
|
448
|
-
if ((0, types_1.isRef)(value.eq)) {
|
|
449
|
-
errors.push({
|
|
450
|
-
message: 'Expected type "flag" but found a link',
|
|
451
|
-
path: [...path],
|
|
452
|
-
code: "wrong-type",
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
468
|
function validateArrayType(value, innerType, types, path, errors) {
|
|
457
|
-
if ((0, types_1.isRef)(value.eq)) {
|
|
458
|
-
errors.push({
|
|
459
|
-
message: `Expected type "${innerType}[]" but found a link`,
|
|
460
|
-
path: [...path],
|
|
461
|
-
code: "wrong-type",
|
|
462
|
-
});
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
465
469
|
if (!Array.isArray(value.eq)) {
|
|
466
470
|
errors.push({
|
|
467
471
|
message: `Expected type "${innerType}[]" but value is not an array`,
|
|
@@ -472,18 +476,20 @@ function validateArrayType(value, innerType, types, path, errors) {
|
|
|
472
476
|
}
|
|
473
477
|
for (let i = 0; i < value.eq.length; i++) {
|
|
474
478
|
const elemPath = [...path, `[${i}]`];
|
|
475
|
-
|
|
479
|
+
const elemPv = value.eq[i];
|
|
480
|
+
if ((0, types_1.isRef)(elemPv)) {
|
|
481
|
+
errors.push({
|
|
482
|
+
message: `Expected type "${innerType}" but found a link`,
|
|
483
|
+
path: elemPath,
|
|
484
|
+
code: "wrong-type",
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
validateBaseType(elemPv, innerType, types, elemPath, errors);
|
|
489
|
+
}
|
|
476
490
|
}
|
|
477
491
|
}
|
|
478
492
|
function validateEnum(value, allowed, path, errors) {
|
|
479
|
-
if ((0, types_1.isRef)(value.eq)) {
|
|
480
|
-
errors.push({
|
|
481
|
-
message: "Expected an enum value but found a link",
|
|
482
|
-
path: [...path],
|
|
483
|
-
code: "wrong-type",
|
|
484
|
-
});
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
487
493
|
const nodeEq = value.eq;
|
|
488
494
|
if (nodeEq === undefined ||
|
|
489
495
|
(typeof nodeEq !== "string" &&
|
|
@@ -498,7 +504,7 @@ function validateEnum(value, allowed, path, errors) {
|
|
|
498
504
|
return;
|
|
499
505
|
}
|
|
500
506
|
const matches = allowed.some((a) => {
|
|
501
|
-
if ((0, types_1.isRef)(a
|
|
507
|
+
if ((0, types_1.isRef)(a))
|
|
502
508
|
return false;
|
|
503
509
|
const aeq = a.eq;
|
|
504
510
|
if (aeq instanceof Date && nodeEq instanceof Date) {
|
|
@@ -508,7 +514,7 @@ function validateEnum(value, allowed, path, errors) {
|
|
|
508
514
|
});
|
|
509
515
|
if (!matches) {
|
|
510
516
|
const allowedStrs = allowed
|
|
511
|
-
.filter((a) => !(0, types_1.isRef)(a
|
|
517
|
+
.filter((a) => !(0, types_1.isRef)(a))
|
|
512
518
|
.map((a) => {
|
|
513
519
|
const aeq = a.eq;
|
|
514
520
|
return JSON.stringify(String(aeq));
|
|
@@ -524,14 +530,6 @@ function validatePattern(value, matchesNode, path, errors) {
|
|
|
524
530
|
const pattern = getEqString(matchesNode);
|
|
525
531
|
if (pattern === undefined)
|
|
526
532
|
return;
|
|
527
|
-
if ((0, types_1.isRef)(value.eq)) {
|
|
528
|
-
errors.push({
|
|
529
|
-
message: "Expected a value matching a pattern but found a link",
|
|
530
|
-
path: [...path],
|
|
531
|
-
code: "wrong-type",
|
|
532
|
-
});
|
|
533
|
-
return;
|
|
534
|
-
}
|
|
535
533
|
if (typeof value.eq !== "string") {
|
|
536
534
|
errors.push({
|
|
537
535
|
message: `Expected a string matching pattern "${pattern}"`,
|
|
@@ -561,18 +559,20 @@ function validatePattern(value, matchesNode, path, errors) {
|
|
|
561
559
|
function validateUnion(value, oneOfNode, types, path, errors) {
|
|
562
560
|
if (!Array.isArray(oneOfNode.eq))
|
|
563
561
|
return;
|
|
564
|
-
|
|
565
|
-
|
|
562
|
+
// We need to wrap value as a MOTLYPropertyValue for validate_value_type
|
|
563
|
+
const valuePv = value;
|
|
564
|
+
for (const typePv of oneOfNode.eq) {
|
|
565
|
+
const typeName = pvEqString(typePv);
|
|
566
566
|
if (typeName === undefined)
|
|
567
567
|
continue;
|
|
568
568
|
const trialErrors = [];
|
|
569
569
|
const synthetic = makeTypeSpecNode(typeName);
|
|
570
|
-
validateValueType(
|
|
570
|
+
validateValueType(valuePv, synthetic, types, path, trialErrors);
|
|
571
571
|
if (trialErrors.length === 0)
|
|
572
572
|
return;
|
|
573
573
|
}
|
|
574
574
|
const typeStrs = oneOfNode.eq
|
|
575
|
-
.map((v) =>
|
|
575
|
+
.map((v) => pvEqString(v))
|
|
576
576
|
.filter((s) => s !== undefined);
|
|
577
577
|
errors.push({
|
|
578
578
|
message: `Value does not match any type in oneOf: [${typeStrs.join(", ")}]`,
|