From 2cc8b4df6dcaf1ee8feb35037a2def1426c46193 Mon Sep 17 00:00:00 2001 From: tada5hi Date: Tue, 14 Feb 2023 18:10:38 +0100 Subject: [PATCH] feat: enhance parsing of parameters --- src/build/module.ts | 5 +-- src/build/parameter/type.ts | 2 +- src/parameter/fields/parse.ts | 41 ++++++++------------ src/parameter/fields/utils/input.ts | 43 +++++++++------------ src/parameter/filters/build.ts | 2 +- src/parameter/filters/parse.ts | 2 +- src/parameter/pagination/build.ts | 9 ++--- src/parameter/pagination/parse.ts | 4 +- src/parameter/pagination/type.ts | 2 +- src/parameter/relations/build.ts | 2 +- src/parameter/relations/parse.ts | 30 ++++++-------- src/parameter/sort/parse.ts | 13 +++---- src/parameter/utils/parse/allowed-option.ts | 12 ++++-- src/parse/module.ts | 4 +- src/parse/parameter/module.ts | 4 +- src/parse/parameter/utils.ts | 8 ++-- src/utils/array.ts | 2 +- src/utils/field.ts | 2 +- src/utils/mapping.ts | 6 ++- tsconfig.json | 28 +++----------- 20 files changed, 96 insertions(+), 125 deletions(-) diff --git a/src/build/module.ts b/src/build/module.ts index d08f568b..f8080b25 100644 --- a/src/build/module.ts +++ b/src/build/module.ts @@ -26,10 +26,7 @@ import { export function buildQuery( input?: BuildInput, ) : string { - if ( - typeof input === 'undefined' || - input === null - ) { + if (!input) { return ''; } diff --git a/src/build/parameter/type.ts b/src/build/parameter/type.ts index 6068de2a..c614229e 100644 --- a/src/build/parameter/type.ts +++ b/src/build/parameter/type.ts @@ -29,7 +29,7 @@ export type BuildParameterInput< P extends `${Parameter.RELATIONS}` | `${URLParameter.RELATIONS}` ? RelationsBuildInput : P extends `${Parameter.PAGINATION}` | `${URLParameter.PAGINATION}` ? - PaginationBuildInput : + PaginationBuildInput : P extends `${Parameter.SORT}` | `${URLParameter.SORT}` ? SortBuildInput : never; diff --git a/src/parameter/fields/parse.ts b/src/parameter/fields/parse.ts index 933d153d..a5a3cd03 100644 --- a/src/parameter/fields/parse.ts +++ b/src/parameter/fields/parse.ts @@ -5,7 +5,7 @@ * view the LICENSE file that was distributed with this source code. */ -import { merge } from 'smob'; +import { isObject, merge } from 'smob'; import { ObjectLiteral } from '../../type'; import { applyMapping, buildFieldWithPath, groupArrayByKeyPath, hasOwnProperty, isFieldPathAllowedByRelations, @@ -37,7 +37,7 @@ function buildReverseRecord( } export function parseQueryFields( - data: unknown, + input: unknown, options?: FieldsParseOptions, ) : FieldsParseOutput { options = options || {}; @@ -63,26 +63,22 @@ export function parseQueryFields( ( typeof options.default !== 'undefined' || typeof options.allowed !== 'undefined' - ) && keys.length === 0 + ) && + keys.length === 0 ) { return []; } - const prototype: string = Object.prototype.toString.call(data); - if ( - prototype !== '[object Object]' && - prototype !== '[object Array]' && - prototype !== '[object String]' - ) { - data = { [DEFAULT_ID]: [] }; - } - - if (prototype === '[object String]') { - data = { [DEFAULT_ID]: data }; - } + let data : Record = { + [DEFAULT_ID]: [], + }; - if (prototype === '[object Array]') { - data = { [DEFAULT_ID]: data }; + if (isObject(input)) { + data = input; + } else if (typeof input === 'string') { + data = { [DEFAULT_ID]: input }; + } else if (Array.isArray(input)) { + data = { [DEFAULT_ID]: input }; } options.mapping = options.mapping || {}; @@ -106,16 +102,13 @@ export function parseQueryFields( let fields : string[] = []; - if ( - hasOwnProperty(data, path) - ) { + if (hasOwnProperty(data, path)) { fields = parseFieldsInput(data[path]); } else if ( - hasOwnProperty(reverseMapping, path) + hasOwnProperty(reverseMapping, path) && + hasOwnProperty(data, reverseMapping[path]) ) { - if (hasOwnProperty(data, reverseMapping[path])) { - fields = parseFieldsInput(data[reverseMapping[path]]); - } + fields = parseFieldsInput(data[reverseMapping[path]]); } let transformed : FieldsInputTransformed = { diff --git a/src/parameter/fields/utils/input.ts b/src/parameter/fields/utils/input.ts index f75492c8..3d535d8d 100644 --- a/src/parameter/fields/utils/input.ts +++ b/src/parameter/fields/utils/input.ts @@ -9,8 +9,10 @@ import { FieldsInputTransformed } from '../type'; import { FieldOperator } from '../constants'; export function removeFieldInputOperator(field: string) { - return field.substring(0, 1) === FieldOperator.INCLUDE || - field.substring(0, 1) === FieldOperator.EXCLUDE ? + const firstCharacter = field.substring(0, 1); + + return firstCharacter === FieldOperator.INCLUDE || + firstCharacter === FieldOperator.EXCLUDE ? field.substring(1) : field; } @@ -27,9 +29,11 @@ export function transformFieldsInput( for (let i = 0; i < fields.length; i++) { let operator: FieldOperator | undefined; - if (fields[i].substring(0, 1) === FieldOperator.INCLUDE) { + const character = fields[i].substring(0, 1); + + if (character === FieldOperator.INCLUDE) { operator = FieldOperator.INCLUDE; - } else if (fields[i].substring(0, 1) === FieldOperator.EXCLUDE) { + } else if (character === FieldOperator.EXCLUDE) { operator = FieldOperator.EXCLUDE; } @@ -54,27 +58,18 @@ export function transformFieldsInput( return output; } -export function parseFieldsInput(data: unknown): string[] { - const valuePrototype: string = Object.prototype.toString.call(data); - if ( - valuePrototype !== '[object Array]' && - valuePrototype !== '[object String]' - ) { - return []; - } +export function parseFieldsInput(input: unknown): string[] { + let output: string[] = []; - let fieldsArr: string[] = []; - - /* istanbul ignore next */ - if (valuePrototype === '[object String]') { - fieldsArr = (data as string).split(','); - } - - /* istanbul ignore next */ - if (valuePrototype === '[object Array]') { - fieldsArr = (data as unknown[]) - .filter((val) => typeof val === 'string') as string[]; + if (typeof input === 'string') { + output = input.split(','); + } else if (Array.isArray(input)) { + for (let i = 0; i < input.length; i++) { + if (typeof input[i] === 'string') { + output.push(input[i]); + } + } } - return fieldsArr; + return output; } diff --git a/src/parameter/filters/build.ts b/src/parameter/filters/build.ts index a07a9d91..539b615e 100644 --- a/src/parameter/filters/build.ts +++ b/src/parameter/filters/build.ts @@ -52,7 +52,7 @@ export function buildQueryFilters( }); } -export function mergeQueryFilters( +export function mergeQueryFilters( target?: Record, source?: Record, ) : Record { diff --git a/src/parameter/filters/parse.ts b/src/parameter/filters/parse.ts index 9df7e18b..9540b066 100644 --- a/src/parameter/filters/parse.ts +++ b/src/parameter/filters/parse.ts @@ -51,7 +51,7 @@ function transformFiltersParseOutputElement(element: FiltersParseOutputElement) function buildDefaultFiltersParseOutput( options: FiltersParseOptions, - input?: Record, + input: Record = {}, ) : FiltersParseOutput { const inputKeys = Object.keys(input || {}); diff --git a/src/parameter/pagination/build.ts b/src/parameter/pagination/build.ts index 124c35e2..9c164243 100644 --- a/src/parameter/pagination/build.ts +++ b/src/parameter/pagination/build.ts @@ -6,12 +6,11 @@ */ import { merge } from 'smob'; -import { ObjectLiteral } from '../../type'; import { PaginationBuildInput } from './type'; -export function mergeQueryPagination( - target?: PaginationBuildInput, - source?: PaginationBuildInput, -) : PaginationBuildInput { +export function mergeQueryPagination( + target?: PaginationBuildInput, + source?: PaginationBuildInput, +) : PaginationBuildInput { return merge({}, target || {}, source || {}); } diff --git a/src/parameter/pagination/parse.ts b/src/parameter/pagination/parse.ts index ea035aba..d0ee56ad 100644 --- a/src/parameter/pagination/parse.ts +++ b/src/parameter/pagination/parse.ts @@ -5,6 +5,7 @@ * view the LICENSE file that was distributed with this source code. */ +import { isObject } from 'smob'; import { PaginationParseOptions, PaginationParseOutput } from './type'; // -------------------------------------------------- @@ -46,8 +47,7 @@ export function parseQueryPagination( const pagination : PaginationParseOutput = {}; - const prototype: string = Object.prototype.toString.call(data); - if (prototype !== '[object Object]') { + if (!isObject(data)) { return finalizePagination(pagination, options); } diff --git a/src/parameter/pagination/type.ts b/src/parameter/pagination/type.ts index ad8370b8..2d232133 100644 --- a/src/parameter/pagination/type.ts +++ b/src/parameter/pagination/type.ts @@ -9,7 +9,7 @@ // Build // ----------------------------------------------------------- -export type PaginationBuildInput = { +export type PaginationBuildInput = { limit?: number, offset?: number }; diff --git a/src/parameter/relations/build.ts b/src/parameter/relations/build.ts index d3091dd7..a5878797 100644 --- a/src/parameter/relations/build.ts +++ b/src/parameter/relations/build.ts @@ -14,7 +14,7 @@ export function buildQueryRelations( input?: RelationsBuildInput, ) : string[] { if (typeof input === 'undefined') { - return input; + return []; } return flattenToKeyPathArray(input); diff --git a/src/parameter/relations/parse.ts b/src/parameter/relations/parse.ts index 173d7ca3..c6201b9a 100644 --- a/src/parameter/relations/parse.ts +++ b/src/parameter/relations/parse.ts @@ -15,8 +15,8 @@ import { includeParents, isValidRelationPath } from './utils'; // -------------------------------------------------- export function parseQueryRelations( - data: unknown, - options?: RelationsParseOptions, + input: unknown, + options: RelationsParseOptions = {}, ): RelationsParseOutput { options = options || {}; @@ -36,20 +36,14 @@ export function parseQueryRelations( let items: string[] = []; - const prototype: string = Object.prototype.toString.call(data); - if ( - prototype !== '[object Array]' && - prototype !== '[object String]' - ) { - return []; - } - - if (prototype === '[object String]') { - items = (data as string).split(','); - } - - if (prototype === '[object Array]') { - items = (data as any[]).filter((el) => typeof el === 'string'); + if (typeof input === 'string') { + items = input.split(','); + } else if (Array.isArray(input)) { + for (let i = 0; i < input.length; i++) { + if (typeof input[i] === 'string') { + items.push(input[i]); + } + } } if (items.length === 0) { @@ -64,7 +58,7 @@ export function parseQueryRelations( } if (options.allowed) { - items = items.filter((item) => isPathCoveredByParseAllowedOption(options.allowed, item)); + items = items.filter((item) => isPathCoveredByParseAllowedOption(options.allowed as string[], item)); } else { items = items.filter((item) => isValidRelationPath(item)); } @@ -94,7 +88,7 @@ export function parseQueryRelations( ) { value = options.pathMapping[key]; } else { - value = parts.pop(); + value = parts.pop() as string; } return { diff --git a/src/parameter/sort/parse.ts b/src/parameter/sort/parse.ts index d4c1b53d..2bc77510 100644 --- a/src/parameter/sort/parse.ts +++ b/src/parameter/sort/parse.ts @@ -5,6 +5,7 @@ * view the LICENSE file that was distributed with this source code. */ +import { isObject } from 'smob'; import { ObjectLiteral } from '../../type'; import { applyMapping, @@ -87,13 +88,11 @@ export function parseQuerySort( options.mapping = options.mapping || {}; - const prototype = Object.prototype.toString.call(data); - /* istanbul ignore next */ if ( - prototype !== '[object String]' && - prototype !== '[object Array]' && - prototype !== '[object Object]' + typeof data !== 'string' && + !Array.isArray(data) && + !isObject(data) ) { return buildDefaultSortParseOutput(options); } @@ -198,7 +197,7 @@ export function parseQuerySort( for (let i = 0; i < options.allowed.length; i++) { const temp : SortParseOutput = []; - const keyPaths = flattenParseAllowedOption(options.allowed[i]); + const keyPaths = flattenParseAllowedOption(options.allowed[i] as string[]); for (let j = 0; j < keyPaths.length; j++) { let keyWithAlias : string = keyPaths[j]; @@ -206,7 +205,7 @@ export function parseQuerySort( const parts = keyWithAlias.split('.'); if (parts.length > 1) { - key = parts.pop(); + key = parts.pop() as string; } else { key = keyWithAlias; diff --git a/src/parameter/utils/parse/allowed-option.ts b/src/parameter/utils/parse/allowed-option.ts index 4a4c9634..69692fc6 100644 --- a/src/parameter/utils/parse/allowed-option.ts +++ b/src/parameter/utils/parse/allowed-option.ts @@ -5,17 +5,21 @@ * view the LICENSE file that was distributed with this source code. */ -import { NestedKeys, NestedResourceKeys } from '../../../type'; +import { NestedKeys, NestedResourceKeys, ObjectLiteral } from '../../../type'; import { flattenToKeyPathArray } from '../../../utils'; import { ParseAllowedOption } from '../../type'; -export function flattenParseAllowedOption( - input: ParseAllowedOption, +export function flattenParseAllowedOption( + input?: ParseAllowedOption, ) : string[] { + if (typeof input === 'undefined') { + return []; + } + return flattenToKeyPathArray(input); } -export function isPathCoveredByParseAllowedOption( +export function isPathCoveredByParseAllowedOption( input: ParseAllowedOption | NestedKeys[] | NestedResourceKeys[], diff --git a/src/parse/module.ts b/src/parse/module.ts index 7ea294cb..e72bbb32 100644 --- a/src/parse/module.ts +++ b/src/parse/module.ts @@ -19,7 +19,7 @@ import { ParseInput, ParseOptions, ParseOutput } from './type'; export function parseQuery( input: ParseInput, - options?: ParseOptions, + options: ParseOptions = {}, ) : ParseOutput { options = options || {}; @@ -30,7 +30,7 @@ export function parseQuery( } } - return data; + return data || {} as T; }; const output : ParseOutput = {}; diff --git a/src/parse/parameter/module.ts b/src/parse/parameter/module.ts index 1b6dd479..6c6e38b7 100644 --- a/src/parse/parameter/module.ts +++ b/src/parse/parameter/module.ts @@ -74,6 +74,6 @@ function invalidToEmptyObject( ): NonNullable { return typeof value === 'boolean' || typeof value === 'undefined' ? - {} as V : - value; + {} as NonNullable : + value as NonNullable; } diff --git a/src/parse/parameter/utils.ts b/src/parse/parameter/utils.ts index a3216b9b..9fe07773 100644 --- a/src/parse/parameter/utils.ts +++ b/src/parse/parameter/utils.ts @@ -5,14 +5,16 @@ * view the LICENSE file that was distributed with this source code. */ +import { isObject } from 'smob'; + export function buildQueryParameterOptions>( input?: T | boolean, ) : T { - if (typeof input === 'boolean') { - return {} as T; + if (isObject(input)) { + return input; } - return input; + return {} as T; } export function isQueryParameterEnabled>( diff --git a/src/utils/array.ts b/src/utils/array.ts index a3b6d2df..6d191db9 100644 --- a/src/utils/array.ts +++ b/src/utils/array.ts @@ -122,7 +122,7 @@ export function groupArrayByKeyPath(input: string[]): Record { key = DEFAULT_ID; name = input[i]; } else { - name = parts.pop(); + name = parts.pop() as string; key = parts.join('.'); } diff --git a/src/utils/field.ts b/src/utils/field.ts index 0fdd22ed..631927d9 100644 --- a/src/utils/field.ts +++ b/src/utils/field.ts @@ -11,7 +11,7 @@ export function getFieldDetails(field: string) : FieldDetails { const parts : string[] = field.split('.'); return { - name: parts.pop(), + name: parts.pop() as string, path: parts.length > 0 ? parts.join('.') : undefined, }; } diff --git a/src/utils/mapping.ts b/src/utils/mapping.ts index 71ae9e53..ab2f1391 100644 --- a/src/utils/mapping.ts +++ b/src/utils/mapping.ts @@ -57,7 +57,11 @@ export function applyMapping( } if (onlyKey) { - return output.pop(); + return output.pop() || name; + } + + if (output.length === 0) { + return name; } return output.join('.'); diff --git a/tsconfig.json b/tsconfig.json index b2a5626d..074512fe 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,28 +1,12 @@ { + "extends": "@tada5hi/tsconfig", "compilerOptions": { - "strict": true, - "skipLibCheck": true, - "esModuleInterop": true, - "declaration": true, - "declarationDir": "dist", - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "lib": [ - "ESNext" - ], - "module": "ESNext", - "moduleResolution": "Node", - "newLine": "LF", - "noFallthroughCasesInSwitch": true, - "noImplicitAny": true, - "noImplicitReturns": true, - "noImplicitThis": false, - "noUnusedParameters": false, - "noUnusedLocals": true, "outDir": "dist", - "sourceMap": true, - "strictNullChecks": false, - "target": "ESNext" + "declarationMap": true, + + "lib": ["ESNext"], + "module": "ESNext", + "target": "ES2020" }, "include": [ "src/**/*.ts"