Skip to content

Commit

Permalink
feat(core): Add type support for nest objects in filter
Browse files Browse the repository at this point in the history
  • Loading branch information
doug-martin committed Jun 30, 2020
1 parent efe0e0a commit cd9d0b5
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 6 deletions.
62 changes: 62 additions & 0 deletions packages/core/__tests__/helpers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
transformQuery,
transformSort,
} from '../src';
import { getFilterFields } from '../src/helpers/query.helpers';

class TestDTO {
first!: string;
Expand Down Expand Up @@ -324,3 +325,64 @@ describe('applyFilter', () => {
expect(applyFilter({ first: 'fo', last: 'ba' }, filter)).toBe(false);
});
});

describe('getFilterFields', () => {
class Test {
strField!: string;

boolField!: string;

testRelation!: Test;
}

it('should get all fields at root of filter', () => {
const filter: Filter<Test> = {
boolField: { is: true },
strField: { eq: '' },
testRelation: {
boolField: { is: false },
},
};
expect(getFilterFields(filter).sort()).toEqual(['boolField', 'strField', 'testRelation']);
});

it('should get all fields in and', () => {
const filter: Filter<Test> = {
and: [
{ boolField: { is: true } },
{ strField: { eq: '' } },
{
testRelation: {
boolField: { is: false },
},
},
],
};
expect(getFilterFields(filter).sort()).toEqual(['boolField', 'strField', 'testRelation']);
});

it('should get all fields in or', () => {
const filter: Filter<Test> = {
or: [
{ boolField: { is: true } },
{ strField: { eq: '' } },
{
testRelation: {
boolField: { is: false },
},
},
],
};
expect(getFilterFields(filter).sort()).toEqual(['boolField', 'strField', 'testRelation']);
});

it('should merge all identifiers between root, and, or', () => {
const filter: Filter<Test> = {
or: [{ and: [{ boolField: { is: true } }, { strField: { eq: '' } }] }],
testRelation: {
boolField: { is: false },
},
};
expect(getFilterFields(filter).sort()).toEqual(['boolField', 'strField', 'testRelation']);
});
});
1 change: 1 addition & 0 deletions packages/core/src/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export {
transformFilter,
transformQuery,
transformSort,
getFilterFields,
} from './query.helpers';
17 changes: 17 additions & 0 deletions packages/core/src/helpers/query.helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,21 @@ export const mergeQuery = <T>(base: Query<T>, source: Query<T>): Query<T> => {
return merge(base, source);
};

export const getFilterFields = <DTO>(filter: Filter<DTO>): string[] => {
const fieldSet: Set<string> = Object.keys(filter).reduce((fields: Set<string>, filterField: string): Set<string> => {
if (filterField === 'and' || filterField === 'or') {
const andOrFilters = filter[filterField];
if (andOrFilters !== undefined) {
return andOrFilters.reduce((andOrFields, andOrFilter) => {
return new Set<string>([...andOrFields, ...getFilterFields(andOrFilter)]);
}, fields);
}
} else {
fields.add(filterField);
}
return fields;
}, new Set<string>());
return [...fieldSet];
};

export const applyFilter = <DTO>(dto: DTO, filter: Filter<DTO>): boolean => FilterBuilder.build(filter)(dto);
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export {
NoOpQueryService,
QueryServiceRelation,
} from './services';
export { transformFilter, transformQuery, transformSort, applyFilter, QueryFieldMap } from './helpers';
export { transformFilter, transformQuery, transformSort, applyFilter, getFilterFields, QueryFieldMap } from './helpers';
export {
ClassTransformerAssembler,
DefaultAssembler,
Expand Down
31 changes: 26 additions & 5 deletions packages/core/src/interfaces/filter-field-comparison.interface.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/**
* Field comparisons with a type of `boolean`.
*/
import { Filter } from './filter.interface';

export interface BooleanFieldComparisons {
/**
* Is operator.
Expand Down Expand Up @@ -179,20 +181,39 @@ export interface StringFieldComparisons extends CommonFieldComparisonType<string
notILike?: string;
}

type BuiltInTypes =
| boolean
| boolean
| string
| string
| number
| Date
| RegExp
| bigint
| symbol
| null
| undefined
| never;

/**
* Type for field comparisons.
*
* * `string` - [[StringFieldComparisons]]
* * `boolean|null|undefined|never` - [[BooleanFieldComparisons]]
* * all other types use [[CommonFieldComparisonType]]
*/
export type FilterFieldComparison<FieldType> = FieldType extends string
? StringFieldComparisons
: FieldType extends boolean
// eslint-disable-next-line @typescript-eslint/ban-types
export type FilterFieldComparison<FieldType> = FieldType extends string | String
? StringFieldComparisons // eslint-disable-next-line @typescript-eslint/ban-types
: FieldType extends boolean | Boolean
? BooleanFieldComparisons
: FieldType extends null | undefined | never
? BooleanFieldComparisons
: CommonFieldComparisonType<FieldType>;
? BooleanFieldComparisons // eslint-disable-next-line @typescript-eslint/no-explicit-any
: FieldType extends number | Date | RegExp | bigint | BuiltInTypes[] | symbol
? CommonFieldComparisonType<FieldType>
: FieldType extends Array<infer U>
? CommonFieldComparisonType<U> | Filter<U>
: CommonFieldComparisonType<FieldType> | Filter<FieldType>;

/**
* Type for all comparison operators for a field type.
Expand Down

0 comments on commit cd9d0b5

Please sign in to comment.