@nerd-bible/valio
v0.0.12
Published
Encode and decode Typescript types with extensible error handling.
Downloads
18
Readme
valio
Encode and decode Typescript types with extensible error handling.
Why?
I like Zod, but its codecs don't support custom error contexts. I tried adding support to Zod but found it easier to start from scratch.
Theory
- Data flows through "pipes" with specific input and output types.
- Both input and output are validated
- Pipes are bidirectional.
- Input -> output is "decoding"
- Output -> input is "encoding"
- When encoding or decoding a
Contextis passed which holds:- The path to the current value
- Error formatting function
- Errors grouped by path
Practice
Use classes, inheritance, and result types. No errors are thrown.
Usage
For primitive types the input and output are the same.
import * as v from "@nerd-bible/valio";
const schema = v.number(); // Pipe<number, number>
expect(schema.decode(5)).toEqual({ success: true, output: 5 });
expect(schema.decodeAny("5")).toEqual({
success: false,
errors: { ".": [{ input: "5", message: "not type number" }] },
});You can add custom checks, but be sure to provide context for error messages.
import * as v from "@nerd-bible/valio";
const schema = v.number().refine((n) => n == 5, "eq", { n: 5 });
expect(schema.decode(5)).toEqual({ success: true, output: 5 });
expect(schema.encode(3)).toEqual({
success: false,
errors: { ".": [{ input: 3, message: "must be 5" }] },
});There are common builtin codecs for coercion.
import * as v from "@nerd-bible/valio";
const schema = v.codecs.number();
expect(schema.decode("13")).toEqual({ success: true, output: 13 });Extending
To make a codec with a custom transformer, you can use v.codecs.custom.
import * as v from "@nerd-bible/valio";
function transformToNumber(any: number): number {
if (typeof any == "number") return any;
return NaN;
}
const schema = v.codecs.custom(
v.union([v.string(), v.number(), v.null(), v.undefined()]),
v.number(),
{ decode: transformToNumber },
);To make a pipe with any types and transformers, simply extend the class with
your input and output HalfPipes.
import { Pipe, HalfPipe } from "@nerd-bible/valio";
function transformToNumber(any: number): number {
if (typeof any == "number") return any;
return NaN;
}
class AnyToNumber extends Pipe<any, number> {
constructor() {
super(
new HalfPipe("any", (v): v is any => true, transformToNumber),
new HalfPipe("number", (v): v is number => typeof v == "number"),
);
}
}Custom error messages
Override errorFmt in Context and pass it to encode and decode.
import * as v from "@nerd-bible/valio";
const schema = v.number().refine((n) => n == 5, "eq", { n: 5 });
class MyContext extends v.Context {
errorFmt() {
return "You done messed up";
}
}
expect(schema.decode(3, new MyContext())).toEqual({
success: false,
errors: { ".": [{ input: 3, message: "You done messed up" }] },
});