Skip to content

Instantly share code, notes, and snippets.

@kbkk
Last active February 28, 2020 13:57
Show Gist options
  • Save kbkk/684a855d8aba5446973e741074ff2553 to your computer and use it in GitHub Desktop.
Save kbkk/684a855d8aba5446973e741074ff2553 to your computer and use it in GitHub Desktop.
Validators with TypeScript
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