Skip to content

Commit 885cd8f

Browse files
committed
fix: make respect jsopath criteria compliant with rfc9535
1 parent 279b8be commit 885cd8f

File tree

20 files changed

+97
-296
lines changed

20 files changed

+97
-296
lines changed

packages/respect-core/src/modules/__tests__/flow-runner/success-criteria/check-success-criteria.test.ts

Lines changed: 67 additions & 65 deletions
Large diffs are not rendered by default.
Lines changed: 4 additions & 205 deletions
Original file line numberDiff line numberDiff line change
@@ -1,211 +1,10 @@
11
import { query, type JsonValue } from 'jsonpath-rfc9535';
22

3-
export function evaluateJSONPathCondition(condition: string, context: JsonValue) {
3+
export function evaluateJSONPathCondition(condition: string, context: JsonValue): boolean {
44
try {
5-
const resolvedCondition = parseExpressions(condition, context);
6-
const evaluateFn = new Function(`return ${resolvedCondition};`);
7-
8-
return !!evaluateFn();
9-
} catch (error) {
5+
const result = query([context], condition);
6+
return result.length > 0;
7+
} catch {
108
return false;
119
}
1210
}
13-
14-
function parseExpressions(condition: string, context: JsonValue): string {
15-
const expressionsParts: Array<string> = [];
16-
17-
let i = 0;
18-
let expressionElements = '';
19-
20-
while (i < condition.length) {
21-
if (condition[i] === '$') {
22-
if (expressionElements.length > 0) {
23-
expressionsParts.push(expressionElements);
24-
expressionElements = '';
25-
}
26-
const start = i;
27-
const expression = parseSingleJSONPath(condition, i);
28-
29-
if (expression.length > 1) {
30-
const evaluatedExpression = evaluateJSONPathExpression(expression, context);
31-
32-
expressionsParts.push(evaluatedExpression);
33-
}
34-
i = start + expression.length;
35-
} else {
36-
expressionElements += condition[i];
37-
i++;
38-
}
39-
}
40-
41-
// Push any remaining content after the while loop
42-
if (expressionElements.length > 0) {
43-
expressionsParts.push(expressionElements);
44-
}
45-
46-
return expressionsParts.join('');
47-
}
48-
49-
function parseSingleJSONPath(condition: string, startIndex: number): string {
50-
let jsonpath = '$';
51-
let bracketDepth = 0;
52-
let inQuotes = false;
53-
let quoteChar = '';
54-
let inFilter = false;
55-
let filterDepth = 0;
56-
let i = startIndex + 1; // Skip the '$'
57-
58-
while (i < condition.length) {
59-
const char = condition[i];
60-
61-
if ((char === '"' || char === "'") && !inQuotes) {
62-
inQuotes = true;
63-
quoteChar = char;
64-
jsonpath += char;
65-
i++;
66-
continue;
67-
}
68-
69-
if (char === quoteChar && inQuotes) {
70-
inQuotes = false;
71-
jsonpath += char;
72-
i++;
73-
continue;
74-
}
75-
76-
if (inQuotes) {
77-
jsonpath += char;
78-
i++;
79-
continue;
80-
}
81-
82-
if (char === '[') {
83-
bracketDepth++;
84-
if (i + 1 < condition.length && condition[i + 1] === '?') {
85-
inFilter = true;
86-
filterDepth = bracketDepth;
87-
}
88-
jsonpath += char;
89-
i++;
90-
continue;
91-
}
92-
93-
if (char === ']') {
94-
bracketDepth--;
95-
if (inFilter && bracketDepth < filterDepth) {
96-
inFilter = false;
97-
}
98-
jsonpath += char;
99-
i++;
100-
continue;
101-
}
102-
103-
// Stop at logical operators, comparison operators, or whitespace (outside of filters)
104-
if (!inFilter && (/\s/.test(char) || /[<>=!&|,)]/.test(char))) {
105-
break;
106-
}
107-
108-
jsonpath += char;
109-
i++;
110-
}
111-
112-
return jsonpath;
113-
}
114-
115-
function evaluateJSONPathExpression(expression: string, context: JsonValue): string {
116-
// Handle legacy .length suffix for backward compatibility that is not a valid RFC 9535 expression
117-
if (expression.endsWith('.length')) {
118-
const basePath = expression.slice(0, -'.length'.length);
119-
const normalizedPath = transformHyphensToUnderscores(basePath);
120-
const result = query(context, normalizedPath);
121-
const value = result[0] ?? null;
122-
return Array.isArray(value) ? String(value.length) : '0';
123-
}
124-
125-
if (expression.includes('[?(') && expression.includes(')]')) {
126-
return handleFilterExpression(expression, context);
127-
}
128-
129-
const normalizedPath = transformHyphensToUnderscores(expression);
130-
const result = query(context, normalizedPath);
131-
return JSON.stringify(result[0]);
132-
}
133-
134-
function handleFilterExpression(expression: string, context: JsonValue): string {
135-
const filterMatch = expression.match(/\[\?\((.*)\)\]/);
136-
if (!filterMatch) return 'false';
137-
138-
const filterCondition = filterMatch[1];
139-
const basePath = expression.substring(0, expression.indexOf('[?('));
140-
const normalizedBasePath = transformHyphensToUnderscores(basePath);
141-
const jsonpathResult = query(context, normalizedBasePath);
142-
143-
// Flatten the result in case JSONPath returns nested arrays
144-
const arrayToFilter = Array.isArray(jsonpathResult) ? jsonpathResult.flat() : jsonpathResult;
145-
146-
if (!Array.isArray(arrayToFilter)) {
147-
return 'false';
148-
}
149-
150-
const filteredArray = arrayToFilter.filter((item: unknown) => {
151-
const convertedCondition = processFilterCondition(filterCondition, item);
152-
153-
try {
154-
const safeEval = new Function('item', `return ${convertedCondition};`);
155-
return !!safeEval(item);
156-
} catch {
157-
return false;
158-
}
159-
});
160-
161-
const afterFilter = expression.substring(expression.indexOf(')]') + 2);
162-
163-
if (afterFilter.startsWith('.')) {
164-
const propertyMatch = afterFilter.match(/\.([a-zA-Z0-9_-]+)/);
165-
if (propertyMatch) {
166-
const propertyName = propertyMatch[1].replace(/-/g, '_');
167-
const propertyValues = filteredArray.map(
168-
(item: unknown) => (item as Record<string, unknown>)[propertyName]
169-
);
170-
return JSON.stringify(propertyValues);
171-
}
172-
}
173-
174-
return filteredArray.length > 0 ? JSON.stringify(filteredArray) : 'false';
175-
}
176-
177-
function processFilterCondition(filterCondition: string, item: unknown): string {
178-
let convertedCondition = filterCondition;
179-
180-
// Handle @.property.match(/pattern/) expressions
181-
convertedCondition = convertedCondition.replace(
182-
/@\.([a-zA-Z0-9_-]+)\.match\(([^)]+)\)/g,
183-
(_, prop: string, pattern: string) => {
184-
const normalizedProp = prop.replace(/-/g, '_');
185-
const value = (item as Record<string, unknown>)[normalizedProp];
186-
if (typeof value !== 'string') return 'false';
187-
188-
try {
189-
let cleanPattern = pattern.replace(/^["']|["']$/g, ''); // Remove quotes
190-
cleanPattern = cleanPattern.replace(/^\/|\/$/g, ''); // Remove leading/trailing slashes
191-
const regex = new RegExp(cleanPattern);
192-
return String(regex.test(value));
193-
} catch {
194-
return 'false';
195-
}
196-
}
197-
);
198-
199-
// Handle @.property expressions (simple property access)
200-
convertedCondition = convertedCondition.replace(/@\.([a-zA-Z0-9_-]+)/g, (_, prop: string) => {
201-
const normalizedProp = prop.replace(/-/g, '_');
202-
const value = (item as Record<string, unknown>)[normalizedProp];
203-
return JSON.stringify(value);
204-
});
205-
206-
return convertedCondition;
207-
}
208-
209-
function transformHyphensToUnderscores(path: string): string {
210-
return path.replace(/\.([a-zA-Z0-9_-]+)/g, (_, prop) => '.' + prop.replace(/-/g, '_'));
211-
}

resources/museum-api.arazzo.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ workflows:
6767
successCriteria:
6868
- condition: $statusCode == 201
6969
- context: $response.body
70-
condition: $.name == 'Mermaid Treasure Identification and Analysis'
70+
condition: "$[?@.name == 'Mermaid Treasure Identification and Analysis']"
7171
type: jsonpath
7272
outputs:
7373
createdEventId: $response.body#/eventId
@@ -104,7 +104,7 @@ workflows:
104104
successCriteria:
105105
- condition: $statusCode == 200
106106
- context: $response.body
107-
condition: $.name == 'Orca Identification and Analysis'
107+
condition: "$[?@.name == 'Orca Identification and Analysis']"
108108
type:
109109
type: jsonpath
110110
version: draft-goessner-dispatch-jsonpath-00

tests/e2e/respect/inputs-passed-to-step-target-workflow-and-remapped/__snapshots__/inputs-passed-to-step-target-workflow-and-remapped.test.ts.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ exports[`should pass inputs to step target workflow with additional input parame
4343
      }
4444
4545
    ✗ success criteria check - $statusCode == 201
46-
    ✗ success criteria check - $.name == 'Mermaid Treasure Identification and Ana...
46+
✗ success criteria check - $[?@.name == 'Mermaid Treasure Identification and ...
4747
    ✓ status code check - $statusCode in [201, 400, 404]
4848
    ✓ content-type check
4949
    ✓ schema check
@@ -59,7 +59,7 @@ exports[`should pass inputs to step target workflow with additional input parame
5959
      Checking simple criteria: {"condition":"$statusCode == 201"}
6060
      
6161
    ✗ success criteria check
62-
      Checking jsonpath criteria: $.name == 'Mermaid Treasure Identification and Analysis'
62+
Checking jsonpath criteria: $[?@.name == 'Mermaid Treasure Identification and Analysis']
6363
      
6464
  Summary for inputs-passed-to-step-target-workflow-and-remapped.arazzo.yaml
6565
  

tests/e2e/respect/inputs-passed-to-step-target-workflow-and-remapped/events.arazzo.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,5 @@ workflows:
5959
successCriteria:
6060
- condition: $statusCode == 201
6161
- context: $response.body
62-
condition: $.name == 'Mermaid Treasure Identification and Analysis'
62+
condition: "$[?@.name == 'Mermaid Treasure Identification and Analysis']"
6363
type: jsonpath

tests/e2e/respect/inputs-passed-to-step-target-workflow/__snapshots__/inputs-passed-to-step-target-workflow.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ exports[`should pass inputs to step target workflow with additional input parame
5050
      }
5151
5252
    ✓ success criteria check - $statusCode == 201
53-
    ✓ success criteria check - $.name == 'Mermaid Treasure Identification and Ana...
53+
    ✓ success criteria check - $[?@.name == 'Mermaid Treasure Identification and ...
5454
    ✓ status code check - $statusCode in [201, 400, 404]
5555
    ✓ content-type check
5656
    ✓ schema check

tests/e2e/respect/inputs-passed-to-step-target-workflow/events.arazzo.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,5 @@ workflows:
5858
successCriteria:
5959
- condition: $statusCode == 201
6060
- context: $response.body
61-
condition: $.name == 'Mermaid Treasure Identification and Analysis'
61+
condition: "$[?@.name == 'Mermaid Treasure Identification and Analysis']"
6262
type: jsonpath

tests/e2e/respect/inputs-with-cli-and-env/__snapshots__/inputs-with-cli-and-env.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ exports[`should use inputs from CLI and env 1`] = `
229229
      }
230230
231231
    ✓ success criteria check - $statusCode == 201
232-
    ✓ success criteria check - $.name == 'Mermaid Treasure Identification and Ana...
232+
    ✓ success criteria check - $[?@.name == 'Mermaid Treasure Identification and ...
233233
    ✓ status code check - $statusCode in [201, 400, 404]
234234
    ✓ content-type check
235235
    ✓ schema check

tests/e2e/respect/inputs-with-cli-and-env/inputs-with-cli-and-env.arazzo.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,5 @@ workflows:
9696
successCriteria:
9797
- condition: $statusCode == 201
9898
- context: $response.body
99-
condition: $.name == 'Mermaid Treasure Identification and Analysis'
99+
condition: "$[?@.name == 'Mermaid Treasure Identification and Analysis']"
100100
type: jsonpath

tests/e2e/respect/mask-input-secrets/__snapshots__/mask-input-secrets.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ exports[`should hide sensitive input values 1`] = `
262262
      }
263263
264264
    ✓ success criteria check - $statusCode == 201
265-
    ✓ success criteria check - $.name == 'Mermaid Treasure Identification and Ana...
265+
    ✓ success criteria check - $[?@.name == 'Mermaid Treasure Identification and ...
266266
    ✓ status code check - $statusCode in [201, 400, 404]
267267
    ✓ content-type check
268268
    ✓ schema check

0 commit comments

Comments
 (0)