@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,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filter query evaluator.
|
|
3
|
+
*
|
|
4
|
+
* Evaluates FilterQuery expressions which contain either:
|
|
5
|
+
* - RelQuery (@.path) - relative to current node
|
|
6
|
+
* - JsonPathQuery ($.path) - absolute from root
|
|
7
|
+
*
|
|
8
|
+
* Returns a nodelist (array of matched values).
|
|
9
|
+
*
|
|
10
|
+
* @see https://www.rfc-editor.org/rfc/rfc9535#section-2.3.5.2.1
|
|
11
|
+
*/
|
|
12
|
+
import visitSelector from "../visitors/selector.mjs";
|
|
13
|
+
import visitBracketedSelection from "../visitors/bracketed-selection.mjs";
|
|
14
|
+
/**
|
|
15
|
+
* Apply a segment to a value and collect all results.
|
|
16
|
+
*
|
|
17
|
+
* @param {object} ctx - Evaluation context
|
|
18
|
+
* @param {unknown} value - Current value
|
|
19
|
+
* @param {object} segment - Segment AST node
|
|
20
|
+
* @returns {unknown[]} - Array of matched values
|
|
21
|
+
*/
|
|
22
|
+
const applySegment = (ctx, value, segment) => {
|
|
23
|
+
const results = [];
|
|
24
|
+
const emit = selectedValue => {
|
|
25
|
+
results.push(selectedValue);
|
|
26
|
+
};
|
|
27
|
+
const {
|
|
28
|
+
selector
|
|
29
|
+
} = segment;
|
|
30
|
+
switch (selector.type) {
|
|
31
|
+
case 'BracketedSelection':
|
|
32
|
+
visitBracketedSelection(ctx, value, selector, emit);
|
|
33
|
+
break;
|
|
34
|
+
case 'NameSelector':
|
|
35
|
+
case 'WildcardSelector':
|
|
36
|
+
case 'IndexSelector':
|
|
37
|
+
case 'SliceSelector':
|
|
38
|
+
case 'FilterSelector':
|
|
39
|
+
visitSelector(ctx, value, selector, emit);
|
|
40
|
+
break;
|
|
41
|
+
default:
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
return results;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Apply segments to get nodelist, supporting descendant segments.
|
|
49
|
+
*
|
|
50
|
+
* @param {object} ctx - Evaluation context
|
|
51
|
+
* @param {unknown} root - Root value ($)
|
|
52
|
+
* @param {unknown[]} values - Current nodelist
|
|
53
|
+
* @param {object[]} segments - Remaining segments
|
|
54
|
+
* @returns {unknown[]} - Result nodelist
|
|
55
|
+
*/
|
|
56
|
+
const applySegments = (ctx, root, values, segments) => {
|
|
57
|
+
let current = values;
|
|
58
|
+
for (const segment of segments) {
|
|
59
|
+
const next = [];
|
|
60
|
+
if (segment.type === 'DescendantSegment') {
|
|
61
|
+
// Descendant segment: apply to current and all descendants
|
|
62
|
+
const collectDescendants = value => {
|
|
63
|
+
const {
|
|
64
|
+
realm
|
|
65
|
+
} = ctx;
|
|
66
|
+
// Apply selector at current level
|
|
67
|
+
const results = applySegment(ctx, value, segment);
|
|
68
|
+
next.push(...results);
|
|
69
|
+
|
|
70
|
+
// Recurse into children
|
|
71
|
+
for (const [, child] of realm.entries(value)) {
|
|
72
|
+
collectDescendants(child);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
for (const value of current) {
|
|
76
|
+
collectDescendants(value);
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
// Child segment: apply to current values only
|
|
80
|
+
for (const value of current) {
|
|
81
|
+
const results = applySegment(ctx, value, segment);
|
|
82
|
+
next.push(...results);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
current = next;
|
|
86
|
+
}
|
|
87
|
+
return current;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Evaluate a RelQuery (@.path).
|
|
92
|
+
*
|
|
93
|
+
* @param {object} ctx - Evaluation context
|
|
94
|
+
* @param {unknown} root - Root value ($)
|
|
95
|
+
* @param {unknown} current - Current value (@)
|
|
96
|
+
* @param {object} node - RelQuery AST node
|
|
97
|
+
* @returns {unknown[]} - Nodelist of matched values
|
|
98
|
+
*/
|
|
99
|
+
const evaluateRelQuery = (ctx, root, current, node) => {
|
|
100
|
+
const {
|
|
101
|
+
segments
|
|
102
|
+
} = node;
|
|
103
|
+
if (segments.length === 0) {
|
|
104
|
+
// @ with no segments returns current as single-element nodelist
|
|
105
|
+
return [current];
|
|
106
|
+
}
|
|
107
|
+
return applySegments(ctx, root, [current], segments);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Evaluate a JsonPathQuery ($.path).
|
|
112
|
+
*
|
|
113
|
+
* @param {object} ctx - Evaluation context
|
|
114
|
+
* @param {unknown} root - Root value ($)
|
|
115
|
+
* @param {unknown} current - Current value (unused)
|
|
116
|
+
* @param {object} node - JsonPathQuery AST node
|
|
117
|
+
* @returns {unknown[]} - Nodelist of matched values
|
|
118
|
+
*/
|
|
119
|
+
const evaluateJsonPathQuery = (ctx, root, current, node) => {
|
|
120
|
+
const {
|
|
121
|
+
segments
|
|
122
|
+
} = node;
|
|
123
|
+
if (segments.length === 0) {
|
|
124
|
+
// $ with no segments returns root as single-element nodelist
|
|
125
|
+
return [root];
|
|
126
|
+
}
|
|
127
|
+
return applySegments(ctx, root, [root], segments);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Mark an array as a nodelist.
|
|
132
|
+
* This helps functions distinguish nodelists from array values.
|
|
133
|
+
*
|
|
134
|
+
* @param {unknown[]} arr - Array to mark
|
|
135
|
+
* @returns {unknown[]} - Marked array
|
|
136
|
+
*/
|
|
137
|
+
const markAsNodelist = arr => {
|
|
138
|
+
Object.defineProperty(arr, '_isNodelist', {
|
|
139
|
+
value: true,
|
|
140
|
+
enumerable: false,
|
|
141
|
+
writable: false
|
|
142
|
+
});
|
|
143
|
+
return arr;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Evaluate a FilterQuery.
|
|
148
|
+
*
|
|
149
|
+
* @param {object} ctx - Evaluation context
|
|
150
|
+
* @param {unknown} root - Root value ($)
|
|
151
|
+
* @param {unknown} current - Current value (@)
|
|
152
|
+
* @param {object} node - FilterQuery AST node
|
|
153
|
+
* @returns {unknown[]} - Nodelist of matched values
|
|
154
|
+
*/
|
|
155
|
+
const evaluateFilterQuery = (ctx, root, current, node) => {
|
|
156
|
+
const {
|
|
157
|
+
query
|
|
158
|
+
} = node;
|
|
159
|
+
let result;
|
|
160
|
+
switch (query.type) {
|
|
161
|
+
case 'RelQuery':
|
|
162
|
+
result = evaluateRelQuery(ctx, root, current, query);
|
|
163
|
+
break;
|
|
164
|
+
case 'JsonPathQuery':
|
|
165
|
+
result = evaluateJsonPathQuery(ctx, root, current, query);
|
|
166
|
+
break;
|
|
167
|
+
default:
|
|
168
|
+
result = [];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Mark result as a nodelist so functions can handle type coercion
|
|
172
|
+
return markAsNodelist(result);
|
|
173
|
+
};
|
|
174
|
+
export default evaluateFilterQuery;
|
|
175
|
+
export { evaluateRelQuery, evaluateJsonPathQuery };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Function expression evaluator.
|
|
3
|
+
*
|
|
4
|
+
* Evaluates function calls like length(@.items), match(@.name, "pattern"), etc.
|
|
5
|
+
*
|
|
6
|
+
* @see https://www.rfc-editor.org/rfc/rfc9535#section-2.4
|
|
7
|
+
*/
|
|
8
|
+
import evaluateComparable from "./comparable.mjs";
|
|
9
|
+
import evaluateFilterQuery from "./filter-query.mjs";
|
|
10
|
+
/**
|
|
11
|
+
* Evaluate a function argument.
|
|
12
|
+
* Arguments can be:
|
|
13
|
+
* - Literals
|
|
14
|
+
* - Singular queries (@.path, $.path)
|
|
15
|
+
* - Filter queries (for NodesType parameters)
|
|
16
|
+
* - Function expressions (nested calls)
|
|
17
|
+
* - Logical expressions (for LogicalType parameters)
|
|
18
|
+
*
|
|
19
|
+
* Special case: When a TestExpr contains only a FilterQuery,
|
|
20
|
+
* we evaluate it as a nodelist (for functions like count() that expect NodesType).
|
|
21
|
+
*
|
|
22
|
+
* @param {object} ctx - Evaluation context
|
|
23
|
+
* @param {unknown} root - Root value ($)
|
|
24
|
+
* @param {unknown} current - Current value (@)
|
|
25
|
+
* @param {object} arg - Argument AST node
|
|
26
|
+
* @returns {unknown} - Evaluated argument value
|
|
27
|
+
*/
|
|
28
|
+
const evaluateArgument = (ctx, root, current, arg) => {
|
|
29
|
+
switch (arg.type) {
|
|
30
|
+
case 'Literal':
|
|
31
|
+
case 'RelSingularQuery':
|
|
32
|
+
case 'AbsSingularQuery':
|
|
33
|
+
case 'FunctionExpr':
|
|
34
|
+
return evaluateComparable(ctx, root, current, arg);
|
|
35
|
+
case 'FilterQuery':
|
|
36
|
+
// FilterQuery produces a nodelist (array of values)
|
|
37
|
+
return evaluateFilterQuery(ctx, root, current, arg);
|
|
38
|
+
case 'TestExpr':
|
|
39
|
+
// TestExpr can contain FilterQuery (for NodesType/ValueType) or FunctionExpr
|
|
40
|
+
if (arg.expression.type === 'FilterQuery') {
|
|
41
|
+
// Always return the nodelist - functions handle type coercion internally
|
|
42
|
+
// Per RFC 9535 Section 2.4.1: if a function expects ValueType and gets NodesType,
|
|
43
|
+
// it auto-converts (single node -> value, otherwise -> Nothing)
|
|
44
|
+
return evaluateFilterQuery(ctx, root, current, arg.expression);
|
|
45
|
+
}
|
|
46
|
+
if (arg.expression.type === 'FunctionExpr') {
|
|
47
|
+
// FunctionExpr as argument - evaluate the nested function
|
|
48
|
+
return evaluateComparable(ctx, root, current, arg.expression);
|
|
49
|
+
}
|
|
50
|
+
// Otherwise evaluate as logical expression
|
|
51
|
+
// eslint-disable-next-line no-use-before-define
|
|
52
|
+
return evaluateLogicalExpr(ctx, root, current, arg);
|
|
53
|
+
case 'LogicalOrExpr':
|
|
54
|
+
case 'LogicalAndExpr':
|
|
55
|
+
case 'LogicalNotExpr':
|
|
56
|
+
case 'ComparisonExpr':
|
|
57
|
+
// Import dynamically to avoid circular dependency
|
|
58
|
+
// eslint-disable-next-line no-use-before-define
|
|
59
|
+
return evaluateLogicalExpr(ctx, root, current, arg);
|
|
60
|
+
default:
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Lazy import to avoid circular dependency
|
|
66
|
+
let evaluateLogicalExpr;
|
|
67
|
+
export const setLogicalExprEvaluator = fn => {
|
|
68
|
+
evaluateLogicalExpr = fn;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Evaluate a function expression.
|
|
73
|
+
*
|
|
74
|
+
* @param {object} ctx - Evaluation context
|
|
75
|
+
* @param {unknown} root - Root value ($)
|
|
76
|
+
* @param {unknown} current - Current value (@)
|
|
77
|
+
* @param {object} node - AST node
|
|
78
|
+
* @param {string} node.name - Function name
|
|
79
|
+
* @param {object[]} node.arguments - Array of argument AST nodes
|
|
80
|
+
* @returns {unknown} - Function result
|
|
81
|
+
*/
|
|
82
|
+
const evaluateFunctionExpr = (ctx, root, current, node) => {
|
|
83
|
+
const {
|
|
84
|
+
name,
|
|
85
|
+
arguments: args
|
|
86
|
+
} = node;
|
|
87
|
+
const fn = ctx.functions[name];
|
|
88
|
+
if (typeof fn !== 'function') {
|
|
89
|
+
// Unknown function returns Nothing
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Evaluate all arguments
|
|
94
|
+
const evaluatedArgs = args.map(arg => evaluateArgument(ctx, root, current, arg));
|
|
95
|
+
|
|
96
|
+
// Call the function with realm and evaluated arguments
|
|
97
|
+
return fn(ctx.realm, ...evaluatedArgs);
|
|
98
|
+
};
|
|
99
|
+
export default evaluateFunctionExpr;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Literal evaluator.
|
|
3
|
+
*
|
|
4
|
+
* Evaluates a literal value (string, number, boolean, null).
|
|
5
|
+
* Literals appear in comparisons and function arguments.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Evaluate a literal AST node.
|
|
10
|
+
*
|
|
11
|
+
* @param {object} ctx - Evaluation context (unused for literals)
|
|
12
|
+
* @param {unknown} root - Root value (unused for literals)
|
|
13
|
+
* @param {unknown} current - Current value (unused for literals)
|
|
14
|
+
* @param {object} node - AST node
|
|
15
|
+
* @param {unknown} node.value - The literal value
|
|
16
|
+
* @returns {unknown} - The literal value
|
|
17
|
+
*/
|
|
18
|
+
const evaluateLiteral = (ctx, root, current, node) => {
|
|
19
|
+
return node.value;
|
|
20
|
+
};
|
|
21
|
+
export default evaluateLiteral;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logical expression evaluator.
|
|
3
|
+
*
|
|
4
|
+
* Evaluates logical expressions:
|
|
5
|
+
* - LogicalOrExpr (||)
|
|
6
|
+
* - LogicalAndExpr (&&)
|
|
7
|
+
* - LogicalNotExpr (!)
|
|
8
|
+
* - TestExpr (existence test or function result)
|
|
9
|
+
* - ComparisonExpr (routed to comparison evaluator)
|
|
10
|
+
*
|
|
11
|
+
* @see https://www.rfc-editor.org/rfc/rfc9535#section-2.3.5.2
|
|
12
|
+
*/
|
|
13
|
+
import evaluateComparisonExpr from "./comparison-expr.mjs";
|
|
14
|
+
import evaluateFunctionExpr, { setLogicalExprEvaluator } from "./function-expr.mjs";
|
|
15
|
+
import evaluateFilterQuery from "./filter-query.mjs";
|
|
16
|
+
import { isArray } from "../utils/guards.mjs";
|
|
17
|
+
/**
|
|
18
|
+
* Evaluate a logical expression.
|
|
19
|
+
*
|
|
20
|
+
* @param {object} ctx - Evaluation context
|
|
21
|
+
* @param {unknown} root - Root value ($)
|
|
22
|
+
* @param {unknown} current - Current value (@)
|
|
23
|
+
* @param {object} node - Logical expression AST node
|
|
24
|
+
* @returns {boolean} - Logical result
|
|
25
|
+
*/
|
|
26
|
+
const evaluateLogicalExpr = (ctx, root, current, node) => {
|
|
27
|
+
switch (node.type) {
|
|
28
|
+
case 'LogicalOrExpr':
|
|
29
|
+
{
|
|
30
|
+
// Short-circuit OR
|
|
31
|
+
const left = evaluateLogicalExpr(ctx, root, current, node.left);
|
|
32
|
+
if (left) return true;
|
|
33
|
+
return evaluateLogicalExpr(ctx, root, current, node.right);
|
|
34
|
+
}
|
|
35
|
+
case 'LogicalAndExpr':
|
|
36
|
+
{
|
|
37
|
+
// Short-circuit AND
|
|
38
|
+
const left = evaluateLogicalExpr(ctx, root, current, node.left);
|
|
39
|
+
if (!left) return false;
|
|
40
|
+
return evaluateLogicalExpr(ctx, root, current, node.right);
|
|
41
|
+
}
|
|
42
|
+
case 'LogicalNotExpr':
|
|
43
|
+
{
|
|
44
|
+
return !evaluateLogicalExpr(ctx, root, current, node.expression);
|
|
45
|
+
}
|
|
46
|
+
case 'TestExpr':
|
|
47
|
+
{
|
|
48
|
+
// TestExpr wraps a FilterQuery or FunctionExpr
|
|
49
|
+
const {
|
|
50
|
+
expression
|
|
51
|
+
} = node;
|
|
52
|
+
if (expression.type === 'FilterQuery') {
|
|
53
|
+
// Existence test: true if nodelist is non-empty
|
|
54
|
+
const nodelist = evaluateFilterQuery(ctx, root, current, expression);
|
|
55
|
+
return nodelist.length > 0;
|
|
56
|
+
}
|
|
57
|
+
if (expression.type === 'FunctionExpr') {
|
|
58
|
+
// Function result converted to boolean
|
|
59
|
+
const result = evaluateFunctionExpr(ctx, root, current, expression);
|
|
60
|
+
// LogicalType functions return boolean directly
|
|
61
|
+
// ValueType functions: undefined (Nothing) is false, truthy values are true
|
|
62
|
+
if (typeof result === 'boolean') {
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
// Nothing is false
|
|
66
|
+
if (result === undefined) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
// NodesType (array): non-empty is true
|
|
70
|
+
if (isArray(result)) {
|
|
71
|
+
return result.length > 0;
|
|
72
|
+
}
|
|
73
|
+
// Other ValueType: truthy check
|
|
74
|
+
return Boolean(result);
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
case 'ComparisonExpr':
|
|
79
|
+
{
|
|
80
|
+
return evaluateComparisonExpr(ctx, root, current, node);
|
|
81
|
+
}
|
|
82
|
+
default:
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Register with function-expr to break circular dependency
|
|
88
|
+
setLogicalExprEvaluator(evaluateLogicalExpr);
|
|
89
|
+
export default evaluateLogicalExpr;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Singular query evaluator.
|
|
3
|
+
*
|
|
4
|
+
* Evaluates RelSingularQuery (@.path) and AbsSingularQuery ($.path) expressions.
|
|
5
|
+
* These appear in comparison expressions within filters.
|
|
6
|
+
*
|
|
7
|
+
* A singular query can only contain name selectors and index selectors,
|
|
8
|
+
* ensuring it produces at most one value.
|
|
9
|
+
*
|
|
10
|
+
* @see https://www.rfc-editor.org/rfc/rfc9535#section-2.3.5.2.2
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Apply a singular query segment (name or index selector).
|
|
15
|
+
*
|
|
16
|
+
* @param {object} ctx - Evaluation context
|
|
17
|
+
* @param {unknown} value - Current value
|
|
18
|
+
* @param {object} segment - Segment AST node
|
|
19
|
+
* @returns {unknown} - Selected value or undefined (Nothing)
|
|
20
|
+
*/
|
|
21
|
+
const applySingularSegment = (ctx, value, segment) => {
|
|
22
|
+
const {
|
|
23
|
+
realm
|
|
24
|
+
} = ctx;
|
|
25
|
+
const {
|
|
26
|
+
selector
|
|
27
|
+
} = segment;
|
|
28
|
+
switch (selector.type) {
|
|
29
|
+
case 'NameSelector':
|
|
30
|
+
{
|
|
31
|
+
const {
|
|
32
|
+
value: name
|
|
33
|
+
} = selector;
|
|
34
|
+
if (realm.isObject(value) && realm.hasProperty(value, name)) {
|
|
35
|
+
return realm.getProperty(value, name);
|
|
36
|
+
}
|
|
37
|
+
return undefined; // Nothing
|
|
38
|
+
}
|
|
39
|
+
case 'IndexSelector':
|
|
40
|
+
{
|
|
41
|
+
const {
|
|
42
|
+
value: index
|
|
43
|
+
} = selector;
|
|
44
|
+
if (!realm.isArray(value)) return undefined;
|
|
45
|
+
const length = realm.getLength(value);
|
|
46
|
+
const normalizedIndex = index >= 0 ? index : length + index;
|
|
47
|
+
if (normalizedIndex >= 0 && normalizedIndex < length) {
|
|
48
|
+
return realm.getElement(value, normalizedIndex);
|
|
49
|
+
}
|
|
50
|
+
return undefined; // Nothing
|
|
51
|
+
}
|
|
52
|
+
default:
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Evaluate a RelSingularQuery (@.path).
|
|
59
|
+
*
|
|
60
|
+
* @param {object} ctx - Evaluation context
|
|
61
|
+
* @param {unknown} root - Root value (unused)
|
|
62
|
+
* @param {unknown} current - Current value (@)
|
|
63
|
+
* @param {object} node - AST node
|
|
64
|
+
* @param {object[]} node.segments - Array of singular query segments
|
|
65
|
+
* @returns {unknown} - Result value or undefined (Nothing)
|
|
66
|
+
*/
|
|
67
|
+
export const evaluateRelSingularQuery = (ctx, root, current, node) => {
|
|
68
|
+
let value = current;
|
|
69
|
+
for (const segment of node.segments) {
|
|
70
|
+
value = applySingularSegment(ctx, value, segment);
|
|
71
|
+
if (value === undefined) {
|
|
72
|
+
return undefined; // Nothing - short circuit
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return value;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Evaluate an AbsSingularQuery ($.path).
|
|
80
|
+
*
|
|
81
|
+
* @param {object} ctx - Evaluation context
|
|
82
|
+
* @param {unknown} root - Root value ($)
|
|
83
|
+
* @param {unknown} current - Current value (unused)
|
|
84
|
+
* @param {object} node - AST node
|
|
85
|
+
* @param {object[]} node.segments - Array of singular query segments
|
|
86
|
+
* @returns {unknown} - Result value or undefined (Nothing)
|
|
87
|
+
*/
|
|
88
|
+
export const evaluateAbsSingularQuery = (ctx, root, current, node) => {
|
|
89
|
+
let value = root;
|
|
90
|
+
for (const segment of node.segments) {
|
|
91
|
+
value = applySingularSegment(ctx, value, segment);
|
|
92
|
+
if (value === undefined) {
|
|
93
|
+
return undefined; // Nothing - short circuit
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return value;
|
|
97
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 9535 count() function.
|
|
3
|
+
*
|
|
4
|
+
* @see https://www.rfc-editor.org/rfc/rfc9535#section-2.4.4
|
|
5
|
+
*
|
|
6
|
+
* Parameters:
|
|
7
|
+
* NodesType (nodelist)
|
|
8
|
+
*
|
|
9
|
+
* Returns:
|
|
10
|
+
* ValueType (number)
|
|
11
|
+
*
|
|
12
|
+
* Result:
|
|
13
|
+
* The number of nodes in the nodelist.
|
|
14
|
+
*/
|
|
15
|
+
import { isNodelist } from "../utils/guards.mjs";
|
|
16
|
+
/**
|
|
17
|
+
* Count the number of nodes in a nodelist.
|
|
18
|
+
*
|
|
19
|
+
* @param {object} realm - Evaluation realm (unused, for consistent signature)
|
|
20
|
+
* @param {unknown} nodelist - A nodelist (array of values)
|
|
21
|
+
* @returns {number} - Number of nodes
|
|
22
|
+
*/
|
|
23
|
+
const count = (realm, nodelist) => {
|
|
24
|
+
if (isNodelist(nodelist)) {
|
|
25
|
+
return nodelist.length;
|
|
26
|
+
}
|
|
27
|
+
// Not a nodelist (NodesType): return Nothing per RFC 9535
|
|
28
|
+
return undefined;
|
|
29
|
+
};
|
|
30
|
+
export default count;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 9535 built-in functions.
|
|
3
|
+
*
|
|
4
|
+
* All functions defined in RFC 9535 Section 2.4.
|
|
5
|
+
* Can be extended or overridden via the `functions` option in evaluate().
|
|
6
|
+
*
|
|
7
|
+
* @see https://www.rfc-editor.org/rfc/rfc9535#section-2.4
|
|
8
|
+
*/
|
|
9
|
+
export { default as length } from "./length.mjs";
|
|
10
|
+
export { default as count } from "./count.mjs";
|
|
11
|
+
export { default as value } from "./value.mjs";
|
|
12
|
+
export { default as match } from "./match.mjs";
|
|
13
|
+
export { default as search } from "./search.mjs";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 9535 length() function.
|
|
3
|
+
*
|
|
4
|
+
* @see https://www.rfc-editor.org/rfc/rfc9535#section-2.4.5
|
|
5
|
+
*
|
|
6
|
+
* Parameters:
|
|
7
|
+
* ValueType (single value)
|
|
8
|
+
*
|
|
9
|
+
* Returns:
|
|
10
|
+
* ValueType (number or Nothing)
|
|
11
|
+
*
|
|
12
|
+
* Result:
|
|
13
|
+
* - String: number of Unicode scalar values (not UTF-16 code units)
|
|
14
|
+
* - Array: number of elements
|
|
15
|
+
* - Object: number of members (key-value pairs)
|
|
16
|
+
* - Other: Nothing (undefined)
|
|
17
|
+
*/
|
|
18
|
+
import { coerceToValueType, isNothing } from "../utils/guards.mjs";
|
|
19
|
+
/**
|
|
20
|
+
* Get the length of a value.
|
|
21
|
+
*
|
|
22
|
+
* @param {object} realm - Evaluation realm
|
|
23
|
+
* @param {unknown} value - The value to measure (may be a nodelist)
|
|
24
|
+
* @returns {number | undefined} - Length or Nothing (undefined)
|
|
25
|
+
*/
|
|
26
|
+
const length = (realm, value) => {
|
|
27
|
+
// Coerce nodelist to single value if needed
|
|
28
|
+
const coerced = coerceToValueType(value);
|
|
29
|
+
|
|
30
|
+
// Nothing returns Nothing
|
|
31
|
+
if (isNothing(coerced)) return undefined;
|
|
32
|
+
|
|
33
|
+
// Use realm to get length (handles strings, arrays, objects)
|
|
34
|
+
const len = realm.getLength(coerced);
|
|
35
|
+
return len > 0 || realm.isString(coerced) || realm.isArray(coerced) || realm.isObject(coerced) ? len : undefined;
|
|
36
|
+
};
|
|
37
|
+
export default length;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 9535 match() function.
|
|
3
|
+
*
|
|
4
|
+
* @see https://www.rfc-editor.org/rfc/rfc9535#section-2.4.1
|
|
5
|
+
*
|
|
6
|
+
* Parameters:
|
|
7
|
+
* ValueType (string), ValueType (I-Regexp pattern string)
|
|
8
|
+
*
|
|
9
|
+
* Returns:
|
|
10
|
+
* LogicalType (boolean)
|
|
11
|
+
*
|
|
12
|
+
* Result:
|
|
13
|
+
* true if the entire string matches the I-Regexp pattern, false otherwise.
|
|
14
|
+
* The pattern is implicitly anchored (^(?:pattern)$).
|
|
15
|
+
*/
|
|
16
|
+
import { coerceToValueType } from "../utils/guards.mjs";
|
|
17
|
+
import { constructRegex } from "../utils/i-regexp.mjs";
|
|
18
|
+
/**
|
|
19
|
+
* Test if entire string matches I-Regexp pattern.
|
|
20
|
+
*
|
|
21
|
+
* @param {object} realm - Evaluation realm
|
|
22
|
+
* @param {unknown} value - The string to test (may be a nodelist)
|
|
23
|
+
* @param {unknown} pattern - The I-Regexp pattern (may be a nodelist)
|
|
24
|
+
* @returns {boolean} - true if entire string matches
|
|
25
|
+
*/
|
|
26
|
+
const match = (realm, value, pattern) => {
|
|
27
|
+
// Coerce nodelists to single values
|
|
28
|
+
const coercedValue = coerceToValueType(value);
|
|
29
|
+
const coercedPattern = coerceToValueType(pattern);
|
|
30
|
+
|
|
31
|
+
// Get raw string values from realm
|
|
32
|
+
const strValue = realm.getString(coercedValue);
|
|
33
|
+
const strPattern = realm.getString(coercedPattern);
|
|
34
|
+
if (strValue === undefined || strPattern === undefined) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
const regex = constructRegex(strPattern, true); // anchored
|
|
38
|
+
if (regex === null) {
|
|
39
|
+
// Invalid I-Regexp pattern
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return regex.test(strValue);
|
|
43
|
+
};
|
|
44
|
+
export default match;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 9535 search() function.
|
|
3
|
+
*
|
|
4
|
+
* @see https://www.rfc-editor.org/rfc/rfc9535#section-2.4.2
|
|
5
|
+
*
|
|
6
|
+
* Parameters:
|
|
7
|
+
* ValueType (string), ValueType (I-Regexp pattern string)
|
|
8
|
+
*
|
|
9
|
+
* Returns:
|
|
10
|
+
* LogicalType (boolean)
|
|
11
|
+
*
|
|
12
|
+
* Result:
|
|
13
|
+
* true if any substring matches the I-Regexp pattern, false otherwise.
|
|
14
|
+
* The pattern is not anchored (can match anywhere in string).
|
|
15
|
+
*/
|
|
16
|
+
import { coerceToValueType } from "../utils/guards.mjs";
|
|
17
|
+
import { constructRegex } from "../utils/i-regexp.mjs";
|
|
18
|
+
/**
|
|
19
|
+
* Test if any substring matches I-Regexp pattern.
|
|
20
|
+
*
|
|
21
|
+
* @param {object} realm - Evaluation realm
|
|
22
|
+
* @param {unknown} value - The string to search (may be a nodelist)
|
|
23
|
+
* @param {unknown} pattern - The I-Regexp pattern (may be a nodelist)
|
|
24
|
+
* @returns {boolean} - true if any substring matches
|
|
25
|
+
*/
|
|
26
|
+
const search = (realm, value, pattern) => {
|
|
27
|
+
// Coerce nodelists to single values
|
|
28
|
+
const coercedValue = coerceToValueType(value);
|
|
29
|
+
const coercedPattern = coerceToValueType(pattern);
|
|
30
|
+
|
|
31
|
+
// Get raw string values from realm
|
|
32
|
+
const strValue = realm.getString(coercedValue);
|
|
33
|
+
const strPattern = realm.getString(coercedPattern);
|
|
34
|
+
if (strValue === undefined || strPattern === undefined) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
const regex = constructRegex(strPattern, false); // not anchored
|
|
38
|
+
if (regex === null) {
|
|
39
|
+
// Invalid I-Regexp pattern
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return regex.test(strValue);
|
|
43
|
+
};
|
|
44
|
+
export default search;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 9535 value() function.
|
|
3
|
+
*
|
|
4
|
+
* @see https://www.rfc-editor.org/rfc/rfc9535#section-2.4.7
|
|
5
|
+
*
|
|
6
|
+
* Parameters:
|
|
7
|
+
* NodesType (nodelist)
|
|
8
|
+
*
|
|
9
|
+
* Returns:
|
|
10
|
+
* ValueType (the single value or Nothing)
|
|
11
|
+
*
|
|
12
|
+
* Result:
|
|
13
|
+
* - If nodelist has exactly one node: that node's value
|
|
14
|
+
* - Otherwise: Nothing (undefined)
|
|
15
|
+
*/
|
|
16
|
+
import { isNodelist } from "../utils/guards.mjs";
|
|
17
|
+
/**
|
|
18
|
+
* Extract the single value from a nodelist.
|
|
19
|
+
*
|
|
20
|
+
* @param {object} realm - Evaluation realm (unused, for consistent signature)
|
|
21
|
+
* @param {unknown} nodelist - A nodelist (array of values)
|
|
22
|
+
* @returns {unknown} - The single value or Nothing (undefined)
|
|
23
|
+
*/
|
|
24
|
+
const value = (realm, nodelist) => {
|
|
25
|
+
if (isNodelist(nodelist) && nodelist.length === 1) {
|
|
26
|
+
return nodelist[0];
|
|
27
|
+
}
|
|
28
|
+
// Nothing for empty nodelist, multiple values, or non-nodelist
|
|
29
|
+
return undefined;
|
|
30
|
+
};
|
|
31
|
+
export default value;
|