fix(rest-api): handle mixed bare filters and conjunctions in filter parser
https://sonarly.com/issue/32412?type=bug The REST API filter parser's `addDefaultConjunctionIfMissing` function uses a naive heuristic — checking for the presence of any parentheses — to decide whether to wrap the filter in a default `and()` conjunction. When a filter mixes bare conditions with explicit conjunctions (e.g., `status[eq]:'TODO',and(title[ilike]:'%test%')`), the parentheses from the nested conjunction trick the function into thinking a root conjunction already exists, causing the entire filter string to be parsed as a single base filter and producing a 400 error. Fix: **What changed:** Replaced the naive parentheses-presence heuristic in `addDefaultConjunctionIfMissing` with the same regex pattern used by `parseFilter` to detect a root-level conjunction. **Before:** The function checked `filterQuery.includes('(') && filterQuery.includes(')')`. If the filter contained *any* parentheses (e.g. from a nested `and(...)` or `or(...)`), it assumed a root conjunction already existed and returned the input unchanged. This meant `status[eq]:'TODO',and(title[ilike]:'%test%')` was NOT wrapped, causing a parse failure downstream. **After:** The function checks `^(or|and|not)\((.+)\)$` — the exact same regex `parseFilter` uses to detect a root conjunction. This correctly identifies that `status[eq]:'TODO',and(title[ilike]:'%test%')` does NOT start with a root conjunction and wraps it in `and(...)`. **Files changed:** 1. `add-default-conjunction.util.ts` — Replaced `includes('(')` check with `ROOT_CONJUNCTION_REGEX.test()` using the same regex pattern as `parseFilter`. 2. `__tests__/add-default-conjunction.util.spec.ts` — Added 5 test cases covering: mixed bare filters with nested conjunctions (the reported bug), `or` and `not` root conjunctions, and multiple bare filters without parentheses.
This commit is contained in:
+34
@@ -12,4 +12,38 @@ describe('addDefaultConjunctionIfMissing', () => {
|
||||
'and(field[eq]:1)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should add default conjunction when bare filters are mixed with nested conjunctions', () => {
|
||||
expect(
|
||||
addDefaultConjunctionIfMissing(
|
||||
"status[eq]:'TODO',and(title[ilike]:'%test%')",
|
||||
),
|
||||
).toEqual("and(status[eq]:'TODO',and(title[ilike]:'%test%'))");
|
||||
});
|
||||
|
||||
it('should add default conjunction for multiple bare filters with nested or', () => {
|
||||
expect(
|
||||
addDefaultConjunctionIfMissing(
|
||||
"field1[in]:['a','b'],or(field2[like]:'%x%')",
|
||||
),
|
||||
).toEqual("and(field1[in]:['a','b'],or(field2[like]:'%x%'))");
|
||||
});
|
||||
|
||||
it('should not add default conjunction for or root conjunction', () => {
|
||||
expect(
|
||||
addDefaultConjunctionIfMissing('or(field[eq]:1,field[eq]:2)'),
|
||||
).toEqual('or(field[eq]:1,field[eq]:2)');
|
||||
});
|
||||
|
||||
it('should not add default conjunction for not root conjunction', () => {
|
||||
expect(addDefaultConjunctionIfMissing('not(field[eq]:1)')).toEqual(
|
||||
'not(field[eq]:1)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should add default conjunction for multiple bare filters', () => {
|
||||
expect(
|
||||
addDefaultConjunctionIfMissing('field1[eq]:1,field2[gte]:10'),
|
||||
).toEqual('and(field1[eq]:1,field2[gte]:10)');
|
||||
});
|
||||
});
|
||||
|
||||
+5
-1
@@ -2,8 +2,12 @@ import { Conjunctions } from 'src/engine/api/rest/input-request-parsers/filter-p
|
||||
|
||||
export const DEFAULT_CONJUNCTION = Conjunctions.and;
|
||||
|
||||
const ROOT_CONJUNCTION_REGEX = new RegExp(
|
||||
`^(${Object.values(Conjunctions).join('|')})\\((.+)\\)$`,
|
||||
);
|
||||
|
||||
export const addDefaultConjunctionIfMissing = (filterQuery: string): string => {
|
||||
if (!(filterQuery.includes('(') && filterQuery.includes(')'))) {
|
||||
if (!ROOT_CONJUNCTION_REGEX.test(filterQuery)) {
|
||||
return `${DEFAULT_CONJUNCTION}(${filterQuery})`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user