Skip to content

Commit

Permalink
feat: add filter validate option
Browse files Browse the repository at this point in the history
  • Loading branch information
tada5hi committed Oct 21, 2022
1 parent edd44eb commit 882a5dc
Show file tree
Hide file tree
Showing 15 changed files with 168 additions and 101 deletions.
6 changes: 3 additions & 3 deletions src/parameter/fields/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from '../../type';
import { RelationsParseOutput } from '../relations';
import {
ParseOptionsAllowed,
ParseAllowedKeys,
} from '../type';
import { FieldOperator } from './constants';

Expand Down Expand Up @@ -48,8 +48,8 @@ export type FieldsParseOptions<
T extends Record<string, any> = Record<string, any>,
> = {
mapping?: Record<string, string>,
allowed?: ParseOptionsAllowed<T>,
default?: ParseOptionsAllowed<T>,
allowed?: ParseAllowedKeys<T>,
default?: ParseAllowedKeys<T>,
defaultPath?: string,
relations?: RelationsParseOutput,
};
Expand Down
4 changes: 2 additions & 2 deletions src/parameter/filters/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { merge } from 'smob';
import { ObjectLiteral } from '../../type';
import { FiltersBuildInput } from './type';
import { FilterOperator } from './constants';
import { isFilterOperatorConfig } from './utils';
import { isFilterValueConfig } from './utils';
import { flattenNestedObject } from '../../utils';

const OperatorWeight = {
Expand Down Expand Up @@ -44,7 +44,7 @@ export function buildQueryFilters<T extends ObjectLiteral = ObjectLiteral>(
return true;
}

if (isFilterOperatorConfig(input)) {
if (isFilterValueConfig(input)) {
if (typeof input.value === 'undefined') {
input.value = null;
}
Expand Down
1 change: 1 addition & 0 deletions src/parameter/filters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from './constants';
export * from './build';
export * from './parse';
export * from './type';
export * from './utils';
42 changes: 25 additions & 17 deletions src/parameter/filters/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* view the LICENSE file that was distributed with this source code.
*/

import { ObjectLiteral } from '../../type';
import { NestedKeys, ObjectLiteral } from '../../type';
import {
FieldDetails,
applyMapping,
Expand All @@ -14,7 +14,7 @@ import {
getFieldDetails,
hasOwnProperty, isFieldNonRelational, isFieldPathAllowedByRelations,
} from '../../utils';
import { isPathCoveredByParseOptionsAllowed } from '../utils';
import { isPathCoveredByParseAllowed } from '../utils';
import { FiltersParseOptions, FiltersParseOutput, FiltersParseOutputElement } from './type';
import { determineFilterOperatorLabelsByValue, transformFilterValue } from './utils';

Expand Down Expand Up @@ -95,6 +95,7 @@ function buildDefaultFiltersParseOutput<T extends ObjectLiteral = ObjectLiteral>
} else if (options.defaultPath) {
path = options.defaultPath;
}

output.push(transformFiltersParseOutputElement({
...(path ? { path } : {}),
key: fieldDetails.name,
Expand Down Expand Up @@ -139,7 +140,7 @@ export function parseQueryFilters<T extends ObjectLiteral = ObjectLiteral>(
);
}

const temp : Record<string, FiltersParseOutputElement> = {};
const items : Record<string, FiltersParseOutputElement> = {};

// transform to appreciate data format & validate input
const keys = Object.keys(data);
Expand Down Expand Up @@ -186,26 +187,33 @@ export function parseQueryFilters<T extends ObjectLiteral = ObjectLiteral>(

if (
options.allowed &&
!isPathCoveredByParseOptionsAllowed(options.allowed, [keys[i], fullKey])
!isPathCoveredByParseAllowed(options.allowed, [keys[i], fullKey])
) {
continue;
}

let path : string | undefined;
if (fieldDetails.path) {
path = fieldDetails.path;
} else if (options.defaultPath) {
path = options.defaultPath;
}

temp[fullKey] = {
...(path ? { path } : {}),
const filter = transformFiltersParseOutputElement({
key: fieldDetails.name,
value: value as string | boolean | number,
};
});

if (options.validate) {
if (Array.isArray(filter.value)) {
filter.value = (filter.value as any[]).filter((item: unknown) => options.validate(filter.key as NestedKeys<T>, item));
if (filter.value.length === 0) {
continue;
}
} else if (!options.validate(filter.key as NestedKeys<T>, filter.value)) {
continue;
}
}

if (fieldDetails.path || options.defaultPath) {
filter.path = fieldDetails.path || options.defaultPath;
}

items[fullKey] = filter;
}

return transformFiltersParseOutput(
buildDefaultFiltersParseOutput(options, temp),
);
return buildDefaultFiltersParseOutput(options, items);
}
27 changes: 15 additions & 12 deletions src/parameter/filters/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

import {
Flatten,
NestedKeys, OnlyObject, OnlyScalar, TypeFromNestedKeyPath,
NestedKeys, ObjectLiteral, OnlyObject, OnlyScalar, TypeFromNestedKeyPath,
} from '../../type';
import { RelationsParseOutput } from '../relations';
import {
ParseOptionsAllowed,
ParseAllowedKeys,
} from '../type';
import { FilterOperator, FilterOperatorLabel } from './constants';

Expand All @@ -20,16 +20,16 @@ import { FilterOperator, FilterOperatorLabel } from './constants';
type FilterValueInputPrimitive = boolean | number | string;
type FilterValueInput = FilterValueInputPrimitive | null | undefined;

export type FilterValueSimple<V extends FilterValueInput = FilterValueInput> = V extends FilterValueInputPrimitive ? (V | V[]) : V;
export type FilterValueSimple<V extends FilterValueInput = FilterValueInput> = V extends string | number ? (V | V[]) : V;
export type FilterValueWithOperator<V extends FilterValueInput = FilterValueInput> = V extends string | number ?
`!${V}` | `!~${V}` | `~${V}` | `${V}~` | `~${V}~` | `<${V}` | `<=${V}` | `>${V}` | `>=${V}` | null | '!null' :
V extends boolean ? null | '!null' : never;

export type FilterValue<V extends FilterValueInput = FilterValueInput> = V extends FilterValueInputPrimitive ?
export type FilterValue<V extends FilterValueInput = FilterValueInput> = V extends string | number ?
(FilterValueSimple<V> | FilterValueWithOperator<V> | Array<FilterValueWithOperator<V>>) :
V;

export type FilterOperatorConfig<V extends FilterValueInput = FilterValueInput> = {
export type FilterValueConfig<V extends FilterValueInput = FilterValueInput> = {
operator: `${FilterOperator}` | (`${FilterOperator}`)[];
value: FilterValueSimple<V>
};
Expand All @@ -39,7 +39,7 @@ export type FilterOperatorConfig<V extends FilterValueInput = FilterValueInput>
// -----------------------------------------------------------

export type FiltersBuildInputValue<T> = T extends OnlyScalar<T> ?
T | FilterValue<T> | FilterOperatorConfig<T> :
T | FilterValue<T> | FilterValueConfig<T> :
never;

export type FiltersBuildInput<T extends Record<string, any>> = {
Expand All @@ -54,23 +54,26 @@ export type FiltersBuildInput<T extends Record<string, any>> = {
// Parse
// -----------------------------------------------------------

export type FiltersParseOptionsDefault<T extends Record<string, any>> = {
export type FiltersDefaultKeys<T extends Record<string, any>> = {
[K in keyof T]?: Flatten<T[K]> extends OnlyObject<T[K]> ?
FiltersParseOptionsDefault<Flatten<T[K]>> :
FiltersDefaultKeys<Flatten<T[K]>> :
(K extends string ? FilterValue<TypeFromNestedKeyPath<T, K>> : never)
} | {
[K in NestedKeys<T>]?: FilterValue<TypeFromNestedKeyPath<T, K>>
};

export type FiltersValidator<K extends string> = (key: K, value: unknown) => boolean;

export type FiltersParseOptions<
T extends Record<string, any> = Record<string, any>,
T extends ObjectLiteral = ObjectLiteral,
> = {
mapping?: Record<string, string>,
allowed?: ParseOptionsAllowed<T>,
default?: FiltersParseOptionsDefault<T>,
allowed?: ParseAllowedKeys<T>,
default?: FiltersDefaultKeys<T>,
defaultByElement?: boolean,
defaultPath?: string,
relations?: RelationsParseOutput
relations?: RelationsParseOutput,
validate?: FiltersValidator<NestedKeys<T>>
};

export type FiltersParseOutputElement = {
Expand Down
4 changes: 2 additions & 2 deletions src/parameter/filters/utils/operator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import {
FilterOperatorConfig,
FilterValueConfig,
} from '../type';
import { hasOwnProperty, isSimpleValue } from '../../../utils';
import { FilterOperator, FilterOperatorLabel } from '../constants';
Expand Down Expand Up @@ -67,7 +67,7 @@ export function determineFilterOperatorLabelsByValue(input: string) : {
};
}

export function isFilterOperatorConfig(data: unknown) : data is FilterOperatorConfig<any> {
export function isFilterValueConfig(data: unknown) : data is FilterValueConfig<any> {
if (typeof data !== 'object' || data === null) {
return false;
}
Expand Down
8 changes: 4 additions & 4 deletions src/parameter/filters/utils/value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@
* view the LICENSE file that was distributed with this source code.
*/

import { FilterValueSimple } from '../type';
import { FilterValue } from '../type';

export function transformFilterValue<T>(input: FilterValueSimple) : FilterValueSimple {
export function transformFilterValue(input: FilterValue) : FilterValue {
if (Array.isArray(input)) {
for (let i = 0; i < input.length; i++) {
input[i] = transformFilterValue(input[i]) as string | number | boolean;
input[i] = transformFilterValue(input[i]) as string | number;
}

return (input as unknown[])
.filter((n) => n === 0 || !!n) as FilterValueSimple;
.filter((n) => n === 0 || !!n) as FilterValue;
}

if (typeof input === 'undefined' || input === null) {
Expand Down
4 changes: 2 additions & 2 deletions src/parameter/relations/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import minimatch from 'minimatch';
import { ObjectLiteral } from '../../type';
import { applyMapping, hasOwnProperty } from '../../utils';
import { isPathCoveredByParseOptionsAllowed } from '../utils';
import { isPathCoveredByParseAllowed } from '../utils';

import { RelationsParseOptions, RelationsParseOutput } from './type';
import { includeParents } from './utils';
Expand Down Expand Up @@ -60,7 +60,7 @@ export function parseQueryRelations<T extends ObjectLiteral = ObjectLiteral>(
}

if (options.allowed) {
items = items.filter((item) => isPathCoveredByParseOptionsAllowed(options.allowed, item));
items = items.filter((item) => isPathCoveredByParseAllowed(options.allowed, item));
}

if (options.includeParents) {
Expand Down
4 changes: 2 additions & 2 deletions src/parameter/sort/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
hasOwnProperty, isFieldNonRelational,
isFieldPathAllowedByRelations,
} from '../../utils';
import { flattenParseOptionsAllowed, isPathCoveredByParseOptionsAllowed } from '../utils';
import { flattenParseOptionsAllowed, isPathCoveredByParseAllowed } from '../utils';

import {
SortParseOptions,
Expand Down Expand Up @@ -149,7 +149,7 @@ export function parseQuerySort<T extends ObjectLiteral = ObjectLiteral>(
if (
typeof options.allowed !== 'undefined' &&
!isMultiDimensionalArray(options.allowed) &&
!isPathCoveredByParseOptionsAllowed(options.allowed, [key, keyWithAlias])
!isPathCoveredByParseAllowed(options.allowed, [key, keyWithAlias])
) {
continue;
}
Expand Down
12 changes: 6 additions & 6 deletions src/parameter/sort/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from '../../type';
import { RelationsParseOutput } from '../relations';
import {
ParseOptionsAllowed,
ParseAllowedKeys,
} from '../type';

export enum SortDirection {
Expand All @@ -31,14 +31,14 @@ export type SortBuildInput<T extends Record<string, any>> =
`${SortDirection}`
}
|
[
SortWithOperator<SimpleKeys<T>>[],
(
SortWithOperator<SimpleKeys<T>>[] |
{
[K in keyof T]?: Flatten<T[K]> extends OnlyObject<T[K]> ?
SortBuildInput<Flatten<T[K]>> :
`${SortDirection}`
},
]
}
)[]
|
SortWithOperator<NestedKeys<T>>[] |
SortWithOperator<NestedKeys<T>>;
Expand All @@ -58,7 +58,7 @@ export type SortParseOptionsDefault<T extends Record<string, any>> = {
export type SortParseOptions<
T extends Record<string, any> = Record<string, any>,
> = {
allowed?: ParseOptionsAllowed<T> | ParseOptionsAllowed<T>[],
allowed?: ParseAllowedKeys<T>,
mapping?: Record<string, string>,
default?: SortParseOptionsDefault<T>,
defaultPath?: string,
Expand Down
23 changes: 13 additions & 10 deletions src/parameter/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,24 @@
*/

import {
Flatten, NestedKeys, OnlyObject, SimpleKeys,
Flatten, NestedKeys, ObjectLiteral, OnlyObject, SimpleKeys,
} from '../type';

// -----------------------------------------------------------

export type ParseOptionsAllowedObject<T extends Record<string, any>> = {
type ParseAllowedObject<T extends ObjectLiteral = ObjectLiteral> = {
[K in keyof T]?: T[K] extends OnlyObject<T[K]> ?
ParseOptionsAllowed<Flatten<T[K]>> :
ParseAllowedKeys<Flatten<T[K]>> :
never
};

export type ParseOptionsAllowed<T extends Record<string, any>> = ParseOptionsAllowedObject<T> |
[
SimpleKeys<T>[],
ParseOptionsAllowedObject<T>,
]
|
NestedKeys<T>[];
export type ParseAllowedKeys<T extends ObjectLiteral = ObjectLiteral> = T extends ObjectLiteral ?
(
ParseAllowedObject<T> |
(
SimpleKeys<T>[] |
ParseAllowedObject<T>
)[]
|
NestedKeys<T>[]
) : string[];
11 changes: 7 additions & 4 deletions src/parameter/utils/parse/options-allowed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@
* view the LICENSE file that was distributed with this source code.
*/

import { NestedKeys, NestedResourceKeys } from '../../../type';
import { flattenToKeyPathArray } from '../../../utils';
import { ParseOptionsAllowed } from '../../type';
import { ParseAllowedKeys } from '../../type';

export function flattenParseOptionsAllowed<T>(
input: ParseOptionsAllowed<T>,
input: ParseAllowedKeys<T>,
) : string[] {
return flattenToKeyPathArray(input);
}

export function isPathCoveredByParseOptionsAllowed<T>(
input: ParseOptionsAllowed<T>,
export function isPathCoveredByParseAllowed<T>(
input: ParseAllowedKeys<T> |
NestedKeys<T>[] |
NestedResourceKeys<T>[],
path: string | string[],
) : boolean {
const paths = Array.isArray(path) ? path : [path];
Expand Down
Loading

0 comments on commit 882a5dc

Please sign in to comment.