Aller au contenu principal

ensure()

ensure<T>(schema, input): Result<T, string>

ensure<S>(schema, input): Result<Infer<S>, string>

Parses a value against a Kanon schema and returns a Zygos Result. Bridge between Kanon validation and Zygos error handling: instead of checking result.success, you get a chainable Result<T, string>.

💎 Why is this a Hidden Gem?

Validate with Kanon, get a Result from Zygos. One call bridges both worlds.


Type Parameters

T: T

The expected output type of the schema.

S: S extends GenericSchema


Parameters

Overload 1:

schema: Schema<T>

Kanon schema to validate against.

input: unknown

Value to validate.

Overload 2:

schema: S

input: unknown


Returns: Result<T, string>

Ok<T> if validation succeeds, Err<string> otherwise.


Since

2.1.0


Example

import { ensure } from "@pithos/core/bridges/ensure";
import { object, string, number } from "@pithos/core/kanon";

const schema = object({ name: string(), age: number() });

ensure(schema, data)
.map(user => user.name.toUpperCase())
.mapErr(error => `Validation failed: ${error}`);

Use Cases

Normalize user input after validation 📌

Validate a form payload and clean up the data in one chain. Essential for any form handler that needs both validation and normalization.

import { ensure } from "@pithos/core/bridges/ensure";
import { string, object } from "@pithos/core/kanon";
import { capitalize } from "@pithos/core/arkhe/string/capitalize";

const contactSchema = object({
firstName: string(),
lastName: string(),
email: string(),
});

function processForm(data: unknown) {
return ensure(contactSchema, data)
.map(contact => ({
...contact,
firstName: capitalize(contact.firstName),
lastName: capitalize(contact.lastName),
email: contact.email.toLowerCase(),
}))
.mapErr(error => `Please fix the form: ${error}`);
}

const result = processForm({ firstName: "john", lastName: "DOE", email: "John@Example.COM" });
// Ok({ firstName: "John", lastName: "Doe", email: "john@example.com" })

Enforce a business rule after schema validation 📌

Validate the shape first, then apply a domain constraint. The Result chain stays flat, no nested if/else. Perfect for order processing, booking systems, or any domain with conditional rules.

import { ensure } from "@pithos/core/bridges/ensure";
import { string, object, number } from "@pithos/core/kanon";
import { ok, err } from "@pithos/core/zygos/result/result";

const orderSchema = object({
productId: string(),
quantity: number(),
});

const inventory: Record<string, number> = { "SKU-001": 5, "SKU-002": 0 };

function placeOrder(data: unknown) {
return ensure(orderSchema, data)
.andThen(order => {
const stock = inventory[order.productId] ?? 0;
if (order.quantity > stock) {
return err(`Only ${stock} units left for ${order.productId}`);
}
return ok({ ...order, total: order.quantity * 29.99 });
});
}

placeOrder({ productId: "SKU-001", quantity: 3 });
// Ok({ productId: "SKU-001", quantity: 3, total: 89.97 })

placeOrder({ productId: "SKU-002", quantity: 1 });
// Err("Only 0 units left for SKU-002")

Branch explicitly on success or failure

Handle both outcomes in one expression. No forgotten error paths, no unchecked assumptions. Essential for request handlers that must always return a response.

import { ensure } from "@pithos/core/bridges/ensure";
import { string, object, number, optional } from "@pithos/core/kanon";

const configSchema = object({
host: string(),
port: number(),
debug: optional(string()),
});

function loadConfig(raw: unknown): string {
return ensure(configSchema, raw)
.match(
config => `Server ready at ${config.host}:${config.port}`,
error => `Invalid config: ${error}`,
);
}

loadConfig({ host: "localhost", port: 3000 });
// "Server ready at localhost:3000"

loadConfig({ host: "localhost" });
// "Invalid config: ..."

Fallback to defaults when validation fails

Validate environment variables at startup and fall back to safe defaults if anything is missing. Perfect for application bootstrap where crashing is not an option.

import { ensure } from "@pithos/core/bridges/ensure";
import { string, object, number, optional, coerceNumber } from "@pithos/core/kanon";

const envSchema = object({
DATABASE_URL: string(),
PORT: coerceNumber(),
LOG_LEVEL: optional(string()),
});

const config = ensure(envSchema, {
DATABASE_URL: process.env.DATABASE_URL,
PORT: process.env.PORT,
LOG_LEVEL: process.env.LOG_LEVEL,
}).unwrapOr({
DATABASE_URL: "postgres://localhost:5432/dev",
PORT: 3000,
LOG_LEVEL: undefined,
});

// config is always defined, even if env vars are missing
console.log(`Connecting to ${config.DATABASE_URL} on port ${config.PORT}`);

Validate multiple inputs in parallel

Validate independent inputs and combine the results. If any fails, the first error propagates. Essential for API handlers that receive headers, body, and params separately.

import { ensure } from "@pithos/core/bridges/ensure";
import { string, object, number } from "@pithos/core/kanon";
import { Result } from "@pithos/core/zygos/result/result";

const headerSchema = object({ authorization: string() });
const bodySchema = object({ title: string(), amount: number() });
const paramsSchema = object({ id: string() });

function handleRequest(headers: unknown, body: unknown, params: unknown) {
return Result.combine([
ensure(headerSchema, headers),
ensure(bodySchema, body),
ensure(paramsSchema, params),
]).map(([auth, payload, route]) => ({
token: auth.authorization,
title: payload.title,
amount: payload.amount,
id: route.id,
}));
}

handleRequest(
{ authorization: "Bearer abc" },
{ title: "Invoice", amount: 100 },
{ id: "inv-42" },
);
// Ok({ token: "Bearer abc", title: "Invoice", amount: 100, id: "inv-42" })

Validate overlay configuration before opening

Validate overlay options before creating the overlay to catch misconfigurations early. Essential for CDK-style overlay services that accept user-provided configuration.

import { ensure } from "@pithos/core/bridges/ensure";
import { string, object, number, boolean, optional } from "@pithos/core/kanon";

const overlayConfigSchema = object({
width: optional(string()),
height: optional(string()),
hasBackdrop: boolean(),
panelClass: optional(string()),
minWidth: optional(number()),
maxWidth: optional(number()),
});

function openOverlay(rawConfig: unknown) {
return ensure(overlayConfigSchema, rawConfig)
.map(config => ({
...config,
width: config.width ?? "auto",
hasBackdrop: config.hasBackdrop ?? true,
}))
.match(
config => overlayService.create(config),
error => console.error(`Invalid overlay config: ${error}`),
);
}

Validate stepper form data at each step

Validate the current step's form data before allowing navigation to the next step. Essential for multi-step wizard components with per-step validation.

import { ensure } from "@pithos/core/bridges/ensure";
import { string, object, number, optional } from "@pithos/core/kanon";

const stepSchemas = [
object({ email: string(), password: string() }),
object({ firstName: string(), lastName: string(), age: optional(number()) }),
object({ street: string(), city: string(), zip: string() }),
];

function validateStep(stepIndex: number, formData: unknown) {
const schema = stepSchemas[stepIndex];
if (!schema) return err("Invalid step index");

return ensure(schema, formData)
.match(
data => { goToStep(stepIndex + 1); return data; },
error => { showStepErrors(stepIndex, error); return null; },
);
}

nextButton.addEventListener("click", () => {
validateStep(currentStep, getFormData(currentStep));
});

Validate environment variables at CI startup

Validate all required environment variables before a CI pipeline runs. Essential for catching misconfigured deployments early.

import { ensure } from "@pithos/core/bridges/ensure";
import { string, object, optional } from "@pithos/core/kanon";

const envSchema = object({
DATABASE_URL: string(),
API_KEY: string(),
DEPLOY_TARGET: string(),
SENTRY_DSN: optional(string()),
});

const envResult = ensure(envSchema, {
DATABASE_URL: process.env.DATABASE_URL,
API_KEY: process.env.API_KEY,
DEPLOY_TARGET: process.env.DEPLOY_TARGET,
SENTRY_DSN: process.env.SENTRY_DSN,
});

envResult.match(
(env) => startPipeline(env),
(error) => {
console.error(`CI aborted: missing env vars - ${error}`);
process.exit(1);
},
);