-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added transformAndValidateObject
- refactored @dereekb/model transform lib directory
- Loading branch information
Showing
7 changed files
with
263 additions
and
75 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
export * from './type'; | ||
export * from './type.annotation'; | ||
export * from './transform'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { transformAndValidateObjectResult, TransformAndValidateObjectResultFunction, TransformAndValidateObjectSuccessResultOutput, TransformAndValidateObjectErrorResultOutput, transformAndValidateObjectFactory } from './transform'; | ||
import { Expose } from 'class-transformer'; | ||
import { IsBoolean } from 'class-validator'; | ||
|
||
export class TestTransformAndValidateClass { | ||
|
||
@Expose() | ||
@IsBoolean() | ||
valid?: boolean; | ||
|
||
} | ||
|
||
describe('transformAndValidateObjectFactory()', () => { | ||
|
||
const errorValue = 0; | ||
const factory = transformAndValidateObjectFactory({ onValidationError: async () => errorValue }); | ||
|
||
it('should create a transformAndValidateFunction', async () => { | ||
const successValue = 100; | ||
|
||
const fn = factory(TestTransformAndValidateClass, async () => successValue); | ||
expect(fn).toBeDefined(); | ||
expect(typeof fn).toBe('function'); | ||
}); | ||
|
||
describe('function', () => { | ||
|
||
it('should return the success value for valid input.', async () => { | ||
const successValue = 100; | ||
const fn = factory(TestTransformAndValidateClass, async () => successValue); | ||
|
||
const result = await fn({ valid: true }); | ||
expect(result).toBe(successValue); | ||
}); | ||
|
||
it('should handle the validation error', async () => { | ||
const fn = factory(TestTransformAndValidateClass, async () => 0); | ||
|
||
const result = await fn({ valid: true }); | ||
expect(result).toBe(errorValue); | ||
}); | ||
|
||
}); | ||
|
||
}); | ||
|
||
describe('transformAndValidateObjectResult()', () => { | ||
|
||
const transformResult: TransformAndValidateObjectResultFunction<object, { value: TestTransformAndValidateClass }> = transformAndValidateObjectResult(TestTransformAndValidateClass, async (value) => { | ||
return { value }; | ||
}); | ||
|
||
it('should return success when the input is valid', async () => { | ||
const result = await transformResult({ valid: true }) as TransformAndValidateObjectSuccessResultOutput<{ value: TestTransformAndValidateClass }>; | ||
|
||
expect(result.success).toBe(true); | ||
expect(result.result).toBeDefined(); | ||
expect(result.result.value).toBeDefined(); | ||
expect(result.result.value.valid).toBe(true); | ||
}); | ||
|
||
it('should return validation errors when the input is invalid', async () => { | ||
const result = await transformResult({ invalid: true }) as TransformAndValidateObjectErrorResultOutput; | ||
|
||
expect(result.success).toBe(false); | ||
expect(result.validationErrors.length > 0).toBe(true); | ||
expect(result.validationErrors[0].property).toBe('valid'); // missing | ||
}); | ||
|
||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,52 +1,97 @@ | ||
import { MapStringFn, Maybe, splitCommaSeparatedString } from "@dereekb/util"; | ||
import { Transform, TransformFnParams } from "class-transformer"; | ||
|
||
// MARK: String | ||
export function transformStringToBoolean(defaultValue?: boolean | undefined): (params: TransformFnParams) => Maybe<boolean> { | ||
return (params: TransformFnParams) => { | ||
if (params.value) { | ||
switch (params.value.toLowerCase()) { | ||
case 't': | ||
case 'true': | ||
return true; | ||
case 'f': | ||
case 'false': | ||
return false; | ||
default: | ||
return defaultValue; | ||
} | ||
import { ClassType } from "@dereekb/util"; | ||
import { plainToClass } from "class-transformer"; | ||
import { validate, ValidationError } from "class-validator"; | ||
|
||
// MARK: Transform and Validate | ||
export type TransformAndValidateObjectFunction<I, O> = (input: I) => Promise<O>; | ||
export type TransformAndValidateObjectHandleValidate<O = any> = (validationErrors: ValidationError[]) => Promise<O>; | ||
|
||
/** | ||
* transformAndValidateObject() configuration that also provides error handling. | ||
*/ | ||
export interface TransformAndValidateObject<T extends object, O> { | ||
readonly classType: ClassType<T>; | ||
readonly fn: (parsed: T) => Promise<O>; | ||
readonly onValidationError: TransformAndValidateObjectHandleValidate<O>; | ||
} | ||
|
||
export function transformAndValidateObject<T extends object, O, I = any>(config: TransformAndValidateObject<T, O>): TransformAndValidateObjectFunction<I, O> { | ||
const transformToResult = transformAndValidateObjectResult(config.classType, config.fn); | ||
const { onValidationError } = config; | ||
|
||
return (input) => transformToResult(input).then((x) => { | ||
if (x.success) { | ||
return x.result; | ||
} else { | ||
return defaultValue; | ||
return onValidationError(x.validationErrors); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
// MARK: Comma Separated Values | ||
export function transformCommaSeparatedValueToArray<T>(mapFn: MapStringFn<T>): (params: TransformFnParams) => Maybe<T[]> { | ||
return (params: TransformFnParams) => { | ||
let result: Maybe<T[]>; | ||
|
||
if (params.value) { | ||
if (Array.isArray(params.value)) { | ||
result = params.value; | ||
} else { | ||
result = splitCommaSeparatedString(params.value, mapFn); | ||
} | ||
} | ||
// MARK: Transform and Validate Factory | ||
/** | ||
* Configuration for the transformAndValidateObject function from transformAndValidateObjectFactory(). | ||
*/ | ||
export interface TransformAndValidateObjectFactoryDefaults { | ||
readonly onValidationError: TransformAndValidateObjectHandleValidate<any>; | ||
} | ||
|
||
/** | ||
* Factory for generating TransformAndValidateObjectFunction functions. | ||
*/ | ||
export type TransformAndValidateObjectFactory = <T extends object, O, I = any>(classType: ClassType<T>, fn: (parsed: T) => Promise<O>, onValidationError?: TransformAndValidateObjectHandleValidate<any>) => TransformAndValidateObjectFunction<I, O>; | ||
|
||
export function transformAndValidateObjectFactory(defaults: TransformAndValidateObjectFactoryDefaults): TransformAndValidateObjectFactory { | ||
const { onValidationError: defaultOnValidationError } = defaults; | ||
|
||
return result; | ||
} | ||
return <T extends object, O>(classType: ClassType<T>, fn: (parsed: T) => Promise<O>, onValidationError?: TransformAndValidateObjectHandleValidate<any>) => { | ||
const config: TransformAndValidateObject<T, O> = { | ||
classType, | ||
fn, | ||
onValidationError: onValidationError ?? defaultOnValidationError | ||
}; | ||
|
||
return transformAndValidateObject(config); | ||
}; | ||
} | ||
|
||
export const transformCommaSeparatedNumberValueToArray = transformCommaSeparatedValueToArray((x) => Number(x)); | ||
export const transformCommaSeparatedStringValueToArray = transformCommaSeparatedValueToArray((x) => x); | ||
// MARK: Transform And Validate Result | ||
export type TransformAndValidateObjectResultFunction<I, O> = (input: I) => Promise<TransformAndValidateObjectResultOutput<O>>; | ||
|
||
export type TransformAndValidateObjectResultOutput<O> = TransformAndValidateObjectSuccessResultOutput<O> | TransformAndValidateObjectErrorResultOutput; | ||
|
||
// MARK: Transform Annotations | ||
export function TransformCommaSeparatedValueToArray<T>(mapFn: MapStringFn<T>) { | ||
return Transform(transformCommaSeparatedValueToArray(mapFn)); | ||
export interface TransformAndValidateObjectSuccessResultOutput<O> { | ||
readonly success: true; | ||
readonly result: O; | ||
} | ||
|
||
export const TransformCommaSeparatedStringValueToArray = () => Transform(transformCommaSeparatedStringValueToArray); | ||
export const TransformCommaSeparatedNumberValueToArray = () => Transform(transformCommaSeparatedNumberValueToArray); | ||
export interface TransformAndValidateObjectErrorResultOutput { | ||
readonly success: false; | ||
readonly validationErrors: ValidationError[]; | ||
} | ||
|
||
/** | ||
* Factory function that wraps the input class type and handler function to first transform the input object to a the given class, and then validate it. | ||
* | ||
* @param classType | ||
* @param fn | ||
* @returns | ||
*/ | ||
export function transformAndValidateObjectResult<T extends object, O, I = any>(classType: ClassType<T>, fn: (parsed: T) => Promise<O>): TransformAndValidateObjectResultFunction<I, O> { | ||
return async (input: I) => { | ||
|
||
const object: T = plainToClass(classType, input, { | ||
// Note: Each variable on the target class must be marked with the @Expose() annotation. | ||
excludeExtraneousValues: true | ||
}); | ||
|
||
export const TransformStringValueToBoolean = () => Transform(transformStringToBoolean()); | ||
const validationErrors: ValidationError[] = await validate(object); | ||
|
||
if (validationErrors.length) { | ||
return { validationErrors, success: false }; | ||
} else { | ||
const result = await fn(object); | ||
return { result, success: true }; | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { MapStringFn } from "@dereekb/util"; | ||
import { Transform } from "class-transformer"; | ||
import { transformCommaSeparatedValueToArray, transformCommaSeparatedStringValueToArray, transformCommaSeparatedNumberValueToArray, transformStringToBoolean } from "./type"; | ||
|
||
// MARK: Transform Annotations | ||
export function TransformCommaSeparatedValueToArray<T>(mapFn: MapStringFn<T>) { | ||
return Transform(transformCommaSeparatedValueToArray(mapFn)); | ||
} | ||
|
||
export const TransformCommaSeparatedStringValueToArray = () => Transform(transformCommaSeparatedStringValueToArray); | ||
export const TransformCommaSeparatedNumberValueToArray = () => Transform(transformCommaSeparatedNumberValueToArray); | ||
|
||
export const TransformStringValueToBoolean = () => Transform(transformStringToBoolean()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { MapStringFn, Maybe, splitCommaSeparatedString } from "@dereekb/util"; | ||
import { TransformFnParams } from "class-transformer"; | ||
|
||
// MARK: String | ||
export function transformStringToBoolean(defaultValue?: boolean | undefined): (params: TransformFnParams) => Maybe<boolean> { | ||
return (params: TransformFnParams) => { | ||
if (params.value) { | ||
switch (params.value.toLowerCase()) { | ||
case 't': | ||
case 'true': | ||
return true; | ||
case 'f': | ||
case 'false': | ||
return false; | ||
default: | ||
return defaultValue; | ||
} | ||
} else { | ||
return defaultValue; | ||
} | ||
} | ||
} | ||
|
||
// MARK: Comma Separated Values | ||
export function transformCommaSeparatedValueToArray<T>(mapFn: MapStringFn<T>): (params: TransformFnParams) => Maybe<T[]> { | ||
return (params: TransformFnParams) => { | ||
let result: Maybe<T[]>; | ||
|
||
if (params.value) { | ||
if (Array.isArray(params.value)) { | ||
result = params.value; | ||
} else { | ||
result = splitCommaSeparatedString(params.value, mapFn); | ||
} | ||
} | ||
|
||
return result; | ||
} | ||
} | ||
|
||
export const transformCommaSeparatedNumberValueToArray = transformCommaSeparatedValueToArray((x) => Number(x)); | ||
export const transformCommaSeparatedStringValueToArray = transformCommaSeparatedValueToArray((x) => x); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters