Initial commit
This commit is contained in:
315
templates/validation-valibot.ts
Normal file
315
templates/validation-valibot.ts
Normal file
@@ -0,0 +1,315 @@
|
||||
/**
|
||||
* 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,
|
||||
}
|
||||
Reference in New Issue
Block a user