Class: Tina4::Validator

Inherits:
Object
  • Object
show all
Defined in:
lib/tina4/validator.rb

Overview

Request body validator with chainable rules.

Usage:

validator = Tina4::Validator.new(request.body)
validator.required("name", "email")
       .email("email")
       .min_length("name", 2)
       .max_length("name", 100)
       .integer("age")
       .min("age", 0)
       .max("age", 150)
       .in_list("role", ["admin", "user", "guest"])
       .regex("phone", /^\+?[\d\s\-]+$/)

unless validator.is_valid?
return response.error("VALIDATION_FAILED", validator.errors.first[:message], 400)
end

Constant Summary collapse

EMAIL_REGEX =
/\A[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}\z/

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data = {}) ⇒ Validator

Returns a new instance of Validator.



29
30
31
32
33
34
# File 'lib/tina4/validator.rb', line 29

def initialize(data = {})
  @data = data.is_a?(Hash) ? data : {}
  # Normalise keys to strings for consistent lookup
  @data = @data.transform_keys(&:to_s) unless @data.empty?
  @validation_errors = []
end

Instance Attribute Details

#validation_errorsObject (readonly)

Returns the value of attribute validation_errors.



25
26
27
# File 'lib/tina4/validator.rb', line 25

def validation_errors
  @validation_errors
end

Instance Method Details

#email(field) ⇒ Object

Check that a field contains a valid email address.



49
50
51
52
53
54
55
56
57
58
# File 'lib/tina4/validator.rb', line 49

def email(field)
  key = field.to_s
  value = @data[key]
  return self if value.nil?

  unless value.is_a?(String) && value.match?(EMAIL_REGEX)
    @validation_errors << { field: key, message: "#{key} must be a valid email address" }
  end
  self
end

#errorsObject

Return the list of validation errors (empty if valid).



168
169
170
# File 'lib/tina4/validator.rb', line 168

def errors
  @validation_errors.dup
end

#in_list(field, allowed) ⇒ Object

Check that a field's value is one of the allowed values.



139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/tina4/validator.rb', line 139

def in_list(field, allowed)
  key = field.to_s
  value = @data[key]
  return self if value.nil?

  unless allowed.include?(value)
    # Canonical wording (VALID-TWO-MESSAGES): render the allowed set as
    # compact JSON so every framework prints the SAME list -- Node
    # JSON.stringify, PHP json_encode, Python json.dumps and this to_json all
    # emit ["admin","user","guest"] (no spaces), never a language repr.
    @validation_errors << { field: key, message: "#{key} must be one of #{allowed.to_json}" }
  end
  self
end

#integer(field) ⇒ Object

Check that a field is an integer (or can be parsed as one).



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/tina4/validator.rb', line 85

def integer(field)
  key = field.to_s
  value = @data[key]
  return self if value.nil?

  if value.is_a?(Integer)
    return self
  end

  begin
    Integer(value)
  rescue ArgumentError, TypeError
    @validation_errors << { field: key, message: "#{key} must be an integer" }
  end
  self
end

#is_valid?Boolean Also known as: valid?

Return true if no validation errors have been recorded.

Returns:

  • (Boolean)


173
174
175
# File 'lib/tina4/validator.rb', line 173

def is_valid?
  @validation_errors.empty?
end

#max(field, maximum) ⇒ Object

Check that a numeric field is <= maximum.



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/tina4/validator.rb', line 121

def max(field, maximum)
  key = field.to_s
  value = @data[key]
  return self if value.nil?

  begin
    num = Float(value)
  rescue ArgumentError, TypeError
    return self
  end

  if num > maximum
    @validation_errors << { field: key, message: "#{key} must be at most #{maximum}" }
  end
  self
end

#max_length(field, length) ⇒ Object

Check that a string field has at most length characters.



73
74
75
76
77
78
79
80
81
82
# File 'lib/tina4/validator.rb', line 73

def max_length(field, length)
  key = field.to_s
  value = @data[key]
  return self if value.nil?

  unless value.is_a?(String) && value.length <= length
    @validation_errors << { field: key, message: "#{key} must be at most #{length} characters" }
  end
  self
end

#min(field, minimum) ⇒ Object

Check that a numeric field is >= minimum.



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/tina4/validator.rb', line 103

def min(field, minimum)
  key = field.to_s
  value = @data[key]
  return self if value.nil?

  begin
    num = Float(value)
  rescue ArgumentError, TypeError
    return self
  end

  if num < minimum
    @validation_errors << { field: key, message: "#{key} must be at least #{minimum}" }
  end
  self
end

#min_length(field, length) ⇒ Object

Check that a string field has at least length characters.



61
62
63
64
65
66
67
68
69
70
# File 'lib/tina4/validator.rb', line 61

def min_length(field, length)
  key = field.to_s
  value = @data[key]
  return self if value.nil?

  unless value.is_a?(String) && value.length >= length
    @validation_errors << { field: key, message: "#{key} must be at least #{length} characters" }
  end
  self
end

#regex(field, pattern) ⇒ Object

Check that a field matches a regular expression.



155
156
157
158
159
160
161
162
163
164
165
# File 'lib/tina4/validator.rb', line 155

def regex(field, pattern)
  key = field.to_s
  value = @data[key]
  return self if value.nil?

  regexp = pattern.is_a?(Regexp) ? pattern : Regexp.new(pattern)
  unless value.is_a?(String) && value.match?(regexp)
    @validation_errors << { field: key, message: "#{key} does not match the required format" }
  end
  self
end

#required(*fields) ⇒ Object

Check that one or more fields are present and non-empty.



37
38
39
40
41
42
43
44
45
46
# File 'lib/tina4/validator.rb', line 37

def required(*fields)
  fields.each do |field|
    key = field.to_s
    value = @data[key]
    if value.nil? || (value.is_a?(String) && value.strip.empty?)
      @validation_errors << { field: key, message: "#{key} is required" }
    end
  end
  self
end