Files
gh-jezweb-claude-skills-ski…/templates/validation-valibot.ts
2025-11-30 08:24:59 +08:00

316 lines
8.4 KiB
TypeScript

/**
* Hono Validation with Valibot
*
* Complete examples for request validation using Valibot validator.
* Valibot is lighter and faster than Zod, with modular imports.
*/
import { Hono } from 'hono'
import { vValidator } from '@hono/valibot-validator'
import * as v from 'valibot'
import { HTTPException } from 'hono/http-exception'
const app = new Hono()
// ============================================================================
// BASIC VALIDATION
// ============================================================================
// JSON body validation
const userSchema = v.object({
name: v.pipe(v.string(), v.minLength(1), v.maxLength(100)),
email: v.pipe(v.string(), v.email()),
age: v.optional(v.pipe(v.number(), v.integer(), v.minValue(18))),
})
app.post('/users', vValidator('json', userSchema), (c) => {
const data = c.req.valid('json') // Type-safe!
return c.json({
success: true,
data,
})
})
// ============================================================================
// QUERY PARAMETER VALIDATION
// ============================================================================
const searchSchema = v.object({
q: v.pipe(v.string(), v.minLength(1)),
page: v.pipe(v.string(), v.transform(Number), v.number(), v.integer(), v.minValue(1)),
limit: v.optional(
v.pipe(v.string(), v.transform(Number), v.number(), v.integer(), v.minValue(1), v.maxValue(100)),
'10'
),
sort: v.optional(v.picklist(['name', 'date', 'relevance'])),
})
app.get('/search', vValidator('query', searchSchema), (c) => {
const { q, page, limit, sort } = c.req.valid('query')
return c.json({
query: q,
page,
limit,
sort,
results: [],
})
})
// ============================================================================
// ROUTE PARAMETER VALIDATION
// ============================================================================
// UUID parameter
const uuidParamSchema = v.object({
id: v.pipe(v.string(), v.uuid()),
})
app.get('/users/:id', vValidator('param', uuidParamSchema), (c) => {
const { id } = c.req.valid('param')
return c.json({
userId: id,
name: 'John Doe',
})
})
// Numeric ID parameter
const numericIdSchema = v.object({
id: v.pipe(v.string(), v.transform(Number), v.number(), v.integer(), v.minValue(1)),
})
app.get('/posts/:id', vValidator('param', numericIdSchema), (c) => {
const { id } = c.req.valid('param') // Type: number
return c.json({
postId: id,
title: 'Post Title',
})
})
// ============================================================================
// HEADER VALIDATION
// ============================================================================
const headerSchema = v.object({
'authorization': v.pipe(v.string(), v.startsWith('Bearer ')),
'content-type': v.literal('application/json'),
'x-api-version': v.optional(v.picklist(['1.0', '2.0'])),
})
app.post('/auth', vValidator('header', headerSchema), (c) => {
const headers = c.req.valid('header')
return c.json({
authenticated: true,
apiVersion: headers['x-api-version'] || '1.0',
})
})
// ============================================================================
// CUSTOM VALIDATION HOOKS
// ============================================================================
const customErrorSchema = v.object({
email: v.pipe(v.string(), v.email()),
age: v.pipe(v.number(), v.integer(), v.minValue(18)),
})
app.post(
'/register',
vValidator('json', customErrorSchema, (result, c) => {
if (!result.success) {
return c.json(
{
error: 'Validation failed',
issues: result.issues,
},
400
)
}
}),
(c) => {
const data = c.req.valid('json')
return c.json({ success: true, data })
}
)
// ============================================================================
// COMPLEX SCHEMA VALIDATION
// ============================================================================
const addressSchema = v.object({
street: v.string(),
city: v.string(),
zipCode: v.pipe(v.string(), v.regex(/^\d{5}$/)),
country: v.pipe(v.string(), v.length(2)),
})
const profileSchema = v.object({
name: v.string(),
email: v.pipe(v.string(), v.email()),
address: addressSchema,
tags: v.pipe(v.array(v.string()), v.minLength(1), v.maxLength(5)),
})
app.post('/profile', vValidator('json', profileSchema), (c) => {
const data = c.req.valid('json')
return c.json({
success: true,
profile: data,
})
})
// ============================================================================
// DISCRIMINATED UNION
// ============================================================================
const paymentSchema = v.variant('method', [
v.object({
method: v.literal('card'),
cardNumber: v.pipe(v.string(), v.regex(/^\d{16}$/)),
cvv: v.pipe(v.string(), v.regex(/^\d{3}$/)),
}),
v.object({
method: v.literal('paypal'),
email: v.pipe(v.string(), v.email()),
}),
v.object({
method: v.literal('bank'),
accountNumber: v.string(),
routingNumber: v.string(),
}),
])
app.post('/payment', vValidator('json', paymentSchema), (c) => {
const payment = c.req.valid('json')
if (payment.method === 'card') {
console.log('Processing card payment:', payment.cardNumber)
}
return c.json({ success: true })
})
// ============================================================================
// TRANSFORMATIONS
// ============================================================================
const eventSchema = v.object({
name: v.string(),
startDate: v.pipe(v.string(), v.transform((val) => new Date(val))),
endDate: v.pipe(v.string(), v.transform((val) => new Date(val))),
capacity: v.pipe(v.string(), v.transform(Number), v.number(), v.integer(), v.minValue(1)),
})
app.post('/events', vValidator('json', eventSchema), (c) => {
const event = c.req.valid('json')
return c.json({
success: true,
event: {
...event,
startDate: event.startDate.toISOString(),
endDate: event.endDate.toISOString(),
},
})
})
// ============================================================================
// OPTIONAL AND DEFAULT VALUES
// ============================================================================
const settingsSchema = v.object({
theme: v.optional(v.picklist(['light', 'dark']), 'light'),
notifications: v.optional(v.boolean()),
language: v.optional(v.string(), 'en'),
maxResults: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1), v.maxValue(100)), 10),
})
app.post('/settings', vValidator('json', settingsSchema), (c) => {
const settings = c.req.valid('json')
return c.json({
success: true,
settings,
})
})
// ============================================================================
// ARRAY VALIDATION
// ============================================================================
const batchCreateSchema = v.object({
users: v.pipe(
v.array(
v.object({
name: v.string(),
email: v.pipe(v.string(), v.email()),
})
),
v.minLength(1),
v.maxLength(100)
),
})
app.post('/batch-users', vValidator('json', batchCreateSchema), (c) => {
const { users } = c.req.valid('json')
return c.json({
success: true,
count: users.length,
users,
})
})
// ============================================================================
// PARTIAL SCHEMAS
// ============================================================================
const updateUserSchema = v.partial(userSchema)
app.patch('/users/:id', vValidator('json', updateUserSchema), (c) => {
const updates = c.req.valid('json')
return c.json({
success: true,
updated: updates,
})
})
// ============================================================================
// UNION TYPES
// ============================================================================
const contentSchema = v.variant('type', [
v.object({ type: v.literal('text'), content: v.string() }),
v.object({ type: v.literal('image'), url: v.pipe(v.string(), v.url()) }),
v.object({ type: v.literal('video'), videoId: v.string() }),
])
app.post('/content', vValidator('json', contentSchema), (c) => {
const content = c.req.valid('json')
return c.json({
success: true,
contentType: content.type,
})
})
// ============================================================================
// EXPORT
// ============================================================================
export default app
export {
userSchema,
searchSchema,
uuidParamSchema,
profileSchema,
paymentSchema,
}