Validation with cast
cast turns unknown into a typed value by running it through a chain of validators, each returning a Result. The chain short-circuits on failure and yields Result<T, InvalidError> — parsing at the boundary, never as:
import {
cast, asObj, forProp,
asNum, asStr, asTime,
} from "plgg";
import type {
Num, Str, Time,
Result, InvalidError,
} from "plgg";
type UserProfile = {
id: Num;
email: Str;
createdAt: Time;
};
const asUserProfile = (
data: unknown,
): Result<UserProfile, InvalidError> =>
cast(
data,
asObj,
forProp("id", asNum),
forProp("email", asStr),
forProp("createdAt", asTime),
);Casters, props, and refinements
asXcasters —asNum,asStr,asTime,asObj, … each takeunknownand return aResult. They come from plgg's validated primitive types (see Structures & errors). Prefer the brandedStr(asStr) for string fields over the bareSoftStr.forProp(key, caster)validates one property and threads it onto the record;forOptionPropdoes the same but yields anOptionfor a field that may be absent.refine(predicate, message?)lifts an arbitrary predicate into a validator:
import { cast, asNum, refine } from "plgg";
const asPort = (data: unknown) =>
cast(
data,
asNum,
refine((n) => n > 0 && n < 65536, "out of range"),
);Error accumulation
When several forProp steps fail, cast does not stop at the first — it collects the per-field failures into the sibling array of a single InvalidError, so the caller sees every invalid field at once. An InvalidError is tagged data:
export type InvalidError = Box<
"InvalidError",
{
message: SoftStr;
sibling: ReadonlyArray<InvalidError>;
cause: Option<Cause>;
}
>;Need async steps too (an existence check against a DB, say)? Reach for proc, which composes both sync and async work.