Context-Aware Validation

Context-Aware Validation

Validation requirements often depend on application context:

class ContextualValidator
  CONTEXTS = {
    registration: {
      username: { pattern: /\A[a-zA-Z0-9_]{3,20}\z/, required: true },
      email: { pattern: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i, required: true },
      password: { min_length: 8, max_length: 100, required: true },
      age: { type: :integer, min: 13, max: 120, required: false }
    },
    search: {
      query: { max_length: 200, sanitize: true, required: true },
      category: { enum: ['products', 'users', 'orders'], required: false },
      limit: { type: :integer, min: 1, max: 100, default: 20 }
    },
    payment: {
      amount: { type: :decimal, min: 0.01, max: 999999.99, required: true },
      currency: { enum: ['USD', 'EUR', 'GBP'], required: true },
      card_token: { pattern: /\A[a-zA-Z0-9_\-]+\z/, required: true }
    }
  }
  
  def self.validate(context, params)
    rules = CONTEXTS[context]
    raise ArgumentError, "Unknown validation context: #{context}" unless rules
    
    validated = {}
    errors = {}
    
    rules.each do |field, rule|
      value = params[field]
      
      # Check required fields
      if rule[:required] && value.nil?
        errors[field] = "is required"
        next
      elsif value.nil?
        validated[field] = rule[:default]
        next
      end
      
      # Type validation
      begin
        validated[field] = validate_field(value, rule)
      rescue => e
        errors[field] = e.message
      end
    end
    
    raise ValidationError.new(errors) if errors.any?
    validated
  end
  
  private
  
  def self.validate_field(value, rule)
    # Pattern matching
    if rule[:pattern] && !value.match?(rule[:pattern])
      raise "invalid format"
    end
    
    # Enum validation  
    if rule[:enum] && !rule[:enum].include?(value)
      raise "must be one of: #{rule[:enum].join(', ')}"
    end
    
    # Type conversion and validation
    case rule[:type]
    when :integer
      value = Integer(value)
      raise "must be at least #{rule[:min]}" if rule[:min] && value < rule[:min]
      raise "must be at most #{rule[:max]}" if rule[:max] && value > rule[:max]
    when :decimal
      value = BigDecimal(value.to_s)
      raise "must be at least #{rule[:min]}" if rule[:min] && value < rule[:min]
      raise "must be at most #{rule[:max]}" if rule[:max] && value > rule[:max]
    end
    
    # String validation
    if value.is_a?(String)
      raise "too short" if rule[:min_length] && value.length < rule[:min_length]
      raise "too long" if rule[:max_length] && value.length > rule[:max_length]
      value = sanitize_string(value) if rule[:sanitize]
    end
    
    value
  end
  
  def self.sanitize_string(str)
    # Remove null bytes and control characters
    str.gsub(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/, '')
       .strip
  end
end