dry-validation 0.6.0 → 0.7.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +1 -0
  3. data/.travis.yml +3 -2
  4. data/CHANGELOG.md +42 -0
  5. data/Gemfile +8 -1
  6. data/README.md +13 -89
  7. data/config/errors.yml +35 -29
  8. data/dry-validation.gemspec +2 -2
  9. data/examples/basic.rb +3 -7
  10. data/examples/each.rb +3 -8
  11. data/examples/form.rb +3 -6
  12. data/examples/nested.rb +7 -15
  13. data/lib/dry/validation.rb +33 -5
  14. data/lib/dry/validation/error.rb +10 -26
  15. data/lib/dry/validation/error_compiler.rb +69 -99
  16. data/lib/dry/validation/error_compiler/input.rb +148 -0
  17. data/lib/dry/validation/hint_compiler.rb +83 -33
  18. data/lib/dry/validation/input_processor_compiler.rb +98 -0
  19. data/lib/dry/validation/input_processor_compiler/form.rb +46 -0
  20. data/lib/dry/validation/input_processor_compiler/sanitizer.rb +46 -0
  21. data/lib/dry/validation/messages/abstract.rb +30 -10
  22. data/lib/dry/validation/messages/i18n.rb +2 -1
  23. data/lib/dry/validation/messages/namespaced.rb +1 -0
  24. data/lib/dry/validation/messages/yaml.rb +8 -5
  25. data/lib/dry/validation/result.rb +33 -25
  26. data/lib/dry/validation/schema.rb +168 -61
  27. data/lib/dry/validation/schema/attr.rb +5 -27
  28. data/lib/dry/validation/schema/check.rb +24 -0
  29. data/lib/dry/validation/schema/dsl.rb +97 -0
  30. data/lib/dry/validation/schema/form.rb +2 -26
  31. data/lib/dry/validation/schema/key.rb +32 -28
  32. data/lib/dry/validation/schema/rule.rb +88 -32
  33. data/lib/dry/validation/schema/value.rb +77 -27
  34. data/lib/dry/validation/schema_compiler.rb +38 -0
  35. data/lib/dry/validation/version.rb +1 -1
  36. data/spec/fixtures/locales/pl.yml +1 -1
  37. data/spec/integration/attr_spec.rb +122 -0
  38. data/spec/integration/custom_error_messages_spec.rb +9 -11
  39. data/spec/integration/custom_predicates_spec.rb +68 -18
  40. data/spec/integration/error_compiler_spec.rb +259 -65
  41. data/spec/integration/hints_spec.rb +28 -9
  42. data/spec/integration/injecting_rules_spec.rb +11 -12
  43. data/spec/integration/localized_error_messages_spec.rb +16 -16
  44. data/spec/integration/messages/i18n_spec.rb +9 -5
  45. data/spec/integration/optional_keys_spec.rb +9 -11
  46. data/spec/integration/schema/array_schema_spec.rb +23 -0
  47. data/spec/integration/schema/check_rules_spec.rb +39 -31
  48. data/spec/integration/schema/check_with_nth_el_spec.rb +25 -0
  49. data/spec/integration/schema/each_with_set_spec.rb +23 -24
  50. data/spec/integration/schema/form_spec.rb +122 -0
  51. data/spec/integration/schema/inheriting_schema_spec.rb +31 -0
  52. data/spec/integration/schema/input_processor_spec.rb +46 -0
  53. data/spec/integration/schema/macros/confirmation_spec.rb +33 -0
  54. data/spec/integration/schema/macros/maybe_spec.rb +32 -0
  55. data/spec/integration/schema/macros/required_spec.rb +59 -0
  56. data/spec/integration/schema/macros/when_spec.rb +65 -0
  57. data/spec/integration/schema/nested_values_spec.rb +41 -0
  58. data/spec/integration/schema/not_spec.rb +14 -14
  59. data/spec/integration/schema/option_with_default_spec.rb +30 -0
  60. data/spec/integration/schema/reusing_schema_spec.rb +33 -0
  61. data/spec/integration/schema/using_types_spec.rb +29 -0
  62. data/spec/integration/schema/xor_spec.rb +17 -14
  63. data/spec/integration/schema_spec.rb +75 -245
  64. data/spec/shared/rule_compiler.rb +8 -0
  65. data/spec/spec_helper.rb +13 -0
  66. data/spec/unit/hint_compiler_spec.rb +10 -10
  67. data/spec/unit/{input_type_compiler_spec.rb → input_processor_compiler/form_spec.rb} +88 -73
  68. data/spec/unit/schema/key_spec.rb +33 -0
  69. data/spec/unit/schema/rule_spec.rb +7 -6
  70. data/spec/unit/schema/value_spec.rb +187 -54
  71. metadata +53 -31
  72. data/.rubocop.yml +0 -16
  73. data/.rubocop_todo.yml +0 -7
  74. data/lib/dry/validation/input_type_compiler.rb +0 -83
  75. data/lib/dry/validation/schema/definition.rb +0 -74
  76. data/lib/dry/validation/schema/result.rb +0 -68
  77. data/rakelib/rubocop.rake +0 -18
  78. data/spec/integration/rule_groups_spec.rb +0 -94
  79. data/spec/integration/schema/attrs_spec.rb +0 -38
  80. data/spec/integration/schema/default_key_behavior_spec.rb +0 -23
  81. data/spec/integration/schema/grouped_rules_spec.rb +0 -57
  82. data/spec/integration/schema/nested_spec.rb +0 -31
  83. data/spec/integration/schema_form_spec.rb +0 -97
