datacaster 2.0.2 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +604 -287
  3. data/config/locales/en.yml +25 -0
  4. data/datacaster.gemspec +3 -1
  5. data/lib/datacaster/absent.rb +4 -0
  6. data/lib/datacaster/and_node.rb +3 -5
  7. data/lib/datacaster/and_with_error_aggregation_node.rb +5 -6
  8. data/lib/datacaster/array_schema.rb +18 -16
  9. data/lib/datacaster/base.rb +33 -44
  10. data/lib/datacaster/caster.rb +4 -8
  11. data/lib/datacaster/checker.rb +8 -10
  12. data/lib/datacaster/comparator.rb +9 -9
  13. data/lib/datacaster/config.rb +28 -0
  14. data/lib/datacaster/context_node.rb +43 -0
  15. data/lib/datacaster/context_nodes/errors_caster.rb +21 -0
  16. data/lib/datacaster/context_nodes/i18n.rb +20 -0
  17. data/lib/datacaster/context_nodes/i18n_keys_mapper.rb +27 -0
  18. data/lib/datacaster/context_nodes/pass_if.rb +11 -0
  19. data/lib/datacaster/context_nodes/structure_cleaner.rb +103 -0
  20. data/lib/datacaster/context_nodes/user_context.rb +20 -0
  21. data/lib/datacaster/definition_dsl.rb +36 -0
  22. data/lib/datacaster/hash_mapper.rb +13 -16
  23. data/lib/datacaster/hash_schema.rb +14 -15
  24. data/lib/datacaster/i18n_values/base.rb +87 -0
  25. data/lib/datacaster/i18n_values/key.rb +34 -0
  26. data/lib/datacaster/i18n_values/scope.rb +28 -0
  27. data/lib/datacaster/message_keys_merger.rb +8 -15
  28. data/lib/datacaster/or_node.rb +3 -4
  29. data/lib/datacaster/predefined.rb +150 -65
  30. data/lib/datacaster/result.rb +39 -18
  31. data/lib/datacaster/runtimes/base.rb +47 -0
  32. data/lib/datacaster/runtimes/i18n.rb +20 -0
  33. data/lib/datacaster/runtimes/structure_cleaner.rb +47 -0
  34. data/lib/datacaster/runtimes/user_context.rb +39 -0
  35. data/lib/datacaster/substitute_i18n.rb +48 -0
  36. data/lib/datacaster/switch_node.rb +72 -0
  37. data/lib/datacaster/then_node.rb +7 -8
  38. data/lib/datacaster/transformer.rb +4 -8
  39. data/lib/datacaster/trier.rb +9 -11
  40. data/lib/datacaster/validator.rb +8 -9
  41. data/lib/datacaster/version.rb +1 -1
  42. data/lib/datacaster.rb +15 -35
  43. metadata +60 -10
  44. data/lib/datacaster/definition_context.rb +0 -20
  45. data/lib/datacaster/terminator.rb +0 -98
@@ -13,21 +13,18 @@ module Datacaster
13
13
  @fields = fields
14
14
  end
15
15
 
16
- def cast(object)
17
- object = super(object)
18
- # return Datacaster.ErrorResult(["must be hash"]) unless object.value.is_a?(Hash)
19
-
20
- checked_schema = object.meta[:checked_schema].dup || {}
21
-
16
+ def cast(object, runtime:)
22
17
  errors = {}
23
18
  result = {}
24
19
 
20
+ runtime.will_check!
21
+
25
22
  @fields.each do |key, validator|
26
- new_value = validator.(object)
23
+ new_value = runtime.ignore_checks! { validator.with_runtime(runtime).(object) }
27
24
 
28
25
  # transform_to_hash([:a, :b, :c] => pick(:a, :b, :c) & ...)
29
26
  if key.is_a?(Array)
30
- unwrapped = new_value.valid? ? new_value.value : new_value.errors
27
+ unwrapped = new_value.valid? ? new_value.value : new_value.raw_errors
31
28
 
32
29
  if key.length != unwrapped.length
33
30
  raise TypeError.new("When using transform_to_hash([:a, :b, :c] => validator), validator should return Array "\
@@ -40,17 +37,17 @@ module Datacaster
40
37
  if key.is_a?(Array)
41
38
  key.zip(new_value.value) do |new_key, new_key_value|
42
39
  result[new_key] = new_key_value
43
- checked_schema[new_key] = true
40
+ runtime.checked_key!(new_key)
44
41
  end
45
42
  else
46
43
  result[key] = new_value.value
47
- checked_schema[key] = true
44
+ runtime.checked_key!(key)
48
45
  end
49
46
  else
50
47
  if key.is_a?(Array)
51
- errors = self.class.merge_errors(errors, key.zip(new_value.errors).to_h)
48
+ errors = self.class.merge_errors(errors, key.zip(new_value.raw_errors).to_h)
52
49
  else
53
- errors = self.class.merge_errors(errors, {key => new_value.errors})
50
+ errors = self.class.merge_errors(errors, {key => new_value.raw_errors})
54
51
  end
55
52
  end
56
53
  end
@@ -58,17 +55,17 @@ module Datacaster
58
55
  errors.delete_if { |_, v| v.empty? }
59
56
 
60
57
  if errors.empty?
61
- # All unchecked key-value pairs of initial hash are passed through, and eliminated by Terminator
58
+ # All unchecked key-value pairs of initial hash are passed through, and eliminated by ContextNode
62
59
  # at the end of the chain. If we weren't dealing with the hash, then ignore that.
63
60
  result_hash =
64
- if object.value.is_a?(Hash)
65
- object.value.merge(result)
61
+ if object.is_a?(Hash)
62
+ object.merge(result)
66
63
  else
67
64
  result
68
65
  end
69
66
 
70
67
  result_hash.keys.each { |k| result_hash.delete(k) if result_hash[k] == Datacaster.absent }
71
- Datacaster.ValidResult(result_hash, meta: {checked_schema: checked_schema})
68
+ Datacaster.ValidResult(result_hash)
72
69
  else
73
70
  Datacaster.ErrorResult(errors)
74
71
  end
@@ -1,43 +1,42 @@
1
1
  module Datacaster
2
2
  class HashSchema < Base
3
- def initialize(fields)
3
+ def initialize(fields, error_key = nil)
4
4
  @fields = fields
5
- # support of shortcut nested validation definitions, e.g. array_schema(a: [integer], b: {c: integer})
6
- @fields.transform_values! { |validator| shortcut_definition(validator) }
5
+
6
+ @error_keys = ['.hash_value', 'datacaster.errors.hash_value']
7
+ @error_keys.unshift(error_key) if error_key
7
8
  end
8
9
 
9
- def cast(object)
10
- object = super(object)
11
- return Datacaster.ErrorResult(["must be hash"]) unless object.value.is_a?(Hash)
10
+ def cast(object, runtime:)
11
+ return Datacaster.ErrorResult(I18nValues::Key.new(@error_keys, value: object)) unless object.is_a?(Hash)
12
12
 
13
- checked_schema = object.meta[:checked_schema].dup || {}
13
+ runtime.will_check!
14
14
 
15
15
  errors = {}
16
16
  result = {}
17
17
 
18
18
  @fields.each do |key, validator|
19
19
  value =
20
- if object.value.key?(key)
21
- object.value[key]
20
+ if object.key?(key)
21
+ object[key]
22
22
  else
23
23
  Datacaster.absent
24
24
  end
25
25
 
26
- new_value = validator.(value)
26
+ new_value = runtime.checked_key!(key) { validator.with_runtime(runtime).(value) }
27
27
  if new_value.valid?
28
28
  result[key] = new_value.value
29
- checked_schema[key] = new_value.meta[:checked_schema].dup || true
30
29
  else
31
- errors[key] = new_value.errors
30
+ errors[key] = new_value.raw_errors
32
31
  end
33
32
  end
34
33
 
35
34
  if errors.empty?
36
- # All unchecked key-value pairs are passed through, and eliminated by Terminator
35
+ # All unchecked key-value pairs are passed through, and eliminated by ContextNode
37
36
  # at the end of the chain
38
- result_hash = object.value.merge(result)
37
+ result_hash = object.merge(result)
39
38
  result_hash.keys.each { |k| result_hash.delete(k) if result_hash[k] == Datacaster.absent }
40
- Datacaster.ValidResult(result_hash, meta: {checked_schema: checked_schema})
39
+ Datacaster.ValidResult(result_hash)
41
40
  else
42
41
  Datacaster.ErrorResult(errors)
43
42
  end
@@ -0,0 +1,87 @@
1
+ module Datacaster
2
+ module I18nValues
3
+ class Base
4
+ attr_reader :args
5
+
6
+ def *(other, additional_scope = [])
7
+ # To allow redefine array/hash errors with #i18n_key
8
+ other = other.first if other.is_a?(Array) && other.length == 1
9
+ result = apply(other, additional_scope)
10
+
11
+ result = [result] unless result.is_a?(Array) || result.is_a?(Hash)
12
+ result
13
+ end
14
+
15
+ def ==(other)
16
+ self.class == other.class && @args == other.args
17
+ end
18
+
19
+ private
20
+
21
+ def apply(other, additional_scope = [])
22
+ if !other.is_a?(Base)
23
+ return apply_to_literal(other, additional_scope)
24
+ end
25
+
26
+ # Key(...) * Scope(...) -> error
27
+ if is_a?(Key) && !other.is_a?(Key)
28
+ raise RuntimeError.new("Can not apply #{inspect} to #{other.inspect}")
29
+ end
30
+
31
+ merged_args = other.args.merge(@args)
32
+
33
+ # Key(...) * Key(...) -> left
34
+ if is_a?(Key) && other.is_a?(Key)
35
+ return Key.new(@keys, merged_args)
36
+ end
37
+
38
+ # Scope('x') * Key(['.relative', 'full_path']) = Key(['x.relative', 'full_path'])
39
+ if is_a?(Scope) && other.is_a?(Key)
40
+ scoped_keys =
41
+ other.keys.flat_map do |x|
42
+ next x if x[0] != '.'
43
+
44
+ keys = ["#{@scope}#{x}"]
45
+ next keys if x.count('.') > 1
46
+
47
+ accumulator = ""
48
+ additional_scope.each do |k|
49
+ accumulator << ".#{k}"
50
+ keys.unshift "#{@scope}#{accumulator}#{x}"
51
+ end
52
+
53
+ keys
54
+ end
55
+ return Key.new(scoped_keys, merged_args)
56
+ end
57
+
58
+ # Scope(...) * Scope(...) -> error
59
+ raise RuntimeError.new("Can not apply #{inspect} to #{other.inspect}")
60
+ end
61
+
62
+ def apply_to_literal(other, additional_scope = [])
63
+ # Base * Other -> Other
64
+ return other if !other.is_a?(Hash) && !other.is_a?(Array)
65
+
66
+ # Key(...) * Array -> Array
67
+ # Key(...) * Hash -> Hash
68
+ return other if is_a?(Key)
69
+
70
+ # Scope(...) * Array -> map
71
+ return other.map { |x| self.*(x, additional_scope) } if other.is_a?(Array)
72
+
73
+ # Scope(...) * Hash -> map values
74
+ other.map do |(k, v)|
75
+ new_value =
76
+ case k
77
+ when String, Symbol
78
+ self.*(v, [*additional_scope, k])
79
+ else
80
+ self.*(v, [*additional_scope])
81
+ end
82
+ [k, new_value]
83
+ end.to_h
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,34 @@
1
+ module Datacaster
2
+ module I18nValues
3
+ class Key < Base
4
+ attr_reader :keys
5
+
6
+ def initialize(keys_or_key, args = {})
7
+ keys = Array(keys_or_key)
8
+ @keys = keys
9
+ @args = args
10
+ end
11
+
12
+ def ==(other)
13
+ super && @keys == other.keys
14
+ end
15
+
16
+ def resolve
17
+ keys = @keys.select { |x| x[0] != '.' }
18
+ if keys.empty?
19
+ raise RuntimeError.new("No absolute keys among #{@keys.inspect}. Use #i18n_key in addition to #i18n_scope.")
20
+ end
21
+ key = keys.find(&Config.i18n_exists?) || keys.first
22
+ Config.i18n_t.(key, **@args)
23
+ end
24
+
25
+ def with_args(args)
26
+ self.class.new(@keys, @args.merge(args))
27
+ end
28
+
29
+ def inspect
30
+ "#<#{self.class.name}(#{@keys.inspect}) #{@args.inspect}>"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,28 @@
1
+ module Datacaster
2
+ module I18nValues
3
+ class Scope < Base
4
+ attr_reader :scope
5
+
6
+ def initialize(scope, args = {})
7
+ @scope = scope
8
+ @args = args
9
+ end
10
+
11
+ def ==(other)
12
+ super && @scope == other.scope
13
+ end
14
+
15
+ def resolve
16
+ raise RuntimeError.new("Tried to resolve i18n scope #{@scope.inspect}. Use #i18n_key in addition to #i18n_scope.")
17
+ end
18
+
19
+ def with_args(args)
20
+ self.class.new(@scope, @args.merge(args))
21
+ end
22
+
23
+ def inspect
24
+ "#<#{self.class.name}(#{@scope.inspect}) #{@args.inspect}>"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -4,11 +4,8 @@ module Datacaster
4
4
  @keys = keys
5
5
  end
6
6
 
7
- def cast(object)
8
- intermediary_result = super(object)
9
- object = intermediary_result.value
10
-
11
- return Datacaster.ErrorResult(["must be Hash"]) unless object.is_a?(Hash)
7
+ def cast(object, runtime:)
8
+ return Datacaster.ErrorResult(I18nValues::Key.new(['.hash_value', 'datacaster.errors.hash_value'], value: object)) unless object.is_a?(Hash)
12
9
 
13
10
  result = set_initial_value(object)
14
11
 
@@ -35,7 +32,7 @@ module Datacaster
35
32
  end
36
33
 
37
34
  def need_hash_merger?(object)
38
- @need_hash_merger =
35
+ @need_hash_merger =
39
36
  @need_hash_merger.nil? ? @keys.any? { |k| object[k].is_a?(Hash) } : @need_hash_merger
40
37
  end
41
38
 
@@ -44,7 +41,7 @@ module Datacaster
44
41
  unit = [unit] unless unit.is_a?(Array)
45
42
 
46
43
  result = clean(unit | merge_with)
47
- result.uniq == [nil] || result == [] ? Datacaster.absent : result
44
+ result == [] ? Datacaster.absent : result
48
45
  end
49
46
 
50
47
  def value_or(value, default)
@@ -54,12 +51,8 @@ module Datacaster
54
51
  def merge_hash_with_hash(result, merge_with)
55
52
  if merge_with.is_a?(Hash)
56
53
  merge_with.each do |k, v|
57
- if merge_with.is_a?(Hash)
58
- result = value_or(result, {})
59
- result[k] = merge_hash_with_hash(result[k], v)
60
- else
61
- result[k] = merge_array_or_scalar(value_or(result[k], []), v)
62
- end
54
+ result = value_or(result, {})
55
+ result[k] = merge_hash_with_hash(result[k], v)
63
56
  end
64
57
 
65
58
  result
@@ -85,12 +78,12 @@ module Datacaster
85
78
  when Array
86
79
  value.delete_if do |v|
87
80
  clean(v) if v.is_a?(Hash) || v.is_a?(Array)
88
- v == Datacaster.absent
81
+ v == Datacaster.absent || v == nil
89
82
  end
90
83
  when Hash
91
84
  value.delete_if do |_k, v|
92
85
  clean(v) if v.is_a?(Hash) || v.is_a?(Array)
93
- v == Datacaster.absent
86
+ v == Datacaster.absent || v == nil
94
87
  end
95
88
  end
96
89
  end
@@ -5,13 +5,12 @@ module Datacaster
5
5
  @right = right
6
6
  end
7
7
 
8
- def cast(object)
9
- object = super(object)
10
- left_result = @left.(object)
8
+ def cast(object, runtime:)
9
+ left_result = @left.with_runtime(runtime).(object)
11
10
 
12
11
  return left_result if left_result.valid?
13
12
 
14
- @right.(object)
13
+ @right.with_runtime(runtime).(object)
15
14
  end
16
15
 
17
16
  def inspect
@@ -4,73 +4,124 @@ module Datacaster
4
4
 
5
5
  # Base types
6
6
 
7
- def cast(name = 'Anonymous', &block)
8
- Caster.new(name, &block)
7
+ def cast(&block)
8
+ Caster.new(&block)
9
9
  end
10
10
 
11
- def check(name = 'Anonymous', error = 'is invalid', &block)
12
- Checker.new(name, error, &block)
11
+ def check(error_key = nil, &block)
12
+ Checker.new(error_key, &block)
13
13
  end
14
14
 
15
- def compare(value, name = 'Anonymous', error = nil)
16
- Comparator.new(value, name, error)
15
+ def compare(value, error_key = nil)
16
+ Comparator.new(value, error_key)
17
17
  end
18
18
 
19
- def transform(name = 'Anonymous', &block)
20
- Transformer.new(name, &block)
19
+ def transform(&block)
20
+ Transformer.new(&block)
21
21
  end
22
22
 
23
- def transform_if_present(name = 'Anonymous', &block)
23
+ def transform_if_present(&block)
24
24
  raise 'Expected block' unless block_given?
25
25
 
26
- Transformer.new(name) { |v| v == Datacaster.absent ? v : block.(v) }
26
+ Transformer.new { |v| v == Datacaster.absent ? v : block.(v) }
27
27
  end
28
28
 
29
- def try(name = 'Anonymous', error = 'is invalid', catched_exception:, &block)
30
- Trier.new(name, error, catched_exception, &block)
29
+ def try(error_key = nil, catched_exception:, &block)
30
+ Trier.new(catched_exception, error_key, &block)
31
31
  end
32
32
 
33
- def array_schema(element_caster)
34
- ArraySchema.new(element_caster)
33
+ def array_schema(element_caster, error_keys = {})
34
+ ArraySchema.new(DefinitionDSL.expand(element_caster), error_keys)
35
35
  end
36
36
  alias_method :array_of, :array_schema
37
37
 
38
- def hash_schema(fields)
39
- HashSchema.new(fields)
38
+ def hash_schema(fields, error_key = nil)
39
+ unless fields.is_a?(Hash)
40
+ raise "Expected field definitions in a form of Hash for hash_schema, got #{fields.inspect} instead"
41
+ end
42
+ HashSchema.new(
43
+ fields.transform_values { |f| DefinitionDSL.expand(f) },
44
+ error_key
45
+ )
40
46
  end
41
47
 
42
48
  def transform_to_hash(fields)
43
- HashMapper.new(fields)
49
+ HashMapper.new(fields.transform_values { |x| DefinitionDSL.expand(x) })
50
+ end
51
+
52
+ def validate(active_model_validations)
53
+ Validator.new(active_model_validations)
54
+ end
55
+
56
+ def schema(base)
57
+ ContextNodes::StructureCleaner.new(base, strategy: :fail)
58
+ end
59
+
60
+ def choosy_schema(base)
61
+ ContextNodes::StructureCleaner.new(base, strategy: :remove)
44
62
  end
45
63
 
46
- def validate(active_model_validations, name = 'Anonymous')
47
- Validator.new(active_model_validations, name)
64
+ def partial_schema(base)
65
+ ContextNodes::StructureCleaner.new(base, strategy: :pass)
48
66
  end
49
67
 
50
68
  # 'Meta' types
51
69
 
52
- def absent
53
- check('Absent', 'must be absent') { |x| x == Datacaster.absent }
70
+ def absent(error_key = nil)
71
+ error_keys = ['.absent', 'datacaster.errors.absent']
72
+ error_keys.unshift(error_key) if error_key
73
+ check { |x| x == Datacaster.absent }.i18n_key(*error_keys)
54
74
  end
55
75
 
56
- def any
57
- check('Any', 'must be set') { |x| x != Datacaster.absent }
76
+ def any(error_key = nil)
77
+ error_keys = ['.any', 'datacaster.errors.any']
78
+ error_keys.unshift(error_key) if error_key
79
+ check { |x| x != Datacaster.absent }.i18n_key(*error_keys)
80
+ end
81
+
82
+ def default(value, on: nil)
83
+ transform do |x|
84
+ if x == Datacaster.absent ||
85
+ (on && x.respond_to?(on) && x.public_send(on))
86
+ value
87
+ else
88
+ x
89
+ end
90
+ end
58
91
  end
59
92
 
60
93
  def transform_to_value(value)
61
- transform('ToValue') { value }
94
+ transform { value }
62
95
  end
63
96
 
64
97
  def remove
65
- transform('Remove') { Datacaster.absent }
98
+ transform { Datacaster.absent }
66
99
  end
67
100
 
68
101
  def pass
69
- transform('Pass', &:itself)
102
+ transform(&:itself)
103
+ end
104
+
105
+ def switch(*base, **on_clauses)
106
+ switch =
107
+ if base.length == 0
108
+ SwitchNode.new
109
+ else
110
+ SwitchNode.new(base)
111
+ end
112
+ on_clauses.reduce(switch) do |result, (k, v)|
113
+ result.on(k, v)
114
+ end
70
115
  end
71
116
 
72
- def pick(*keys)
73
- must_be(Enumerable) & transform("Picker") { |value|
117
+ def pass_if(base)
118
+ ContextNodes::PassIf.new(base)
119
+ end
120
+
121
+ def pick(*keys, strict: false)
122
+ raise RuntimeError.new("provide keys to pick, e.g. pick(:key)") if keys.empty?
123
+
124
+ must_be(Enumerable) & transform { |value|
74
125
  result =
75
126
  keys.map do |key|
76
127
  if value.respond_to?(:key?) && !value.key?(key)
@@ -90,12 +141,16 @@ module Datacaster
90
141
  MessageKeysMerger.new(keys)
91
142
  end
92
143
 
93
- def responds_to(method)
94
- check('RespondsTo', "must respond to #{method.inspect}") { |x| x.respond_to?(method) }
144
+ def responds_to(method, error_key = nil)
145
+ error_keys = ['.responds_to', 'datacaster.errors.responds_to']
146
+ error_keys.unshift(error_key) if error_key
147
+ check { |x| x.respond_to?(method) }.i18n_key(*error_keys, reference: method.to_s)
95
148
  end
96
149
 
97
- def must_be(klass)
98
- check('MustBe', "must be #{klass.inspect}") { |x| x.is_a?(klass) }
150
+ def must_be(klass, error_key = nil)
151
+ error_keys = ['.must_be', 'datacaster.errors.must_be']
152
+ error_keys.unshift(error_key) if error_key
153
+ check { |x| x.is_a?(klass) }.i18n_key(*error_keys, reference: klass.name)
99
154
  end
100
155
 
101
156
  def optional(base)
@@ -104,81 +159,111 @@ module Datacaster
104
159
 
105
160
  # Strict types
106
161
 
107
- def decimal(digits = 8)
108
- Trier.new('Decimal', 'must be decimal', [ArgumentError, TypeError]) do |x|
162
+ def decimal(digits = 8, error_key = nil)
163
+ error_keys = ['.decimal', 'datacaster.errors.decimal']
164
+ error_keys.unshift(error_key) if error_key
165
+
166
+ Trier.new([ArgumentError, TypeError]) do |x|
109
167
  # strictly validate format of string, BigDecimal() doesn't do that
110
168
  Float(x)
111
169
 
112
170
  BigDecimal(x, digits)
113
- end
171
+ end.i18n_key(*error_keys)
114
172
  end
115
173
 
116
- def array
117
- check('Array', 'must be array') { |x| x.is_a?(Array) }
174
+ def array(error_key = nil)
175
+ error_keys = ['.array', 'datacaster.errors.array']
176
+ error_keys.unshift(error_key) if error_key
177
+ check { |x| x.is_a?(Array) }.i18n_key(*error_keys)
118
178
  end
119
179
 
120
- def float
121
- check('Float', 'must be float') { |x| x.is_a?(Float) }
180
+ def float(error_key = nil)
181
+ error_keys = ['.float', 'datacaster.errors.float']
182
+ error_keys.unshift(error_key) if error_key
183
+ check { |x| x.is_a?(Float) }.i18n_key(*error_keys)
122
184
  end
123
185
 
124
- # 'hash' is a bad method name, because it will overwrite built in Object#hash
125
- def hash_value
126
- check('Hash', 'must be hash') { |x| x.is_a?(Hash) }
186
+ # 'hash' would be a bad method name, because it would override built in Object#hash
187
+ def hash_value(error_key = nil)
188
+ error_keys = ['.hash_value', 'datacaster.errors.hash_value']
189
+ error_keys.unshift(error_key) if error_key
190
+ check(error_key) { |x| x.is_a?(Hash) }
127
191
  end
128
192
 
129
- def hash_with_symbolized_keys
130
- hash_value & transform("SymbolizeKeys") { |x| x.symbolize_keys }
193
+ def hash_with_symbolized_keys(error_key = nil)
194
+ hash_value(error_key) & transform { |x| x.symbolize_keys }
131
195
  end
132
196
 
133
- def integer
134
- check('Integer', 'must be integer') { |x| x.is_a?(Integer) }
197
+ def integer(error_key = nil)
198
+ error_keys = ['.integer', 'datacaster.errors.integer']
199
+ error_keys.unshift(error_key) if error_key
200
+ check { |x| x.is_a?(Integer) }.i18n_key(*error_keys)
135
201
  end
136
202
 
137
- def integer32
138
- integer & check('FourBytes', 'out of range') { |x| x.abs <= 2_147_483_647 }
203
+ def integer32(error_key = nil)
204
+ error_keys = ['.integer32', 'datacaster.errors.integer32']
205
+ error_keys.unshift(error_key) if error_key
206
+ integer(error_key) & check { |x| x.abs <= 2_147_483_647 }.i18n_key(*error_keys)
139
207
  end
140
208
 
141
- def string
142
- check('String', 'must be string') { |x| x.is_a?(String) }
209
+ def string(error_key = nil)
210
+ error_keys = ['.string', 'datacaster.errors.string']
211
+ error_keys.unshift(error_key) if error_key
212
+ check { |x| x.is_a?(String) }.i18n_key(*error_keys)
143
213
  end
144
214
 
145
- def non_empty_string
146
- string & check('NonEmptyString', 'must be present') { |x| !x.empty? }
215
+ def non_empty_string(error_key = nil)
216
+ error_keys = ['.non_empty_string', 'datacaster.errors.non_empty_string']
217
+ error_keys.unshift(error_key) if error_key
218
+ string(error_key) & check { |x| !x.empty? }.i18n_key(*error_keys)
147
219
  end
148
220
 
149
221
  # Form request types
150
222
 
151
- def iso8601
152
- string &
153
- try('ISO8601', 'must be iso8601 string', catched_exception: [ArgumentError, TypeError]) { |x| DateTime.iso8601(x) }
223
+ def iso8601(error_key = nil)
224
+ error_keys = ['.iso8601', 'datacaster.errors.iso8601']
225
+ error_keys.unshift(error_key) if error_key
226
+
227
+ string(error_key) &
228
+ try(catched_exception: [ArgumentError, TypeError]) { |x| DateTime.iso8601(x) }.
229
+ i18n_key(*error_keys)
154
230
  end
155
231
 
156
- def to_boolean
157
- cast('ToBoolean') do |x|
232
+ def to_boolean(error_key = nil)
233
+ error_keys = ['.to_boolean', 'datacaster.errors.to_boolean']
234
+ error_keys.unshift(error_key) if error_key
235
+
236
+ cast do |x|
158
237
  if ['true', '1', true].include?(x)
159
238
  Datacaster.ValidResult(true)
160
239
  elsif ['false', '0', false].include?(x)
161
240
  Datacaster.ValidResult(false)
162
241
  else
163
- Datacaster.ErrorResult(['must be boolean'])
242
+ Datacaster.ErrorResult(I18nValues::Key.new(error_keys, value: x))
164
243
  end
165
244
  end
166
245
  end
167
246
 
168
- def to_float
169
- Trier.new('ToFloat', 'must be float', [ArgumentError, TypeError]) do |x|
247
+ def to_float(error_key = nil)
248
+ error_keys = ['.to_float', 'datacaster.errors.to_float']
249
+ error_keys.unshift(error_key) if error_key
250
+
251
+ Trier.new([ArgumentError, TypeError]) do |x|
170
252
  Float(x)
171
- end
253
+ end.i18n_key(*error_keys)
172
254
  end
173
255
 
174
- def to_integer
175
- Trier.new('ToInteger', 'must be integer', [ArgumentError, TypeError]) do |x|
256
+ def to_integer(error_key = nil)
257
+ error_keys = ['.to_integer', 'datacaster.errors.to_integer']
258
+ error_keys.unshift(error_key) if error_key
259
+
260
+ Trier.new([ArgumentError, TypeError]) do |x|
176
261
  Integer(x)
177
- end
262
+ end.i18n_key(*error_keys)
178
263
  end
179
264
 
180
265
  def optional_param(base)
181
- transform_if_present("optional_param(#{base.inspect})") { |x| x == '' ? Datacaster::Absent.instance : x } & (absent | base)
266
+ transform_if_present { |x| x == '' ? Datacaster::Absent.instance : x } & (absent | base)
182
267
  end
183
268
  end
184
269
  end