Skip to content

Commit f498802

Browse files
committed
fix(core): Fix potential stack overflow with filter comparison
1 parent 3582ed2 commit f498802

File tree

3 files changed

+35
-16
lines changed

3 files changed

+35
-16
lines changed

packages/core/__tests__/helpers.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,14 @@ describe('applyFilter', () => {
314314
expect(applyFilter({ first: 'e', last: 'bar' }, filter)).toBe(true);
315315
});
316316

317+
it('should throw an error for an unknown operator', () => {
318+
const filter: Filter<TestDTO> = {
319+
// @ts-ignore
320+
first: { foo: 'bar' },
321+
};
322+
expect(() => applyFilter({ first: 'baz', last: 'kaz' }, filter)).toThrow('unknown comparison "foo"');
323+
});
324+
317325
it('should handle and grouping', () => {
318326
const filter: Filter<TestDTO> = {
319327
and: [{ first: { eq: 'foo' } }, { last: { like: '%bar' } }],

packages/core/src/helpers/comparison.builder.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { CommonFieldComparisonBetweenType, FilterComparisonOperators, Filter } from '../interfaces';
1+
import {
2+
CommonFieldComparisonBetweenType,
3+
FilterComparisonOperators,
4+
Filter,
5+
FilterFieldComparison,
6+
} from '../interfaces';
27
import { ComparisonField, FilterFn } from './types';
38

49
type LikeComparisonOperators = 'like' | 'notLike' | 'iLike' | 'notILike';
@@ -36,7 +41,9 @@ const isBooleanComparisonOperators = (op: any): op is BooleanComparisonOperators
3641
return op === 'eq' || op === 'neq' || op === 'is' || op === 'isNot';
3742
};
3843

39-
export const isComparison = <DTO>(maybeComparison: Filter<DTO>[keyof DTO]): boolean => {
44+
export const isComparison = <DTO, K extends keyof DTO>(
45+
maybeComparison: FilterFieldComparison<DTO[K]> | Filter<DTO[K]>,
46+
): maybeComparison is FilterFieldComparison<DTO[K]> => {
4047
// eslint-disable-next-line @typescript-eslint/no-explicit-any
4148
return Object.keys(maybeComparison as Record<string, any>).every((op) => {
4249
return (

packages/core/src/helpers/filter.builder.ts

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@ export class FilterBuilder {
77
const { and, or } = filter;
88
const filters: FilterFn<DTO>[] = [];
99

10-
if (and) {
10+
if (and && and.length) {
1111
filters.push(this.andFilterFn(...and.map((f) => this.build(f))));
1212
}
1313

14-
if (or) {
14+
if (or && or.length) {
1515
filters.push(this.orFilterFn(...or.map((f) => this.build(f))));
1616
}
17-
18-
filters.push(this.filterFieldsOrNested(filter));
17+
if (Object.keys(filter).length) {
18+
filters.push(this.filterFieldsOrNested(filter));
19+
}
1920
return this.andFilterFn(...filters);
2021
}
2122

@@ -31,16 +32,7 @@ export class FilterBuilder {
3132
return this.andFilterFn(
3233
...Object.keys(filter)
3334
.filter((k) => k !== 'and' && k !== 'or')
34-
.map((fieldOrNested) => {
35-
const value = this.getField(filter as FilterComparisons<DTO>, fieldOrNested as keyof DTO);
36-
37-
if (isComparison(filter[fieldOrNested as keyof DTO])) {
38-
return this.withFilterComparison(fieldOrNested as keyof DTO, value);
39-
}
40-
41-
const nestedFilterFn = this.build(value);
42-
return (dto?: DTO) => nestedFilterFn(dto ? dto[fieldOrNested as keyof DTO] : null);
43-
}),
35+
.map((fieldOrNested) => this.withComparison(filter, fieldOrNested as keyof DTO)),
4436
);
4537
}
4638

@@ -62,4 +54,16 @@ export class FilterBuilder {
6254
),
6355
);
6456
}
57+
58+
private static withComparison<DTO>(filter: FilterComparisons<DTO>, fieldOrNested: keyof DTO): FilterFn<DTO> {
59+
const value = this.getField(filter, fieldOrNested);
60+
if (isComparison(value)) {
61+
return this.withFilterComparison(fieldOrNested, value);
62+
}
63+
if (typeof value !== 'object') {
64+
throw new Error(`unknown comparison ${JSON.stringify(fieldOrNested)}`);
65+
}
66+
const nestedFilterFn = this.build(value);
67+
return (dto?: DTO) => nestedFilterFn(dto ? dto[fieldOrNested] : null);
68+
}
6569
}

0 commit comments

Comments
 (0)