Module: Glia::Errors::Mapper

Defined in:
lib/glia/errors/mapper.rb

Overview

Maps library specific errors to Glia public errors

Constant Summary collapse

INVALID_TYPE_ERROR =
lambda do |field, _value, message|
  type =
    case message
    when 'must be an array', 'must be Array'
      InvalidTypeError::Types::ARRAY
    when 'must be an integer', 'must be Integer'
      InvalidTypeError::Types::INTEGER
    when 'must be a number', 'must be a float', 'must be a decimal', 'must be Float',
         'must be BigDecimal'
      InvalidTypeError::Types::NUMBER
    when 'must be a hash', 'must be Hash'
      InvalidTypeError::Types::OBJECT
    when 'must be boolean', 'must be Boolean'
      InvalidTypeError::Types::BOOLEAN
    when 'must be a string', 'must be String'
      InvalidTypeError::Types::STRING
    end
  InvalidTypeError.new(field: field, type: type)
end
INVALID_FORMAT_ERROR =
->(field, _value, _message) { InvalidFormatError.new(field: field) }
INVALID_UUID_ERROR =
lambda do |field, _value, _message|
  InvalidFormatError.new(field: field, format: InvalidFormatError::Formats::UUID)
end
INVALID_NUMBER_ERROR =
->(field, _value, _message) { InvalidNumberError.new(field: field) }
INVALID_VALUE_ERROR =
->(field, _value, _message) { InvalidValueError.new(field: field) }
INVALID_LENGTH_ERROR =
->(field, _value, _message) { InvalidLengthError.new(field: field) }
MISSING_VALUE_ERROR =
->(field, _value, _message) { MissingValueError.new(field: field) }
INVALID_NUMBER_OR_VALUE_ERROR =

