@swaggerexpert/jsonpath 3.2.5 → 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 +114 -11
- 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
|
@@ -2,13 +2,12 @@ import { utilities, identifiers } from 'apg-lite';
|
|
|
2
2
|
import JSONPathParseError from "../../errors/JSONPathParseError.mjs";
|
|
3
3
|
const cst = nodeType => {
|
|
4
4
|
return (state, chars, phraseIndex, phraseLength, data) => {
|
|
5
|
-
var _data$options, _data$options2;
|
|
6
5
|
if (!(typeof data === 'object' && data !== null && !Array.isArray(data))) {
|
|
7
6
|
throw new JSONPathParseError("parser's user data must be an object");
|
|
8
7
|
}
|
|
9
8
|
|
|
10
9
|
// drop the empty nodes
|
|
11
|
-
if (
|
|
10
|
+
if (data.options?.optimize && phraseLength === 0 && data.options?.droppableTypes?.includes(nodeType)) {
|
|
12
11
|
return;
|
|
13
12
|
}
|
|
14
13
|
if (state === identifiers.SEM_PRE) {
|
|
@@ -20,11 +19,10 @@ const cst = nodeType => {
|
|
|
20
19
|
children: []
|
|
21
20
|
};
|
|
22
21
|
if (data.stack.length > 0) {
|
|
23
|
-
var _data$options3, _data$options4;
|
|
24
22
|
const parent = data.stack[data.stack.length - 1];
|
|
25
23
|
const prevSibling = parent.children[parent.children.length - 1];
|
|
26
24
|
const isTextNodeWithinTextNode = parent.type === 'text' && node.type === 'text';
|
|
27
|
-
const shouldCollapse =
|
|
25
|
+
const shouldCollapse = data.options?.optimize && data.options?.collapsibleTypes?.includes(node.type) && prevSibling?.type === node.type;
|
|
28
26
|
if (shouldCollapse) {
|
|
29
27
|
prevSibling.text += node.text;
|
|
30
28
|
prevSibling.length += node.length;
|
package/es/parse/index.mjs
CHANGED
|
@@ -27,7 +27,9 @@ const parse = (jsonPath, {
|
|
|
27
27
|
trace: parser.trace
|
|
28
28
|
};
|
|
29
29
|
} catch (error) {
|
|
30
|
-
|
|
30
|
+
// Provide specific error message for semantic validation errors
|
|
31
|
+
const message = error instanceof RangeError ? `Invalid JSONPath expression: ${error.message}` : 'Unexpected error during JSONPath parsing';
|
|
32
|
+
throw new JSONPathParseError(message, {
|
|
31
33
|
cause: error,
|
|
32
34
|
jsonPath
|
|
33
35
|
});
|
|
@@ -3,7 +3,7 @@ import { transformCSTtoAST, default as transformers } from "./transformers.mjs";
|
|
|
3
3
|
class ASTTranslator extends CSTOptimizedTranslator {
|
|
4
4
|
getTree() {
|
|
5
5
|
const cst = super.getTree();
|
|
6
|
-
return transformCSTtoAST(cst
|
|
6
|
+
return transformCSTtoAST(cst, transformers);
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
9
|
export default ASTTranslator;
|
|
@@ -1,5 +1,203 @@
|
|
|
1
1
|
import JSONPathParseError from "../../../errors/JSONPathParseError.mjs";
|
|
2
2
|
import { decodeSingleQuotedString, decodeDoubleQuotedString, decodeInteger, decodeJSONValue } from "./decoders.mjs";
|
|
3
|
+
/**
|
|
4
|
+
* RFC 9535 function type signatures.
|
|
5
|
+
* @see https://www.rfc-editor.org/rfc/rfc9535#section-2.4
|
|
6
|
+
*/
|
|
7
|
+
const FUNCTION_SIGNATURES = {
|
|
8
|
+
// count(NodesType) -> ValueType
|
|
9
|
+
count: {
|
|
10
|
+
params: [{
|
|
11
|
+
type: 'NodesType'
|
|
12
|
+
}],
|
|
13
|
+
returns: 'ValueType'
|
|
14
|
+
},
|
|
15
|
+
// length(ValueType) -> ValueType
|
|
16
|
+
length: {
|
|
17
|
+
params: [{
|
|
18
|
+
type: 'ValueType'
|
|
19
|
+
}],
|
|
20
|
+
returns: 'ValueType'
|
|
21
|
+
},
|
|
22
|
+
// value(NodesType) -> ValueType
|
|
23
|
+
value: {
|
|
24
|
+
params: [{
|
|
25
|
+
type: 'NodesType'
|
|
26
|
+
}],
|
|
27
|
+
returns: 'ValueType'
|
|
28
|
+
},
|
|
29
|
+
// match(ValueType, ValueType) -> LogicalType
|
|
30
|
+
match: {
|
|
31
|
+
params: [{
|
|
32
|
+
type: 'ValueType'
|
|
33
|
+
}, {
|
|
34
|
+
type: 'ValueType'
|
|
35
|
+
}],
|
|
36
|
+
returns: 'LogicalType'
|
|
37
|
+
},
|
|
38
|
+
// search(ValueType, ValueType) -> LogicalType
|
|
39
|
+
search: {
|
|
40
|
+
params: [{
|
|
41
|
+
type: 'ValueType'
|
|
42
|
+
}, {
|
|
43
|
+
type: 'ValueType'
|
|
44
|
+
}],
|
|
45
|
+
returns: 'LogicalType'
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if an AST node represents a singular query.
|
|
51
|
+
* Singular queries use only name selectors and index selectors.
|
|
52
|
+
*/
|
|
53
|
+
const isSingularQueryAST = node => {
|
|
54
|
+
if (node.type !== 'FilterQuery') return false;
|
|
55
|
+
const {
|
|
56
|
+
query
|
|
57
|
+
} = node;
|
|
58
|
+
if (!query || !query.segments) return false;
|
|
59
|
+
for (const segment of query.segments) {
|
|
60
|
+
if (segment.type !== 'ChildSegment') return false;
|
|
61
|
+
const {
|
|
62
|
+
selector
|
|
63
|
+
} = segment;
|
|
64
|
+
if (selector.type !== 'NameSelector' && selector.type !== 'IndexSelector') {
|
|
65
|
+
// Check for BracketedSelection with single NameSelector or IndexSelector
|
|
66
|
+
if (selector.type === 'BracketedSelection') {
|
|
67
|
+
if (selector.selectors.length !== 1) return false;
|
|
68
|
+
const inner = selector.selectors[0];
|
|
69
|
+
if (inner.type !== 'NameSelector' && inner.type !== 'IndexSelector') {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return true;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if argument type matches expected parameter type.
|
|
82
|
+
*/
|
|
83
|
+
const checkArgumentType = (argAST, expectedType, funcName) => {
|
|
84
|
+
if (expectedType === 'NodesType') {
|
|
85
|
+
// NodesType requires a filter-query (non-singular)
|
|
86
|
+
// Literals and singular queries are NOT NodesType
|
|
87
|
+
if (argAST.type === 'Literal') {
|
|
88
|
+
throw new RangeError(`Function ${funcName}() requires NodesType argument, got literal`);
|
|
89
|
+
}
|
|
90
|
+
if (argAST.type === 'TestExpr' && argAST.expression?.type === 'FilterQuery') {
|
|
91
|
+
// This is a filter query wrapped in TestExpr - valid NodesType
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (argAST.type === 'FilterQuery') {
|
|
95
|
+
// Direct filter query - valid NodesType
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// Other types are not valid NodesType
|
|
99
|
+
throw new RangeError(`Function ${funcName}() requires NodesType argument`);
|
|
100
|
+
}
|
|
101
|
+
if (expectedType === 'ValueType') {
|
|
102
|
+
// ValueType accepts: literals, singular queries, function expressions
|
|
103
|
+
if (argAST.type === 'Literal') return;
|
|
104
|
+
if (argAST.type === 'FunctionExpr') return;
|
|
105
|
+
|
|
106
|
+
// TestExpr containing FunctionExpr - valid if function returns ValueType
|
|
107
|
+
if (argAST.type === 'TestExpr' && argAST.expression?.type === 'FunctionExpr') {
|
|
108
|
+
// Function expressions that return ValueType are valid
|
|
109
|
+
// Unknown functions are allowed (they return Nothing at runtime)
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// TestExpr containing FilterQuery - check if singular
|
|
114
|
+
if (argAST.type === 'TestExpr' && argAST.expression?.type === 'FilterQuery') {
|
|
115
|
+
if (!isSingularQueryAST(argAST.expression)) {
|
|
116
|
+
throw new RangeError(`Function ${funcName}() requires ValueType argument, got non-singular query`);
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// FilterQuery - check if singular
|
|
122
|
+
if (argAST.type === 'FilterQuery') {
|
|
123
|
+
if (!isSingularQueryAST(argAST)) {
|
|
124
|
+
throw new RangeError(`Function ${funcName}() requires ValueType argument, got non-singular query`);
|
|
125
|
+
}
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// LogicalExpr types are not ValueType
|
|
130
|
+
throw new RangeError(`Function ${funcName}() requires ValueType argument`);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get the return type of an expression AST node.
|
|
136
|
+
*
|
|
137
|
+
* @param {object} node - AST node
|
|
138
|
+
* @returns {string | null} - 'LogicalType', 'ValueType', 'NodesType', or null for unknown
|
|
139
|
+
*/
|
|
140
|
+
const getExpressionReturnType = node => {
|
|
141
|
+
if (!node) return null;
|
|
142
|
+
|
|
143
|
+
// Function expressions return based on signature
|
|
144
|
+
if (node.type === 'FunctionExpr') {
|
|
145
|
+
const signature = FUNCTION_SIGNATURES[node.name];
|
|
146
|
+
return signature ? signature.returns : null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Literals are ValueType
|
|
150
|
+
if (node.type === 'Literal') {
|
|
151
|
+
return 'ValueType';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Singular queries return ValueType
|
|
155
|
+
if (node.type === 'RelSingularQuery' || node.type === 'AbsSingularQuery') {
|
|
156
|
+
return 'ValueType';
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Filter queries return NodesType
|
|
160
|
+
if (node.type === 'FilterQuery') {
|
|
161
|
+
return 'NodesType';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Logical expressions return LogicalType
|
|
165
|
+
if (node.type === 'LogicalOrExpr' || node.type === 'LogicalAndExpr' || node.type === 'LogicalNotExpr' || node.type === 'ComparisonExpr') {
|
|
166
|
+
return 'LogicalType';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// TestExpr: depends on what it wraps
|
|
170
|
+
if (node.type === 'TestExpr') {
|
|
171
|
+
return getExpressionReturnType(node.expression);
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Validate function call against its type signature.
|
|
178
|
+
*/
|
|
179
|
+
const validateFunctionCall = (funcName, argASTs) => {
|
|
180
|
+
const signature = FUNCTION_SIGNATURES[funcName];
|
|
181
|
+
if (!signature) {
|
|
182
|
+
// Unknown function - no validation (will return Nothing at runtime)
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Check argument count
|
|
187
|
+
const expectedCount = signature.params.length;
|
|
188
|
+
const actualCount = argASTs.length;
|
|
189
|
+
if (actualCount < expectedCount) {
|
|
190
|
+
throw new RangeError(`Function ${funcName}() requires ${expectedCount} argument(s), got ${actualCount}`);
|
|
191
|
+
}
|
|
192
|
+
if (actualCount > expectedCount) {
|
|
193
|
+
throw new RangeError(`Function ${funcName}() requires ${expectedCount} argument(s), got ${actualCount}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Check argument types
|
|
197
|
+
for (let i = 0; i < expectedCount; i++) {
|
|
198
|
+
checkArgumentType(argASTs[i], signature.params[i].type, funcName);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
3
201
|
export const transformCSTtoAST = (node, transformerMap, ctx = {
|
|
4
202
|
parent: null,
|
|
5
203
|
path: []
|
|
@@ -118,9 +316,31 @@ const transformers = {
|
|
|
118
316
|
const child = node.children.find(({
|
|
119
317
|
type
|
|
120
318
|
}) => type === 'logical-expr');
|
|
319
|
+
const expressionAST = transformCSTtoAST(child, transformers, ctx);
|
|
320
|
+
|
|
321
|
+
// Validate: ValueType functions cannot be used as existence tests
|
|
322
|
+
// Per RFC 9535 Section 2.4.9: "Type error: ValueType not TestExpr"
|
|
323
|
+
// This only applies when the top-level expression is a TestExpr
|
|
324
|
+
// containing a function that returns ValueType
|
|
325
|
+
if (expressionAST.type === 'TestExpr') {
|
|
326
|
+
const innerType = getExpressionReturnType(expressionAST.expression);
|
|
327
|
+
if (innerType === 'ValueType') {
|
|
328
|
+
const funcName = expressionAST.expression.type === 'FunctionExpr' ? expressionAST.expression.name : 'expression';
|
|
329
|
+
throw new RangeError(`Function ${funcName}() returns ValueType which cannot be used as existence test; result must be compared`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
// Also check for LogicalNotExpr wrapping TestExpr (for negated existence tests)
|
|
333
|
+
if (expressionAST.type === 'LogicalNotExpr' && expressionAST.expression?.type === 'TestExpr') {
|
|
334
|
+
const innerExpr = expressionAST.expression.expression;
|
|
335
|
+
const innerType = getExpressionReturnType(innerExpr);
|
|
336
|
+
if (innerType === 'ValueType') {
|
|
337
|
+
const funcName = innerExpr.type === 'FunctionExpr' ? innerExpr.name : 'expression';
|
|
338
|
+
throw new RangeError(`Function ${funcName}() returns ValueType which cannot be used as existence test; result must be compared`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
121
341
|
return {
|
|
122
342
|
type: 'FilterSelector',
|
|
123
|
-
expression:
|
|
343
|
+
expression: expressionAST
|
|
124
344
|
};
|
|
125
345
|
},
|
|
126
346
|
['logical-expr'](node, ctx) {
|
|
@@ -147,6 +367,7 @@ const transformers = {
|
|
|
147
367
|
right
|
|
148
368
|
};
|
|
149
369
|
}
|
|
370
|
+
return left;
|
|
150
371
|
},
|
|
151
372
|
['logical-and-expr'](node, ctx) {
|
|
152
373
|
const basicExprs = node.children.filter(({
|
|
@@ -191,9 +412,10 @@ const transformers = {
|
|
|
191
412
|
const expression = node.children.find(({
|
|
192
413
|
type
|
|
193
414
|
}) => ['filter-query', 'function-expr'].includes(type));
|
|
415
|
+
const expressionAST = transformCSTtoAST(expression, transformers, ctx);
|
|
194
416
|
const testExpr = {
|
|
195
417
|
type: 'TestExpr',
|
|
196
|
-
expression:
|
|
418
|
+
expression: expressionAST
|
|
197
419
|
};
|
|
198
420
|
return isNegated ? {
|
|
199
421
|
type: 'LogicalNotExpr',
|
|
@@ -221,11 +443,26 @@ const transformers = {
|
|
|
221
443
|
type
|
|
222
444
|
}) => ['comparable', 'comparison-op'].includes(type));
|
|
223
445
|
const [left, op, right] = children;
|
|
446
|
+
const leftAST = transformCSTtoAST(left, transformers, ctx);
|
|
447
|
+
const rightAST = transformCSTtoAST(right, transformers, ctx);
|
|
448
|
+
|
|
449
|
+
// Validate: LogicalType functions cannot be used in comparisons
|
|
450
|
+
// Per RFC 9535 Section 2.4.9: "Type error: no compare to LogicalType"
|
|
451
|
+
const leftType = getExpressionReturnType(leftAST);
|
|
452
|
+
const rightType = getExpressionReturnType(rightAST);
|
|
453
|
+
if (leftType === 'LogicalType') {
|
|
454
|
+
const funcName = leftAST.type === 'FunctionExpr' ? leftAST.name : 'expression';
|
|
455
|
+
throw new RangeError(`Function ${funcName}() returns LogicalType which cannot be compared`);
|
|
456
|
+
}
|
|
457
|
+
if (rightType === 'LogicalType') {
|
|
458
|
+
const funcName = rightAST.type === 'FunctionExpr' ? rightAST.name : 'expression';
|
|
459
|
+
throw new RangeError(`Function ${funcName}() returns LogicalType which cannot be compared`);
|
|
460
|
+
}
|
|
224
461
|
return {
|
|
225
462
|
type: 'ComparisonExpr',
|
|
226
|
-
left:
|
|
463
|
+
left: leftAST,
|
|
227
464
|
op: op.text,
|
|
228
|
-
right:
|
|
465
|
+
right: rightAST
|
|
229
466
|
};
|
|
230
467
|
},
|
|
231
468
|
['literal'](node, ctx) {
|
|
@@ -308,10 +545,14 @@ const transformers = {
|
|
|
308
545
|
const args = node.children.filter(({
|
|
309
546
|
type
|
|
310
547
|
}) => type === 'function-argument');
|
|
548
|
+
const argASTs = args.map(arg => transformCSTtoAST(arg, transformers, ctx));
|
|
549
|
+
|
|
550
|
+
// Validate function call against type signature
|
|
551
|
+
validateFunctionCall(name.text, argASTs);
|
|
311
552
|
return {
|
|
312
553
|
type: 'FunctionExpr',
|
|
313
554
|
name: name.text,
|
|
314
|
-
arguments:
|
|
555
|
+
arguments: argASTs
|
|
315
556
|
};
|
|
316
557
|
},
|
|
317
558
|
['function-argument'](node, ctx) {
|
package/es/test/index.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import parse from "../parse/index.mjs";
|
|
2
|
+
import ASTTranslator from "../parse/translators/ASTTranslator/index.mjs";
|
|
2
3
|
const test = (jsonPath, {
|
|
3
|
-
normalized = false
|
|
4
|
+
normalized = false,
|
|
5
|
+
wellTyped = true
|
|
4
6
|
} = {}) => {
|
|
5
7
|
if (typeof jsonPath !== 'string') return false;
|
|
6
8
|
try {
|
|
@@ -10,7 +12,7 @@ const test = (jsonPath, {
|
|
|
10
12
|
normalized,
|
|
11
13
|
stats: false,
|
|
12
14
|
trace: false,
|
|
13
|
-
translator: null
|
|
15
|
+
translator: wellTyped ? new ASTTranslator() : null
|
|
14
16
|
});
|
|
15
17
|
return result.success;
|
|
16
18
|
} catch {
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"registry": "https://registry.npmjs.org",
|
|
6
6
|
"provenance": true
|
|
7
7
|
},
|
|
8
|
-
"version": "
|
|
8
|
+
"version": "4.0.0",
|
|
9
9
|
"description": "RFC 9535 implementation of JSONPath",
|
|
10
10
|
"main": "./cjs/index.cjs",
|
|
11
11
|
"types": "./types/index.d.ts",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"watch": "npm-watch"
|
|
33
33
|
},
|
|
34
34
|
"engines": {
|
|
35
|
-
"node": ">=
|
|
35
|
+
"node": ">=16.14.0"
|
|
36
36
|
},
|
|
37
37
|
"type": "module",
|
|
38
38
|
"repository": {
|
|
@@ -43,6 +43,8 @@
|
|
|
43
43
|
"jsonpath",
|
|
44
44
|
"parser",
|
|
45
45
|
"validator",
|
|
46
|
+
"compiler",
|
|
47
|
+
"evaluator",
|
|
46
48
|
"rfc9535"
|
|
47
49
|
],
|
|
48
50
|
"author": "Vladimír Gorej <vladimir.gorej@gmail.com>",
|
package/types/index.d.ts
CHANGED
|
@@ -16,8 +16,8 @@ export interface Translator<TTree = unknown> {
|
|
|
16
16
|
export declare class CSTTranslator implements Translator<CSTTree> {
|
|
17
17
|
getTree(): CSTTree;
|
|
18
18
|
}
|
|
19
|
-
export declare class CSTOptimizedTranslator
|
|
20
|
-
constructor(options?: { collapsibleTypes?: string[] });
|
|
19
|
+
export declare class CSTOptimizedTranslator extends CSTTranslator {
|
|
20
|
+
constructor(options?: { collapsibleTypes?: string[]; droppableTypes?: string[] });
|
|
21
21
|
getTree(): CSTTree;
|
|
22
22
|
}
|
|
23
23
|
export declare class ASTTranslator implements Translator<ASTTree> {
|
|
@@ -51,16 +51,13 @@ export interface CSTNode {
|
|
|
51
51
|
readonly children: CSTNode[],
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
export
|
|
55
|
-
readonly root: CSTNode;
|
|
56
|
-
}
|
|
54
|
+
export type CSTTree = CSTNode;
|
|
57
55
|
|
|
58
56
|
export type XMLTree = string;
|
|
59
57
|
|
|
60
58
|
/* AST Tree start */
|
|
61
|
-
export
|
|
62
|
-
|
|
63
|
-
}
|
|
59
|
+
export type ASTTree = JSONPathQueryASTNode;
|
|
60
|
+
|
|
64
61
|
export interface ASTNode {
|
|
65
62
|
readonly type:
|
|
66
63
|
| 'JSONPathQuery'
|
|
@@ -231,9 +228,110 @@ export interface TestOptions {
|
|
|
231
228
|
}
|
|
232
229
|
|
|
233
230
|
/**
|
|
234
|
-
*
|
|
231
|
+
* Normalized Paths
|
|
232
|
+
*/
|
|
233
|
+
export namespace NormalizedPath {
|
|
234
|
+
/**
|
|
235
|
+
* Tests if a string is a valid normalized JSONPath.
|
|
236
|
+
*/
|
|
237
|
+
function test(normalizedPath: string): boolean;
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Creates a normalized path string from a list of selectors.
|
|
241
|
+
* Name selectors are automatically escaped.
|
|
242
|
+
*/
|
|
243
|
+
function from(selectors: (string | number)[]): string;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Parses a normalized path string and returns a list of selectors.
|
|
247
|
+
*/
|
|
248
|
+
function to(normalizedPath: string): (string | number)[];
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Escapes special characters in name selectors for use in normalized paths.
|
|
252
|
+
*/
|
|
253
|
+
function escape(value: string): string;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Evaluation
|
|
258
|
+
*/
|
|
259
|
+
export function evaluate<T = unknown>(data: unknown, expression: string, options?: EvaluateOptions): T[];
|
|
260
|
+
|
|
261
|
+
export interface EvaluateOptions {
|
|
262
|
+
/**
|
|
263
|
+
* Callback function called for each match.
|
|
264
|
+
* Receives the matched value and its normalized path.
|
|
265
|
+
*/
|
|
266
|
+
readonly callback?: (value: unknown, normalizedPath: string) => void;
|
|
267
|
+
/**
|
|
268
|
+
* Custom evaluation realm for different data structures.
|
|
269
|
+
* Default is JSON realm for plain objects/arrays.
|
|
270
|
+
*/
|
|
271
|
+
readonly realm?: EvaluationRealmInterface;
|
|
272
|
+
/**
|
|
273
|
+
* Custom function registry.
|
|
274
|
+
* Can extend or override built-in functions.
|
|
275
|
+
*/
|
|
276
|
+
readonly functions?: Record<string, Function>;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Evaluation realm interface for custom data structures.
|
|
281
|
+
* Implement this to evaluate JSONPath on Immutable.js, ApiDOM, etc.
|
|
235
282
|
*/
|
|
236
|
-
export
|
|
283
|
+
export interface EvaluationRealmInterface {
|
|
284
|
+
name?: string;
|
|
285
|
+
isObject(value: unknown): boolean;
|
|
286
|
+
isArray(value: unknown): boolean;
|
|
287
|
+
isString(value: unknown): boolean;
|
|
288
|
+
isNumber(value: unknown): boolean;
|
|
289
|
+
isBoolean(value: unknown): boolean;
|
|
290
|
+
isNull(value: unknown): boolean;
|
|
291
|
+
getString(value: unknown): string | undefined;
|
|
292
|
+
getProperty(value: unknown, key: string): unknown;
|
|
293
|
+
hasProperty(value: unknown, key: string): boolean;
|
|
294
|
+
getElement(value: unknown, index: number): unknown;
|
|
295
|
+
getKeys(value: unknown): string[];
|
|
296
|
+
getLength(value: unknown): number;
|
|
297
|
+
entries(value: unknown): Iterable<[string | number, unknown]>;
|
|
298
|
+
compare(left: unknown, operator: '==' | '!=' | '<' | '<=' | '>' | '>=', right: unknown): boolean;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Built-in evaluation functions.
|
|
303
|
+
*/
|
|
304
|
+
export const functions: Record<string, Function>;
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Base class for evaluation realms.
|
|
308
|
+
* Extend this class to create custom evaluation realms.
|
|
309
|
+
*/
|
|
310
|
+
export declare class EvaluationRealm implements EvaluationRealmInterface {
|
|
311
|
+
name: string;
|
|
312
|
+
isObject(value: unknown): boolean;
|
|
313
|
+
isArray(value: unknown): boolean;
|
|
314
|
+
isString(value: unknown): boolean;
|
|
315
|
+
isNumber(value: unknown): boolean;
|
|
316
|
+
isBoolean(value: unknown): boolean;
|
|
317
|
+
isNull(value: unknown): boolean;
|
|
318
|
+
getString(value: unknown): string | undefined;
|
|
319
|
+
getProperty(value: unknown, key: string): unknown;
|
|
320
|
+
hasProperty(value: unknown, key: string): boolean;
|
|
321
|
+
getElement(value: unknown, index: number): unknown;
|
|
322
|
+
getKeys(value: unknown): string[];
|
|
323
|
+
getLength(value: unknown): number;
|
|
324
|
+
entries(value: unknown): Iterable<[string | number, unknown]>;
|
|
325
|
+
compare(left: unknown, operator: '==' | '!=' | '<' | '<=' | '>' | '>=', right: unknown): boolean;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* JSON Evaluation Realm for plain JavaScript objects and arrays.
|
|
330
|
+
* This is the default realm used by the evaluate function.
|
|
331
|
+
*/
|
|
332
|
+
export declare class JSONEvaluationRealm extends EvaluationRealm {
|
|
333
|
+
name: 'json';
|
|
334
|
+
}
|
|
237
335
|
|
|
238
336
|
/**
|
|
239
337
|
* Errors
|
|
@@ -248,12 +346,17 @@ export interface JSONPathErrorOptions {
|
|
|
248
346
|
[key: string]: unknown;
|
|
249
347
|
}
|
|
250
348
|
|
|
251
|
-
export declare class
|
|
349
|
+
export declare class JSONNormalizedPathError extends JSONPathError {
|
|
252
350
|
selectors?: (string | number)[];
|
|
351
|
+
normalizedPath?: string;
|
|
253
352
|
}
|
|
254
353
|
|
|
255
354
|
export declare class JSONPathParseError extends JSONPathError {}
|
|
256
355
|
|
|
356
|
+
export declare class JSONPathEvaluateError extends JSONPathError {
|
|
357
|
+
expression?: string;
|
|
358
|
+
}
|
|
359
|
+
|
|
257
360
|
/**
|
|
258
361
|
* Grammar
|
|
259
362
|
*/
|
package/cjs/compile.cjs
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
exports.__esModule = true;
|
|
4
|
-
exports.default = void 0;
|
|
5
|
-
var _escape = _interopRequireDefault(require("./escape.cjs"));
|
|
6
|
-
var _JSONPathCompileError = _interopRequireDefault(require("./errors/JSONPathCompileError.cjs"));
|
|
7
|
-
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
8
|
-
/**
|
|
9
|
-
* Compiles an array of selectors into a normalized JSONPath.
|
|
10
|
-
* Follows RFC 9535 Section 2.7 normalized path format.
|
|
11
|
-
*
|
|
12
|
-
* @param {Array<string|number>} selectors - Array of name selectors (strings) or index selectors (numbers)
|
|
13
|
-
* @returns {string} A normalized JSONPath string
|
|
14
|
-
* @throws {JSONPathCompileError} If selectors is not an array or contains invalid selector types
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* compile(['a', 'b', 1]) // returns "$['a']['b'][1]"
|
|
18
|
-
* compile([]) // returns "$"
|
|
19
|
-
* compile(['foo', 0, 'bar']) // returns "$['foo'][0]['bar']"
|
|
20
|
-
*/
|
|
21
|
-
const compile = selectors => {
|
|
22
|
-
if (!Array.isArray(selectors)) {
|
|
23
|
-
throw new _JSONPathCompileError.default(`Selectors must be an array, got: ${typeof selectors}`, {
|
|
24
|
-
selectors
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
try {
|
|
28
|
-
const segments = selectors.map(selector => {
|
|
29
|
-
if (typeof selector === 'string') {
|
|
30
|
-
// Name selector: escape and wrap in single quotes
|
|
31
|
-
return `['${(0, _escape.default)(selector)}']`;
|
|
32
|
-
}
|
|
33
|
-
if (typeof selector === 'number') {
|
|
34
|
-
// Index selector: must be a non-negative safe integer (RFC 9535 Section 2.1)
|
|
35
|
-
if (!Number.isSafeInteger(selector) || selector < 0) {
|
|
36
|
-
throw new TypeError(`Index selector must be a non-negative safe integer, got: ${selector}`);
|
|
37
|
-
}
|
|
38
|
-
return `[${selector}]`;
|
|
39
|
-
}
|
|
40
|
-
throw new TypeError(`Selector must be a string or non-negative integer, got: ${typeof selector}`);
|
|
41
|
-
});
|
|
42
|
-
return `$${segments.join('')}`;
|
|
43
|
-
} catch (error) {
|
|
44
|
-
throw new _JSONPathCompileError.default('Failed to compile normalized JSONPath', {
|
|
45
|
-
cause: error,
|
|
46
|
-
selectors
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
var _default = exports.default = compile;
|
package/cjs/escape.cjs
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
exports.__esModule = true;
|
|
4
|
-
exports.default = void 0;
|
|
5
|
-
/**
|
|
6
|
-
* Escapes a string for use in a normalized JSONPath name selector.
|
|
7
|
-
* Follows RFC 9535 Section 2.7 escaping rules for single-quoted strings.
|
|
8
|
-
*
|
|
9
|
-
* @param {string} selector - The string to escape
|
|
10
|
-
* @returns {string} The escaped string (without surrounding quotes)
|
|
11
|
-
*/
|
|
12
|
-
const escape = selector => {
|
|
13
|
-
if (typeof selector !== 'string') {
|
|
14
|
-
throw new TypeError('Selector must be a string');
|
|
15
|
-
}
|
|
16
|
-
let escaped = '';
|
|
17
|
-
for (const char of selector) {
|
|
18
|
-
const codePoint = char.codePointAt(0);
|
|
19
|
-
switch (codePoint) {
|
|
20
|
-
case 0x08:
|
|
21
|
-
// backspace
|
|
22
|
-
escaped += '\\b';
|
|
23
|
-
break;
|
|
24
|
-
case 0x09:
|
|
25
|
-
// horizontal tab
|
|
26
|
-
escaped += '\\t';
|
|
27
|
-
break;
|
|
28
|
-
case 0x0a:
|
|
29
|
-
// line feed
|
|
30
|
-
escaped += '\\n';
|
|
31
|
-
break;
|
|
32
|
-
case 0x0c:
|
|
33
|
-
// form feed
|
|
34
|
-
escaped += '\\f';
|
|
35
|
-
break;
|
|
36
|
-
case 0x0d:
|
|
37
|
-
// carriage return
|
|
38
|
-
escaped += '\\r';
|
|
39
|
-
break;
|
|
40
|
-
case 0x27:
|
|
41
|
-
// apostrophe '
|
|
42
|
-
escaped += "\\'";
|
|
43
|
-
break;
|
|
44
|
-
case 0x5c:
|
|
45
|
-
// backslash \
|
|
46
|
-
escaped += '\\\\';
|
|
47
|
-
break;
|
|
48
|
-
default:
|
|
49
|
-
// Other control characters (U+0000-U+001F except those handled above)
|
|
50
|
-
if (codePoint <= 0x1f) {
|
|
51
|
-
escaped += `\\u${codePoint.toString(16).padStart(4, '0')}`;
|
|
52
|
-
} else {
|
|
53
|
-
escaped += char;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return escaped;
|
|
58
|
-
};
|
|
59
|
-
var _default = exports.default = escape;
|