Skip to content

Configuration Validation

bunfig provides comprehensive configuration validation through JSON Schema and custom validation rules, ensuring your configurations are correct and safe before use.

Overview

The validation system offers:

  • JSON Schema support for standard validation patterns
  • Custom validation rules for domain-specific requirements
  • Custom validation functions for complex business logic
  • Detailed error reporting with paths and context
  • Performance optimized validation with caching

JSON Schema Validation

Use standard JSON Schema for type and structure validation:

ts
import { loadConfig } from 'bunfig'

const schema = {
  type: 'object',
  properties: {
    server: {
      type: 'object',
      properties: {
        port: { type: 'number', minimum: 1, maximum: 65535 },
        host: { type: 'string', minLength: 1 },
        ssl: { type: 'boolean' }
      },
      required: ['port', 'host']
    },
    database: {
      type: 'object',
      properties: {
        url: { type: 'string', pattern: '^postgresql://' },
        pool: { type: 'number', minimum: 1, maximum: 100 }
      },
      required: ['url']
    }
  },
  required: ['server']
}

const result = await loadConfig({
  name: 'app',
  defaultConfig: {},
  schema
})

Supported JSON Schema Features

Type Validation

ts
const schema = {
  type: 'object',
  properties: {
    port: { type: 'number' },
    host: { type: 'string' },
    enabled: { type: 'boolean' },
    tags: { type: 'array' },
    metadata: { type: 'object' }
  }
}

String Validation

ts
const schema = {
  type: 'object',
  properties: {
    name: {
      type: 'string',
      minLength: 3,
      maxLength: 50,
      pattern: '^[a-zA-Z0-9-_]+$'
    },
    email: {
      type: 'string',
      pattern: '^[\\w-]+@[\\w-]+\\.[a-z]{2,}$'
    }
  }
}

Number Validation

ts
const schema = {
  type: 'object',
  properties: {
    port: {
      type: 'number',
      minimum: 1,
      maximum: 65535
    },
    timeout: {
      type: 'number',
      minimum: 0,
      maximum: 30000
    }
  }
}

Array Validation

ts
const schema = {
  type: 'object',
  properties: {
    tags: {
      type: 'array',
      items: { type: 'string' },
      minItems: 1,
      maxItems: 10
    },
    servers: {
      type: 'array',
      items: {
        type: 'object',
        properties: {
          host: { type: 'string' },
          port: { type: 'number' }
        },
        required: ['host', 'port']
      }
    }
  }
}

Enum Validation

ts
const schema = {
  type: 'object',
  properties: {
    environment: {
      type: 'string',
      enum: ['development', 'staging', 'production']
    },
    logLevel: {
      type: 'string',
      enum: ['error', 'warn', 'info', 'debug']
    }
  }
}

Custom Validation Rules

Use bunfig's built-in rule system for more flexible validation:

ts
import { loadConfig } from 'bunfig'

const rules = [
  {
    path: 'server.port',
    required: true,
    type: 'number' as const,
    min: 1,
    max: 65535,
    message: 'Server port must be between 1 and 65535'
  },
  {
    path: 'server.host',
    required: true,
    type: 'string' as const,
    min: 1,
    pattern: /^[a-z0-9.-]+$/i,
    message: 'Host must contain only letters, numbers, dots, and hyphens'
  },
  {
    path: 'database.pool',
    type: 'number' as const,
    min: 1,
    max: 100,
    message: 'Database pool size must be between 1 and 100'
  },
  {
    path: 'features',
    type: 'array' as const,
    enum: ['auth', 'logging', 'metrics', 'cache'],
    message: 'Features must be from the allowed list'
  }
]

const result = await loadConfig({
  name: 'app',
  defaultConfig: {},
  schema: rules
})

Rule Properties

ts
interface ValidationRule {
  /** Property path in dot notation */
  path: string

  /** Whether the property is required */
  required?: boolean

  /** Expected type */
  type?: 'string' | 'number' | 'boolean' | 'array' | 'object'

  /** Minimum value/length */
  min?: number

  /** Maximum value/length */
  max?: number

  /** Regular expression pattern for strings */
  pattern?: RegExp

  /** Allowed values */
  enum?: unknown[]