dry-validation shares the same message for ‘eql?` validation for string and number, so we are separating them ourselves

lambda do |field, value, _message|
  if value.is_a?(String)
    InvalidValueError.new(field: field)
  else
    InvalidNumberError.new(field: field)
  end
end
INVALID_DATE_FORMAT =
lambda do |field, _value, message|
  format =
    case message
    when 'must be a date', 'must be Date'
      InvalidFormatError::Formats::DATE
    when 'must be a date time', 'must be DateTime'
      InvalidFormatError::Formats::DATE_TIME
    when 'must be a time', 'must be Time'
      InvalidFormatError::Formats::TIME
    end
  InvalidFormatError.new(field: field, format: format)
end
ERROR_MAP =
{
  # InvalidTypeError
  'must be an array' => INVALID_TYPE_ERROR,
  'must be Array' => INVALID_TYPE_ERROR,
  'must be an integer' => INVALID_TYPE_ERROR,
  'must be Integer' => INVALID_TYPE_ERROR,
  'must be a number' => INVALID_TYPE_ERROR,
  'must be a float' => INVALID_TYPE_ERROR,
  'must be Float' => INVALID_TYPE_ERROR,
  'must be a decimal' => INVALID_TYPE_ERROR,
  'must be BigDecimal' => INVALID_TYPE_ERROR,
  'must be a hash' => INVALID_TYPE_ERROR,
  'must be Hash' => INVALID_TYPE_ERROR,
  'must be boolean' => INVALID_TYPE_ERROR,
  'must be Boolean' => INVALID_TYPE_ERROR,
  'must be a string' => INVALID_TYPE_ERROR,
  'must be String' => INVALID_TYPE_ERROR,
  # InvalidFormatError
  'must be a date time' => INVALID_DATE_FORMAT,
  'must be DateTime' => INVALID_DATE_FORMAT,
  'must be a date' => INVALID_DATE_FORMAT,
  'must be Date' => INVALID_DATE_FORMAT,
  'must be a time' => INVALID_DATE_FORMAT,
  'must be Time' => INVALID_DATE_FORMAT,
  'is in invalid format' => INVALID_FORMAT_ERROR,
  'is not a valid UUID' => INVALID_UUID_ERROR,
  # InvalidNumberError
  'must be less than' => INVALID_NUMBER_ERROR,
  'must be less than or equal to' => INVALID_NUMBER_ERROR,
  'must be greater than' => INVALID_NUMBER_ERROR,
  'must be greater than or equal to' => INVALID_NUMBER_ERROR,
  'must be even' => INVALID_NUMBER_ERROR,
  'must be odd' => INVALID_NUMBER_ERROR,
  # InvalidValueError
  'must be true' => INVALID_VALUE_ERROR,
  'must be false' => INVALID_VALUE_ERROR,
  'must include' => INVALID_VALUE_ERROR,
  'must not include' => INVALID_VALUE_ERROR,
  'must be filled' =>
    lambda do |field, value, _message|
      # Consider `nil` as invalid value error, but empty string or array as invalid length error
      value.nil? ? InvalidValueError.new(field: field) : InvalidLengthError.new(field: field)
    end,
  'cannot be defined' => INVALID_VALUE_ERROR,
  # InvalidNumberError or InvalidValueError
  'must be equal to' => INVALID_NUMBER_OR_VALUE_ERROR,
  'must not be equal to' => INVALID_NUMBER_OR_VALUE_ERROR,
  'must be one of' => INVALID_NUMBER_OR_VALUE_ERROR,
  'must not be one of' => INVALID_NUMBER_OR_VALUE_ERROR,
  # InvalidLengthError
  'size cannot be greater than' => INVALID_LENGTH_ERROR,
  'size cannot be less than' => INVALID_LENGTH_ERROR,
  'size must be' => INVALID_LENGTH_ERROR,
  'size must be within' => INVALID_LENGTH_ERROR,
  'bytesize cannot be less than' => INVALID_LENGTH_ERROR,
  'bytesize cannot be greater than' => INVALID_LENGTH_ERROR,
  'bytes long' => INVALID_LENGTH_ERROR,
  'length must be' => INVALID_LENGTH_ERROR,
  'length must be within' => INVALID_LENGTH_ERROR,
  'must be empty' => INVALID_LENGTH_ERROR,
  'cannot be empty' => INVALID_LENGTH_ERROR,
  # MissingValueError
  'is missing' => MISSING_VALUE_ERROR,
  # Custom format errors
  'must be in E.164 format' => INVALID_FORMAT_ERROR,
  'must be up to 25 symbol string that contains only 1-7 digits and commas' =>
    INVALID_FORMAT_ERROR,
  'must be a valid email' => INVALID_FORMAT_ERROR
}.freeze

Class Method Summary collapse

Class Method Details

.from_dry_validation_error(field, value, message, error_map) ⇒ Object



157
158
159
160
161
162
163
164
165
166
167
# File 'lib/glia/errors/mapper.rb', line 157

def self.from_dry_validation_error(field, value, message, error_map)
  key = error_map.keys.find { |k| message.include?(k) }
  error_or_func = error_map[key]
  if error_or_func.nil?
    UnknownError.new(field: field)
  elsif error_or_func.respond_to?(:call)
    error_or_func.call(field, value, message)
  else
    error_or_func
  end
end

.from_dry_validation_result(output, messages, custom_error_map = {}) ⇒ Object



133
134
135
136
# File 'lib/glia/errors/mapper.rb', line 133

def self.from_dry_validation_result(output, messages, custom_error_map = {})
  error_map = ERROR_MAP.merge(custom_error_map)
  from_dry_validation_result_rec(output, messages, error_map)
end

.from_dry_validation_result_rec(output, messages, error_map) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/glia/errors/mapper.rb', line 138

def self.from_dry_validation_result_rec(output, messages, error_map)
  error_details =
    messages.keys.each_with_object({}) do |field, acc|
      field_messages = messages[field]
      field_value = output[field]
      acc[field] =
        if field_messages.is_a?(Hash)
          # Wrap result in array as by defined structure each field has an array of errors.
          # This is not needed for the `else` case as dry-validation already provides an array.
          [from_dry_validation_result_rec(field_value, field_messages, error_map)]
        else
          field_messages.map do |message|
            from_dry_validation_error(field, field_value, message, error_map)
          end
        end
    end
  InputValidationError.new(error_details: error_details)
end