@@ -0,0 +1,38 @@
1
+ require 'dry/logic/rule_compiler'
2
+
3
+ module Dry
4
+ module Validation
5
+ class Guard
6
+ attr_reader :rule, :deps
7
+
8
+ def initialize(rule, deps)
9
+ @rule = rule
10
+ @deps = deps
11
+ end
12
+
13
+ def call(input, results)
14
+ rule.(input) if deps_valid?(results)
15
+ end
16
+
17
+ private
18
+
19
+ def deps_valid?(results)
20
+ deps.all? do |path|
21
+ result = Array(path).reduce(results) { |a, e| a[e] }
22
+ result.success? if result
23
+ end
24
+ end
25
+ end
26
+
27
+ class SchemaCompiler < Logic::RuleCompiler
28
+ def visit_schema(klass)
29
+ klass.new
30
+ end
31
+
32
+ def visit_guard(node)
33
+ deps, other = node
34
+ Guard.new(visit(other), deps)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,5 +1,5 @@
1
1
  module Dry
2
2
  module Validation
3
- VERSION = '0.6.0'.freeze
3
+ VERSION = '0.7.0'.freeze
4
4
  end
5
5
  end
@@ -1,6 +1,6 @@
1
1
  pl:
2
2
  errors:
3
- filled?: "%{name} nie może być pusty"
3
+ filled?: "nie może być pusty"
4
4
  size?:
5
5
  arg:
6
6
  default: "wielkość musi być równa %{num}"
@@ -0,0 +1,122 @@
1
+ RSpec.describe Dry::Validation::Schema, 'defining attr-based schema' do
2
+ describe 'with a flat structure' do
3
+ subject(:schema) do
4
+ Dry::Validation.Schema do
5
+ attr(:email).required
6
+ attr(:age) { none? | (int? & gt?(18)) }
7
+ end
8
+ end
9
+
10
+ let(:model) { Class.new(OpenStruct) }
11
+
12
+ it 'passes when input is valid' do
13
+ expect(schema.(model.new(email: 'jane@doe', age: 19))).to be_success
14
+ expect(schema.(model.new(email: 'jane@doe', age: nil))).to be_success
15
+ end
16
+
17
+ it 'fails when input is not valid' do
18
+ expect(schema.(model.new(email: 'jane@doe', age: 17))).to_not be_success
19
+ end
20
+ end
21
+
22
+ describe 'with nested structures' do
23
+ subject(:schema) do
24
+ Dry::Validation.Schema do
25
+ attr(:email).required
26
+
27
+ attr(:age) { none? | (int? & gt?(18)) }
28
+
29
+ attr(:address) do
30
+ attr(:city) { min_size?(3) }
31
+
32
+ attr(:street).required
33
+
34
+ attr(:country) do
35
+ attr(:name).required
36
+ attr(:code).required
37
+ end
38
+ end
39
+
40
+ attr(:phone_numbers) do
41
+ array? { each(&:str?) }
42
+ end
43
+ end
44
+ end
45
+
46
+ let(:input) do
47
+ OpenStruct.new(
48
+ email: 'jane@doe.org',
49
+ age: 19,
50
+ address: OpenStruct.new(
51
+ city: 'NYC', street: 'Street 1/2', country: OpenStruct.new(code: 'US', name: 'USA')
52
+ ),
53
+ phone_numbers: [
54
+ '123456', '234567'
55
+ ]
56
+ )
57
+ end
58
+
59
+ describe '#messages' do
60
+ it 'returns compiled error messages' do
61
+ input.email = ''
62
+
63
+ expect(schema.(input).messages).to eql(
64
+ email: ['must be filled']
65
+ )
66
+ end
67
+ end
68
+
69
+ describe '#call' do
70
+ it 'passes when attributes are valid' do
71
+ expect(schema.(input)).to be_success
72
+ end
73
+
74
+ it 'validates presence of an email and min age value' do
75
+ input.email = ''
76
+ input.age = 18
77
+
78
+ expect(schema.(input).messages).to eql(
79
+ email: ['must be filled'], age: ['must be greater than 18']
80
+ )
81
+ end
82
+
83
+ it 'validates type of age value' do
84
+ input.age = '18'
85
+
86
+ expect(schema.(input).messages).to eql(
87
+ age: ['must be an integer', 'must be greater than 18']
88
+ )
89
+ end
90
+
91
+ it 'validates presence of phone_number keys' do
92
+ input.phone_numbers = nil
93
+
94
+ expect(schema.(input).messages).to eql(
95
+ phone_numbers: ['must be an array']
96
+ )
97
+ end
98
+
99
+ it 'validates presence of address street & county & min size of the city' do
100
+ input.address.city = 'NY'
101
+ input.address.street = nil
102
+ input.address.country.name = nil
103
+
104
+ expect(schema.(input).messages).to eql(
105
+ address: {
106
+ street: ['must be filled'],
107
+ country: { name: ['must be filled'] },
108
+ city: ['size cannot be less than 3']
109
+ }
110
+ )
111
+ end
112
+
113
+ it 'validates each phone number' do
114
+ input.phone_numbers = ['123', 312]
115
+
116
+ expect(schema.(input).messages).to eql(
117
+ phone_numbers: { 1 => ['must be a string'] }
118
+ )
119
+ end
120
+ end
121
+ end
122
+ end
@@ -1,22 +1,20 @@
1
1
  require 'dry/validation/messages/i18n'
