Last active
February 28, 2020 13:57
-
-
Save kbkk/684a855d8aba5446973e741074ff2553 to your computer and use it in GitHub Desktop.
Validators with TypeScript
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
type GuardedType<T> = T extends (x: any) => x is (infer T) ? T : never; | |
interface Validator<T> { | |
isValid(value: any): value is any; | |
notRequired(): NotRequiredValidator<T>; | |
required(): RequiredValidator<T>; | |
nullable(): NullableValidator<T>; | |
} | |
interface RequiredValidator<T> extends Validator<T> { | |
isValid(value: any): value is Exclude<T, undefined>; | |
} | |
interface NotRequiredValidator<T> extends Validator<T | undefined> { | |
isValid(value: any): value is T | undefined; | |
} | |
interface NullableValidator<T> extends Validator<T | null> { | |
isValid(value: any): value is T | null; | |
} | |
abstract class BaseValidator<T> implements Validator<T> { | |
protected isRequired = false; | |
notRequired(): NotRequiredValidator<T> { | |
this.isRequired = false; | |
return this; | |
} | |
required(): RequiredValidator<T> { | |
this.isRequired = true; | |
return this; | |
} | |
nullable(): NullableValidator<T> { | |
return this; | |
} | |
abstract isValid(value: any): value is any; | |
} | |
class StringValidator extends BaseValidator<string> { | |
isValid(value: any): value is string { | |
return typeof value === 'string'; | |
} | |
} | |
class NumberValidator extends BaseValidator<number> { | |
isValid(value: any): value is number { | |
return typeof value === 'number'; | |
} | |
} | |
class ObjectValidator<T extends { [id: string]: Validator<any> }> extends BaseValidator<T> { | |
constructor(private schema: T) { | |
super(); | |
} | |
isValid(value: any): value is { [k in keyof T]: GuardedType<T[k]['isValid']> } { | |
return true; | |
} | |
} | |
interface Controller { | |
validator(): Validator<any> | |
// execute(params: GuardedType<this['validator']>): void; // child methods don't inherit argument types :( | |
execute(params: any): void; | |
} | |
type ControllerParams<T extends Controller> = GuardedType<ReturnType<T['validator']>['isValid']>; | |
class RequiredStringValidatedController implements Controller { | |
validator() { | |
return new StringValidator().required(); | |
} | |
execute(params: ControllerParams<this>) { | |
params.concat(); // autocompletion of String.prototype methods | |
} | |
} | |
class NotRequiredStringValidatedController implements Controller { | |
validator() { | |
return new StringValidator().notRequired().nullable(); | |
} | |
execute(params: ControllerParams<this>) { | |
// params.concat(); // fails | |
params?.concat(); | |
if (params) { | |
params.concat(); | |
} | |
} | |
} | |
class ObjectValidatedController implements Controller { | |
validator() { | |
return new ObjectValidator({ | |
prop: new StringValidator().notRequired() | |
}) | |
// return new ObjectValidator(1) | |
} | |
execute(params: ControllerParams<this>) { | |
// params.prop.concat(); // fails | |
if(params.prop) { | |
params.prop.concat(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment