Schema validation

ctx.body.json(schema) accepts three validator shapes, detected in this order. All three throw IngeniumValidationError with a fields: Record<string, string> map on failure.

The three accepted shapes

// 1. Standard Schema v1 (any validator with ["~standard"])
import { type } from 'arktype'
const User = type({ name: 'string', email: 'string' })
app.post('/users', async (ctx) => ctx.body.json(User))

// 2. Zod-like safeParse
import { z } from 'zod'
const User = z.object({ name: z.string(), email: z.string().email() })
app.post('/users', async (ctx) => ctx.body.json(User))

// 3. Plain { parse(input): T }
const User = {
  parse(input: unknown): { name: string } {
    if (typeof input !== 'object') throw new Error('expected object')
    return input as { name: string }
  },
}
app.post('/users', async (ctx) => ctx.body.json(User))

Failure shape

All three throw IngeniumValidationError (HTTP 422) with:

  • message: string - a summary.
  • fields: Record<string, string> - field-path to error-message map.

Standard Schema v1 issues with structured paths are dot-joined (['user', 'email']'user.email').

Catching validation errors

app.onError((err, ctx) => {
  if (err instanceof IngeniumValidationError) {
    return ctx.json({ error: err.message, fields: err.fields }, 422)
  }
  throw err
})

Known gaps

  • ctx.query.parse(schema) does not exist yet - only body validation has the schema affordance. Tracked in the roadmap.
  • ExtractParams does not narrow constrained params (:id(\\d+) stays string).
  • A TypeBox-specific bridge is deferred - Standard Schema covers it today, but a tighter integration may land later.

Where to next?