2
2
 
3
3
  RSpec.describe Dry::Validation do
4
- subject(:validation) { schema.new }
5
-
6
4
  shared_context 'schema with customized messages' do
7
5
  describe '#messages' do
8
6
  it 'returns compiled error messages' do
9
- expect(validation.(email: '').messages).to match_array([
10
- [:email, [['Please provide your email'], '']]
11
- ])
7
+ expect(schema.(email: '').messages).to eql(
8
+ email: ['Please provide your email']
9
+ )
12
10
  end
13
11
  end
14
12
  end
15
13
 
16
14
  context 'yaml' do
17
- let(:schema) do
18
- Class.new(Dry::Validation::Schema) do
19
- configure do |config|
15
+ subject(:schema) do
16
+ Dry::Validation.Schema do
17
+ configure do
20
18
  config.messages_file = SPEC_ROOT.join('fixtures/locales/en.yml')
21
19
  end
22
20
 
@@ -34,9 +32,9 @@ RSpec.describe Dry::Validation do
34
32
  I18n.backend.load_translations
35
33
  end
36
34
 
37
- let(:schema) do
38
- Class.new(Dry::Validation::Schema) do
39
- configure do |config|
35
+ subject(:schema) do
36
+ Dry::Validation.Schema do
37
+ configure do
40
38
  config.messages = :i18n
41
39
  end
42
40
 
@@ -1,28 +1,36 @@
1
1
  RSpec.describe Dry::Validation do
2
- subject(:validation) { schema.new }
3
-
4
2
  shared_context 'uses custom predicates' do
5
3
  it 'uses provided custom predicates' do
6
- expect(validation.(email: 'jane@doe')).to be_empty
4
+ expect(schema.(email: 'jane@doe')).to be_success
7
5
 
8
- expect(validation.(email: nil)).to match_array([
9
- [:error, [:input, [:email, nil, [[:val, [:email, [:predicate, [:filled?, []]]]]]]]]
10
- ])
6
+ expect(schema.(email: nil).messages).to eql(
7
+ email: ['must be filled', 'must be a valid email']
8
+ )
11
9
 
12
- expect(validation.(email: 'jane')).to match_array([
13
- [:error, [:input, [:email, 'jane', [[:val, [:email, [:predicate, [:email?, []]]]]]]]]
14
- ])
10
+ expect(schema.(email: 'jane').messages).to eql(
11
+ email: ['must be a valid email']
12
+ )
13
+ end
14
+ end
15
+
16
+ let(:base_class) do
17
+ Class.new(Dry::Validation::Schema) do
18
+ def self.messages
19
+ Dry::Validation::Messages.default.merge(
20
+ en: { errors: { email?: 'must be a valid email' } }
21
+ )
22
+ end
15
23
  end
