or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

authentication.mdcaching.mdextensions.mdindex.mdplugin-system.mdrequest-processing.mdresponse-management.mdrouting.mdserver-management.mdvalidation.md
tile.json

validation.mddocs/

Validation

Input validation system with support for Joi and other validation libraries for request payload, query parameters, and headers in @hapi/hapi.

Capabilities

Validation Configuration

Configure validation for different parts of the HTTP request including payload, query parameters, path parameters, and headers.

interface ValidationOptions {
    /** Validate request headers */
    headers?: object | boolean;
    /** Validate path parameters */
    params?: object | boolean;
    /** Validate query parameters */
    query?: object | boolean;
    /** Validate request payload */
    payload?: object | boolean;
    /** Validate cookie state */
    state?: object | boolean;
    /** Action to take when validation fails */
    failAction?: ValidationFailAction;
    /** Validation library options */
    options?: object;
    /** Custom error messages */
    errorFields?: object;
}

type ValidationFailAction = 'error' | 'log' | 'ignore' | Function;

Payload Validation

Validate request payload data with comprehensive schema definitions.

// Payload validation using Joi schema
const Joi = require('joi');

interface PayloadValidationSchema {
    /** Required fields validation */
    [key: string]: any;
}

Usage Examples:

const Joi = require('joi');

// Basic payload validation
server.route({
    method: 'POST',
    path: '/users',
    options: {
        validate: {
            payload: Joi.object({
                name: Joi.string().min(2).max(50).required(),
                email: Joi.string().email().required(),
                age: Joi.number().integer().min(18).max(120).optional(),
                address: Joi.object({
                    street: Joi.string().required(),
                    city: Joi.string().required(),
                    zipCode: Joi.string().pattern(/^\d{5}$/).required()
                }).optional()
            })
        }
    },
    handler: async (request, h) => {
        const user = await database.createUser(request.payload);
        return h.response(user).code(201);
    }
});

// Complex payload validation
server.route({
    method: 'POST',
    path: '/orders',
    options: {
        validate: {
            payload: Joi.object({
                customerId: Joi.string().uuid().required(),
                items: Joi.array().items(
                    Joi.object({
                        productId: Joi.string().uuid().required(),
                        quantity: Joi.number().integer().min(1).required(),
                        price: Joi.number().precision(2).positive().required()
                    })
                ).min(1).required(),
                shippingAddress: Joi.object({
                    name: Joi.string().required(),
                    street: Joi.string().required(),
                    city: Joi.string().required(),
                    state: Joi.string().length(2).required(),
                    zipCode: Joi.string().pattern(/^\d{5}(-\d{4})?$/).required()
                }).required(),
                paymentMethod: Joi.string().valid('credit_card', 'paypal', 'bank_transfer').required()
            })
        }
    },
    handler: async (request, h) => {
        const order = await orderService.createOrder(request.payload);
        return h.response(order).code(201);
    }
});

Query Parameter Validation

Validate URL query string parameters with type conversion and constraints.

Usage Examples:

// Query parameter validation
server.route({
    method: 'GET',
    path: '/users',
    options: {
        validate: {
            query: Joi.object({
                page: Joi.number().integer().min(1).default(1),
                limit: Joi.number().integer().min(1).max(100).default(10),
                sort: Joi.string().valid('name', 'email', 'created_at').default('created_at'),
                order: Joi.string().valid('asc', 'desc').default('desc'),
                search: Joi.string().min(2).max(50).optional(),
                active: Joi.boolean().default(true),
                role: Joi.array().items(Joi.string().valid('admin', 'user', 'moderator')).single().optional()
            })
        }
    },
    handler: async (request, h) => {
        const { page, limit, sort, order, search, active, role } = request.query;
        const users = await database.getUsers({
            page,
            limit,
            sort,
            order,
            search,
            active,
            role
        });
        return users;
    }
});

