or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-patterns.mdcustom-extensions.mdindex.mdresults-data.mdschema-validation.mdvalidation-chains.mdvalidators-sanitizers.md
tile.json

advanced-patterns.mddocs/

Advanced Validation Patterns

Complex validation scenarios including alternative validation, exact field checking, and conditional validation patterns for sophisticated validation logic.

Capabilities

Alternative Validation (oneOf)

Requires at least one of the provided validation chains to pass. Useful for scenarios where multiple validation approaches are acceptable.

/**
 * Creates middleware requiring at least one validation to pass
 * @param chains - Array of validation chains or nested arrays
 * @param options - Configuration options including message and error type
 * @returns ContextRunner for middleware execution
 */
function oneOf(
  chains: (ValidationChain | ValidationChain[])[],
  options?: OneOfOptions
): ContextRunner;

type OneOfOptions = {
  message?: AlternativeMessageFactory | ErrorMessage;
  errorType?: 'grouped' | 'least_errored' | 'flat';
};

type AlternativeMessageFactory = (nestedErrors: ValidationError[]) => any;

Usage Examples:

import { oneOf, body, query } from "express-validator";

// Either email or username is required
app.post('/login', [
  oneOf([
    body('email').isEmail(),
    body('username').isLength({ min: 3 })
  ], 'Either email or username is required'),
  body('password').notEmpty()
], loginHandler);

// Multiple contact methods
app.post('/contact', [
  oneOf([
    body('email').isEmail(),
    body('phone').isMobilePhone('any'),
    body('socialMedia').isURL()
  ], 'At least one contact method required')
], contactHandler);

// Flexible ID formats
app.get('/user/:id', [
  oneOf([
    param('id').isUUID(),
    param('id').isEmail(),
    param('id').isAlphanumeric().isLength({ min: 3, max: 20 })
  ], 'Invalid user identifier')
], getUserHandler);

Exact Field Validation (checkExact)

Ensures only specified fields are present in the request, preventing unexpected fields from being processed.

/**
 * Creates middleware ensuring only specified fields are present
 * @param knownFields - Array of allowed field names
 * @param options - Configuration options
 * @returns ContextRunner for middleware execution
 */
function checkExact(
  knownFields: string[],
  options?: {
    message?: string | UnknownFieldMessageFactory;
    locations?: Location[];
  }
): ContextRunner;

type UnknownFieldMessageFactory = (fields: UnknownFieldInstance[]) => any;

interface UnknownFieldInstance {
  path: string;
  location: Location;
}

Usage Examples:

import { checkExact, body } from "express-validator";

// Strict API endpoint - only allow specific fields
app.post('/api/user', [
  checkExact(['name', 'email', 'age'], {
    message: 'Only name, email, and age fields are allowed'
  }),
  body('name').trim().notEmpty(),
  body('email').isEmail(),
  body('age').isInt({ min: 18 })
], createUserHandler);

// Check specific locations only
app.put('/settings', [
  checkExact(['theme', 'language', 'notifications'], {
    locations: ['body'],
    message: 'Invalid settings field provided'
  }),
  body('theme').isIn(['light', 'dark']),
  body('language').isLocale(),
  body('notifications').isBoolean()
], updateSettingsHandler);

// Custom error message with field details
app.post('/product', [
  checkExact(['name', 'price', 'category'], {
    message: (unknownFields) => {
      const fieldNames = unknownFields.map(field => field.path);
      return `Unexpected fields: ${fieldNames.join(', ')}`;
    }
  })
], createProductHandler);

Conditional Validation

Apply validation rules conditionally based on request context or other field values.

interface ValidationChain {
  /**
   * Apply validation conditionally
   * @param condition - Function returning boolean for conditional application
   * @returns ValidationChain for continued chaining
   */
  if(condition: (value: any, meta: Meta) => boolean): ValidationChain;
}

Usage Examples:

import { body } from "express-validator";

// Conditional required fields
app.post('/order', [
  body('orderType').isIn(['pickup', 'delivery']),
  
  // Address required only for delivery
  body('address')
    .if((value, { req }) => req.body.orderType === 'delivery')
    .notEmpty()
    .withMessage('Address required for delivery orders'),
  
  // Phone required only for pickup
  body('phone')
    .if((value, { req }) => req.body.orderType === 'pickup')
    .isMobilePhone('any')
    .withMessage('Phone required for pickup orders')
], orderHandler);

