Validate Untrusted Input At A Single Boundary
A boundary parser that validates raw external input once and hands typed, sanitized values to the rest of the program.
Code
export type FieldKind = "string" | "number" | "boolean";
export interface FieldSpec {
readonly name: string;
readonly kind: FieldKind;
readonly required?: boolean;
readonly min?: number;
readonly max?: number;
readonly minLength?: number;
readonly maxLength?: number;
}
export type BoundaryResult<T> =
| { readonly ok: true; readonly value: T }
| { readonly ok: false; readonly violations: string[] };
function checkConstraints(spec: FieldSpec, value: string | number | boolean): string | null {
if (typeof value === "string") {
if (spec.minLength !== undefined && value.length < spec.minLength) {
return spec.name + " shorter than " + spec.minLength + " characters";
}
if (spec.maxLength !== undefined && value.length > spec.maxLength) {
return spec.name + " longer than " + spec.maxLength + " characters";
}
}
if (typeof value === "number") {
if (spec.min !== undefined && value < spec.min) {
return spec.name + " below minimum " + spec.min;
}
if (spec.max !== undefined && value > spec.max) {
return spec.name + " above maximum " + spec.max;
}
}
return null;
}
export function parseBoundary<T extends Record<string, unknown>>(
raw: unknown,
specs: readonly FieldSpec[],
): BoundaryResult<T> {
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
return { ok: false, violations: ["payload must be an object"] };
}
const record = raw as Record<string, unknown>;
const violations: string[] = [];
const out: Record<string, unknown> = {};
const known = new Set(specs.map((s) => s.name));
for (const key of Object.keys(record)) {
if (!known.has(key)) violations.push("unknown field: " + key);
}
for (const spec of specs) {
const value = record[spec.name];
if (value === undefined || value === null) {
if (spec.required !== false) violations.push(spec.name + " is required");
continue;
}
if (typeof value !== spec.kind) {
violations.push(spec.name + " must be a " + spec.kind);
continue;
}
const problem = checkConstraints(spec, value as string | number | boolean);
if (problem) {
violations.push(problem);
continue;
}
out[spec.name] = value;
}
if (violations.length > 0) return { ok: false, violations };
return { ok: true, value: out as T };
}Report a problem with this exampleOpens a pre-filled issue on GitHub.