Validator.illegal_value?   A
last analyzed

Complexity

Conditions 4

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
rs 9.2
cc 4
1
# frozen_string_literal: true
2
3
require_relative '../../mixin/reflection'
4
require_relative '../../mixin/errors'
5
6
module AMA
7
  module Entity
8
    class Mapper
9
      module Handler
10
        module Attribute
11
          # Default validator for single attribute
12
          class Validator
13
            INSTANCE = new
14
15
            # @param [Object] value Attribute value
16
            # @param [AMA::Entity::Mapper::Type::Attribute] attribute
17
            # @param [AMA::Entity::Mapper::Context] _context
18
            # @return [Array<String>] Single violation, list of violations
19
            def validate(value, attribute, _context)
20
              violation = validate_internal(value, attribute)
21
              violation.nil? ? [] : [violation]
22
            end
23
24
            private
25
26
            def validate_internal(value, attribute)
27
              if illegal_nil?(value, attribute)
28
                return "Attribute #{attribute} could not be nil"
29
              end
30
              if invalid_type?(value, attribute)
31
                return "Provided value doesn't conform to " \
32
                  "any of attribute #{attribute} types " \
33
                  "(#{attribute.types.map(&:to_def).join(', ')})"
34
              end
35
              return unless illegal_value?(value, attribute)
36
              "Provided value doesn't match default value (#{value})" \
37
                " or any of allowed values (#{attribute.values})"
38
            end
39
40
            # @param [Object] value Attribute value
41
            # @param [AMA::Entity::Mapper::Type::Attribute] attribute
42
            # @return [TrueClass, FalseClass]
43
            def illegal_nil?(value, attribute)
44
              return false unless value.nil? && !attribute.nullable
45
              attribute.types.none? { |type| type.instance?(value) }
46
            end
47
48
            # @param [Object] value Attribute value
49
            # @param [AMA::Entity::Mapper::Type::Attribute] attribute
50
            # @return [TrueClass, FalseClass]
51
            def invalid_type?(value, attribute)
52
              attribute.types.all? do |type|
53
                !type.respond_to?(:instance?) || !type.instance?(value)
54
              end
55
            end
56
57
            # @param [Object] value Attribute value
58
            # @param [AMA::Entity::Mapper::Type::Attribute] attribute
59
            # @return [TrueClass, FalseClass]
60
            def illegal_value?(value, attribute)
61
              return false if value == attribute.default
62
              return false if attribute.values.empty? || attribute.values.nil?
63
              !attribute.values.include?(value)
64
            end
65
66
            class << self
67
              include Mixin::Reflection
68
69
              # @param [Validator] validator
70
              # @return [Validator]
71
              def wrap(validator)
72
                handler = handler_factory(validator, INSTANCE)
73
                description = "Safety wrapper for #{validator}"
74
                wrapper = method_object(:validate, to_s: description, &handler)
75
                wrapper.singleton_class.instance_eval do
76
                  include Mixin::Errors
77
                end
78
                wrapper
79
              end
80
81
              private
82
83
              # @param [Validator] validator
84
              # @param [Validator] fallback
85
              # @return [Proc]
86
              def handler_factory(validator, fallback)
87
                lambda do |val, attr, ctx|
88
                  begin
89
                    validator.validate(val, attr, ctx) do |v, a, c|
90
                      fallback.validate(v, a, c)
91
                    end
92
                  rescue StandardError => e
93
                    raise_if_internal(e)
94
                    message = "Unexpected error from validator #{validator}"
95
                    signature = '(value, attribute, context)'
96
                    options = { parent: e, context: ctx, signature: signature }
97
                    compliance_error(message, **options)
98
                  end
99
                end
100
              end
101
            end
102
          end
103
        end
104
      end
105
    end
106
  end
107
end
108