Skip to content

Instantly share code, notes, and snippets.

@alii
Last active April 13, 2022 20:58
Show Gist options
  • Save alii/30b6aa939e9ca12078cf126196d797d4 to your computer and use it in GitHub Desktop.
Save alii/30b6aa939e9ca12078cf126196d797d4 to your computer and use it in GitHub Desktop.
Small schema validator inspired by zod. Built so I could learn to some degree how zod works under the hood.
interface Schema<Out> {
parse(value: unknown, path: string): Out;
}
type Resolve<T> = T extends Schema<infer R> ? Resolve<R> : T;
function bool(): Schema<boolean> {
return {
parse(value, path) {
const valid = typeof value === "boolean";
if (!valid) {
throw new Error(`Expected boolean at \`${path}\`, \`found ${value}\``);
}
return value;
},
};
}
function str(): Schema<string> {
return {
parse(value, path) {
const valid = typeof value === "string";
if (!valid) {
throw new Error(`Expected string at \`${path}\`, found \`${value}\``);
}
return value;
},
};
}
function lit<T>(v: T): Schema<T> {
return {
parse(value, path) {
if (value !== v) {
throw new Error(`Expected ${v} (${typeof v}) at \`${path}\`, found \`${value}\``);
}
return value as T;
},
};
}
function num(): Schema<number> {
return {
parse(value, path) {
const valid = typeof value === "number";
if (!valid) {
throw new Error(`Expected number at \`${path}\`, found \`${value}\``);
}
return value;
},
};
}
function obj<T extends Record<string, Schema<unknown>>>(shape: T): Schema<{ [K in keyof T]: Resolve<T[K]> }> {
return {
parse(value, path) {
if (!value || typeof value !== "object") {
throw new Error(`Expected object at \`${path}\`, found \`${value}\``);
}
const result: Partial<{ [K in keyof T]: Resolve<T[K]> }> = {};
for (const key in shape) {
const schema = shape[key];
result[key] = schema.parse(value[key as keyof typeof value], `${path}.${key}`) as Resolve<
T[Extract<keyof T, string>]
>;
}
return result as { [K in keyof T]: Resolve<T[K]> };
},
};
}
function parse<T>(schema: Schema<T>, value: unknown): T {
return schema.parse(value, "<root>");
}
function safeParse<T>(
schema: Schema<T>,
value: unknown
):
| {
success: true;
data: T;
}
| {
success: false;
error: Error;
} {
try {
const data = parse(schema, value);
return {
success: true as const,
data,
};
} catch (e: unknown) {
return {
success: false as const,
error: e as Error,
};
}
}
const mySchema = obj({
bruh: bool(),
another: lit("bruh" as const),
details: obj({
age: num(),
deep: obj({
something: num(),
deeeeeper: obj({
prop: bool(),
}),
}),
}),
});
const result = parse(mySchema, {
bruh: true,
another: "bruh",
details: {
age: 2,
deep: {
something: 2,
deeeeeper: {
prop: true,
},
},
},
});
@Looskie
Copy link

Looskie commented Apr 13, 2022

deeeeeper

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment