Recipes
Copy-paste patterns for forms, APIs, Rust services, and cross-field rules.
Start here when you already know the job. Each recipe is the smallest useful shape.
Contact Form Schema
import { w, type Infer } from "wellformed-ts";
export const Contact = w.object({
firstName: w.string().trim().minLen(1),
email: w.string().trim().lower().email(),
phone: w.string().phoneUS().optional(),
}).strict();
export type Contact = Infer<typeof Contact>;
export const contactSchema = Contact.toSchema("1.0");Use Contact while authoring and contactSchema anywhere validation happens.
Validate Unknown Input
import type { Schema } from "wellformed-ts/ir";
import { validate } from "wellformed-ts/runtime";
import type { Contact } from "./contact";
export function parseContact(schema: Schema, input: unknown) {
const result = validate(schema, input);
if (!result.valid) {
return { ok: false as const, errors: result.errors };
}
return { ok: true as const, value: result.value as Contact };
}Use result.value. It contains trimmed, lowercased, defaulted, and stripped
output.
Cross-Field Business Rule
const Taxpayer = w.object({
kind: w.enum(["individual", "business"] as const),
ssn: w.string().digitsOnly().ssn().optional(),
ein: w.string().digitsOnly().ein().optional(),
})
.strict()
.when("kind").equals("individual").require("ssn")
.when("kind").equals("business").require("ein")
.mutuallyExclusive("ssn", "ein");The rule serializes with the schema. No hidden .refine() closure.
Store Or Transmit A Schema
import { parseSchema, schemaToJSON } from "wellformed-ts/serialize";
const json = schemaToJSON(contactSchema, true);
const restored = parseSchema(json);Store your own app schema id and version beside the wellformed IR.
{
"schema_id": "contact",
"schema_version": 3,
"wellformed_ir": {}
}React Hook Form Resolver
import type { Resolver } from "react-hook-form";
import type { Schema } from "wellformed-ts/ir";
import { validate } from "wellformed-ts/runtime";
export function wellformedResolver(schema: Schema): Resolver {
return async (values) => {
const result = validate(schema, values);
if (result.valid) {
return { values: result.value as Record<string, unknown>, errors: {} };
}
return {
values: {},
errors: Object.fromEntries(
result.errors.map((error) => [
error.path.replace(/^\//, "").replace(/\//g, "."),
{ type: error.code, message: error.message },
]),
),
};
};
}Use stable error code values for UI logic and localization.
Rust Embedded Schema
use serde_json::json;
use wellformed_macros::wellformed;
const CONTACT: wellformed::EmbeddedSchema = wellformed!("schemas/contact.json");
fn validate_contact() -> wellformed::Result<()> {
let (result, value) = CONTACT.validate_json(r#"{
"firstName": " Ada ",
"email": "ADA@EXAMPLE.COM"
}"#)?;
assert!(result.is_valid());
assert_eq!(value["firstName"], json!("Ada"));
assert_eq!(value["email"], json!("ada@example.com"));
Ok(())
}Use this when Rust validates the same JSON IR but does not need generated structs.
Axum Boundary
use axum::{extract::State, http::StatusCode, Json};
use serde_json::{json, Value};
type ApiError = (StatusCode, Json<Value>);
async fn create_contact(
State(state): State<AppState>,
Json(mut body): Json<Value>,
) -> Result<Json<Contact>, ApiError> {
let result = wellformed::validate(&state.contact_schema, &mut body).map_err(|error| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({ "error": error.to_string() })),
)
})?;
if !result.is_valid() {
return Err((
StatusCode::UNPROCESSABLE_ENTITY,
Json(json!({ "errors": result.errors })),
));
}
let contact = serde_json::from_value(body).map_err(|error| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({ "error": error.to_string() })),
)
})?;
Ok(Json(contact))
}Validate serde_json::Value first, then deserialize into your Rust type. See
Axum for the complete handler.