// Advanced query validation with custom logic
server.route({
    method: 'GET',
    path: '/analytics',
    options: {
        validate: {
            query: Joi.object({
                startDate: Joi.date().iso().required(),
                endDate: Joi.date().iso().min(Joi.ref('startDate')).required(),
                metrics: Joi.array().items(
                    Joi.string().valid('views', 'clicks', 'conversions', 'revenue')
                ).min(1).required(),
                groupBy: Joi.string().valid('day', 'week', 'month').default('day'),
                timezone: Joi.string().default('UTC')
            }).custom((value, helpers) => {
                const { startDate, endDate } = value;
                const daysDiff = (new Date(endDate) - new Date(startDate)) / (1000 * 60 * 60 * 24);
                
                if (daysDiff > 365) {
                    return helpers.error('custom.dateRange', { maxDays: 365 });
                }
                
                return value;
            })
        }
    },
    handler: async (request, h) => {
        const analytics = await analyticsService.getMetrics(request.query);
        return analytics;
    }
});

Path Parameter Validation

Validate URL path parameters with type checking and format validation.

Usage Examples:

// Path parameter validation
server.route({
    method: 'GET',
    path: '/users/{id}',
    options: {
        validate: {
            params: Joi.object({
                id: Joi.string().uuid().required()
            })
        }
    },
    handler: async (request, h) => {
        const user = await database.getUser(request.params.id);
        if (!user) {
            return h.response({ error: 'User not found' }).code(404);
        }
        return user;
    }
});

// Multiple path parameters
server.route({
    method: 'GET',
    path: '/users/{userId}/posts/{postId}',
    options: {
        validate: {
            params: Joi.object({
                userId: Joi.string().uuid().required(),
                postId: Joi.alternatives().try(
                    Joi.string().uuid(),
                    Joi.string().alphanum().min(5).max(20)
                ).required()
            })
        }
    },
    handler: async (request, h) => {
        const { userId, postId } = request.params;
        const post = await database.getUserPost(userId, postId);
        return post;
    }
});

// Path parameter with custom validation
server.route({
    method: 'GET',
    path: '/files/{path*}',
    options: {
        validate: {
            params: Joi.object({
                path: Joi.string().custom((value, helpers) => {
                    // Prevent directory traversal
                    if (value.includes('..') || value.includes('~')) {
                        return helpers.error('custom.invalidPath');
                    }
                    return value;
                }).required()
            })
        }
    },
    handler: async (request, h) => {
        const filePath = path.join('./uploads', request.params.path);
        return h.file(filePath);
    }
});

Header Validation

Validate HTTP headers including custom headers and standard headers.

Usage Examples:

// Header validation
server.route({
    method: 'POST',
    path: '/api/data',
    options: {
        validate: {
            headers: Joi.object({
                'content-type': Joi.string().valid('application/json').required(),
                'x-api-key': Joi.string().alphanum().length(32).required(),
                'user-agent': Joi.string().required(),
                'x-request-id': Joi.string().uuid().optional(),
                'x-client-version': Joi.string().pattern(/^\d+\.\d+\.\d+$/).optional()
            }).unknown(true) // Allow other headers
        }
    },
    handler: async (request, h) => {
        const apiKey = request.headers['x-api-key'];
        const requestId = request.headers['x-request-id'];
        
        // Process with validated headers
        const result = await processData(request.payload, { apiKey, requestId });
        return result;
    }
});

State (Cookie) Validation

Validate cookie values and state information.

Usage Examples:

// State validation
server.route({
    method: 'GET',
    path: '/dashboard',
    options: {
        validate: {
            state: Joi.object({
                session: Joi.object({
                    userId: Joi.string().uuid().required(),
                    role: Joi.string().valid('admin', 'user').required(),
                    expires: Joi.date().timestamp().required()
                }).required(),
                preferences: Joi.object({
                    theme: Joi.string().valid('light', 'dark').default('light'),
                    language: Joi.string().length(2).default('en')
                }).optional()
            })
        }
    },
    handler: async (request, h) => {
        const { session, preferences } = request.state;
        const dashboard = await getDashboard(session.userId, preferences);
        return dashboard;
    }
});

Custom Validation Functions

Create custom validation logic for complex scenarios.

Usage Examples:

// Custom validation function
const validateBusinessRules = (value, helpers) => {
    const { startDate, endDate, eventType } = value;
    
    // Business rule: Premium events require at least 30 days notice
    if (eventType === 'premium') {
        const daysNotice = (new Date(startDate) - new Date()) / (1000 * 60 * 60 * 24);
        if (daysNotice < 30) {
            return helpers.error('custom.premiumEventNotice', { minDays: 30 });
        }
    }
    
    // Business rule: Events cannot span more than 7 days
    const eventDuration = (new Date(endDate) - new Date(startDate)) / (1000 * 60 * 60 * 24);
    if (eventDuration > 7) {
        return helpers.error('custom.eventTooLong', { maxDays: 7 });
    }
    
    return value;
};

