dry-validation 0.9.5 → 0.10.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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -8
- data/CHANGELOG.md +34 -1
- data/Gemfile +2 -0
- data/Rakefile +12 -3
- data/config/errors.yml +1 -0
- data/dry-validation.gemspec +3 -2
- data/lib/dry/validation/executor.rb +3 -27
- data/lib/dry/validation/extensions/monads.rb +17 -0
- data/lib/dry/validation/extensions/struct.rb +32 -0
- data/lib/dry/validation/extensions.rb +7 -0
- data/lib/dry/validation/input_processor_compiler.rb +19 -3
- data/lib/dry/validation/message.rb +43 -30
- data/lib/dry/validation/message_compiler/visitor_opts.rb +39 -0
- data/lib/dry/validation/message_compiler.rb +98 -52
- data/lib/dry/validation/message_set.rb +59 -30
- data/lib/dry/validation/predicate_registry.rb +17 -6
- data/lib/dry/validation/result.rb +39 -17
- data/lib/dry/validation/schema/check.rb +5 -4
- data/lib/dry/validation/schema/class_interface.rb +6 -13
- data/lib/dry/validation/schema/dsl.rb +9 -3
- data/lib/dry/validation/schema/form.rb +12 -3
- data/lib/dry/validation/schema/json.rb +12 -3
- data/lib/dry/validation/schema/key.rb +6 -6
- data/lib/dry/validation/schema/rule.rb +14 -8
- data/lib/dry/validation/schema/value.rb +23 -21
- data/lib/dry/validation/schema.rb +9 -12
- data/lib/dry/validation/schema_compiler.rb +16 -2
- data/lib/dry/validation/version.rb +1 -1
- data/lib/dry/validation.rb +11 -23
- data/spec/extensions/monads/result_spec.rb +38 -0
- data/spec/extensions/struct/schema_spec.rb +32 -0
- data/spec/integration/custom_predicates_spec.rb +7 -6
- data/spec/integration/form/predicates/size/fixed_spec.rb +0 -2
- data/spec/integration/form/predicates/size/range_spec.rb +0 -2
- data/spec/integration/hints_spec.rb +2 -6
- data/spec/integration/json/defining_base_schema_spec.rb +41 -0
- data/spec/integration/{error_compiler_spec.rb → message_compiler_spec.rb} +79 -131
- data/spec/integration/result_spec.rb +26 -4
- data/spec/integration/schema/check_with_nested_el_spec.rb +1 -1
- data/spec/integration/schema/check_with_nth_el_spec.rb +1 -1
- data/spec/integration/schema/defining_base_schema_spec.rb +3 -0
- data/spec/integration/schema/dynamic_predicate_args_spec.rb +34 -9
- data/spec/integration/schema/form/defining_base_schema_spec.rb +41 -0
- data/spec/integration/schema/json_spec.rb +1 -0
- data/spec/integration/schema/macros/input_spec.rb +26 -0
- data/spec/integration/schema/macros/rule_spec.rb +2 -1
- data/spec/integration/schema/macros/value_spec.rb +1 -1
- data/spec/integration/schema/macros/when_spec.rb +1 -24
- data/spec/integration/schema/or_spec.rb +87 -0
- data/spec/integration/schema/predicates/custom_spec.rb +4 -4
- data/spec/integration/schema/predicates/even_spec.rb +10 -10
- data/spec/integration/schema/predicates/odd_spec.rb +10 -10
- data/spec/integration/schema/predicates/size/fixed_spec.rb +0 -3
- data/spec/integration/schema/predicates/size/range_spec.rb +0 -2
- data/spec/integration/schema/predicates/type_spec.rb +22 -0
- data/spec/integration/schema/using_types_spec.rb +14 -41
- data/spec/integration/schema/validate_spec.rb +83 -0
- data/spec/integration/schema/xor_spec.rb +5 -5
- data/spec/integration/schema_builders_spec.rb +4 -2
- data/spec/integration/schema_spec.rb +8 -0
- data/spec/shared/message_compiler.rb +11 -0
- data/spec/shared/predicate_helper.rb +5 -3
- data/spec/spec_helper.rb +15 -0
- data/spec/unit/input_processor_compiler/form_spec.rb +3 -3
- data/spec/unit/message_compiler/visit_failure_spec.rb +38 -0
- data/spec/unit/message_compiler/visit_spec.rb +16 -0
- data/spec/unit/message_compiler_spec.rb +7 -0
- data/spec/unit/predicate_registry_spec.rb +2 -2
- data/spec/unit/schema/key_spec.rb +19 -12
- data/spec/unit/schema/rule_spec.rb +14 -6
- data/spec/unit/schema/value_spec.rb +49 -52
- metadata +50 -20
- data/lib/dry/validation/constants.rb +0 -6
- data/lib/dry/validation/error.rb +0 -26
- data/lib/dry/validation/error_compiler.rb +0 -81
- data/lib/dry/validation/hint_compiler.rb +0 -104
- data/spec/unit/error_compiler_spec.rb +0 -7
- data/spec/unit/hint_compiler_spec.rb +0 -51
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 94d71785e9206636cca2df3765b30ebbf7009b38
|
4
|
+
data.tar.gz: 4d725f61ebc1f28a9dc54a0f6363caa8b66108a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1fe8b86ff9acc5ee111d271a9e20b6a1d07af65719090f65cebe3ec45337bef7534abec1ec4cc9879b281546986cd197c0c6e75d96f7a1994ca9fba890ab7382
|
7
|
+
data.tar.gz: 252091265ccc93ca446e77fc2895ace365505a602f9ff4274df1fb9d00e3d3e582b976c2c3bb58a74ca9bcc1107db1f09b0d996b0687426dc43c6578c84ebc5c
|
data/.travis.yml
CHANGED
@@ -3,26 +3,19 @@ sudo: false
|
|
3
3
|
cache: bundler
|
4
4
|
bundler_args: --without console benchmarks
|
5
5
|
script:
|
6
|
-
- bundle exec rake
|
6
|
+
- bundle exec rake
|
7
7
|
rvm:
|
8
|
-
- 2.0
|
9
8
|
- 2.1.10
|
10
9
|
- 2.2.5
|
11
10
|
- 2.3.1
|
12
11
|
- rbx-2
|
13
12
|
- jruby-9.1.1.0
|
14
|
-
- ruby-head
|
15
13
|
env:
|
16
14
|
global:
|
17
15
|
- JRUBY_OPTS='--dev -J-Xmx1024M'
|
18
16
|
matrix:
|
19
17
|
allow_failures:
|
20
18
|
- rvm: rbx-2
|
21
|
-
- rvm: ruby-head
|
22
|
-
- rvm: jruby-head
|
23
|
-
include:
|
24
|
-
- rvm: jruby-head
|
25
|
-
before_install: gem install bundler --no-ri --no-rdoc
|
26
19
|
|
27
20
|
notifications:
|
28
21
|
email: false
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,37 @@
|
|
1
|
-
# v0.
|
1
|
+
# v0.10.0 to-be-released
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* Support for `validate` DSL which accepts an arbitratry validation block that gets executed in the context of a schema object and is treated as a custom predicate (solnic)
|
6
|
+
* Support for `or` error messages ie "must be a string or must be an integer" (solnic)
|
7
|
+
* Support for retrieving error messages exclusively via `schema.(input).errors` (solnic)
|
8
|
+
* Support for retrieving hint messages exclusively via `schema.(input).hints` (solnic)
|
9
|
+
* Support for opt-in extensions loaded via `Dry::Validation.load_extensions(:my_ext)` (flash-gordon)
|
10
|
+
* Add `:monads` extension which transforms a result instance to `Either` monad, `schema.(input).to_either` (flash-gordon)
|
11
|
+
* Add `dry-struct` integration via an extension activated by `Dry::Validation.load_extension(:struct)` (flash-gordon)
|
12
|
+
|
13
|
+
### Fixed
|
14
|
+
|
15
|
+
* Input rules (defined via `input` macro) are now lazy-initialized which makes it work with predicates defined on the schema object (solnic)
|
16
|
+
* Hints are properly generated based on argument type in cases like `size?`, where the message should be different for strings (uses "length") or other types (uses "size") (solnic)
|
17
|
+
* Defining nested keys without `schema` blocks results in `ArgumentError` (solnic)
|
18
|
+
|
19
|
+
### Changed
|
20
|
+
|
21
|
+
* [BREAKING] `when` macro no longer supports multiple disconnected rules in its block, whatever the block returns will be used for the implication (solnic)
|
22
|
+
* [BREAKING] `rule(some_name: %i(some keys))` will *always* use `:some_name` as the key for failure messages (solnic)
|
23
|
+
|
24
|
+
### Internal
|
25
|
+
|
26
|
+
* ~2 x performance boost (solnic)
|
27
|
+
* Rule AST was updated to latest dry-logic (solnic)
|
28
|
+
* `MessageCompiler` was drastically simplified based on the new result AST from dry-logic (solnic)
|
29
|
+
* `HintCompiler` is gone as hints are now part of the result AST (solnic)
|
30
|
+
* `maybe` macro creates an implication instead of a disjunction (`not(none?).then(*your-predicates)`) (solnic)
|
31
|
+
|
32
|
+
[Compare v0.9.5...v0.10.0](https://github.com/dryrb/dry-validation/compare/v0.9.5...v0.10.0)
|
33
|
+
|
34
|
+
# v0.9.5 2016-08-16
|
2
35
|
|
3
36
|
### Fixed
|
4
37
|
|
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -6,7 +6,16 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
|
6
6
|
require 'rspec/core'
|
7
7
|
require 'rspec/core/rake_task'
|
8
8
|
|
9
|
-
task default: :spec
|
10
|
-
|
11
9
|
desc 'Run all specs in spec directory'
|
12
|
-
|
10
|
+
task :run_specs do
|
11
|
+
require 'rspec/core'
|
12
|
+
|
13
|
+
RSpec::Core::Runner.run(['spec/integration', 'spec/unit'])
|
14
|
+
RSpec.clear_examples
|
15
|
+
Dir[SPEC_ROOT.join('shared/**/*.rb')].each(&method(:load))
|
16
|
+
|
17
|
+
Dry::Validation.load_extensions(:monads, :struct)
|
18
|
+
RSpec::Core::Runner.run(['spec'])
|
19
|
+
end
|
20
|
+
|
21
|
+
task default: :run_specs
|
data/config/errors.yml
CHANGED
data/dry-validation.gemspec
CHANGED
@@ -19,8 +19,9 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.add_runtime_dependency 'dry-configurable', '~> 0.1', '>= 0.1.3'
|
20
20
|
spec.add_runtime_dependency 'dry-container', '~> 0.2', '>= 0.2.8'
|
21
21
|
spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
|
22
|
-
spec.add_runtime_dependency 'dry-logic', '~> 0.
|
23
|
-
spec.add_runtime_dependency 'dry-types', '~> 0.
|
22
|
+
spec.add_runtime_dependency 'dry-logic', '~> 0.4', '>= 0.4.0'
|
23
|
+
spec.add_runtime_dependency 'dry-types', '~> 0.9', '>= 0.9.0'
|
24
|
+
spec.add_runtime_dependency 'dry-core', '~> 0.1'
|
24
25
|
|
25
26
|
spec.add_development_dependency 'bundler'
|
26
27
|
spec.add_development_dependency 'rake'
|
@@ -52,40 +52,16 @@ module Dry
|
|
52
52
|
end
|
53
53
|
|
54
54
|
class BuildErrors
|
55
|
-
attr_reader :path
|
56
|
-
|
57
|
-
def self.[](path)
|
58
|
-
path.nil? || path.empty? ? Flat.new : Nested.new(path)
|
59
|
-
end
|
60
|
-
|
61
|
-
class Flat < BuildErrors
|
62
|
-
def error_path(name)
|
63
|
-
name
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
class Nested < BuildErrors
|
68
|
-
def initialize(path)
|
69
|
-
@path = path
|
70
|
-
end
|
71
|
-
|
72
|
-
def error_path(name)
|
73
|
-
[*path, name]
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
55
|
def call(result)
|
78
|
-
result
|
79
|
-
.select { |_, r| r.failure? }
|
80
|
-
.map { |name, r| Error.new(error_path(name), r) }
|
56
|
+
result.values.select(&:failure?)
|
81
57
|
end
|
82
58
|
end
|
83
59
|
|
84
60
|
class Executor
|
85
61
|
attr_reader :steps, :final
|
86
62
|
|
87
|
-
def self.new(
|
88
|
-
super(BuildErrors
|
63
|
+
def self.new(&block)
|
64
|
+
super(BuildErrors.new).tap { |executor| yield(executor.steps) }.freeze
|
89
65
|
end
|
90
66
|
|
91
67
|
def initialize(final)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'dry/monads/either'
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Validation
|
5
|
+
class Result
|
6
|
+
include Dry::Monads::Either::Mixin
|
7
|
+
|
8
|
+
def to_either(options = EMPTY_HASH)
|
9
|
+
if success?
|
10
|
+
Right(output)
|
11
|
+
else
|
12
|
+
Left(messages(options))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'dry-struct'
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Validation
|
5
|
+
class Schema
|
6
|
+
module StructClassBuilder
|
7
|
+
def create_class(target, other = nil)
|
8
|
+
if other.is_a?(Class) && other < Dry::Struct
|
9
|
+
super do
|
10
|
+
other.schema.each { |attr, type| required(attr).filled(type) }
|
11
|
+
end
|
12
|
+
else
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module StructNode
|
19
|
+
def node(input, *)
|
20
|
+
if input.is_a?(::Class) && input < ::Dry::Struct
|
21
|
+
[type, [name, [:schema, Schema.create_class(self, input)]]]
|
22
|
+
else
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
singleton_class.prepend(StructClassBuilder)
|
29
|
+
Value.prepend(StructNode)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -24,6 +24,11 @@ module Dry
|
|
24
24
|
send(:"visit_#{node[0]}", node[1], *args)
|
25
25
|
end
|
26
26
|
|
27
|
+
def visit_rule(node, *args)
|
28
|
+
_, rule = node
|
29
|
+
visit(rule, *args)
|
30
|
+
end
|
31
|
+
|
27
32
|
def visit_type(type, *args)
|
28
33
|
if type.is_a?(Types::Constructor)
|
29
34
|
[:constructor, [type.primitive, type.fn]]
|
@@ -63,9 +68,20 @@ module Dry
|
|
63
68
|
end
|
64
69
|
end
|
65
70
|
|
66
|
-
def visit_implication(node)
|
67
|
-
|
68
|
-
|
71
|
+
def visit_implication(node, *args)
|
72
|
+
left, right = node
|
73
|
+
|
74
|
+
key = visit(left)
|
75
|
+
|
76
|
+
if key.is_a?(Symbol)
|
77
|
+
[:key, [key, visit(right, false)]]
|
78
|
+
else
|
79
|
+
[:sum, [key, visit(right, false)]]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def visit_not(node, *args)
|
84
|
+
visit(node, *args)
|
69
85
|
end
|
70
86
|
|
71
87
|
def visit_key(node, *args)
|
@@ -1,32 +1,55 @@
|
|
1
|
-
require 'dry/
|
1
|
+
require 'dry/core/constants'
|
2
2
|
|
3
3
|
module Dry
|
4
4
|
module Validation
|
5
5
|
class Message
|
6
|
+
include Core::Constants
|
6
7
|
include Dry::Equalizer(:predicate, :path, :text, :options)
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
9
|
+
attr_reader :predicate, :path, :text, :rule, :args, :options
|
10
|
+
|
11
|
+
class Or
|
12
|
+
attr_reader :left
|
13
|
+
|
14
|
+
attr_reader :right
|
15
|
+
|
16
|
+
attr_reader :path
|
17
|
+
|
18
|
+
attr_reader :messages
|
19
|
+
|
20
|
+
def initialize(left, right, messages)
|
21
|
+
@left = left
|
22
|
+
@right = right
|
23
|
+
@messages = messages
|
24
|
+
@path = left.path
|
11
25
|
end
|
12
|
-
alias_method :to_s, :inspect
|
13
|
-
}.new
|
14
26
|
|
15
|
-
|
27
|
+
def hint?
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
def root?
|
32
|
+
path.empty?
|
33
|
+
end
|
16
34
|
|
17
|
-
|
18
|
-
|
19
|
-
@index_path ||= [*path[0..path.size-2], Index]
|
35
|
+
def to_s
|
36
|
+
[left, right].uniq.join(" #{messages[:or]} ")
|
20
37
|
end
|
38
|
+
end
|
21
39
|
|
22
|
-
|
23
|
-
|
40
|
+
class Check < Message
|
41
|
+
def initialize(*args)
|
42
|
+
super
|
43
|
+
@path = [rule] unless rule.to_s.end_with?('?') || path.include?(rule)
|
24
44
|
end
|
25
45
|
end
|
26
46
|
|
27
47
|
def self.[](predicate, path, text, options)
|
28
|
-
|
29
|
-
|
48
|
+
if options[:check]
|
49
|
+
Message::Check.new(predicate, path, text, options)
|
50
|
+
else
|
51
|
+
Message.new(predicate, path, text, options)
|
52
|
+
end
|
30
53
|
end
|
31
54
|
|
32
55
|
def initialize(predicate, path, text, options)
|
@@ -35,22 +58,19 @@ module Dry
|
|
35
58
|
@text = text
|
36
59
|
@options = options
|
37
60
|
@rule = options[:rule]
|
38
|
-
@each = options[:each] || false
|
39
61
|
@args = options[:args] || EMPTY_ARRAY
|
40
|
-
end
|
41
62
|
|
42
|
-
|
63
|
+
if predicate == :key?
|
64
|
+
@path << rule
|
65
|
+
end
|
66
|
+
end
|
43
67
|
|
44
68
|
def to_s
|
45
69
|
text
|
46
70
|
end
|
47
71
|
|
48
72
|
def signature
|
49
|
-
@signature ||= [predicate, args,
|
50
|
-
end
|
51
|
-
|
52
|
-
def each?
|
53
|
-
@each
|
73
|
+
@signature ||= [predicate, args, path].hash
|
54
74
|
end
|
55
75
|
|
56
76
|
def hint?
|
@@ -68,14 +88,7 @@ module Dry
|
|
68
88
|
|
69
89
|
class Hint < Message
|
70
90
|
def self.[](predicate, path, text, options)
|
71
|
-
|
72
|
-
klass.new(predicate, path, text, options)
|
73
|
-
end
|
74
|
-
|
75
|
-
class Each < Hint
|
76
|
-
def index_path
|
77
|
-
@index_path ||= [*path, Index]
|
78
|
-
end
|
91
|
+
Hint.new(predicate, path, text, options)
|
79
92
|
end
|
80
93
|
|
81
94
|
def hint?
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'dry/core/constants'
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Validation
|
5
|
+
class MessageCompiler
|
6
|
+
class VisitorOpts < Hash
|
7
|
+
def self.new
|
8
|
+
opts = super
|
9
|
+
opts[:path] = Core::Constants::EMPTY_ARRAY
|
10
|
+
opts[:rule] = nil
|
11
|
+
opts[:message_type] = :failure
|
12
|
+
opts
|
13
|
+
end
|
14
|
+
|
15
|
+
def path?
|
16
|
+
! path.empty?
|
17
|
+
end
|
18
|
+
|
19
|
+
def path
|
20
|
+
self[:path]
|
21
|
+
end
|
22
|
+
|
23
|
+
def rule
|
24
|
+
self[:rule]
|
25
|
+
end
|
26
|
+
|
27
|
+
def with_rule(new_rule, **other)
|
28
|
+
opts = dup
|
29
|
+
opts[:rule] = new_rule unless opts.rule
|
30
|
+
opts.(other)
|
31
|
+
end
|
32
|
+
|
33
|
+
def call(other)
|
34
|
+
merge(other.update(path: [*path, *other[:path]]))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -1,85 +1,144 @@
|
|
1
|
-
require 'dry/
|
1
|
+
require 'dry/core/constants'
|
2
2
|
require 'dry/validation/message'
|
3
3
|
require 'dry/validation/message_set'
|
4
|
+
require 'dry/validation/message_compiler/visitor_opts'
|
4
5
|
|
5
6
|
module Dry
|
6
7
|
module Validation
|
7
8
|
class MessageCompiler
|
9
|
+
include Core::Constants
|
10
|
+
|
8
11
|
attr_reader :messages, :options, :locale, :default_lookup_options
|
9
12
|
|
13
|
+
EMPTY_OPTS = VisitorOpts.new
|
14
|
+
LIST_SEPARATOR = ', '.freeze
|
15
|
+
|
10
16
|
def initialize(messages, options = {})
|
11
17
|
@messages = messages
|
12
18
|
@options = options
|
13
19
|
@full = @options.fetch(:full, false)
|
20
|
+
@hints = @options.fetch(:hints, true)
|
14
21
|
@locale = @options.fetch(:locale, messages.default_locale)
|
15
|
-
@default_lookup_options = {
|
16
|
-
end
|
17
|
-
|
18
|
-
def call(ast)
|
19
|
-
MessageSet[ast.map { |node| visit(node) }]
|
22
|
+
@default_lookup_options = { locale: locale }
|
20
23
|
end
|
21
24
|
|
22
25
|
def full?
|
23
26
|
@full
|
24
27
|
end
|
25
28
|
|
29
|
+
def hints?
|
30
|
+
@hints
|
31
|
+
end
|
32
|
+
|
26
33
|
def with(new_options)
|
27
34
|
return self if new_options.empty?
|
28
35
|
self.class.new(messages, options.merge(new_options))
|
29
36
|
end
|
30
37
|
|
38
|
+
def call(ast)
|
39
|
+
MessageSet[ast.map { |node| visit(node) }, failures: options.fetch(:failures, true)]
|
40
|
+
end
|
41
|
+
|
31
42
|
def visit(node, *args)
|
32
43
|
__send__(:"visit_#{node[0]}", node[1], *args)
|
33
44
|
end
|
34
45
|
|
35
|
-
def
|
36
|
-
|
46
|
+
def visit_failure(node, opts = EMPTY_OPTS)
|
47
|
+
rule, other = node
|
48
|
+
visit(other, opts.(rule: rule))
|
49
|
+
end
|
50
|
+
|
51
|
+
def visit_hint(node, opts = EMPTY_OPTS)
|
52
|
+
if hints?
|
53
|
+
visit(node, opts.(message_type: :hint))
|
54
|
+
end
|
55
|
+
end
|
37
56
|
|
38
|
-
|
57
|
+
def visit_each(node, opts = EMPTY_OPTS)
|
58
|
+
# TODO: we can still generate a hint for elements here!
|
59
|
+
[]
|
60
|
+
end
|
39
61
|
|
40
|
-
|
62
|
+
def visit_not(node, opts = EMPTY_OPTS)
|
63
|
+
visit(node, opts.(not: true))
|
64
|
+
end
|
65
|
+
|
66
|
+
def visit_check(node, opts = EMPTY_OPTS)
|
67
|
+
keys, other = node
|
68
|
+
visit(other, opts.(path: keys.last, check: true))
|
69
|
+
end
|
41
70
|
|
42
|
-
|
43
|
-
|
71
|
+
def visit_rule(node, opts = EMPTY_OPTS)
|
72
|
+
name, other = node
|
73
|
+
visit(other, opts.(rule: name))
|
74
|
+
end
|
75
|
+
|
76
|
+
def visit_schema(node, opts = EMPTY_OPTS)
|
77
|
+
node.rule_ast.map { |rule| visit(rule, opts) }
|
78
|
+
end
|
79
|
+
|
80
|
+
def visit_and(node, opts = EMPTY_OPTS)
|
81
|
+
left, right = node.map { |n| visit(n, opts) }
|
82
|
+
|
83
|
+
if right
|
84
|
+
[left, right]
|
85
|
+
else
|
86
|
+
left
|
44
87
|
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def visit_or(node, opts = EMPTY_OPTS)
|
91
|
+
left, right = node.map { |n| visit(n, opts) }
|
45
92
|
|
46
|
-
|
93
|
+
if [left, right].flatten.map(&:path).uniq.size == 1
|
94
|
+
Message::Or.new(left, right, -> k { messages[k, default_lookup_options] })
|
95
|
+
elsif right.is_a?(Array)
|
96
|
+
right
|
97
|
+
else
|
98
|
+
[left, right]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def visit_predicate(node, base_opts = EMPTY_OPTS)
|
103
|
+
predicate, args = node
|
104
|
+
|
105
|
+
*arg_vals, val = args.map(&:last)
|
106
|
+
tokens = message_tokens(args)
|
107
|
+
|
108
|
+
input = val != Undefined ? val : nil
|
109
|
+
|
110
|
+
options = base_opts.update(lookup_options(arg_vals: arg_vals, input: input))
|
47
111
|
msg_opts = options.update(tokens)
|
48
112
|
|
49
|
-
|
50
|
-
|
113
|
+
rule = msg_opts[:rule]
|
114
|
+
path = msg_opts[:path]
|
51
115
|
|
52
|
-
template = messages[predicate, msg_opts]
|
116
|
+
template = messages[rule] || messages[predicate, msg_opts]
|
53
117
|
|
54
118
|
unless template
|
55
119
|
raise MissingMessageError, "message for #{predicate} was not found"
|
56
120
|
end
|
57
121
|
|
58
122
|
text = message_text(rule, template, tokens, options)
|
59
|
-
|
123
|
+
|
124
|
+
message_class = options[:message_type] == :hint ? Hint : Message
|
60
125
|
|
61
126
|
message_class[
|
62
127
|
predicate, path, text,
|
63
|
-
args: arg_vals,
|
128
|
+
args: arg_vals,
|
129
|
+
input: input,
|
130
|
+
rule: rule,
|
131
|
+
check: base_opts[:check]
|
64
132
|
]
|
65
133
|
end
|
66
134
|
|
67
|
-
def visit_key(node, opts =
|
68
|
-
name,
|
69
|
-
visit(
|
135
|
+
def visit_key(node, opts = EMPTY_OPTS)
|
136
|
+
name, other = node
|
137
|
+
visit(other, opts.(path: name))
|
70
138
|
end
|
71
139
|
|
72
|
-
def
|
73
|
-
visit(
|
74
|
-
end
|
75
|
-
|
76
|
-
def visit_set(node, opts = EMPTY_HASH)
|
77
|
-
node.map { |input| visit(input, opts) }
|
78
|
-
end
|
79
|
-
|
80
|
-
def visit_el(node, opts = EMPTY_HASH)
|
81
|
-
idx, el = node
|
82
|
-
visit(el, opts.merge(path: opts[:path] + [idx]))
|
140
|
+
def visit_set(node, opts = EMPTY_OPTS)
|
141
|
+
node.map { |el| visit(el, opts) }
|
83
142
|
end
|
84
143
|
|
85
144
|
def visit_implication(node, *args)
|
@@ -87,14 +146,15 @@ module Dry
|
|
87
146
|
visit(right, *args)
|
88
147
|
end
|
89
148
|
|
90
|
-
def visit_xor(node,
|
91
|
-
|
92
|
-
visit(right,
|
149
|
+
def visit_xor(node, opts = EMPTY_OPTS)
|
150
|
+
left, right = node
|
151
|
+
[visit(left, opts), visit(right, opts)].uniq
|
93
152
|
end
|
94
153
|
|
95
|
-
def lookup_options(
|
154
|
+
def lookup_options(arg_vals: [], input: nil)
|
96
155
|
default_lookup_options.merge(
|
97
|
-
arg_type: arg_vals.size == 1 && arg_vals[0].class
|
156
|
+
arg_type: arg_vals.size == 1 && arg_vals[0].class,
|
157
|
+
val_type: input.class
|
98
158
|
)
|
99
159
|
end
|
100
160
|
|
@@ -109,25 +169,11 @@ module Dry
|
|
109
169
|
end
|
110
170
|
end
|
111
171
|
|
112
|
-
def message_path(opts, name)
|
113
|
-
if name.is_a?(Array)
|
114
|
-
name
|
115
|
-
else
|
116
|
-
path = opts[:path] || Array(name)
|
117
|
-
|
118
|
-
if name && path.last != name
|
119
|
-
path += [name]
|
120
|
-
end
|
121
|
-
|
122
|
-
path
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
172
|
def message_tokens(args)
|
127
173
|
args.each_with_object({}) { |arg, hash|
|
128
174
|
case arg[1]
|
129
175
|
when Array
|
130
|
-
hash[arg[0]] = arg[1].join(
|
176
|
+
hash[arg[0]] = arg[1].join(LIST_SEPARATOR)
|
131
177
|
when Range
|
132
178
|
hash["#{arg[0]}_left".to_sym] = arg[1].first
|
133
179
|
hash["#{arg[0]}_right".to_sym] = arg[1].last
|