  /** Custom validation function */
  validator?: (value: unknown) => string | null

  /** Custom error message */
  message?: string
}

Custom Validation Functions

For complex validation logic, use custom validation functions:

ts
function customValidator(config: any) {
  const errors: string[] = []

  // Business logic validation
  if (config.ssl && !config.cert) {
    errors.push('SSL configuration requires a certificate')
  }

  if (config.database?.pool > config.database?.maxConnections) {
    errors.push('Database pool size cannot exceed max connections')
  }

  // Environment-specific validation
  if (config.environment === 'production') {
    if (!config.logging?.level || config.logging.level === 'debug') {
      errors.push('Production environment cannot use debug logging')
    }

    if (!config.security?.enabled) {
      errors.push('Security must be enabled in production')
    }
  }

  // Cross-field validation
  if (config.cache?.enabled && !config.cache?.ttl) {
    errors.push('Cache TTL is required when cache is enabled')
  }

  return errors.length > 0 ? errors : undefined
}

const result = await loadConfig({
  name: 'app',
  defaultConfig: {},
  validate: customValidator
})

ConfigValidator Service

Use the ConfigValidator service directly for advanced scenarios:

ts
import { ConfigValidator } from 'bunfig'

const validator = new ConfigValidator()

// Validate with JSON Schema
const schemaResult = await validator.validateConfiguration(
  config,
  jsonSchema,
  {
    validateRequired: true,
    validateTypes: true,
    stopOnFirstError: false
  }
)

// Validate with custom rules
const rulesResult = await validator.validateConfiguration(
  config,
  customRules,
  {
    stopOnFirstError: true
  }
)

// Check results
if (!schemaResult.isValid) {
  console.log('Schema validation errors:', schemaResult.errors)
}

Validation Options

ts
interface ValidationOptions {
  /** Whether to stop on first error */
  stopOnFirstError?: boolean

  /** Whether to validate required fields */
  validateRequired?: boolean

  /** Whether to validate types */
  validateTypes?: boolean

  /** Custom validation rules to add */
  customRules?: ValidationRule[]

  /** Track performance metrics */
  trackPerformance?: boolean

  /** Enable verbose logging */
  verbose?: boolean
}

Common Validation Patterns

Server Configuration

ts
const serverRules = [
  { path: 'port', required: true, type: 'number', min: 1, max: 65535 },
  { path: 'host', required: true, type: 'string', min: 1 },
  { path: 'ssl', type: 'boolean' },
  { path: 'cors.enabled', type: 'boolean' },
  { path: 'cors.origins', type: 'array' }
]

Database Configuration

ts
const databaseRules = [
  {
    path: 'url',
    required: true,
    type: 'string',
    pattern: /^(postgresql|mysql|sqlite):\/\//,
    message: 'Database URL must use a supported protocol'
  },
  { path: 'pool', type: 'number', min: 1, max: 100 },
  { path: 'timeout', type: 'number', min: 0 },
  { path: 'ssl', type: 'boolean' }
]

API Configuration

ts
const apiRules = [
  {
    path: 'baseUrl',
    required: true,
    type: 'string',
    pattern: /^https?:\/\//,
    message: 'Base URL must be a valid HTTP/HTTPS URL'
  },
  { path: 'timeout', type: 'number', min: 0 },
  { path: 'retries', type: 'number', min: 0, max: 10 },
  { path: 'rateLimit.enabled', type: 'boolean' },
  { path: 'rateLimit.requests', type: 'number', min: 1 }
]

Using Common Rules

bunfig provides pre-built validation rules for common patterns:

ts
import { ConfigValidator } from 'bunfig'

const commonRules = ConfigValidator.createCommonRules()

// Use server rules
const serverResult = await validator.validateConfiguration(
  serverConfig,
  commonRules.server
)

// Use database rules
const dbResult = await validator.validateConfiguration(
  databaseConfig,
  commonRules.database
)

// Use API rules
const apiResult = await validator.validateConfiguration(
  apiConfig,
  commonRules.api
)

Error Handling

Validation errors provide detailed information for debugging:

ts
import { ValidationError } from 'bunfig'

