From cd9d0b524c1f4c384dc9e5ac6baeb5a49bc068e7 Mon Sep 17 00:00:00 2001 From: doug-martin Date: Mon, 29 Jun 2020 21:21:51 -0500 Subject: [PATCH] feat(core): Add type support for nest objects in filter --- packages/core/__tests__/helpers.spec.ts | 62 +++++++++++++++++++ packages/core/src/helpers/index.ts | 1 + packages/core/src/helpers/query.helpers.ts | 17 +++++ packages/core/src/index.ts | 2 +- .../filter-field-comparison.interface.ts | 31 ++++++++-- 5 files changed, 107 insertions(+), 6 deletions(-) diff --git a/packages/core/__tests__/helpers.spec.ts b/packages/core/__tests__/helpers.spec.ts index e0cc5b5e0..0c911b0bd 100644 --- a/packages/core/__tests__/helpers.spec.ts +++ b/packages/core/__tests__/helpers.spec.ts @@ -9,6 +9,7 @@ import { transformQuery, transformSort, } from '../src'; +import { getFilterFields } from '../src/helpers/query.helpers'; class TestDTO { first!: string; @@ -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 = { + 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 = { + 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 = { + 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 = { + or: [{ and: [{ boolField: { is: true } }, { strField: { eq: '' } }] }], + testRelation: { + boolField: { is: false }, + }, + }; + expect(getFilterFields(filter).sort()).toEqual(['boolField', 'strField', 'testRelation']); + }); +}); diff --git a/packages/core/src/helpers/index.ts b/packages/core/src/helpers/index.ts index fc517a6e1..cba7ad9e8 100644 --- a/packages/core/src/helpers/index.ts +++ b/packages/core/src/helpers/index.ts @@ -5,4 +5,5 @@ export { transformFilter, transformQuery, transformSort, + getFilterFields, } from './query.helpers'; diff --git a/packages/core/src/helpers/query.helpers.ts b/packages/core/src/helpers/query.helpers.ts index 34ba37b0a..649ccc109 100644 --- a/packages/core/src/helpers/query.helpers.ts +++ b/packages/core/src/helpers/query.helpers.ts @@ -54,4 +54,21 @@ export const mergeQuery = (base: Query, source: Query): Query => { return merge(base, source); }; +export const getFilterFields = (filter: Filter): string[] => { + const fieldSet: Set = Object.keys(filter).reduce((fields: Set, filterField: string): Set => { + if (filterField === 'and' || filterField === 'or') { + const andOrFilters = filter[filterField]; + if (andOrFilters !== undefined) { + return andOrFilters.reduce((andOrFields, andOrFilter) => { + return new Set([...andOrFields, ...getFilterFields(andOrFilter)]); + }, fields); + } + } else { + fields.add(filterField); + } + return fields; + }, new Set()); + return [...fieldSet]; +}; + export const applyFilter = (dto: DTO, filter: Filter): boolean => FilterBuilder.build(filter)(dto); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index b0c9d8ff1..fcda8b272 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -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, diff --git a/packages/core/src/interfaces/filter-field-comparison.interface.ts b/packages/core/src/interfaces/filter-field-comparison.interface.ts index 45a96f506..0fcde3757 100644 --- a/packages/core/src/interfaces/filter-field-comparison.interface.ts +++ b/packages/core/src/interfaces/filter-field-comparison.interface.ts @@ -1,6 +1,8 @@ /** * Field comparisons with a type of `boolean`. */ +import { Filter } from './filter.interface'; + export interface BooleanFieldComparisons { /** * Is operator. @@ -179,6 +181,20 @@ export interface StringFieldComparisons extends CommonFieldComparisonType = FieldType extends string - ? StringFieldComparisons - : FieldType extends boolean +// eslint-disable-next-line @typescript-eslint/ban-types +export type FilterFieldComparison = 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; + ? BooleanFieldComparisons // eslint-disable-next-line @typescript-eslint/no-explicit-any + : FieldType extends number | Date | RegExp | bigint | BuiltInTypes[] | symbol + ? CommonFieldComparisonType + : FieldType extends Array + ? CommonFieldComparisonType | Filter + : CommonFieldComparisonType | Filter; /** * Type for all comparison operators for a field type.