16
24
  end
17
25
 
18
26
  describe 'defining schema with custom predicates container' do
19
- let(:schema) do
20
- Class.new(Dry::Validation::Schema) do
21
- configure do |config|
27
+ subject(:schema) do
28
+ Dry::Validation.Schema(base_class) do
29
+ configure do
22
30
  config.predicates = Test::Predicates
23
31
  end
24
32
 
25
- key(:email) { |value| value.filled? & value.email? }
33
+ key(:email) { filled? & email? }
26
34
  end
27
35
  end
28
36
 
@@ -42,16 +50,58 @@ RSpec.describe Dry::Validation do
42
50
  end
43
51
 
44
52
  describe 'defining schema with custom predicate methods' do
45
- let(:schema) do
46
- Class.new(Dry::Validation::Schema) do
47
- key(:email) { |value| value.filled? & value.email? }
53
+ subject(:schema) do
54
+ Dry::Validation.Schema(base_class) do
55
+ configure do
56
+ def email?(value)
57
+ value.include?('@')
58
+ end
59
+ end
60
+
61
+ key(:email) { filled? & email? }
62
+ end
63
+ end
64
+
65
+ include_context 'uses custom predicates'
66
+ end
48
67
 
68
+ describe 'custom predicate which requires an arbitrary dependency' do
69
+ subject(:schema) do
70
+ Dry::Validation.Schema(base_class) do
71
+ key(:email).required(:email?)
72
+
73
+ configure do
74
+ option :email_check
75
+
76
+ def email?(value)
77
+ email_check.(value)
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ it 'uses injected dependency for the custom predicate' do
84
+ email_check = -> input { input.include?('@') }
85
+
86
+ expect(schema.with(email_check: email_check).(email: 'foo').messages).to eql(
87
+ email: ['must be a valid email']
88
+ )
89
+ end
90
+ end
91
+
92
+ it 'raises an error when message is missing' do
93
+ schema = Dry::Validation.Schema do
94
+ configure do
49
95
  def email?(value)
50
- value.include?('@')
96
+ false
51
97
  end
52
98
  end
99
+
100
+ key(:email).required(:email?)
53
101
  end
54
102
 
55
- include_context 'uses custom predicates'
103
+ expect { schema.(email: 'foo').messages }.to raise_error(
104
+ Dry::Validation::MissingMessageError, /email/
105
+ )
56
106
  end
57
107
  end
@@ -9,239 +9,433 @@ RSpec.describe Dry::Validation::ErrorCompiler do
9
9
  en: {
10
10
  errors: {
11
11
  key?: '+%{name}+ key is missing in the hash',
12
- attr?: 'Object does not respond to the +%{name}+ attr',
13
12
  rules: {
14
13
  address: {
15
14
  filled?: 'Please provide your address'
16
15
  }
17
16
  }
18
17
  }
18
+ },
19
+ pl: {
20
+ rules: {
21
+ email: 'adres email'
22
+ },
23
+ errors: {
24
+ email?: 'nie jest poprawny'
25
+ }
19
26
  }
20
27
  )
21
28
  end
22
29
 
23
- describe '#call' do
30
+ describe '#call with flat inputs' do
24
31
  let(:ast) do
25
32
  [
26
- [:error, [:input, [:name, nil, [[:key, [:name, [:predicate, [:key?, []]]]]]]]],
27
- [:error, [:input, [:phone, nil, [[:attr, [:phone, [:predicate, [:attr?, []]]]]]]]],
28
- [:error, [:input, [:age, 18, [[:val, [:age, [:predicate, [:gt?, [18]]]]]]]]],
29
- [:error, [:input, [:email, "", [[:val, [:email, [:predicate, [:filled?, []]]]]]]]],
30
- [:error, [:input, [:address, "", [[:val, [:address, [:predicate, [:filled?, []]]]]]]]]
33
+ [:error, [:name, [:input, [:name, [:result, [nil, [:val, [:predicate, [:key?, [:name]]]]]]]]]],
34
+ [:error, [:age, [:input, [:age, [:result, [18, [:val, [:predicate, [:gt?, [18]]]]]]]]]],
35
+ [:error, [:email, [:input, [:email, [:result, ["", [:val, [:predicate, [:filled?, []]]]]]]]]],
36
+ [:error, [:address, [:input, [:address, [:result, ["", [:val, [:predicate, [:filled?, []]]]]]]]]]
31
37
  ]
32
38
  end
33
39
 
34
40
  it 'converts error ast into another format' do
