@swaggerexpert/jsonpath 3.2.4 → 4.0.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 +225 -18
- package/cjs/errors/JSONNormalizedPathError.cjs +8 -0
- package/cjs/errors/JSONPathError.cjs +1 -1
- package/cjs/errors/{JSONPathCompileError.cjs → JSONPathEvaluateError.cjs} +2 -2
- package/cjs/evaluate/evaluators/comparable.cjs +44 -0
- package/cjs/evaluate/evaluators/comparison-expr.cjs +37 -0
- package/cjs/evaluate/evaluators/filter-query.cjs +182 -0
- package/cjs/evaluate/evaluators/function-expr.cjs +106 -0
- package/cjs/evaluate/evaluators/literal.cjs +25 -0
- package/cjs/evaluate/evaluators/logical-expr.cjs +96 -0
- package/cjs/evaluate/evaluators/singular-query.cjs +103 -0
- package/cjs/evaluate/functions/count.cjs +35 -0
- package/cjs/evaluate/functions/index.cjs +15 -0
- package/cjs/evaluate/functions/length.cjs +42 -0
- package/cjs/evaluate/functions/match.cjs +49 -0
- package/cjs/evaluate/functions/search.cjs +49 -0
- package/cjs/evaluate/functions/value.cjs +36 -0
- package/cjs/evaluate/index.cjs +182 -0
- package/cjs/evaluate/realms/EvaluationRealm.cjs +154 -0
- package/cjs/evaluate/realms/json/index.cjs +246 -0
- package/cjs/evaluate/utils/guards.cjs +129 -0
- package/cjs/evaluate/utils/i-regexp.cjs +118 -0
- package/cjs/evaluate/visitors/bracketed-selection.cjs +35 -0
- package/cjs/evaluate/visitors/filter-selector.cjs +43 -0
- package/cjs/evaluate/visitors/index-selector.cjs +55 -0
- package/cjs/evaluate/visitors/name-selector.cjs +38 -0
- package/cjs/evaluate/visitors/segment.cjs +99 -0
- package/cjs/evaluate/visitors/selector.cjs +47 -0
- package/cjs/evaluate/visitors/slice-selector.cjs +115 -0
- package/cjs/evaluate/visitors/wildcard-selector.cjs +32 -0
- package/cjs/index.cjs +16 -7
- package/cjs/normalized-path.cjs +145 -0
- package/cjs/parse/callbacks/cst.cjs +2 -4
- package/cjs/parse/index.cjs +3 -1
- package/cjs/parse/translators/ASTTranslator/index.cjs +1 -1
- package/cjs/parse/translators/ASTTranslator/transformers.cjs +246 -5
- package/cjs/parse/translators/CSTOptimizedTranslator.cjs +1 -3
- package/cjs/parse/translators/CSTTranslator.cjs +1 -2
- package/cjs/test/index.cjs +4 -2
- package/es/errors/JSONNormalizedPathError.mjs +3 -0
- package/es/errors/JSONPathError.mjs +1 -1
- package/es/errors/JSONPathEvaluateError.mjs +3 -0
- package/es/evaluate/evaluators/comparable.mjs +38 -0
- package/es/evaluate/evaluators/comparison-expr.mjs +31 -0
- package/es/evaluate/evaluators/filter-query.mjs +175 -0
- package/es/evaluate/evaluators/function-expr.mjs +99 -0
- package/es/evaluate/evaluators/literal.mjs +21 -0
- package/es/evaluate/evaluators/logical-expr.mjs +89 -0
- package/es/evaluate/evaluators/singular-query.mjs +97 -0
- package/es/evaluate/functions/count.mjs +30 -0
- package/es/evaluate/functions/index.mjs +13 -0
- package/es/evaluate/functions/length.mjs +37 -0
- package/es/evaluate/functions/match.mjs +44 -0
- package/es/evaluate/functions/search.mjs +44 -0
- package/es/evaluate/functions/value.mjs +31 -0
- package/es/evaluate/index.mjs +174 -0
- package/es/evaluate/realms/EvaluationRealm.mjs +148 -0
- package/es/evaluate/realms/json/index.mjs +240 -0
- package/es/evaluate/utils/guards.mjs +114 -0
- package/es/evaluate/utils/i-regexp.mjs +113 -0
- package/es/evaluate/visitors/bracketed-selection.mjs +29 -0
- package/es/evaluate/visitors/filter-selector.mjs +37 -0
- package/es/evaluate/visitors/index-selector.mjs +51 -0
- package/es/evaluate/visitors/name-selector.mjs +34 -0
- package/es/evaluate/visitors/segment.mjs +91 -0
- package/es/evaluate/visitors/selector.mjs +41 -0
- package/es/evaluate/visitors/slice-selector.mjs +111 -0
- package/es/evaluate/visitors/wildcard-selector.mjs +28 -0
- package/es/index.mjs +7 -3
- package/es/normalized-path.mjs +136 -0
- package/es/parse/callbacks/cst.mjs +2 -4
- package/es/parse/index.mjs +3 -1
- package/es/parse/translators/ASTTranslator/index.mjs +1 -1
- package/es/parse/translators/ASTTranslator/transformers.mjs +246 -5
- package/es/parse/translators/CSTOptimizedTranslator.mjs +1 -3
- package/es/parse/translators/CSTTranslator.mjs +1 -2
- package/es/test/index.mjs +4 -2
- package/package.json +4 -2
- package/types/index.d.ts +135 -8
- package/cjs/compile.cjs +0 -50
- package/cjs/escape.cjs +0 -59
- package/es/compile.mjs +0 -45
- package/es/errors/JSONPathCompileError.mjs +0 -3
- package/es/escape.mjs +0 -55
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.to = exports.test = exports.from = exports.escape = void 0;
|
|
5
|
+
var _index = _interopRequireDefault(require("./parse/index.cjs"));
|
|
6
|
+
var _index2 = _interopRequireDefault(require("./test/index.cjs"));
|
|
7
|
+
var _JSONNormalizedPathError = _interopRequireDefault(require("./errors/JSONNormalizedPathError.cjs"));
|
|
8
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
/**
|
|
10
|
+
* Tests if a string is a valid normalized JSONPath.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} normalizedPath - The string to test
|
|
13
|
+
* @returns {boolean} True if valid normalized path, false otherwise
|
|
14
|
+
*/
|
|
15
|
+
const test = normalizedPath => (0, _index2.default)(normalizedPath, {
|
|
16
|
+
normalized: true
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Escapes a string for use in a normalized JSONPath name selector.
|
|
21
|
+
* Follows RFC 9535 Section 2.7 escaping rules for single-quoted strings.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} selector - The string to escape
|
|
24
|
+
* @returns {string} The escaped string (without surrounding quotes)
|
|
25
|
+
*/
|
|
26
|
+
exports.test = test;
|
|
27
|
+
const escape = selector => {
|
|
28
|
+
if (typeof selector !== 'string') {
|
|
29
|
+
throw new TypeError('Selector must be a string');
|
|
30
|
+
}
|
|
31
|
+
let escaped = '';
|
|
32
|
+
for (const char of selector) {
|
|
33
|
+
const codePoint = char.codePointAt(0);
|
|
34
|
+
switch (codePoint) {
|
|
35
|
+
case 0x08:
|
|
36
|
+
// backspace
|
|
37
|
+
escaped += '\\b';
|
|
38
|
+
break;
|
|
39
|
+
case 0x09:
|
|
40
|
+
// horizontal tab
|
|
41
|
+
escaped += '\\t';
|
|
42
|
+
break;
|
|
43
|
+
case 0x0a:
|
|
44
|
+
// line feed
|
|
45
|
+
escaped += '\\n';
|
|
46
|
+
break;
|
|
47
|
+
case 0x0c:
|
|
48
|
+
// form feed
|
|
49
|
+
escaped += '\\f';
|
|
50
|
+
break;
|
|
51
|
+
case 0x0d:
|
|
52
|
+
// carriage return
|
|
53
|
+
escaped += '\\r';
|
|
54
|
+
break;
|
|
55
|
+
case 0x27:
|
|
56
|
+
// apostrophe '
|
|
57
|
+
escaped += "\\'";
|
|
58
|
+
break;
|
|
59
|
+
case 0x5c:
|
|
60
|
+
// backslash \
|
|
61
|
+
escaped += '\\\\';
|
|
62
|
+
break;
|
|
63
|
+
default:
|
|
64
|
+
// Other control characters (U+0000-U+001F except those handled above)
|
|
65
|
+
if (codePoint <= 0x1f) {
|
|
66
|
+
escaped += `\\u${codePoint.toString(16).padStart(4, '0')}`;
|
|
67
|
+
} else {
|
|
68
|
+
escaped += char;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return escaped;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Creates a normalized path string from a list of selectors.
|
|
77
|
+
* Name selectors are automatically escaped.
|
|
78
|
+
*
|
|
79
|
+
* @param {Array<string|number>} selectors - Array of name selectors (strings) or index selectors (numbers)
|
|
80
|
+
* @returns {string} A normalized JSONPath string
|
|
81
|
+
* @throws {JSONNormalizedPathError} If selectors is not an array or contains invalid selector types
|
|
82
|
+
*/
|
|
83
|
+
exports.escape = escape;
|
|
84
|
+
const from = selectors => {
|
|
85
|
+
if (!Array.isArray(selectors)) {
|
|
86
|
+
throw new _JSONNormalizedPathError.default(`Selectors must be an array, got: ${typeof selectors}`, {
|
|
87
|
+
selectors
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const segments = selectors.map(selector => {
|
|
92
|
+
if (typeof selector === 'string') {
|
|
93
|
+
// Name selector: escape and wrap in single quotes
|
|
94
|
+
return `['${escape(selector)}']`;
|
|
95
|
+
}
|
|
96
|
+
if (typeof selector === 'number') {
|
|
97
|
+
// Index selector: must be a non-negative safe integer (RFC 9535 Section 2.1)
|
|
98
|
+
if (!Number.isSafeInteger(selector) || selector < 0) {
|
|
99
|
+
throw new TypeError(`Index selector must be a non-negative safe integer, got: ${selector}`);
|
|
100
|
+
}
|
|
101
|
+
return `[${selector}]`;
|
|
102
|
+
}
|
|
103
|
+
throw new TypeError(`Selector must be a string or non-negative integer, got: ${typeof selector}`);
|
|
104
|
+
});
|
|
105
|
+
return `$${segments.join('')}`;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
throw new _JSONNormalizedPathError.default('Failed to compile normalized JSONPath', {
|
|
108
|
+
cause: error,
|
|
109
|
+
selectors
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Parses a normalized path string and returns a list of selectors.
|
|
116
|
+
*
|
|
117
|
+
* @param {string} normalizedPath - A normalized JSONPath string
|
|
118
|
+
* @returns {Array<string|number>} Array of name selectors (strings) or index selectors (numbers)
|
|
119
|
+
* @throws {JSONNormalizedPathError} If the normalized path is invalid
|
|
120
|
+
*/
|
|
121
|
+
exports.from = from;
|
|
122
|
+
const to = normalizedPath => {
|
|
123
|
+
if (typeof normalizedPath !== 'string') {
|
|
124
|
+
throw new _JSONNormalizedPathError.default(`Normalized path must be a string, got: ${typeof normalizedPath}`, {
|
|
125
|
+
normalizedPath
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
const parseResult = (0, _index.default)(normalizedPath, {
|
|
129
|
+
normalized: true
|
|
130
|
+
});
|
|
131
|
+
if (!parseResult.result.success) {
|
|
132
|
+
throw new _JSONNormalizedPathError.default('Invalid normalized path', {
|
|
133
|
+
normalizedPath
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
const {
|
|
137
|
+
tree
|
|
138
|
+
} = parseResult;
|
|
139
|
+
|
|
140
|
+
// Extract selectors from AST segments
|
|
141
|
+
// Normalized path grammar only allows NameSelector and IndexSelector
|
|
142
|
+
// For normalized paths, segment.selector is directly the selector (not BracketedSelection)
|
|
143
|
+
return tree.segments.map(segment => segment.selector.value);
|
|
144
|
+
};
|
|
145
|
+
exports.to = to;
|
|
@@ -7,13 +7,12 @@ var _JSONPathParseError = _interopRequireDefault(require("../../errors/JSONPathP
|
|
|
7
7
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
8
8
|
const cst = nodeType => {
|
|
9
9
|
return (state, chars, phraseIndex, phraseLength, data) => {
|
|
10
|
-
var _data$options, _data$options2;
|
|
11
10
|
if (!(typeof data === 'object' && data !== null && !Array.isArray(data))) {
|
|
12
11
|
throw new _JSONPathParseError.default("parser's user data must be an object");
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
// drop the empty nodes
|
|
16
|
-
if (
|
|
15
|
+
if (data.options?.optimize && phraseLength === 0 && data.options?.droppableTypes?.includes(nodeType)) {
|
|
17
16
|
return;
|
|
18
17
|
}
|
|
19
18
|
if (state === _apgLite.identifiers.SEM_PRE) {
|
|
@@ -25,11 +24,10 @@ const cst = nodeType => {
|
|
|
25
24
|
children: []
|
|
26
25
|
};
|
|
27
26
|
if (data.stack.length > 0) {
|
|
28
|
-
var _data$options3, _data$options4;
|
|
29
27
|
const parent = data.stack[data.stack.length - 1];
|
|
30
28
|
const prevSibling = parent.children[parent.children.length - 1];
|
|
31
29
|
const isTextNodeWithinTextNode = parent.type === 'text' && node.type === 'text';
|
|
32
|
-
const shouldCollapse =
|
|
30
|
+
const shouldCollapse = data.options?.optimize && data.options?.collapsibleTypes?.includes(node.type) && prevSibling?.type === node.type;
|
|
33
31
|
if (shouldCollapse) {
|
|
34
32
|
prevSibling.text += node.text;
|
|
35
33
|
prevSibling.length += node.length;
|
package/cjs/parse/index.cjs
CHANGED
|
@@ -32,7 +32,9 @@ const parse = (jsonPath, {
|
|
|
32
32
|
trace: parser.trace
|
|
33
33
|
};
|
|
34
34
|
} catch (error) {
|
|
35
|
-
|
|
35
|
+
// Provide specific error message for semantic validation errors
|
|
36
|
+
const message = error instanceof RangeError ? `Invalid JSONPath expression: ${error.message}` : 'Unexpected error during JSONPath parsing';
|
|
37
|
+
throw new _JSONPathParseError.default(message, {
|
|
36
38
|
cause: error,
|
|
37
39
|
jsonPath
|
|
38
40
|
});
|
|
@@ -9,7 +9,7 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
|
|
|
9
9
|
class ASTTranslator extends _CSTOptimizedTranslator.default {
|
|
10
10
|
getTree() {
|
|
11
11
|
const cst = super.getTree();
|
|
12
|
-
return (0, _transformers.transformCSTtoAST)(cst
|
|
12
|
+
return (0, _transformers.transformCSTtoAST)(cst, _transformers.default);
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
var _default = exports.default = ASTTranslator;
|
|
@@ -5,6 +5,204 @@ exports.transformCSTtoAST = exports.default = void 0;
|
|
|
5
5
|
var _JSONPathParseError = _interopRequireDefault(require("../../../errors/JSONPathParseError.cjs"));
|
|
6
6
|
var _decoders = require("./decoders.cjs");
|
|
7
7
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
8
|
+
/**
|
|
9
|
+
* RFC 9535 function type signatures.
|
|
10
|
+
* @see https://www.rfc-editor.org/rfc/rfc9535#section-2.4
|
|
11
|
+
*/
|
|
12
|
+
const FUNCTION_SIGNATURES = {
|
|
13
|
+
// count(NodesType) -> ValueType
|
|
14
|
+
count: {
|
|
15
|
+
params: [{
|
|
16
|
+
type: 'NodesType'
|
|
17
|
+
}],
|
|
18
|
+
returns: 'ValueType'
|
|
19
|
+
},
|
|
20
|
+
// length(ValueType) -> ValueType
|
|
21
|
+
length: {
|
|
22
|
+
params: [{
|
|
23
|
+
type: 'ValueType'
|
|
24
|
+
}],
|
|
25
|
+
returns: 'ValueType'
|
|
26
|
+
},
|
|
27
|
+
// value(NodesType) -> ValueType
|
|
28
|
+
value: {
|
|
29
|
+
params: [{
|
|
30
|
+
type: 'NodesType'
|
|
31
|
+
}],
|
|
32
|
+
returns: 'ValueType'
|
|
33
|
+
},
|
|
34
|
+
// match(ValueType, ValueType) -> LogicalType
|
|
35
|
+
match: {
|
|
36
|
+
params: [{
|
|
37
|
+
type: 'ValueType'
|
|
38
|
+
}, {
|
|
39
|
+
type: 'ValueType'
|
|
40
|
+
}],
|
|
41
|
+
returns: 'LogicalType'
|
|
42
|
+
},
|
|
43
|
+
// search(ValueType, ValueType) -> LogicalType
|
|
44
|
+
search: {
|
|
45
|
+
params: [{
|
|
46
|
+
type: 'ValueType'
|
|
47
|
+
}, {
|
|
48
|
+
type: 'ValueType'
|
|
49
|
+
}],
|
|
50
|
+
returns: 'LogicalType'
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Check if an AST node represents a singular query.
|
|
56
|
+
* Singular queries use only name selectors and index selectors.
|
|
57
|
+
*/
|
|
58
|
+
const isSingularQueryAST = node => {
|
|
59
|
+
if (node.type !== 'FilterQuery') return false;
|
|
60
|
+
const {
|
|
61
|
+
query
|
|
62
|
+
} = node;
|
|
63
|
+
if (!query || !query.segments) return false;
|
|
64
|
+
for (const segment of query.segments) {
|
|
65
|
+
if (segment.type !== 'ChildSegment') return false;
|
|
66
|
+
const {
|
|
67
|
+
selector
|
|
68
|
+
} = segment;
|
|
69
|
+
if (selector.type !== 'NameSelector' && selector.type !== 'IndexSelector') {
|
|
70
|
+
// Check for BracketedSelection with single NameSelector or IndexSelector
|
|
71
|
+
if (selector.type === 'BracketedSelection') {
|
|
72
|
+
if (selector.selectors.length !== 1) return false;
|
|
73
|
+
const inner = selector.selectors[0];
|
|
74
|
+
if (inner.type !== 'NameSelector' && inner.type !== 'IndexSelector') {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Check if argument type matches expected parameter type.
|
|
87
|
+
*/
|
|
88
|
+
const checkArgumentType = (argAST, expectedType, funcName) => {
|
|
89
|
+
if (expectedType === 'NodesType') {
|
|
90
|
+
// NodesType requires a filter-query (non-singular)
|
|
91
|
+
// Literals and singular queries are NOT NodesType
|
|
92
|
+
if (argAST.type === 'Literal') {
|
|
93
|
+
throw new RangeError(`Function ${funcName}() requires NodesType argument, got literal`);
|
|
94
|
+
}
|
|
95
|
+
if (argAST.type === 'TestExpr' && argAST.expression?.type === 'FilterQuery') {
|
|
96
|
+
// This is a filter query wrapped in TestExpr - valid NodesType
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (argAST.type === 'FilterQuery') {
|
|
100
|
+
// Direct filter query - valid NodesType
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// Other types are not valid NodesType
|
|
104
|
+
throw new RangeError(`Function ${funcName}() requires NodesType argument`);
|
|
105
|
+
}
|
|
106
|
+
if (expectedType === 'ValueType') {
|
|
107
|
+
// ValueType accepts: literals, singular queries, function expressions
|
|
108
|
+
if (argAST.type === 'Literal') return;
|
|
109
|
+
if (argAST.type === 'FunctionExpr') return;
|
|
110
|
+
|
|
111
|
+
// TestExpr containing FunctionExpr - valid if function returns ValueType
|
|
112
|
+
if (argAST.type === 'TestExpr' && argAST.expression?.type === 'FunctionExpr') {
|
|
113
|
+
// Function expressions that return ValueType are valid
|
|
114
|
+
// Unknown functions are allowed (they return Nothing at runtime)
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// TestExpr containing FilterQuery - check if singular
|
|
119
|
+
if (argAST.type === 'TestExpr' && argAST.expression?.type === 'FilterQuery') {
|
|
120
|
+
if (!isSingularQueryAST(argAST.expression)) {
|
|
121
|
+
throw new RangeError(`Function ${funcName}() requires ValueType argument, got non-singular query`);
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// FilterQuery - check if singular
|
|
127
|
+
if (argAST.type === 'FilterQuery') {
|
|
128
|
+
if (!isSingularQueryAST(argAST)) {
|
|
129
|
+
throw new RangeError(`Function ${funcName}() requires ValueType argument, got non-singular query`);
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// LogicalExpr types are not ValueType
|
|
135
|
+
throw new RangeError(`Function ${funcName}() requires ValueType argument`);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get the return type of an expression AST node.
|
|
141
|
+
*
|
|
142
|
+
* @param {object} node - AST node
|
|
143
|
+
* @returns {string | null} - 'LogicalType', 'ValueType', 'NodesType', or null for unknown
|
|
144
|
+
*/
|
|
145
|
+
const getExpressionReturnType = node => {
|
|
146
|
+
if (!node) return null;
|
|
147
|
+
|
|
148
|
+
// Function expressions return based on signature
|
|
149
|
+
if (node.type === 'FunctionExpr') {
|
|
150
|
+
const signature = FUNCTION_SIGNATURES[node.name];
|
|
151
|
+
return signature ? signature.returns : null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Literals are ValueType
|
|
155
|
+
if (node.type === 'Literal') {
|
|
156
|
+
return 'ValueType';
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Singular queries return ValueType
|
|
160
|
+
if (node.type === 'RelSingularQuery' || node.type === 'AbsSingularQuery') {
|
|
161
|
+
return 'ValueType';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Filter queries return NodesType
|
|
165
|
+
if (node.type === 'FilterQuery') {
|
|
166
|
+
return 'NodesType';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Logical expressions return LogicalType
|
|
170
|
+
if (node.type === 'LogicalOrExpr' || node.type === 'LogicalAndExpr' || node.type === 'LogicalNotExpr' || node.type === 'ComparisonExpr') {
|
|
171
|
+
return 'LogicalType';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// TestExpr: depends on what it wraps
|
|
175
|
+
if (node.type === 'TestExpr') {
|
|
176
|
+
return getExpressionReturnType(node.expression);
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Validate function call against its type signature.
|
|
183
|
+
*/
|
|
184
|
+
const validateFunctionCall = (funcName, argASTs) => {
|
|
185
|
+
const signature = FUNCTION_SIGNATURES[funcName];
|
|
186
|
+
if (!signature) {
|
|
187
|
+
// Unknown function - no validation (will return Nothing at runtime)
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Check argument count
|
|
192
|
+
const expectedCount = signature.params.length;
|
|
193
|
+
const actualCount = argASTs.length;
|
|
194
|
+
if (actualCount < expectedCount) {
|
|
195
|
+
throw new RangeError(`Function ${funcName}() requires ${expectedCount} argument(s), got ${actualCount}`);
|
|
196
|
+
}
|
|
197
|
+
if (actualCount > expectedCount) {
|
|
198
|
+
throw new RangeError(`Function ${funcName}() requires ${expectedCount} argument(s), got ${actualCount}`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Check argument types
|
|
202
|
+
for (let i = 0; i < expectedCount; i++) {
|
|
203
|
+
checkArgumentType(argASTs[i], signature.params[i].type, funcName);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
8
206
|
const transformCSTtoAST = (node, transformerMap, ctx = {
|
|
9
207
|
parent: null,
|
|
10
208
|
path: []
|
|
@@ -124,9 +322,31 @@ const transformers = {
|
|
|
124
322
|
const child = node.children.find(({
|
|
125
323
|
type
|
|
126
324
|
}) => type === 'logical-expr');
|
|
325
|
+
const expressionAST = transformCSTtoAST(child, transformers, ctx);
|
|
326
|
+
|
|
327
|
+
// Validate: ValueType functions cannot be used as existence tests
|
|
328
|
+
// Per RFC 9535 Section 2.4.9: "Type error: ValueType not TestExpr"
|
|
329
|
+
// This only applies when the top-level expression is a TestExpr
|
|
330
|
+
// containing a function that returns ValueType
|
|
331
|
+
if (expressionAST.type === 'TestExpr') {
|
|
332
|
+
const innerType = getExpressionReturnType(expressionAST.expression);
|
|
333
|
+
if (innerType === 'ValueType') {
|
|
334
|
+
const funcName = expressionAST.expression.type === 'FunctionExpr' ? expressionAST.expression.name : 'expression';
|
|
335
|
+
throw new RangeError(`Function ${funcName}() returns ValueType which cannot be used as existence test; result must be compared`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
// Also check for LogicalNotExpr wrapping TestExpr (for negated existence tests)
|
|
339
|
+
if (expressionAST.type === 'LogicalNotExpr' && expressionAST.expression?.type === 'TestExpr') {
|
|
340
|
+
const innerExpr = expressionAST.expression.expression;
|
|
341
|
+
const innerType = getExpressionReturnType(innerExpr);
|
|
342
|
+
if (innerType === 'ValueType') {
|
|
343
|
+
const funcName = innerExpr.type === 'FunctionExpr' ? innerExpr.name : 'expression';
|
|
344
|
+
throw new RangeError(`Function ${funcName}() returns ValueType which cannot be used as existence test; result must be compared`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
127
347
|
return {
|
|
128
348
|
type: 'FilterSelector',
|
|
129
|
-
expression:
|
|
349
|
+
expression: expressionAST
|
|
130
350
|
};
|
|
131
351
|
},
|
|
132
352
|
['logical-expr'](node, ctx) {
|
|
@@ -153,6 +373,7 @@ const transformers = {
|
|
|
153
373
|
right
|
|
154
374
|
};
|
|
155
375
|
}
|
|
376
|
+
return left;
|
|
156
377
|
},
|
|
157
378
|
['logical-and-expr'](node, ctx) {
|
|
158
379
|
const basicExprs = node.children.filter(({
|
|
@@ -197,9 +418,10 @@ const transformers = {
|
|
|
197
418
|
const expression = node.children.find(({
|
|
198
419
|
type
|
|
199
420
|
}) => ['filter-query', 'function-expr'].includes(type));
|
|
421
|
+
const expressionAST = transformCSTtoAST(expression, transformers, ctx);
|
|
200
422
|
const testExpr = {
|
|
201
423
|
type: 'TestExpr',
|
|
202
|
-
expression:
|
|
424
|
+
expression: expressionAST
|
|
203
425
|
};
|
|
204
426
|
return isNegated ? {
|
|
205
427
|
type: 'LogicalNotExpr',
|
|
@@ -227,11 +449,26 @@ const transformers = {
|
|
|
227
449
|
type
|
|
228
450
|
}) => ['comparable', 'comparison-op'].includes(type));
|
|
229
451
|
const [left, op, right] = children;
|
|
452
|
+
const leftAST = transformCSTtoAST(left, transformers, ctx);
|
|
453
|
+
const rightAST = transformCSTtoAST(right, transformers, ctx);
|
|
454
|
+
|
|
455
|
+
// Validate: LogicalType functions cannot be used in comparisons
|
|
456
|
+
// Per RFC 9535 Section 2.4.9: "Type error: no compare to LogicalType"
|
|
457
|
+
const leftType = getExpressionReturnType(leftAST);
|
|
458
|
+
const rightType = getExpressionReturnType(rightAST);
|
|
459
|
+
if (leftType === 'LogicalType') {
|
|
460
|
+
const funcName = leftAST.type === 'FunctionExpr' ? leftAST.name : 'expression';
|
|
461
|
+
throw new RangeError(`Function ${funcName}() returns LogicalType which cannot be compared`);
|
|
462
|
+
}
|
|
463
|
+
if (rightType === 'LogicalType') {
|
|
464
|
+
const funcName = rightAST.type === 'FunctionExpr' ? rightAST.name : 'expression';
|
|
465
|
+
throw new RangeError(`Function ${funcName}() returns LogicalType which cannot be compared`);
|
|
466
|
+
}
|
|
230
467
|
return {
|
|
231
468
|
type: 'ComparisonExpr',
|
|
232
|
-
left:
|
|
469
|
+
left: leftAST,
|
|
233
470
|
op: op.text,
|
|
234
|
-
right:
|
|
471
|
+
right: rightAST
|
|
235
472
|
};
|
|
236
473
|
},
|
|
237
474
|
['literal'](node, ctx) {
|
|
@@ -314,10 +551,14 @@ const transformers = {
|
|
|
314
551
|
const args = node.children.filter(({
|
|
315
552
|
type
|
|
316
553
|
}) => type === 'function-argument');
|
|
554
|
+
const argASTs = args.map(arg => transformCSTtoAST(arg, transformers, ctx));
|
|
555
|
+
|
|
556
|
+
// Validate function call against type signature
|
|
557
|
+
validateFunctionCall(name.text, argASTs);
|
|
317
558
|
return {
|
|
318
559
|
type: 'FunctionExpr',
|
|
319
560
|
name: name.text,
|
|
320
|
-
arguments:
|
|
561
|
+
arguments: argASTs
|
|
321
562
|
};
|
|
322
563
|
},
|
|
323
564
|
['function-argument'](node, ctx) {
|
|
@@ -31,9 +31,7 @@ class CSTOptimizedTranslator extends _CSTTranslator.default {
|
|
|
31
31
|
options
|
|
32
32
|
};
|
|
33
33
|
this.translate(data);
|
|
34
|
-
|
|
35
|
-
delete data.options;
|
|
36
|
-
return data;
|
|
34
|
+
return data.root;
|
|
37
35
|
}
|
|
38
36
|
}
|
|
39
37
|
var _default = exports.default = CSTOptimizedTranslator;
|
package/cjs/test/index.cjs
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
exports.__esModule = true;
|
|
4
4
|
exports.default = void 0;
|
|
5
5
|
var _index = _interopRequireDefault(require("../parse/index.cjs"));
|
|
6
|
+
var _index2 = _interopRequireDefault(require("../parse/translators/ASTTranslator/index.cjs"));
|
|
6
7
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
8
|
const test = (jsonPath, {
|
|
8
|
-
normalized = false
|
|
9
|
+
normalized = false,
|
|
10
|
+
wellTyped = true
|
|
9
11
|
} = {}) => {
|
|
10
12
|
if (typeof jsonPath !== 'string') return false;
|
|
11
13
|
try {
|
|
@@ -15,7 +17,7 @@ const test = (jsonPath, {
|
|
|
15
17
|
normalized,
|
|
16
18
|
stats: false,
|
|
17
19
|
trace: false,
|
|
18
|
-
translator: null
|
|
20
|
+
translator: wellTyped ? new _index2.default() : null
|
|
19
21
|
});
|
|
20
22
|
return result.success;
|
|
21
23
|
} catch {
|
|
@@ -15,7 +15,7 @@ class JSONPathError extends Error {
|
|
|
15
15
|
* This needs to stay here until our minimum supported version of Node.js is >= 16.9.0.
|
|
16
16
|
* Node.js is >= 16.9.0 supports error causes natively.
|
|
17
17
|
*/
|
|
18
|
-
if (options != null && typeof options === 'object' && Object.
|
|
18
|
+
if (options != null && typeof options === 'object' && Object.hasOwn(options, 'cause') && !('cause' in this)) {
|
|
19
19
|
const {
|
|
20
20
|
cause
|
|
21
21
|
} = options;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comparable evaluator.
|
|
3
|
+
*
|
|
4
|
+
* Evaluates comparables which can be:
|
|
5
|
+
* - Literal (string, number, boolean, null)
|
|
6
|
+
* - RelSingularQuery (@.path)
|
|
7
|
+
* - AbsSingularQuery ($.path)
|
|
8
|
+
* - FunctionExpr (function call)
|
|
9
|
+
*
|
|
10
|
+
* @see https://www.rfc-editor.org/rfc/rfc9535#section-2.3.5.2.2
|
|
11
|
+
*/
|
|
12
|
+
import evaluateLiteral from "./literal.mjs";
|
|
13
|
+
import { evaluateRelSingularQuery, evaluateAbsSingularQuery } from "./singular-query.mjs";
|
|
14
|
+
import evaluateFunctionExpr from "./function-expr.mjs";
|
|
15
|
+
/**
|
|
16
|
+
* Evaluate a comparable expression.
|
|
17
|
+
*
|
|
18
|
+
* @param {object} ctx - Evaluation context
|
|
19
|
+
* @param {unknown} root - Root value ($)
|
|
20
|
+
* @param {unknown} current - Current value (@)
|
|
21
|
+
* @param {object} node - Comparable AST node
|
|
22
|
+
* @returns {unknown} - Evaluated value
|
|
23
|
+
*/
|
|
24
|
+
const evaluateComparable = (ctx, root, current, node) => {
|
|
25
|
+
switch (node.type) {
|
|
26
|
+
case 'Literal':
|
|
27
|
+
return evaluateLiteral(ctx, root, current, node);
|
|
28
|
+
case 'RelSingularQuery':
|
|
29
|
+
return evaluateRelSingularQuery(ctx, root, current, node);
|
|
30
|
+
case 'AbsSingularQuery':
|
|
31
|
+
return evaluateAbsSingularQuery(ctx, root, current, node);
|
|
32
|
+
case 'FunctionExpr':
|
|
33
|
+
return evaluateFunctionExpr(ctx, root, current, node);
|
|
34
|
+
default:
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
export default evaluateComparable;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comparison expression evaluator.
|
|
3
|
+
*
|
|
4
|
+
* Evaluates comparison expressions like @.price < 10, @.name == "foo", etc.
|
|
5
|
+
*
|
|
6
|
+
* @see https://www.rfc-editor.org/rfc/rfc9535#section-2.3.5.2.3
|
|
7
|
+
*/
|
|
8
|
+
import evaluateComparable from "./comparable.mjs";
|
|
9
|
+
/**
|
|
10
|
+
* Evaluate a comparison expression.
|
|
11
|
+
*
|
|
12
|
+
* @param {object} ctx - Evaluation context
|
|
13
|
+
* @param {unknown} root - Root value ($)
|
|
14
|
+
* @param {unknown} current - Current value (@)
|
|
15
|
+
* @param {object} node - ComparisonExpr AST node
|
|
16
|
+
* @param {object} node.left - Left comparable
|
|
17
|
+
* @param {string} node.op - Comparison operator (==, !=, <, <=, >, >=)
|
|
18
|
+
* @param {object} node.right - Right comparable
|
|
19
|
+
* @returns {boolean} - Comparison result
|
|
20
|
+
*/
|
|
21
|
+
const evaluateComparisonExpr = (ctx, root, current, node) => {
|
|
22
|
+
const {
|
|
23
|
+
left,
|
|
24
|
+
op,
|
|
25
|
+
right
|
|
26
|
+
} = node;
|
|
27
|
+
const leftValue = evaluateComparable(ctx, root, current, left);
|
|
28
|
+
const rightValue = evaluateComparable(ctx, root, current, right);
|
|
29
|
+
return ctx.realm.compare(leftValue, op, rightValue);
|
|
30
|
+
};
|
|
31
|
+
export default evaluateComparisonExpr;
|