Skip to content

Commit

Permalink
fix: in filter with key-config
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim Streicher authored and coronoro committed Nov 16, 2023
1 parent 6c38760 commit 11fec30
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 124 deletions.
16 changes: 8 additions & 8 deletions src/test/conversion-union.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,28 @@ interface Data {
}

test('it should convert a primitive of a union type', () => {
const simpleConfig: FilterSetConfig<Data> = {
const config: FilterSetConfig<Data> = {
number: {lt: 123},
}
const converted = convertFilterSetConfig(simpleConfig)
const converted = convertFilterSetConfig(config)
// eslint-disable-next-line camelcase
expect(converted).toEqual({number__lt: 123})
})

test('it should convert a complex type of a union type', () => {
const special = {internal: 123}
const simpleConfig: FilterSetConfig<Data> = {
const config: FilterSetConfig<Data> = {
number: {value: special},
}
const converted = convertFilterSetConfig(simpleConfig)
const converted = convertFilterSetConfig(config)
expect(converted).toEqual({number: special})
})

test('the config should be able to filter attributes of complex types', () => {
const simpleConfig: FilterSetConfig<Data> = {
number: {internal: {value: 123}},
const config: FilterSetConfig<Data> = {
number: {internal: {lt: 123}},
}
const converted = convertFilterSetConfig(simpleConfig)
const converted = convertFilterSetConfig(config)
// eslint-disable-next-line camelcase
expect(converted).toEqual({number__internal: 123})
expect(converted).toEqual({number__internal__lt: 123})
})
49 changes: 49 additions & 0 deletions src/test/type-basic.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type {FilterSetConfig} from '../types'
import {convertFilterSetConfig} from '../middleware'

interface ComplexData {
id: number
}

interface Data {
complex?: ComplexData
number?: number
text?: string
new?: boolean
}
test('Error when type of attribute is passed without filter', () => {
const simpleConfig: FilterSetConfig<Data> = {
// @ts-expect-error plain passing is not allowed
text: 'string',
}
expect(() => convertFilterSetConfig(simpleConfig)).toThrow('Cannot use \'in\' operator to search for \'value\' in string')
})

test('Error when attribute is not in data type', () => {
const simpleConfig: FilterSetConfig<Data> = {
// @ts-expect-error number is not part of data type
foo: {value: 123},
}
const converted = convertFilterSetConfig(simpleConfig)
expect(converted).toEqual({foo: 123})
})

test('Error when unknown filter is used', () => {
const simpleConfig: FilterSetConfig<Data> = {
// @ts-expect-error foo is not defined as a filter
number: {foo: 123},
}
const converted = convertFilterSetConfig(simpleConfig)
// eslint-disable-next-line camelcase
expect(converted).toEqual({number__foo: 123})
})

test('Error when value has the wrong type', () => {
const simpleConfig: FilterSetConfig<Data> = {
// @ts-expect-error text is of type string
text: {value: 123},
}
const converted = convertFilterSetConfig(simpleConfig)
expect(converted).toEqual({text: 123})
})

60 changes: 60 additions & 0 deletions src/test/type-custom.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type {FilterSetConfig} from '../types'
import {convertFilterSetConfig} from '../middleware'

interface ComplexData {
id: number
}

interface Data {
complex?: ComplexData
number?: number
text?: string
new?: boolean
}

interface FilterSetMappingCustom {
data: 'lt' | 'gt' | 'custom1'
text: 'startswith' | 'exact' | 'custom2'
}

interface CustomFilter {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
custom1: any[]
custom2: number
}

test('error when custom filter key is not allowed', () => {
const config: FilterSetConfig<Data, FilterSetMappingCustom, CustomFilter> = {
text: {
// @ts-expect-error custom filter is not allowed
custom1: '',
},
}
const converted = convertFilterSetConfig(config)
// eslint-disable-next-line camelcase
expect(converted).toEqual({text__custom1: ''})
})

test('error when custom has the wrong type', () => {
const config: FilterSetConfig<Data, FilterSetMappingCustom, CustomFilter> = {
text: {
// @ts-expect-error custom filter has wrong type
custom2: '',
},
}
const converted = convertFilterSetConfig(config)
// eslint-disable-next-line camelcase
expect(converted).toEqual({text__custom2: ''})
})

test('error when custom has the wrong type', () => {
const simpleConfig: FilterSetConfig<Data, FilterSetMappingCustom, CustomFilter> = {
text: {
// @ts-expect-error custom filter has wrong type
custom2: '',
},
}
const converted = convertFilterSetConfig(simpleConfig)
// eslint-disable-next-line camelcase
expect(converted).toEqual({text__custom2: ''})
})
75 changes: 75 additions & 0 deletions src/test/type-key-config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import type {FilterSetConfig} from '../types'
import {convertFilterSetConfig} from '../middleware'

interface ComplexData {
id: number
}

interface Data {
complex?: ComplexData
number?: number
text?: string
new?: boolean
}

interface FilterSetKeyConfig {
complex: {
id: 'in'
}
number: 'lt' | 'gt' | 'in'
text: 'startswith' | 'exact'
}

test('it should accept a KeyConfig', () => {
const numberArray = [1, 2, 3]
const config: FilterSetConfig<Data, FilterSetKeyConfig> = {
number: {
in: numberArray,
lt: 5,
},
}
const converted = convertFilterSetConfig(config)
// eslint-disable-next-line camelcase
expect(converted).toEqual({number__in: numberArray, number__lt: 5})
})

test('error when filter key is disallowed in mapping', () => {
const numberData = {id: 123}
const config: FilterSetConfig<Data, FilterSetKeyConfig> = {
number: {
// @ts-expect-error lte is not allowed
lte: numberData,
},
}
const converted = convertFilterSetConfig(config)
// eslint-disable-next-line camelcase
expect(converted).toEqual({number__lte: numberData})
})

test('should handle nested key configs', () => {
const numberArray = [1, 2, 3]
const config: FilterSetConfig<Data, FilterSetKeyConfig> = {
complex: {
id: {
in: numberArray,
},
},
}
const converted = convertFilterSetConfig(config)
// eslint-disable-next-line camelcase
expect(converted).toEqual({complex__id__in: numberArray})
})

test('error when filter key is disallowed in nested mapping', () => {
const config: FilterSetConfig<Data, FilterSetKeyConfig> = {
complex: {
id: {
// @ts-expect-error lt is not allowed for nested property
lt: 1,
},
},
}
const converted = convertFilterSetConfig(config)
// eslint-disable-next-line camelcase
expect(converted).toEqual({complex__id__lt: 1})
})
114 changes: 0 additions & 114 deletions src/test/type.test.ts

This file was deleted.

10 changes: 8 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,14 @@ export type CustomKeyConfig = Record<any, any>
}

type AllowedFSKeys<D, K extends FSKeyConfig<D>, key extends keyof D, C extends CustomKeyConfig | null> =
// we exclude the types from the custom config because they are defined there. If not they might allow wrong types
(Exclude<K[key], keyof C> extends (string | number | symbol) ? Partial<Record<Exclude<K[key], keyof C>, D[key]>> : never)
// we exclude the types from the custom config because they are defined there. If not they might allow wrong types.
(Exclude<K[key], keyof C> extends (string | number | symbol) ?
Partial<
Record< // record for allowed keys
Exclude<K[key], keyof C>, // exclude keys that are not part of the FSKeyConfig
FilterSet<D[key]>[K[key]]> // The type needs to be looked up in the FilterSet as in is not simply D[key]
>
: never)

type ConfiguredCustomKeys<D, K extends FSKeyConfig<D>, key extends keyof D, C extends CustomKeyConfig> =
Extract<keyof C, K[key] extends (string | number | symbol) ? K[key] : never>
Expand Down

0 comments on commit 11fec30

Please sign in to comment.