Last active
February 1, 2022 01:06
-
-
Save CMCDragonkai/d1739230b49fd9871fd81157957d7480 to your computer and use it in GitHub Desktop.
General Data Validation #typescript #javascript
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
import { CustomError } from 'ts-custom-error'; | |
class ErrorParse extends CustomError { | |
public readonly errors: Array<ErrorRule>; | |
constructor(errors: Array<ErrorRule>) { | |
const message = errors.map((e) => e.message).join('; '); | |
super(message); | |
this.errors = errors; | |
} | |
} | |
class ErrorRule extends CustomError { | |
public keyPath: Array<string>; | |
public value: any; | |
public context: object; | |
constructor(message?: string) { | |
super(message); | |
} | |
} | |
/** | |
* Functional data-validation, inspired by recursion schemes and `JSON.parse` reviver | |
* Post-fix depth first traversal on the data structure tree | |
* Capable of modifying the data structure during traversal | |
* Works like `JSON.parse` but on POJOs rather than JSON strings | |
* Compose validation rules into the `rules` function | |
*/ | |
async function parse( | |
rules: (keyPath: Array<string>, value: any) => Promise<any>, | |
data: any, | |
options: { mode: 'greedy' | 'lazy' } = { mode: 'lazy' } | |
): Promise<any> { | |
const errors: Array<ErrorRule> = []; | |
const parse_ = async (keyPath: Array<string>, value: any, context: object) => { | |
if (typeof value === 'object' && value != null) { | |
for (const key in value) { | |
value[key] = await parse_([...keyPath, key], value[key], value); | |
} | |
} | |
try { | |
value = await rules.bind(context)(keyPath, value); | |
} catch (e) { | |
if (e instanceof ErrorRule) { | |
e.keyPath = keyPath; | |
e.value = value; | |
e.context = context; | |
errors.push(e); | |
// If lazy mode, short circuit evaluation | |
// And throw the error up | |
if (options.mode === 'lazy') { | |
throw e; | |
} | |
} else { | |
throw e; | |
} | |
} | |
return value; | |
}; | |
try { | |
// The root context is an object containing the root data but keyed with undefined | |
data = await parse_([], data, { undefined: data }); | |
} catch (e) { | |
if (e instanceof ErrorRule) { | |
throw new ErrorParse(errors); | |
} else { | |
throw e; | |
} | |
} | |
if (errors.length > 0) { | |
throw new ErrorParse(errors); | |
} | |
return data; | |
} | |
export { parse }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Use it like: