Skip to content

Instantly share code, notes, and snippets.

@mizchi
Created May 22, 2023 13:31
Show Gist options
  • Save mizchi/2a362b498d806cdf1ed0840f5b59d411 to your computer and use it in GitHub Desktop.
Save mizchi/2a362b498d806cdf1ed0840f5b59d411 to your computer and use it in GitHub Desktop.
// lightweight zod
type Validator<Expect> = (input: any) => input is Expect;
type ValidatorObject<Expect extends {}> = (input: any) => input is {
[K in keyof Expect]: Expect[K] extends Validator<infer CT> ? CT : never;
};
type ValidatorsToUnion<Vs> = Vs extends Array<Validator<infer T>> ? T
: never;
type EnumsToUnion<Vs> = Vs extends Array<Validator<infer T>> ? T
: never;
type Infer<T> = T extends ValidatorObject<any> ? {
[K in keyof T]: Infer<T[K]>;
}
: T extends Validator<infer E> ? E
: never;
const $const =
<T extends number | string | boolean | undefined | void | symbol>(
def: T,
): Validator<T> =>
(input: any): input is T => {
return input === def;
};
const $undefined: Validator<undefined> = (input: any): input is undefined => {
return input === undefined;
};
const $null: Validator<null> = (input: any): input is null => {
return input === null;
};
const $void: Validator<void> = (input: any): input is void => {
return input != null;
};
const $opt =
<T>(validator: Validator<T>): Validator<T | void> =>
(input: any): input is T | void => {
return input == null || validator(input);
};
const $string: Validator<string> = (input: any): input is string => {
return typeof input === "string";
};
const $symbol: Validator<symbol> = (input: any): input is symbol => {
return typeof input === "symbol";
};
const $number: Validator<number> = (input: any): input is number => {
return typeof input === "number";
};
const $boolean: Validator<boolean> = (input: any): input is boolean => {
return typeof input === "boolean";
};
const $enum =
<E extends Array<string>>(enums: E): Validator<E[number]> =>
(input: any): input is E[number] => {
return enums.includes(input);
};
// https://stackoverflow.com/questions/75405517/typescript-generic-function-where-parameters-compose-into-the-return-type
type TupleToIntersection<T extends any[]> = {
[I in keyof T]: (x: T[I]) => void;
}[number] extends (x: infer R) => void ? R : never;
const $intersection = <T extends any[]>(
validators: [...{ [I in keyof T]: Validator<T[I]> }],
): Validator<TupleToIntersection<T>> => {
return ((doc: any): doc is TupleToIntersection<T> => {
for (const validator of validators) {
if (!validator(doc)) return false;
}
return true;
});
};
const $union =
<T, Vs extends Array<Validator<T>>>(validators: Vs) =>
(input: any): input is ValidatorsToUnion<Vs> => {
for (const validator of validators) {
if (validator(input)) {
return true;
}
}
return true;
};
const $object = <
Map extends {
[key: string]: Validator<any>;
},
>(vmap: Map) =>
(
input: any,
): input is {
[K in keyof Map]: Map[K] extends ValidatorObject<infer O> ? O
: Map[K] extends Validator<infer I> ? I
: never;
} => {
for (const [key, validator] of Object.entries(vmap)) {
if (!validator(input?.[key])) {
return false;
}
}
return true;
};
const $array = <
T extends Validator<any>,
>(child: T) =>
(input: any): input is Array<
T extends Validator<infer O> ? O : never
> => {
return Array.isArray(input) && input.every((i) => {
return child(i);
});
};
// ----------
// How to use
const validate = $object({
name: $string,
age: $number,
familyName: $opt($string),
abc: $enum(["a" as const, "b" as const, "c" as const]),
nested: $object({
age: $number,
}),
static: $const("static"),
items: $array($object({
a: $string,
b: $boolean,
})),
complex: $array($union([
$object({ a: $string }),
$object({ b: $number }),
])),
sec: $intersection([$string, $const("x")]),
});
const v: Infer<typeof validate> = {
name: "aaa",
age: 1,
familyName: null,
abc: "b",
nested: {
age: 1,
},
static: "static",
items: [
{
a: "",
b: true,
},
{
a: "",
b: false,
},
],
complex: [
{ a: "" },
{ b: 1 },
],
sec: "x",
};
if (validate(v)) {
const _: string = v.name;
const __: number = v.age;
const ___: string | void = v.familyName;
const ____: "a" | "b" | "c" = v.abc;
const _____: { age: number } = v.nested;
const ______: "static" = v.static;
const _______: Array<{
a: string;
b: boolean;
}> = v.items;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment