Class: Kabk::Validator

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

Overview

Handles input sanitization (strong parameters) and validation

Class Method Summary collapse

Class Method Details

.collect_rule_errors(field, rules, value) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/kabk/validator.rb', line 55

def self.collect_rule_errors(field, rules, value)
  errs = []

  if field.type.to_s == "string" && value.is_a?(String)
    len_err = validate_string_length(rules, value)
    errs << len_err if len_err
  end

  if (rules[:pattern] || rules["pattern"]) && value.is_a?(String)
    pat_err = validate_pattern(rules, value)
    errs << pat_err if pat_err
  end

  if field.type.to_s == "number"
    num_err = validate_numeric_range(rules, value)
    errs << num_err if num_err
  end

  errs
end

.parse_numeric(value) ⇒ Object



110
111
112
113
114
115
# File 'lib/kabk/validator.rb', line 110

def self.parse_numeric(value)
  return value if value.is_a?(Numeric)
  return nil unless value.is_a?(String) && value =~ /\A-?\d+(\.\d+)?\z/

  value.include?(".") ? value.to_f : value.to_i
end

.resolve_message(rules, default_message) ⇒ Object



117
118
119
# File 'lib/kabk/validator.rb', line 117

def self.resolve_message(rules, default_message)
  rules&.dig(:custom_message) || rules&.dig("custom_message") || default_message
end

.validate_and_sanitize!(resource, params, is_update: false) ⇒ Hash

Sanitizes input parameters against defined fields and validates them

Parameters:

  • resource (Kabk::Resource)
  • params (Hash)

    Request payload

  • is_update (Boolean) (defaults to: false)

    Is this a PUT/update request?

Returns:

  • (Hash)

    Sanitized parameters ready for the database adapter

Raises:



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/kabk/validator.rb', line 13

def self.validate_and_sanitize!(resource, params, is_update: false)
  sanitized = {}
  errors = {}

  resource.fields.each do |field|
    next if field.primary_key || field.readonly

    key_sym = field.name.to_sym
    key_str = field.name.to_s
    has_key = params.key?(key_sym) || params.key?(key_str)
    value = params.key?(key_sym) ? params[key_sym] : params[key_str]

    field_errors = validate_field(field, value, has_key, is_update)
    errors[key_str] = field_errors unless field_errors.empty?

    sanitized[key_sym] = value if has_key
  end

  # Ensure the concurrency field is included for OCC validation.
  if resource.concurrency_field
    c_field = resource.concurrency_field
    sanitized[c_field.to_sym] = params[c_field.to_sym] || params[c_field.to_s] if params.key?(c_field.to_sym) || params.key?(c_field.to_s)
  end

  raise ValidationError.new(fields: errors) unless errors.empty?

  sanitized
end

.validate_field(field, value, has_key, is_update) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/kabk/validator.rb', line 42

def self.validate_field(field, value, has_key, is_update)
  rules = field.validation.is_a?(Hash) ? field.validation : nil

  if field.required
    return [resolve_message(rules, "This field is required")] if !has_key && !is_update
    return [resolve_message(rules, "This field is required")] if has_key && (value.nil? || value.to_s.strip.empty?)
  end

  return [] unless has_key && !value.nil? && rules

  collect_rule_errors(field, rules, value)
end

.validate_numeric_range(rules, value) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/kabk/validator.rb', line 96

def self.validate_numeric_range(rules, value)
  min = rules[:min] || rules["min"]
  max = rules[:max] || rules["max"]
  return nil unless min || max

  num = parse_numeric(value)
  return nil unless num

  return resolve_message(rules, "Minimum value is #{min}") if min && num < min
  return resolve_message(rules, "Maximum value is #{max}") if max && num > max

  nil
end

.validate_pattern(rules, value) ⇒ Object



86
87
88
89
90
91
92
93
94
# File 'lib/kabk/validator.rb', line 86

def self.validate_pattern(rules, value)
  pattern = rules[:pattern] || rules["pattern"]
  return nil unless pattern

  regex = pattern.is_a?(Regexp) ? pattern : Regexp.new(pattern.to_s)
  return nil if regex.match?(value)

  resolve_message(rules, "Does not match the required format")
end

.validate_string_length(rules, value) ⇒ Object



76
77
78
79
80
81
82
83
84
# File 'lib/kabk/validator.rb', line 76

def self.validate_string_length(rules, value)
  min_len = rules[:min_length] || rules["min_length"]
  max_len = rules[:max_length] || rules["max_length"]

  return resolve_message(rules, "Minimum length is #{min_len}") if min_len && value.length < min_len
  return resolve_message(rules, "Maximum length is #{max_len}") if max_len && value.length > max_len

  nil
end