// Conditional validation based on user role
app.post('/admin/user', [
  body('role').isIn(['user', 'admin', 'moderator']),
  
  // Admin-specific fields
  body('permissions')
    .if((value, { req }) => req.body.role === 'admin')
    .isArray({ min: 1 })
    .withMessage('Admin role requires permissions'),
  
  // Moderator-specific fields
  body('categories')
    .if((value, { req }) => req.body.role === 'moderator')
    .isArray({ min: 1 })
    .withMessage('Moderator role requires categories')
], createUserHandler);

Nested oneOf Validation

Complex alternative validation with nested validation groups.

// Payment method validation with nested alternatives
app.post('/payment', [
  body('method').isIn(['card', 'bank', 'wallet']),
  
  oneOf([
    // Credit card payment
    [
      body('method').equals('card'),
      body('cardNumber').isCreditCard(),
      body('expiryMonth').isInt({ min: 1, max: 12 }),
      body('expiryYear').isInt({ min: new Date().getFullYear() }),
      body('cvv').isLength({ min: 3, max: 4 })
    ],
    
    // Bank transfer
    [
      body('method').equals('bank'),
      body('bankCode').isLength({ min: 3, max: 11 }),
      body('accountNumber').isNumeric(),
      body('routingNumber').isLength({ min: 9, max: 9 })
    ],
    
    // Digital wallet
    [
      body('method').equals('wallet'),
      body('walletId').isUUID(),
      body('pin').isLength({ min: 4, max: 6 }).isNumeric()
    ]
  ], 'Invalid payment information for selected method')
], paymentHandler);

Complex Field Dependencies

Validate fields that depend on combinations of other fields.

app.post('/shipping', [
  body('method').isIn(['standard', 'express', 'overnight']),
  body('weight').isFloat({ min: 0.1 }),
  body('dimensions.length').isFloat({ min: 1 }),
  body('dimensions.width').isFloat({ min: 1 }),
  body('dimensions.height').isFloat({ min: 1 }),
  
  // Express shipping restrictions
  body('weight')
    .if((value, { req }) => {
      return req.body.method === 'express' && value > 50;
    })
    .custom(() => {
      throw new Error('Express shipping limited to 50lbs');
    }),
  
  // Overnight shipping restrictions
  oneOf([
    [
      body('method').not().equals('overnight')
    ],
    [
      body('method').equals('overnight'),
      body('weight').isFloat({ max: 25 }),
      body('dimensions.length').isFloat({ max: 24 }),
      body('dimensions.width').isFloat({ max: 18 }),
      body('dimensions.height').isFloat({ max: 12 })
    ]
  ], 'Overnight shipping has size/weight restrictions')
], shippingHandler);

Validation with Side Effects

Perform validation while maintaining state or making external calls.

app.post('/reservation', [
  body('eventId').isUUID(),
  body('attendees').isInt({ min: 1, max: 10 }),
  
  // Check availability with side effects
  body('eventId').custom(async (eventId, { req }) => {
    const event = await Event.findById(eventId);
    if (!event) {
      throw new Error('Event not found');
    }
    
    const requestedAttendees = req.body.attendees;
    if (event.availableSpots < requestedAttendees) {
      throw new Error(`Only ${event.availableSpots} spots available`);
    }
    
    // Store event in request for later use
    req.event = event;
    return true;
  }),
  
  // Validate based on stored event data
  body('attendees').custom((attendees, { req }) => {
    if (req.event && req.event.maxPerReservation < attendees) {
      throw new Error(`Maximum ${req.event.maxPerReservation} attendees per reservation`);
    }
    return true;
  })
], reservationHandler);

Error Aggregation Patterns

Handle complex error scenarios with custom error formatting.

// Custom error handling for oneOf
app.post('/flexible-auth', [
  oneOf([
    body('email').isEmail(),
    body('phone').isMobilePhone('any'),
    body('username').isAlphanumeric().isLength({ min: 3 })
  ], (nestedErrors) => {
    // Custom error message based on what was attempted
    const attempts = nestedErrors.map(error => error.path).join(', ');
    return `Invalid credentials. Tried validating: ${attempts}`;
  }),
  
  body('password').isLength({ min: 6 })
], authHandler);

// Exact validation with detailed error reporting
app.post('/strict-api', [
  checkExact(['name', 'email'], {
    message: (unknownFields) => {
      const fields = unknownFields.map(field => 
        `${field.path} (in ${field.location})`
      );
      return {
        error: 'UNEXPECTED_FIELDS',
        message: 'Request contains unexpected fields',
        fields: fields
      };
    }
  })
], strictApiHandler);