Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- type Validated<E, A> = ValidatedOk<E, A> | ValidatedError<E, A>
- type ErrorOfValidated<V extends Validated<any, any>> = V['errorType']
- type ValueOfValidated<V extends Validated<any, any>> = V['valueType']
- const Validated = {
- ok<E, A>(a: A): ValidatedOk<E, A> {
- return new ValidatedOk<E, A>(a)
- },
- error<E, A>(error: E): ValidatedError<E, A> {
- return new ValidatedError<E, A>([error])
- },
- combine<O extends { [k: string]: Validated<any, any> }>(o: O): Validated<ErrorOfValidated<O[keyof O]>, { [K in keyof O]: ValueOfValidated<O[K]> }> {
- const errors: any[] = []
- const values: { [K in keyof O]?: any } = {}
- Object.keys(o).forEach(key => {
- const validated = o[key]
- if (validated.kind === ValidatedOk.kind) {
- values[key] = validated.value
- } else if (validated.kind === ValidatedError.kind) {
- validated.errors.forEach((e: any) => {
- errors.push(e)
- })
- } else {
- const exhaustive: never = validated
- throw exhaustive
- }
- })
- if (errors.length > 0) {
- return new ValidatedError(errors)
- } else {
- return new ValidatedOk(values as any)
- }
- }
- }
- class ValidatedOk<E, A> {
- static readonly kind = 'ok'
- readonly kind = ValidatedOk.kind
- readonly valueType: A = null as A
- readonly errorType: E = null as E
- constructor(readonly value: A) { }
- map<B>(fn: (a: A) => B): Validated<E, B> {
- return new ValidatedOk<E, B>(fn(this.value))
- }
- fold<B>(ok: (v: ValidatedOk<E, A>) => B, error: (v: ValidatedError<E, A>) => B): B {
- return ok(this)
- }
- }
- class ValidatedError<E, A> {
- static readonly kind = 'error'
- readonly kind = ValidatedError.kind
- readonly valueType: A = null as A
- readonly errorType: E = null as E
- constructor(readonly errors: E[]) { }
- map<B>(fn: (a: A) => B): Validated<E, B> {
- return new ValidatedError<E, B>(this.errors)
- }
- fold<B>(ok: (v: ValidatedOk<E, A>) => B, error: (v: ValidatedError<E, A>) => B): B {
- return error(this)
- }
- coerceValue<B>(): ValidatedError<E, B> {
- return new ValidatedError<E, B>(this.errors)
- }
- }
- class ValidationRule<E, A, B> {
- readonly sourceType: A = null as A
- readonly targetType: B = null as B
- readonly errorType: E = null as E
- constructor(readonly rule: (a: A) => Validated<E, B>) { }
- followedBy<C>(rule: (b: B) => Validated<E, C>): ValidationRule<E, A, C> {
- const composedRule = (a: A) => this.rule(a).fold(
- ok => rule(ok.value),
- err => err.coerceValue<C>()
- )
- return new ValidationRule(composedRule)
- }
- static fromPredicate<E, A>(predicate: (a: A) => boolean, error: E): ValidationRule<E, A, A> {
- return new ValidationRule<E, A, A>((a: A) =>
- predicate(a) ? new ValidatedOk<E, A>(a) : new ValidatedError<E, A>([error])
- )
- }
- static combine<O extends { [k: string]: ValidationRule<any, any, any> }>(o: O): ValidationRule<ErrorOfValidationRule<O[keyof O]>, {[K in keyof O]: SourceOfValidationRule<O[K]> }, {[K in keyof O]: TargetOfValidationRule<O[K]> }> {
- const rule = a => {
- const validatedsToCombine: { [K in keyof O]?: Validated<any, any> } = {}
- Object.keys(o).forEach(key => {
- validatedsToCombine[key] = o[key].rule(a[key])
- })
- return Validated.combine(validatedsToCombine)
- }
- return new ValidationRule(rule)
- }
- }
- type SourceOfValidationRule<V extends ValidationRule<any, any, any>> = V['sourceType']
- type TargetOfValidationRule<V extends ValidationRule<any, any, any>> = V['targetType']
- type ErrorOfValidationRule<V extends ValidationRule<any, any, any>> = V['errorType']
- const isBiggerThanTwo = new ValidationRule<string, number, string>(n =>
- n > 2
- ? Validated.ok('Big ' + n)
- : Validated.error('not.bigger.than.two')
- )
- const isFalse = ValidationRule.fromPredicate(b => b === false, 'not.false')
- const rules = {
- n: isBiggerThanTwo,
- b: isFalse
- }
- const rulesV = ValidationRule.combine(rules)
- console.log(rulesV.rule({ n: 4, b: true }))
- console.log(rulesV.rule({ n: 4, b: false }))
- console.log(rulesV.rule({ n: 1, b: true }))
Add Comment
Please, Sign In to add comment