35
41
  expect(error_compiler.(ast)).to eql(
36
- name: [["+name+ key is missing in the hash"], nil],
37
- phone: [["Object does not respond to the +phone+ attr"], nil],
38
- age: [["age must be greater than 18"], 18],
39
- email: [["email must be filled"], ''],
40
- address: [["Please provide your address"], '']
42
+ name: ["+name+ key is missing in the hash"],
43
+ age: ["must be greater than 18"],
44
+ email: ["must be filled"],
45
+ address: ["Please provide your address"]
41
46
  )
42
47
  end
43
48
  end
44
49
 
45
- describe '#visit_predicate' do
50
+ describe '#call with check errors' do
51
+ let(:ast) do
52
+ [[:error, [:newsletter, [
53
+ :input, [[:settings, :newsletter], [
54
+ :result, [
55
+ [true, true],
56
+ [
57
+ :check, [
58
+ :newsletter,
59
+ [:implication, [
60
+ [:key, [[:settings, :offers], [:predicate, [:true?, []]]]],
61
+ [:key, [[:settings, :newsletter], [:predicate, [:false?, []]]]]]
62
+ ]
63
+ ]
64
+ ]
65
+ ]
66
+ ]
67
+ ]]]
68
+ ]]
69
+ end
70
+
71
+ it 'converts error ast into another format' do
72
+ expect(error_compiler.(ast)).to eql(
73
+ settings: { newsletter: ['must be false'] }
74
+ )
75
+ end
76
+ end
77
+
78
+ describe '#call with arr inputs' do
79
+ let(:ast) do
80
+ [[:error, [:payments,
81
+ [:input, [
82
+ :payments, [:result, [
83
+ [{ method: "cc", amount: 1.23 }, { amount: 4.56 }], [:each, [
84
+ [:el, [
85
+ 1, [
86
+ :result, [{ amount: 4.56 }, [:val, [:predicate, [:key?, [:method]]]]]
87
+ ]
88
+ ]]
89
+ ]]]
90
+ ]]]
91
+ ]]]
92
+ end
93
+
94
+ it 'converts error ast into another format' do
95
+ expect(error_compiler.(ast)).to eql(
96
+ payments: { 1 => { method: ['+method+ key is missing in the hash'] } }
97
+ )
98
+ end
99
+ end
100
+
101
+ describe '#visit with an :input node' do
102
+ context 'full message' do
103
+ it 'returns full message including rule name' do
104
+ msg = error_compiler.with(full: true).visit(
105
+ [:input, [:num, [
106
+ :result, ['2', [:val, [:predicate, [:int?, []]]]]]
107
+ ]]
108
+ )
109
+
110
+ expect(msg).to eql(num: ['num must be an integer'])
111
+ end
112
+ end
113
+
114
+ context 'rule name translations' do
115
+ it 'translates rule name and its message' do
116
+ msg = error_compiler.with(locale: :pl, full: true).visit(
117
+ [:input, [:email, [
118
+ :result, ['oops', [:val, [:predicate, [:email?, []]]]]]
119
+ ]]
120
+ )
121
+
122
+ expect(msg).to eql(email: ['adres email nie jest poprawny'])
123
+ end
124
+ end
125
+
46
126
  describe ':empty?' do
47
127
  it 'returns valid message' do
48
- msg = error_compiler.visit_predicate([:empty?, []], [], :tags)
128
+ msg = error_compiler.visit(
129
+ [:input, [:tags, [:result, [nil, [:val, [:predicate, [:empty?, []]]]]]]]
130
+ )
49
131
 
50
- expect(msg).to eql('tags cannot be empty')
132
+ expect(msg).to eql(tags: ['cannot be empty'])
51
133
  end
52
134
  end
53
135
 
54
136
  describe ':exclusion?' do
55
137
  it 'returns valid message' do
56
- msg = error_compiler.visit_predicate([:exclusion?, [[1, 2, 3]]], 2, :num)
138
+ msg = error_compiler.visit(
139
+ [:input, [:num, [
140
+ :result, [2, [:val, [:predicate, [:exclusion?, [[1, 2, 3]]]]]]]
141
+ ]]
142
+ )
57
143
 
58
- expect(msg).to eql('num must not be one of: 1, 2, 3')
144
+ expect(msg).to eql(num: ['must not be one of: 1, 2, 3'])
59
145
  end
60
146
  end
61
147
 
62
148
  describe ':inclusion?' do
63
149
  it 'returns valid message' do
64
- msg = error_compiler.visit_predicate([:inclusion?, [[1, 2, 3]]], 2, :num)
150
+ msg = error_compiler.visit(
151
+ [:input, [:num, [
152
+ :result, [2, [:val, [:predicate, [:inclusion?, [[1, 2, 3]]]]]]]
153
+ ]]
154
+ )
65
155
 
66
- expect(msg).to eql('num must be one of: 1, 2, 3')
156
+ expect(msg).to eql(num: ['must be one of: 1, 2, 3'])
67
157
  end
68
158
  end
69
159
 
70
160
  describe ':gt?' do
71
161
  it 'returns valid message' do
72
- msg = error_compiler.visit_predicate([:gt?, [3]], 2, :num)
162
+ msg = error_compiler.visit(
163
+ [:input, [:num, [
164
+ :result, [2, [:val, [:predicate, [:gt?, [3]]]]]]
165
+ ]]
166
+ )
73
167
 
74
- expect(msg).to eql('num must be greater than 3')
168
+ expect(msg).to eql(num: ['must be greater than 3'])
75
169
  end
76
170
  end
77
171
 
78
172
  describe ':gteq?' do
79
173
  it 'returns valid message' do
80
- msg = error_compiler.visit_predicate([:gteq?, [3]], 2, :num)
174
+ msg = error_compiler.visit(
175
+ [:input, [:num, [
176
+ :result, [2, [:val, [:predicate, [:gteq?, [3]]]]]]
177
+ ]]
178
+ )
81
179
 
82
- expect(msg).to eql('num must be greater than or equal to 3')
180
+ expect(msg).to eql(num: ['must be greater than or equal to 3'])
83
181
  end
84
182
  end
85
183
 
86
184
  describe ':lt?' do
87
185
  it 'returns valid message' do
88
- msg = error_compiler.visit_predicate([:lt?, [3]], 2, :num)
186
+ msg = error_compiler.visit(
187
+ [:input, [:num, [
188
+ :result, [2, [:val, [:predicate, [:lt?, [3]]]]]]
189
+ ]]
190
+ )
89
191
 
90
- expect(msg).to eql('num must be less than 3 (2 was given)')
192
+ expect(msg).to eql(num: ['must be less than 3 (2 was given)'])
91
193
  end
92
194
  end
93
195
 
94
196
  describe ':lteq?' do
95
197
  it 'returns valid message' do
96
- msg = error_compiler.visit_predicate([:lteq?, [3]], 2, :num)
198
+ msg = error_compiler.visit(
199
+ [:input, [:num, [
200
+ :result, [2, [:val, [:predicate, [:lteq?, [3]]]]]]
201
+ ]]
202
+ )
97
203
 
98
- expect(msg).to eql('num must be less than or equal to 3')
204
+ expect(msg).to eql(num: ['must be less than or equal to 3'])
99
205
  end
100
206
  end
101
207
 
102
208
  describe ':hash?' do
103
209
  it 'returns valid message' do
104
- msg = error_compiler.visit_predicate([:hash?, []], '', :address)
210
+ msg = error_compiler.visit(
211
+ [:input, [:address, [
212
+ :result, ['', [:val, [:predicate, [:hash?, []]]]]]
213
+ ]]
214
+ )
105
215
 
106
- expect(msg).to eql('address must be a hash')
216
+ expect(msg).to eql(address: ['must be a hash'])
107
217
  end
108
218
  end
109
219
 
110
220
  describe ':array?' do
111
221
  it 'returns valid message' do
112
- msg = error_compiler.visit_predicate([:array?, []], '', :phone_numbers)
222
+ msg = error_compiler.visit(
223
+ [:input, [:phone_numbers, [
224
+ :result, ['', [:val, [:predicate, [:array?, []]]]]]
225
+ ]]
226
+ )
113
227
 
114
- expect(msg).to eql('phone_numbers must be an array')
228
+ expect(msg).to eql(phone_numbers: ['must be an array'])
115
229
  end
116
230
  end
117
231
 
118
232
  describe ':int?' do
119
233
  it 'returns valid message' do
120
- msg = error_compiler.visit_predicate([:int?, []], '2', :num)
234
+ msg = error_compiler.visit(
235
+ [:input, [:num, [
236
+ :result, ['2', [:val, [:predicate, [:int?, []]]]]]
237
+ ]]
238
+ )
121
239
 
122
- expect(msg).to eql('num must be an integer')
240
+ expect(msg).to eql(num: ['must be an integer'])
123
241
  end
124
242
  end
125
243
 
126
244
  describe ':float?' do
127
245
  it 'returns valid message' do
128
- msg = error_compiler.visit_predicate([:float?, []], '2', :num)
246
+ msg = error_compiler.visit(
247
+ [:input, [:num, [
248
+ :result, ['2', [:val, [:predicate, [:float?, []]]]]]
249
+ ]]
250
+ )
129
251
 
130
- expect(msg).to eql('num must be a float')
252
+ expect(msg).to eql(num: ['must be a float'])
131
253
  end
132
254
  end
133
255
 
134
256
  describe ':decimal?' do
135
257
  it 'returns valid message' do
136
- msg = error_compiler.visit_predicate([:decimal?, []], '2', :num)
258
+ msg = error_compiler.visit(
259
+ [:input, [:num, [
260
+ :result, ['2', [:val, [:predicate, [:decimal?, []]]]]]
261
+ ]]
262
+ )
137
263
 
138
- expect(msg).to eql('num must be a decimal')
264
+ expect(msg).to eql(num: ['must be a decimal'])
139
265
  end
140
266
  end
141
267
 
142
268
  describe ':date?' do
143
269
  it 'returns valid message' do
144
- msg = error_compiler.visit_predicate([:date?, []], '2', :num)
270
+ msg = error_compiler.visit(
271
+ [:input, [:num, [
272
+ :result, ['2', [:val, [:predicate, [:date?, []]]]]]
273
+ ]]
274
+ )
145
275
 
146
- expect(msg).to eql('num must be a date')
276
+ expect(msg).to eql(num: ['must be a date'])
147
277
  end
148
278
  end
149
279
 
150
280
  describe ':date_time?' do
151
281
  it 'returns valid message' do
152
- msg = error_compiler.visit_predicate([:date_time?, []], '2', :num)
282
+ msg = error_compiler.visit(
283
+ [:input, [:num, [
284
+ :result, ['2', [:val, [:predicate, [:date_time?, []]]]]]
285
+ ]]
286
+ )
153
287
 
154
- expect(msg).to eql('num must be a date time')
288
+ expect(msg).to eql(num: ['must be a date time'])
155
289
  end
156
290
  end
157
291
 
158
292
  describe ':time?' do
159
293
  it 'returns valid message' do
160
- msg = error_compiler.visit_predicate([:time?, []], '2', :num)
294
+ msg = error_compiler.visit(
295
+ [:input, [:num, [
296
+ :result, ['2', [:val, [:predicate, [:time?, []]]]]]
297
+ ]]
298
+ )
161
299
 
162
- expect(msg).to eql('num must be a time')
300
+ expect(msg).to eql(num: ['must be a time'])
163
301
  end
164
302
  end
165
303
 
166
304
  describe ':max_size?' do
167
305
  it 'returns valid message' do
168
- msg = error_compiler.visit_predicate([:max_size?, [3]], 'abcd', :num)
306
+ msg = error_compiler.visit(
307
+ [:input, [:num, [
308
+ :result, ['abcd', [:val, [:predicate, [:max_size?, [3]]]]]]
309
+ ]]
310
+ )
169
311
 
170
- expect(msg).to eql('num size cannot be greater than 3')
312
+ expect(msg).to eql(num: ['size cannot be greater than 3'])
171
313
  end
172
314
  end
173
315
 
174
316
  describe ':min_size?' do
175
317
  it 'returns valid message' do
176
- msg = error_compiler.visit_predicate([:min_size?, [3]], 'ab', :num)
318
+ msg = error_compiler.visit(
319
+ [:input, [:num, [
320
+ :result, ['ab', [:val, [:predicate, [:min_size?, [3]]]]]]
321
+ ]]
322
+ )
177
323
 
178
- expect(msg).to eql('num size cannot be less than 3')
324
+ expect(msg).to eql(num: ['size cannot be less than 3'])
179
325
  end
180
326
  end
181
327
 
182
328
  describe ':none?' do
183
329
  it 'returns valid message' do
184
- msg = error_compiler.visit_predicate([:none?, []], nil, :num)
330
+ msg = error_compiler.visit(
331
+ [:input, [:num, [
332
+ :result, [nil, [:val, [:predicate, [:none?, []]]]]]
333
+ ]]
334
+ )
185
335
 
186
- expect(msg).to eql('num cannot be defined')
336
+ expect(msg).to eql(num: ['cannot be defined'])
187
337
  end
188
338
  end
189
339
 
190
340
  describe ':size?' do
191
341
  it 'returns valid message when val is array and arg is int' do
192
- msg = error_compiler.visit_predicate([:size?, [3]], [1], :numbers)
342
+ msg = error_compiler.visit(
343
+ [:input, [:numbers, [
344
+ :result, [[1], [:val, [:predicate, [:size?, [3]]]]]]
345
+ ]]
346
+ )
193
347
 
194
- expect(msg).to eql('numbers size must be 3')
348
+ expect(msg).to eql(numbers: ['size must be 3'])
195
349
  end
196
350
 
197
351
  it 'returns valid message when val is array and arg is range' do
198
- msg = error_compiler.visit_predicate([:size?, [3..4]], [1], :numbers)
352
+ msg = error_compiler.visit(
353
+ [:input, [:numbers, [
354
+ :result, [[1], [:val, [:predicate, [:size?, [3..4]]]]]]
355
+ ]]
356
+ )
199
357
 
200
- expect(msg).to eql('numbers size must be within 3 - 4')
358
+ expect(msg).to eql(numbers: ['size must be within 3 - 4'])
201
359
  end
202
360
 
203
361
  it 'returns valid message when arg is int' do
204
- msg = error_compiler.visit_predicate([:size?, [3]], 'ab', :num)
362
+ msg = error_compiler.visit(
363
+ [:input, [:num, [
364
+ :result, ['ab', [:val, [:predicate, [:size?, [3]]]]]]
365
+ ]]
366
+ )
205
367
 
206
- expect(msg).to eql('num length must be 3')
368
+ expect(msg).to eql(num: ['length must be 3'])
207
369
  end
208
370
 
209
371
  it 'returns valid message when arg is range' do
210
- msg = error_compiler.visit_predicate([:size?, [3..4]], 'ab', :num)
372
+ msg = error_compiler.visit(
373
+ [:input, [:num, [
374
+ :result, ['ab', [:val, [:predicate, [:size?, [3..4]]]]]]
375
+ ]]
376
+ )
211
377
 
212
- expect(msg).to eql('num length must be within 3 - 4')
378
+ expect(msg).to eql(num: ['length must be within 3 - 4'])
213
379
  end
214
380
  end
215
381
 
216
382
  describe ':str?' do
217
383
  it 'returns valid message' do
218
- msg = error_compiler.visit_predicate([:str?, []], 3, :num)
384
+ msg = error_compiler.visit(
385
+ [:input, [:num, [
386
+ :result, [3, [:val, [:predicate, [:str?, []]]]]]
387
+ ]]
388
+ )
219
389
 
220
- expect(msg).to eql('num must be a string')
390
+ expect(msg).to eql(num: ['must be a string'])
221
391
  end
222
392
  end
223
393
 
224
394
  describe ':bool?' do
225
395
  it 'returns valid message' do
226
- msg = error_compiler.visit_predicate([:bool?, []], 3, :num)
396
+ msg = error_compiler.visit(
397
+ [:input, [:num, [
398
+ :result, [3, [:val, [:predicate, [:bool?, []]]]]]
399
+ ]]
400
+ )
227
401
 
228
- expect(msg).to eql('num must be boolean')
402
+ expect(msg).to eql(num: ['must be boolean'])
229
403
  end
230
404
  end
231
405
 
232
406
  describe ':format?' do
233
407
  it 'returns valid message' do
234
- msg = error_compiler.visit_predicate([:format?, [/^F/]], 'Bar', :str)
408
+ msg = error_compiler.visit(
409
+ [:input, [:str, [
410
+ :result, ['Bar', [:val, [:predicate, [:format?, [/^F/]]]]]]
411
+ ]]
412
+ )
235
413
 
236
- expect(msg).to eql('str is in invalid format')
414
+ expect(msg).to eql(str: ['is in invalid format'])
237
415
  end
238
416
  end
239
417
 
240
418
  describe ':eql?' do
241
419
  it 'returns valid message' do
242
- msg = error_compiler.visit_predicate([:eql?, ['Bar']], 'Foo', :str)
420
+ msg = error_compiler.visit(
421
+ [:input, [:str, [
422
+ :result, ['Foo', [:val, [:predicate, [:eql?, ['Bar']]]]]]
423
+ ]]
424
+ )
425
+
426
+ expect(msg).to eql(str: ['must be equal to Bar'])
427
+ end
428
+ end
429
+
430
+ describe ':type??' do
431
+ it 'returns valid message' do
432
+ msg = error_compiler.visit(
433
+ [:input, [:age, [
434
+ :result, ['1', [:val, [:predicate, [:type?, [Integer]]]]]]
435
+ ]]
436
+ )
243
437
 
244
- expect(msg).to eql('str must be equal to Bar')
438
+ expect(msg).to eql(age: ['must be Integer'])
245
439
  end
246
440
  end
247
441
  end