try {
  const result = await loadConfig({
    name: 'app',
    defaultConfig: {},
    schema: validationRules
  })
}
catch (error) {
  if (error instanceof ValidationError) {
    console.log(`Validation failed for ${error.context.configName}`)
    console.log(`Total errors: ${error.context.errors.length}`)

    error.context.errors.forEach((err) => {
      console.log(`- ${err.path}: ${err.message}`)
      console.log(`  Expected: ${err.expected}`)
      console.log(`  Actual: ${err.actual}`)
      console.log(`  Rule: ${err.rule}`)
    })
  }
}

Error Context

Validation errors include comprehensive context:

ts
interface ValidationError {
  path: string // Property path where error occurred
  message: string // Human-readable error message
  expected?: string // Expected value or type
  actual?: unknown // Actual value that failed
  rule?: string // Validation rule that failed
}

Performance Optimization

Validation Caching

Validation results are automatically cached based on configuration content:

ts
const result = await loadConfig({
  name: 'app',
  defaultConfig: {},
  schema: validationSchema,
  cache: {
    enabled: true,
    ttl: 5000 // Cache validation results for 5 seconds
  }
})

Optimized Rule Sets

Create optimized rule sets for better performance:

ts
// Separate critical from non-critical validations
const criticalRules = [
  { path: 'server.port', required: true, type: 'number' },
  { path: 'database.url', required: true, type: 'string' }
]

const nonCriticalRules = [
  { path: 'logging.level', type: 'string', enum: ['error', 'warn', 'info'] },
  { path: 'features', type: 'array' }
]

// Validate critical rules first with stopOnFirstError
const criticalResult = await validator.validateConfiguration(
  config,
  criticalRules,
  { stopOnFirstError: true }
)

if (criticalResult.isValid) {
  // Only validate non-critical if critical passes
  const fullResult = await validator.validateConfiguration(
    config,
    [...criticalRules, ...nonCriticalRules]
  )
}

TypeScript Integration

Generate validation rules from TypeScript interfaces:

ts
const interfaceCode = `
  interface ServerConfig {
    port: number
    host: string
    ssl?: boolean
    cors?: {
      enabled: boolean
      origins: string[]
    }
  }
`

const rules = validator.generateRulesFromInterface(interfaceCode)
const result = await validator.validateConfiguration(config, rules)

Best Practices

Schema Organization

Organize schemas by feature or module:

ts
// schemas/server.ts
export const serverSchema = {
  type: 'object',
  properties: {
    port: { type: 'number', minimum: 1, maximum: 65535 },
    host: { type: 'string', minLength: 1 }
  },
  required: ['port', 'host']
}

// schemas/database.ts
export const databaseSchema = {
  type: 'object',
  properties: {
    url: { type: 'string', pattern: '^postgresql://' },
    pool: { type: 'number', minimum: 1, maximum: 100 }
  },
  required: ['url']
}

// schemas/index.ts
export const appSchema = {
  type: 'object',
  properties: {
    server: serverSchema,
    database: databaseSchema
  }
}

Validation Composition

Compose validation rules for reusability:

ts
const baseServerRules = [
  { path: 'port', required: true, type: 'number', min: 1, max: 65535 },
  { path: 'host', required: true, type: 'string', min: 1 }
]

const httpsServerRules = [
  ...baseServerRules,
  { path: 'ssl', required: true, type: 'boolean' },
  { path: 'cert', required: true, type: 'string', min: 1 },
  { path: 'key', required: true, type: 'string', min: 1 }
]

const loadBalancedServerRules = [
  ...baseServerRules,
  { path: 'upstream', required: true, type: 'array', min: 1 },
  { path: 'strategy', required: true, type: 'string', enum: ['round-robin', 'least-conn'] }
]

Error Recovery

Implement graceful error recovery:

ts
async function loadConfigWithFallback(name: string, fallbackConfig: any) {
  try {
    return await loadConfig({
      name,
      defaultConfig: {},
      schema: validationSchema
    })
  }
  catch (error) {
    if (error instanceof ValidationError) {
      console.warn(`Validation failed for ${name}, using fallback config`)
      console.warn('Validation errors:', error.context.errors)
      return { config: fallbackConfig }
    }
    throw error
  }
}

Released under the MIT License.