Skip to content

Commit 90c5965

Browse files
author
Tim Streicher
committed
feat: add filterhandlers and object ref
1 parent 3ff49df commit 90c5965

File tree

3 files changed

+207
-48
lines changed

3 files changed

+207
-48
lines changed

src/middleware.ts

+66-25
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,84 @@
1-
import type {Axios, AxiosInstance} from 'axios'
2-
import type {DRFAxiosConfig, FilterSetConfig} from './types.js'
1+
import type {AxiosInstance} from 'axios'
2+
import type {CustomKeyConfig, DRFAxiosConfig, FSKeyConfig, Filter, FilterHandler, FilterSetConfig} from './types'
3+
import {DRFFilters} from './types'
34

4-
function addFilterKey(object: unknown, key: string, value: any) {
5-
// @ts-expect-error we don't know the type at this point
5+
/**
6+
* The default config
7+
*/
8+
const defaultConfig: DRFAxiosConfig = {
9+
filterKey: 'filterSet',
10+
11+
filterHandlers: undefined,
12+
13+
}
14+
15+
/**
16+
*Convenience method to set a filter key.
17+
*
18+
* @param object
19+
* @param key
20+
* @param value
21+
*/
22+
function addFilterKey(object: Record<string, unknown>, key: string, value: unknown) {
623
object[key] = value
724
}
825

926
/**
1027
*
11-
* @param config
28+
* @param key
29+
* @param data
30+
* @param filterHandlers
1231
*/
13-
export function convertFilterSetConfig(config: FilterSetConfig<any, any, any>) {
14-
const conversion = {}
15-
for (const key in config) {
16-
const entry = config[key]
17-
if ('value' in entry) { // plain filter
18-
addFilterKey(conversion, key, entry.value)
32+
function parseFilters(key: string, data: Record<string, any>, filterHandlers?: Record<string, FilterHandler>): Array<Filter> {
33+
const filters: Array<Filter> = []
34+
for (const filterSuffix in data) {
35+
if ('value' in data) { // plain filter
36+
filters.push({key, value: data.value})
1937
}
20-
else { // custom or complex filter
21-
for (const filterSuffix in entry) {
38+
else if (filterHandlers && (filterSuffix in filterHandlers)) {
39+
// user set a custom handler for this key, so we give him the data and expect him to return a valid filter.
40+
filterHandlers[filterSuffix](filterSuffix, data).forEach((filter) => {
41+
filters.push({key: `${key}__${filter.key}`, value: filter.value})
42+
})
43+
}
44+
else {
45+
const filterValue = data[filterSuffix]
46+
if (
47+
!DRFFilters.includes(filterSuffix) // if the filter is part of the drf-filters we can just append it as we know how to handle these
48+
&& !Array.isArray(filterValue) // arrays are objects as well, but we don't want to go deeper into them as we know how to handle these
49+
&& typeof filterValue === 'object') {
50+
// got some more parsing to do
51+
parseFilters(filterSuffix, filterValue as Record<string, unknown>, filterHandlers).forEach((filter) => {
52+
filters.push({key: `${key}__${filter.key}`, value: filter.value})
53+
})
54+
}
55+
else {
56+
// no complex objects just plain filters
2257
const filterKey = `${key}__${filterSuffix}`
23-
// @ts-expect-error can be any type
24-
const filterValue = entry[filterSuffix]
25-
addFilterKey(conversion, filterKey, filterValue)
58+
filters.push({key: filterKey, value: filterValue})
2659
}
2760
}
2861
}
29-
return conversion
62+
return filters
3063
}
3164

3265
/**
33-
* The default config
66+
*
67+
* @param config
68+
* @param filterSetHandlers
3469
*/
35-
const defaultConfig: DRFAxiosConfig = {
36-
filterKey: 'filterSet',
37-
70+
export function convertFilterSetConfig<D, K extends FSKeyConfig<D> | null, C extends CustomKeyConfig | null>(
71+
config: FilterSetConfig<D, K, C>,
72+
filterSetHandlers?: Record<string, FilterHandler>,
73+
) {
74+
const conversion = {}
75+
for (const key in config) {
76+
const entry = config[key]
77+
parseFilters(key, entry, filterSetHandlers).forEach((filter) => {
78+
addFilterKey(conversion, filter.key, filter.value)
79+
})
80+
}
81+
return conversion
3882
}
3983

4084
export const applyDRFInterceptor = (axios: AxiosInstance, options: DRFAxiosConfig = defaultConfig) => {
@@ -48,14 +92,11 @@ export const applyDRFInterceptor = (axios: AxiosInstance, options: DRFAxiosConfi
4892
const filterSet = params[key]
4993
if (filterSet) {
5094
delete params[key]
51-
const filterSetParams = convertFilterSetConfig(filterSet as FilterSetConfig<any>)
95+
const filterSetParams = convertFilterSetConfig(filterSet as FilterSetConfig, options.filterHandlers)
5296
config.params = {...params, ...filterSetParams}
5397
break
5498
}
5599
}
56-
else {
57-
// TODO if there is no filter key then we assume that the FilterSetConfig is given per request as an additional parameter to the config
58-
}
59100
}
60101
}
61102
return config
+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import {convertFilterSetConfig} from '../middleware'
2+
import type {FilterSetConfig} from '../types'
3+
4+
interface NumberData {
5+
number: number
6+
noNumber?: Date
7+
}
8+
9+
interface Data {
10+
data: NumberData
11+
text?: string
12+
new?: boolean
13+
}
14+
15+
test('it should convert objects with standard filters', () => {
16+
const numberData = {number: 123}
17+
const simpleConfig: FilterSetConfig<Data> = {
18+
data: {
19+
lt: numberData,
20+
},
21+
}
22+
const converted = convertFilterSetConfig(simpleConfig)
23+
// eslint-disable-next-line camelcase
24+
expect(converted).toEqual({data__lt: numberData})
25+
})
26+
27+
test('it should convert objects with value filters', () => {
28+
const date = new Date()
29+
const simpleConfig: FilterSetConfig<Data> = {
30+
data: {
31+
number: {value: 123},
32+
noNumber: {value: date},
33+
},
34+
}
35+
const converted = convertFilterSetConfig(simpleConfig)
36+
// eslint-disable-next-line camelcase
37+
expect(converted).toEqual({data__number: 123, data__noNumber: date})
38+
})
39+
40+
test('it should convert complex objects with filters', () => {
41+
const simpleConfig: FilterSetConfig<Data> = {
42+
data: {
43+
number: {lt: 123},
44+
},
45+
}
46+
const converted = convertFilterSetConfig(simpleConfig)
47+
// eslint-disable-next-line camelcase
48+
expect(converted).toEqual({data__number__lt: 123})
49+
})
50+
51+
interface FilterSetMapping {
52+
data: 'lt' | 'gt' | 'custom1'
53+
text: 'lt' | 'exact'
54+
}
55+
56+
test('it should convert objects with standard filters and a Mapping', () => {
57+
const numberData = {number: 123}
58+
const simpleConfig: FilterSetConfig<Data, FilterSetMapping> = {
59+
data: {
60+
number: {custom1: 123},
61+
},
62+
}
63+
const converted = convertFilterSetConfig(simpleConfig)
64+
// eslint-disable-next-line camelcase
65+
expect(converted).toEqual({data__number__lte: numberData})
66+
})
67+
68+
test('it should convert complex objects with standard filters', () => {
69+
const simpleConfig: FilterSetConfig<Data> = {
70+
data: {number: {lt: 123}},
71+
}
72+
const converted = convertFilterSetConfig(simpleConfig)
73+
// eslint-disable-next-line camelcase
74+
expect(converted).toEqual({data__number__lt: 123, text: 'string'})
75+
})
76+

src/types.ts

+65-23
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,84 @@
1-
export interface FilterSetExact<F> {
1+
export const DRFFilters = ['in', 'exact', 'lt', 'gt', 'lte', 'gte', 'startswith', 'endswith']
2+
3+
/**
4+
*
5+
*/
6+
interface NotIn {
7+
in?: never
8+
}
9+
10+
interface NotExact {
11+
exact?: never
12+
}
13+
14+
export interface FilterSetExact<F> extends NotIn {
215
exact: F
316
startswith?: never
17+
endswith?: never
418
lte?: never
519
gte?: never
620
lt?: never
721
gt?: never
822
}
923

10-
export interface FilterSetStartsWith<F> {
11-
exact?: never
12-
startswith?: F
24+
export interface FilterSetIn<F> extends NotExact {
25+
startswith?: never
26+
endswith?: never
27+
in: Array<F>
1328
lte?: never
1429
gte?: never
1530
lt?: never
1631
gt?: never
1732
}
1833

34+
export interface FilterSetAffix<F> extends NotExact, NotIn {
35+
startswith?: F
36+
endswith?: F
37+
}
38+
1939
type FilterSetRange<T> =
2040
(FilterSetRangeLT<T> & FilterSetRangeGTE<T>) | (FilterSetRangeLT<T> & FilterSetRangeGT<T>)
2141
| (FilterSetRangeLTE<T> & FilterSetRangeGTE<T>) | (FilterSetRangeLTE<T> & FilterSetRangeGT<T>)
2242
| (FilterSetRangeGT<T> & FilterSetRangeLTE<T>) | (FilterSetRangeGT<T> & FilterSetRangeLT<T>)
2343
| (FilterSetRangeGTE<T> & FilterSetRangeLTE<T>) | (FilterSetRangeGTE<T> & FilterSetRangeLT<T>)
2444

25-
interface NotExact {
26-
exact?: never
27-
}
28-
29-
interface NotStartsWith {
30-
startswith?: never
31-
}
32-
33-
export interface FilterSetRangeLT<T> extends NotExact, NotStartsWith {
45+
export interface FilterSetRangeLT<T> extends FilterSetAffix<T> {
3446
lte?: never
3547
lt?: T
3648
}
3749

38-
export interface FilterSetRangeLTE<T> extends NotExact, NotStartsWith {
50+
export interface FilterSetRangeLTE<T> extends FilterSetAffix<T> {
3951
lte?: T
4052
lt?: never
4153
}
4254

43-
export interface FilterSetRangeGT<T> extends NotExact, NotStartsWith {
55+
export interface FilterSetRangeGT<T> extends FilterSetAffix<T> {
4456
gte?: never
4557
gt?: T
4658
}
4759

48-
export interface FilterSetRangeGTE<T> extends NotExact, NotStartsWith {
60+
export interface FilterSetRangeGTE<T> extends FilterSetAffix<T> {
4961
gte?: T
5062
gt?: never
5163

5264
}
5365

54-
type FilterSet<T> = FilterSetRange<T> | FilterSetExact<T> | FilterSetStartsWith<T>
66+
/**
67+
* all FilterSets
68+
*/
69+
type FilterSet<T> = FilterSetRange<T> | FilterSetExact<T> | FilterSetAffix<T> | FilterSetIn<T>
5570

5671
// Config to exclude certain filters and enable custom filters
57-
type FSKeyConfig<D> = Partial<Record<keyof D, string>>
72+
export type FSKeyConfig<D> = Partial<Record<keyof D, string>>
5873

59-
type CustomKeyConfig = {[key: string]: any}
74+
export type CustomKeyConfig = {[key: string]: unknown}
6075
& {
6176
[key in keyof FilterSet<unknown>]?: never
6277
}
6378

64-
type AllowedFSKeys<D, K extends FSKeyConfig<D>, key extends keyof D> =
65-
K[key] extends (string | number | symbol) ? Partial<Record<K[key], D[key]>> : never
79+
type AllowedFSKeys<D, K extends FSKeyConfig<D>, key extends keyof D, C extends CustomKeyConfig | null> =
80+
// we exclude the types from the custom config because they are defined there. If not they might allow wrong types
81+
(Exclude<K[key], keyof C> extends (string | number | symbol) ? Partial<Record<Exclude<K[key], keyof C>, D[key]>> : never)
6682

6783
type ConfiguredCustomKeys<D, K extends FSKeyConfig<D>, key extends keyof D, C extends CustomKeyConfig> =
6884
Extract<keyof C, K[key] extends (string | number | symbol) ? K[key] : never>
@@ -73,7 +89,7 @@ type CustomKey<D, K extends FSKeyConfig<D>, key extends keyof D, C extends Custo
7389
ConfiguredCustomKeys<D, K, key, C> //
7490
, C[ConfiguredCustomKeys<D, K, key, C>]
7591
>
76-
>
92+
>
7793

7894
type CheckCustomKeys<D, K extends FSKeyConfig<D>, key extends keyof D, C extends CustomKeyConfig | null> =
7995
C extends null ?
@@ -88,7 +104,7 @@ type CheckConfigKeys<D, K extends FSKeyConfig<D>, key extends keyof D, C extends
88104
key extends keyof K ? // check if there is a FSKeyConfig
89105
(
90106
CheckCustomKeys<D, K, key, C>
91-
| AllowedFSKeys<D, K, key> // every key that is defined in the config is allowed
107+
| AllowedFSKeys<D, K, key, C> // every key that is defined in the config is allowed
92108
)
93109
: FilterSet<D[key]> // no config for the key so we take the default combinations
94110

@@ -99,9 +115,30 @@ export type FilterSetConfig<D = Record<any, any>, K extends FSKeyConfig<D> | nul
99115
K extends null ? // check if we have a config
100116
FilterSet<D[key]> // no config so we take the default combinations for each key
101117
: CheckConfigKeys<D, Exclude<K, null>, key, C> // check if key is inside the config
118+
) | (
119+
D[key] extends Record<any, any> ? // check if the type of the key is a Record, so we add extended references
120+
FilterSetConfig<D[key]>
121+
: never // no types added otherwise
102122
)
103123
}
104124

125+
/**
126+
* Internal Representation of a Filter
127+
*/
128+
export interface Filter {
129+
key: string
130+
value: unknown
131+
}
132+
133+
/**
134+
* @param key: the defined key
135+
* @param data: the enclosing data containing the data of the key as well as other data on the same hierarchy
136+
*/
137+
export type FilterHandler = (key: string, data: unknown) => Array<Filter>
138+
139+
/**
140+
*
141+
*/
105142
export interface DRFAxiosConfig {
106143

107144
/** The name of the key where one can put their FilterSetConfig under.
@@ -110,4 +147,9 @@ export interface DRFAxiosConfig {
110147
*/
111148
filterKey: string
112149

150+
/**
151+
* A Record which contains for a filter the custom filter handler
152+
*/
153+
filterHandlers?: Record<string, FilterHandler>
154+
113155
}

0 commit comments

Comments
 (0)