Skip to content

Commit b877fef

Browse files
fix: send query languages strings in request body payload (#2807)
1 parent 2deb9d5 commit b877fef

3 files changed

Lines changed: 70 additions & 14 deletions

File tree

.changeset/silent-words-divide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@redocly/respect-core": patch
3+
---
4+
5+
Fixed an issue where query-language strings (JSONPath, XPath, SPARQL, OPA) in Respect request bodies were incorrectly treated as runtime expressions.

packages/respect-core/src/modules/__tests__/runtime-expressions/evaluate.test.ts

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,33 @@ describe('evaluateRuntimeExpressionPayload', () => {
339339
).toEqual('2023-12-15');
340340
});
341341

342+
it('should not evaluate query-language strings as runtime expressions', () => {
343+
const payload = {
344+
jsonpath: "$.items[*].attributes[?(@.type=='foo')]",
345+
xpath: '$bookstore/book[price>35]/title',
346+
sparql: 'SELECT $item WHERE { $item <http://example.com/type> "foo" }',
347+
opa: 'data.items[_].type == "foo"',
348+
};
349+
350+
expect(
351+
evaluateRuntimeExpressionPayload({ payload, context: runtimeExpressionContext, logger })
352+
).toEqual(payload);
353+
});
354+
355+
it('should evaluate {$faker.*} wrapped expression embedded in object payload', () => {
356+
const payload = { x: '{$faker.number.integer({ min: 5, max: 5 })}' };
357+
expect(
358+
evaluateRuntimeExpressionPayload({ payload, context: runtimeExpressionContext, logger })
359+
).toEqual({ x: '5' });
360+
});
361+
362+
it('should evaluate {$faker.*} wrapped expression mixed with surrounding text', () => {
363+
const payload = '{$faker.number.integer({ min: 5, max: 5 })} suffix';
364+
expect(
365+
evaluateRuntimeExpressionPayload({ payload, context: runtimeExpressionContext, logger })
366+
).toEqual('5 suffix');
367+
});
368+
342369
it('should evaluate $faker runtime expression value', () => {
343370
const payload = '$faker.number.integer({ min: 1, max: 10 })';
344371
expect(
@@ -352,13 +379,13 @@ describe('evaluateRuntimeExpressionPayload', () => {
352379

353380
it('should evaluate $faker inside string runtime expression value', () => {
354381
const payload = 'Some text {$faker.number.integer({ min: 1, max: 10 })}';
355-
expect(
356-
typeof evaluateRuntimeExpressionPayload({
357-
payload,
358-
context: runtimeExpressionContext,
359-
logger,
360-
})
361-
).toBe('string');
382+
const result = evaluateRuntimeExpressionPayload({
383+
payload,
384+
context: runtimeExpressionContext,
385+
logger,
386+
});
387+
expect(typeof result).toBe('string');
388+
expect(result).toMatch(/^Some text \d+$/);
362389
});
363390

364391
it('should evaluate multiword runtime expression value', () => {
@@ -607,9 +634,9 @@ describe('evaluateRuntimeExpression', () => {
607634

608635
it('should evaluate $faker inside string runtime expression value', () => {
609636
const payload = 'Some text {$faker.number.integer({ min: 1, max: 10 })}';
610-
expect(typeof evaluateRuntimeExpression(payload, runtimeExpressionContext, logger)).toBe(
611-
'string'
612-
);
637+
const result = evaluateRuntimeExpression(payload, runtimeExpressionContext, logger);
638+
expect(typeof result).toBe('string');
639+
expect(result).toMatch(/^Some text \d+$/);
613640
});
614641

615642
it('should evaluete list runtime expression value', () => {

packages/respect-core/src/modules/runtime-expressions/evaluate.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,10 @@ function evaluateExpressionsInString(
240240
return expression.replace(regex, (match) => {
241241
const exprToEvaluate = match.trim();
242242

243+
if (!isValidRuntimeExpression(exprToEvaluate) && !exprToEvaluate.includes('$faker.')) {
244+
return match;
245+
}
246+
243247
// For dollar expressions, include the leading $ when passing to evaluateRuntimeExpression
244248
const evaluatedValue = evaluateRuntimeExpression(exprToEvaluate, context, logger);
245249

@@ -249,9 +253,29 @@ function evaluateExpressionsInString(
249253
}
250254

251255
export function isPureRuntimeExpression(expression: string): boolean {
252-
// Regular expression to match runtime expressions
253-
const regex = /^\$[^\s{}]+(\([^)]*\))?$|^\{[^{}]*\}$/;
256+
const trimmedExpression = expression.trim();
257+
258+
if (trimmedExpression.startsWith('$faker.')) {
259+
return true;
260+
}
261+
262+
if (
263+
!(
264+
(trimmedExpression.startsWith('$') && !/\s/.test(trimmedExpression)) ||
265+
(trimmedExpression.startsWith('{') && trimmedExpression.endsWith('}'))
266+
)
267+
) {
268+
return false;
269+
}
270+
271+
return isValidRuntimeExpression(trimmedExpression);
272+
}
254273

255-
// Check if the expression matches the runtime expression format
256-
return regex.test(expression.trim());
274+
function isValidRuntimeExpression(expression: string): boolean {
275+
try {
276+
lintExpression(expression);
277+
return true;
278+
} catch {
279+
return false;
280+
}
257281
}

0 commit comments

Comments
 (0)