Loading...
Loading...
Type-driven design principle: transform unstructured data into structured types at system boundaries, making illegal states unrepresentable. Use when writing or reviewing code that validates input, designs data types, defines function signatures, handles errors, or models domain logic. Use when you see validation functions that return void/undefined, redundant null checks, stringly-typed data, boolean flags controlling behavior, or functions that can receive data they shouldn't. Triggers on: "parse don't validate", "type-driven design", "make illegal states unrepresentable", "input validation", "data modeling", "refactor types", "strengthen types", "smart constructor", "newtype", "branded type".
npx skill4agent add caidanw/skills parse-dont-validate// VALIDATION: checks a property, returns nothing useful
function validateNonEmpty(list: string[]): void {
if (list.length === 0) throw new Error("list cannot be empty");
}
// PARSING: checks the same property, returns proof in the type
function parseNonEmpty<T>(list: T[]): [T, ...T[]] {
if (list.length === 0) throw new Error("list cannot be empty");
return list as [T, ...T[]];
}parseNonEmptyvalidateNonEmptyfunction head<T>(list: T[]): T | undefined {
return list[0];
}undefined// should never happenfunction head<T>(list: [T, ...T[]]): T {
return list[0];
}// BAD: allows duplicate keys, order might matter or might not
type Config = Array<[string, string]>;
// GOOD: duplicates impossible by construction
type Config = Map<string, string>;
// or even better if keys are known:
type Config = { host: string; port: number; debug: boolean };// BAD: raw data flows deep into the system, validated ad-hoc
function processUser(data: unknown) {
// 50 lines later...
if (typeof data.email !== "string") throw new Error("invalid email");
}
// GOOD: parse at the boundary, use precise types everywhere else
interface User { name: string; email: string; age: number; }
function parseUser(data: unknown): User {
// validate and parse here, once
}
function processUser(user: User) {
// no validation needed -- the type guarantees it
}voidvoid// SUSPICIOUS: checks something, returns nothing
function validateAge(age: number): void {
if (age < 0 || age > 150) throw new Error("invalid age");
}
// BETTER: returns proof of validity as a branded type
type ValidAge = number & { readonly __brand: "ValidAge" };
function parseAge(age: number): ValidAge {
if (age < 0 || age > 150) throw new Error("invalid age");
return age as ValidAge;
}type EmailAddress = string & { readonly __brand: "EmailAddress" };
function parseEmail(input: string): EmailAddress {
if (!input.includes("@")) throw new Error("invalid email");
return input as EmailAddress;
}
// Now functions can demand EmailAddress instead of string
function sendEmail(to: EmailAddress, body: string): void { /* ... */ }stringEmailAddressparseEmailboolean// BAD: boolean flag controlling behavior
interface Request { url: string; isAuthenticated: boolean; token?: string; }
// GOOD: discriminated union makes invalid state impossible
type Request =
| { kind: "anonymous"; url: string }
| { kind: "authenticated"; url: string; token: string };// Fine: first parse the header to determine the format, then parse the body
const header = parseHeader(raw);
const body = parseBody(raw, header.format);stringvoid// should never happen// impossibleunknownanyobjectnull