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