server.route({
    method: 'POST',
    path: '/events',
    options: {
        validate: {
            payload: Joi.object({
                title: Joi.string().min(5).max(100).required(),
                description: Joi.string().max(1000).optional(),
                startDate: Joi.date().iso().min('now').required(),
                endDate: Joi.date().iso().min(Joi.ref('startDate')).required(),
                eventType: Joi.string().valid('basic', 'premium').required(),
                attendeeLimit: Joi.number().integer().min(1).max(1000).required()
            }).custom(validateBusinessRules)
        }
    },
    handler: async (request, h) => {
        const event = await eventService.createEvent(request.payload);
        return h.response(event).code(201);
    }
});

Validation Error Handling

Handle validation errors with custom error messages and responses.

interface ValidationFailActionFunction {
    (request: Request, h: ResponseToolkit, error: Error): any;
}

Usage Examples:

// Custom validation error handler
const customValidationHandler = (request, h, error) => {
    const details = error.details.map(detail => ({
        field: detail.path.join('.'),
        message: detail.message,
        value: detail.context?.value
    }));
    
    return h.response({
        error: 'Validation Failed',
        statusCode: 400,
        details: details,
        timestamp: new Date().toISOString(),
        path: request.path
    }).code(400).takeover();
};

server.route({
    method: 'POST',
    path: '/users',
    options: {
        validate: {
            payload: Joi.object({
                name: Joi.string().min(2).required(),
                email: Joi.string().email().required(),
                password: Joi.string().min(8).required()
            }),
            failAction: customValidationHandler
        }
    },
    handler: async (request, h) => {
        const user = await userService.createUser(request.payload);
        return h.response(user).code(201);
    }
});

// Different fail actions per validation type
server.route({
    method: 'GET',
    path: '/search',
    options: {
        validate: {
            query: Joi.object({
                q: Joi.string().min(2).required(),
                category: Joi.string().valid('all', 'posts', 'users').default('all')
            }),
            failAction: 'log' // Just log query validation errors, don't fail request
        }
    },
    handler: async (request, h) => {
        // Will use default values for invalid query params
        const results = await searchService.search(request.query);
        return results;
    }
});

Server-Level Validation Configuration

Set global validation settings and custom validators.

/**
 * Set validation engine for the server
 * @param validator - Validation library (Joi, etc.)
 */
validator(validator: object): void;

/**
 * Set validation rules processor
 * @param processor - Rules processing function
 * @param options - Processor options
 */
rules(processor: Function, options?: object): void;

Usage Examples:

const Joi = require('joi');

// Set global validation engine
server.validator(Joi);

// Custom validation rules
server.rules((schema, options) => {
    // Custom preprocessing of validation schemas
    return schema.options({
        allowUnknown: options.allowUnknown !== false,
        stripUnknown: true,
        abortEarly: false
    });
});

// Global validation defaults in server options
const server = Hapi.server({
    port: 3000,
    routes: {
        validate: {
            options: {
                abortEarly: false,
                stripUnknown: true
            },
            failAction: (request, h, error) => {
                console.log('Validation error:', error.details);
                throw error;
            }
        }
    }
});

Types

interface ValidationError extends Error {
    /** Validation error details */
    details: ValidationErrorDetail[];
    /** Whether error is a validation error */
    isJoi: boolean;
}

interface ValidationErrorDetail {
    /** Error message */
    message: string;
    /** Field path that failed validation */
    path: (string | number)[];
    /** Validation error type */
    type: string;
    /** Validation context */
    context?: {
        value?: any;
        key?: string;
        label?: string;
        [key: string]: any;
    };
}

interface ValidateRouteOptions {
    /** Header validation schema */
    headers?: object | boolean;
    /** Parameter validation schema */
    params?: object | boolean;
    /** Query validation schema */
    query?: object | boolean;
    /** Payload validation schema */
    payload?: object | boolean;
    /** State validation schema */
    state?: object | boolean;
    /** Validation failure action */
    failAction?: ValidationFailAction;
    /** Validation options */
    options?: object;
}