dry-validation 0.1.0 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +969 -1
  3. data/LICENSE +1 -1
  4. data/README.md +19 -286
  5. data/config/errors.yml +4 -35
  6. data/dry-validation.gemspec +38 -22
  7. data/lib/dry/validation/config.rb +24 -0
  8. data/lib/dry/validation/constants.rb +43 -0
  9. data/lib/dry/validation/contract/class_interface.rb +230 -0
  10. data/lib/dry/validation/contract.rb +173 -0
  11. data/lib/dry/validation/evaluator.rb +233 -0
  12. data/lib/dry/validation/extensions/hints.rb +67 -0
  13. data/lib/dry/validation/extensions/monads.rb +34 -0
  14. data/lib/dry/validation/extensions/predicates_as_macros.rb +75 -0
  15. data/lib/dry/validation/failures.rb +70 -0
  16. data/lib/dry/validation/function.rb +43 -0
  17. data/lib/dry/validation/macro.rb +38 -0
  18. data/lib/dry/validation/macros.rb +104 -0
  19. data/lib/dry/validation/message.rb +100 -0
  20. data/lib/dry/validation/message_set.rb +97 -0
  21. data/lib/dry/validation/messages/resolver.rb +129 -0
  22. data/lib/dry/validation/result.rb +206 -38
  23. data/lib/dry/validation/rule.rb +116 -106
  24. data/lib/dry/validation/schema_ext.rb +19 -0
  25. data/lib/dry/validation/values.rb +108 -0
  26. data/lib/dry/validation/version.rb +3 -1
  27. data/lib/dry/validation.rb +55 -7
  28. data/lib/dry-validation.rb +3 -1
  29. metadata +80 -106
  30. data/.gitignore +0 -8
  31. data/.rspec +0 -3
  32. data/.rubocop.yml +0 -16
  33. data/.rubocop_todo.yml +0 -7
  34. data/.travis.yml +0 -29
  35. data/Gemfile +0 -11
  36. data/Rakefile +0 -12
  37. data/examples/basic.rb +0 -21
  38. data/examples/nested.rb +0 -30
  39. data/examples/rule_ast.rb +0 -33
  40. data/lib/dry/validation/error.rb +0 -43
  41. data/lib/dry/validation/error_compiler.rb +0 -116
  42. data/lib/dry/validation/messages.rb +0 -71
  43. data/lib/dry/validation/predicate.rb +0 -39
  44. data/lib/dry/validation/predicate_set.rb +0 -22
  45. data/lib/dry/validation/predicates.rb +0 -88
  46. data/lib/dry/validation/rule_compiler.rb +0 -57
  47. data/lib/dry/validation/schema/definition.rb +0 -15
  48. data/lib/dry/validation/schema/key.rb +0 -39
  49. data/lib/dry/validation/schema/rule.rb +0 -28
  50. data/lib/dry/validation/schema/value.rb +0 -31
  51. data/lib/dry/validation/schema.rb +0 -74
  52. data/rakelib/rubocop.rake +0 -18
  53. data/spec/fixtures/errors.yml +0 -4
  54. data/spec/integration/custom_error_messages_spec.rb +0 -35
  55. data/spec/integration/custom_predicates_spec.rb +0 -57
  56. data/spec/integration/validation_spec.rb +0 -118
  57. data/spec/shared/predicates.rb +0 -31
  58. data/spec/spec_helper.rb +0 -18
  59. data/spec/unit/error_compiler_spec.rb +0 -165
  60. data/spec/unit/predicate_spec.rb +0 -37
  61. data/spec/unit/predicates/empty_spec.rb +0 -38
  62. data/spec/unit/predicates/eql_spec.rb +0 -21
  63. data/spec/unit/predicates/exclusion_spec.rb +0 -35
  64. data/spec/unit/predicates/filled_spec.rb +0 -38
  65. data/spec/unit/predicates/format_spec.rb +0 -21
  66. data/spec/unit/predicates/gt_spec.rb +0 -40
  67. data/spec/unit/predicates/gteq_spec.rb +0 -40
  68. data/spec/unit/predicates/inclusion_spec.rb +0 -35
  69. data/spec/unit/predicates/int_spec.rb +0 -34
  70. data/spec/unit/predicates/key_spec.rb +0 -29
  71. data/spec/unit/predicates/lt_spec.rb +0 -40
  72. data/spec/unit/predicates/lteq_spec.rb +0 -40
  73. data/spec/unit/predicates/max_size_spec.rb +0 -49
  74. data/spec/unit/predicates/min_size_spec.rb +0 -49
  75. data/spec/unit/predicates/nil_spec.rb +0 -28
  76. data/spec/unit/predicates/size_spec.rb +0 -49
  77. data/spec/unit/predicates/str_spec.rb +0 -32
  78. data/spec/unit/rule/each_spec.rb +0 -20
  79. data/spec/unit/rule/key_spec.rb +0 -27
  80. data/spec/unit/rule/set_spec.rb +0 -32
  81. data/spec/unit/rule/value_spec.rb +0 -42
  82. data/spec/unit/rule_compiler_spec.rb +0 -86
@@ -1,71 +0,0 @@
1
- require 'yaml'
2
- require 'pathname'
3
-
4
- module Dry
5
- module Validation
6
- class Messages
7
- DEFAULT_PATH = Pathname(__dir__).join('../../../config/errors.yml').freeze
8
-
9
- attr_reader :data
10
-
11
- def self.default
12
- load(DEFAULT_PATH)
13
- end
14
-
15
- def self.load(path)
16
- new(load_yaml(path))
17
- end
18
-
19
- def self.load_yaml(path)
20
- symbolize_keys(YAML.load_file(path))
21
- end
22
-
23
- def self.symbolize_keys(hash)
24
- hash.each_with_object({}) do |(k, v), r|
25
- r[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v
26
- end
27
- end
28
-
29
- class Namespaced
30
- attr_reader :namespace, :fallback
31
-
32
- def initialize(namespace, fallback)
33
- @namespace = namespace
34
- @fallback = fallback
35
- end
36
-
37
- def lookup(*args)
38
- namespace.lookup(*args) { fallback.lookup(*args) }
39
- end
40
- end
41
-
42
- def initialize(data)
43
- @data = data
44
- end
45
-
46
- def merge(overrides)
47
- if overrides.is_a?(Hash)
48
- self.class.new(data.merge(overrides))
49
- else
50
- self.class.new(data.merge(Messages.load_yaml(overrides)))
51
- end
52
- end
53
-
54
- def namespaced(namespace)
55
- Namespaced.new(Messages.new(data[namespace]), self)
56
- end
57
-
58
- def lookup(identifier, key, arg, &block)
59
- message = data.fetch(:attributes, {}).fetch(key, {}).fetch(identifier) do
60
- data.fetch(identifier, &block)
61
- end
62
-
63
- if message.is_a?(Hash)
64
- message.fetch(arg.class.name.downcase.to_sym, message.fetch(:default))
65
- else
66
- message
67
- end
68
- end
69
- end
70
- end
71
- end
@@ -1,39 +0,0 @@
1
- module Dry
2
- module Validation
3
- def self.Predicate(block)
4
- case block
5
- when Method then Predicate.new(block.name, &block)
6
- else raise ArgumentError, 'predicate needs an :id'
7
- end
8
- end
9
-
10
- class Predicate
11
- include Dry::Equalizer(:id)
12
-
13
- attr_reader :id, :args, :fn
14
-
15
- def initialize(id, *args, &block)
16
- @id = id
17
- @fn = block
18
- @args = args
19
- end
20
-
21
- def call(*args)
22
- fn.(*args)
23
- end
24
-
25
- def negation
26
- self.class.new(:"not_#{id}") { |input| !fn.(input) }
27
- end
28
-
29
- def curry(*args)
30
- self.class.new(id, *args, &fn.curry.(*args))
31
- end
32
-
33
- def to_ary
34
- [:predicate, [id, args]]
35
- end
36
- alias_method :to_a, :to_ary
37
- end
38
- end
39
- end
@@ -1,22 +0,0 @@
1
- require 'dry/validation/predicate'
2
-
3
- module Dry
4
- module Validation
5
- module PredicateSet
6
- module Methods
7
- def predicate(name, &block)
8
- register(name) { Predicate.new(name, &block) }
9
- end
10
-
11
- def import(predicate_set)
12
- merge(predicate_set)
13
- end
14
- end
15
-
16
- def self.extended(other)
17
- super
18
- other.extend(Methods, Dry::Container::Mixin)
19
- end
20
- end
21
- end
22
- end
@@ -1,88 +0,0 @@
1
- require 'dry/validation/predicate_set'
2
-
3
- module Dry
4
- module Validation
5
- module Predicates
6
- extend PredicateSet
7
-
8
- def self.included(other)
9
- super
10
- other.extend(PredicateSet)
11
- other.import(self)
12
- end
13
-
14
- predicate(:nil?) do |input|
15
- input.nil?
16
- end
17
-
18
- predicate(:key?) do |name, input|
19
- input.key?(name)
20
- end
21
-
22
- predicate(:empty?) do |input|
23
- case input
24
- when String, Array, Hash then input.empty?
25
- when nil then true
26
- else
27
- false
28
- end
29
- end
30
-
31
- predicate(:filled?) do |input|
32
- !self[:empty?].(input)
33
- end
34
-
35
- predicate(:int?) do |input|
36
- input.is_a?(Fixnum)
37
- end
38
-
39
- predicate(:str?) do |input|
40
- input.is_a?(String)
41
- end
42
-
43
- predicate(:lt?) do |num, input|
44
- input < num
45
- end
46
-
47
- predicate(:gt?) do |num, input|
48
- input > num
49
- end
50
-
51
- predicate(:lteq?) do |num, input|
52
- !self[:gt?].(num, input)
53
- end
54
-
55
- predicate(:gteq?) do |num, input|
56
- !self[:lt?].(num, input)
57
- end
58
-
59
- predicate(:size?) do |num, input|
60
- input.size == num
61
- end
62
-
63
- predicate(:min_size?) do |num, input|
64
- input.size >= num
65
- end
66
-
67
- predicate(:max_size?) do |num, input|
68
- input.size <= num
69
- end
70
-
71
- predicate(:inclusion?) do |list, input|
72
- list.include?(input)
73
- end
74
-
75
- predicate(:exclusion?) do |list, input|
76
- !self[:inclusion?].(list, input)
77
- end
78
-
79
- predicate(:eql?) do |left, right|
80
- left.eql?(right)
81
- end
82
-
83
- predicate(:format?) do |regex, input|
84
- !regex.match(input).nil?
85
- end
86
- end
87
- end
88
- end
@@ -1,57 +0,0 @@
1
- require 'dry/validation/rule'
2
-
3
- module Dry
4
- module Validation
5
- class RuleCompiler
6
- attr_reader :predicates
7
-
8
- def initialize(predicates)
9
- @predicates = predicates
10
- end
11
-
12
- def call(ast)
13
- ast.map { |node| visit(node) }
14
- end
15
-
16
- def visit(node)
17
- name, nodes = node
18
- send(:"visit_#{name}", nodes)
19
- end
20
-
21
- def visit_key(node)
22
- name, predicate = node
23
- Rule::Key.new(name, visit(predicate))
24
- end
25
-
26
- def visit_val(node)
27
- name, predicate = node
28
- Rule::Value.new(name, visit(predicate))
29
- end
30
-
31
- def visit_set(node)
32
- name, rules = node
33
- Rule::Set.new(name, call(rules))
34
- end
35
-
36
- def visit_each(node)
37
- name, rule = node
38
- Rule::Each.new(name, visit(rule))
39
- end
40
-
41
- def visit_predicate(node)
42
- name, args = node
43
- predicates[name].curry(*args)
44
- end
45
-
46
- def visit_and(node)
47
- left, right = node
48
- visit(left) & visit(right)
49
- end
50
-
51
- def visit_or(node)
52
- left, right = node
53
- visit(left) | visit(right)
54
- end
55
- end
56
- end
57
- end
@@ -1,15 +0,0 @@
1
- module Dry
2
- module Validation
3
- class Schema
4
- module Definition
5
- def key(name, &block)
6
- Key.new(name, rules).key?(&block)
7
- end
8
- end
9
- end
10
- end
11
- end
12
-
13
- require 'dry/validation/schema/rule'
14
- require 'dry/validation/schema/value'
15
- require 'dry/validation/schema/key'
@@ -1,39 +0,0 @@
1
- require 'dry/validation/rule'
2
-
3
- module Dry
4
- module Validation
5
- class Schema
6
- class Key
7
- attr_reader :name, :rules
8
-
9
- def initialize(name, rules, &block)
10
- @name = name
11
- @rules = rules
12
- end
13
-
14
- private
15
-
16
- def method_missing(meth, *args, &block)
17
- key_rule = [:key, [name, [:predicate, [meth, args]]]]
18
-
19
- if block
20
- val_rule = yield(Value.new(name))
21
-
22
- rules <<
23
- if val_rule.is_a?(Array)
24
- Definition::Rule.new([:and, [key_rule, [:set, [name, val_rule.map(&:to_ary)]]]])
25
- else
26
- Definition::Rule.new([:and, [key_rule, val_rule.to_ary]])
27
- end
28
- else
29
- Definition::Rule.new(key_rule)
30
- end
31
- end
32
-
33
- def respond_to_missing?(meth, _include_private = false)
34
- true
35
- end
36
- end
37
- end
38
- end
39
- end
@@ -1,28 +0,0 @@
1
- module Dry
2
- module Validation
3
- class Schema
4
- module Definition
5
- class Rule
6
- attr_reader :node
7
-
8
- def initialize(node)
9
- @node = node
10
- end
11
-
12
- def to_ary
13
- node
14
- end
15
- alias_method :to_a, :to_ary
16
-
17
- def &(other)
18
- self.class.new([:and, [node, other.to_ary]])
19
- end
20
-
21
- def |(other)
22
- self.class.new([:or, [node, other.to_ary]])
23
- end
24
- end
25
- end
26
- end
27
- end
28
- end
@@ -1,31 +0,0 @@
1
- module Dry
2
- module Validation
3
- class Schema
4
- class Value
5
- include Schema::Definition
6
-
7
- attr_reader :name, :rules
8
-
9
- def initialize(name)
10
- @name = name
11
- @rules = []
12
- end
13
-
14
- def each(&block)
15
- rule = yield(self).to_ary
16
- Definition::Rule.new([:each, [name, rule]])
17
- end
18
-
19
- private
20
-
21
- def method_missing(meth, *args, &block)
22
- Definition::Rule.new([:val, [name, [:predicate, [meth, args]]]])
23
- end
24
-
25
- def respond_to_missing?(meth, _include_private = false)
26
- true
27
- end
28
- end
29
- end
30
- end
31
- end
@@ -1,74 +0,0 @@
1
- require 'dry/validation/schema/definition'
2
- require 'dry/validation/predicates'
3
- require 'dry/validation/error'
4
- require 'dry/validation/rule_compiler'
5
- require 'dry/validation/messages'
6
- require 'dry/validation/error_compiler'
7
-
8
- module Dry
9
- module Validation
10
- class Schema
11
- extend Dry::Configurable
12
- extend Definition
13
-
14
- setting :predicates, Predicates
15
- setting :messages, Messages.default
16
- setting :messages_file
17
- setting :namespace
18
-
19
- def self.predicates
20
- config.predicates
21
- end
22
-
23
- def self.error_compiler
24
- ErrorCompiler.new(messages)
25
- end
26
-
27
- def self.messages
28
- default = config.messages
29
-
30
- if config.messages_file && config.namespace
31
- default.merge(config.messages_file).namespaced(config.namespace)
32
- elsif config.messages_file
33
- default.merge(config.messages_file)
34
- elsif config.namespace
35
- default.namespaced(config.namespace)
36
- else
37
- default
38
- end
39
- end
40
-
41
- def self.rules
42
- @__rules__ ||= []
43
- end
44
-
45
- attr_reader :rules
46
-
47
- attr_reader :error_compiler
48
-
49
- def initialize(error_compiler = self.class.error_compiler)
50
- @rules = RuleCompiler.new(self).(self.class.rules.map(&:to_ary))
51
- @error_compiler = error_compiler
52
- end
53
-
54
- def call(input)
55
- rules.each_with_object(Error::Set.new) do |rule, errors|
56
- result = rule.(input)
57
- errors << Error.new(result) if result.failure?
58
- end
59
- end
60
-
61
- def messages(input)
62
- error_compiler.call(call(input).map(&:to_ary))
63
- end
64
-
65
- def [](name)
66
- if methods.include?(name)
67
- Predicate.new(name, &method(name))
68
- else
69
- self.class.predicates[name]
70
- end
71
- end
72
- end
73
- end
74
- end
data/rakelib/rubocop.rake DELETED
@@ -1,18 +0,0 @@
1
- begin
2
- require 'rubocop/rake_task'
3
-
4
- Rake::Task[:default].enhance [:rubocop]
5
-
6
- RuboCop::RakeTask.new do |task|
7
- task.options << '--display-cop-names'
8
- end
9
-
10
- namespace :rubocop do
11
- desc 'Generate a configuration file acting as a TODO list.'
12
- task :auto_gen_config do
13
- exec 'bundle exec rubocop --auto-gen-config'
14
- end
15
- end
16
-
17
- rescue LoadError
18
- end
@@ -1,4 +0,0 @@
1
- user:
2
- attributes:
3
- email:
4
- filled?: "%{name} can't be blank"
@@ -1,35 +0,0 @@
1
- RSpec.describe Dry::Validation, 'with custom messages' do
2
- subject(:validation) { schema.new }
3
-
4
- describe 'defining schema' do
5
- let(:schema) do
6
- Class.new(Dry::Validation::Schema) do
7
- configure do |config|
8
- config.messages_file = SPEC_ROOT.join('fixtures/errors.yml')
9
- config.namespace = :user
10
- end
11
-
12
- key(:email) { |email| email.filled? }
13
- end
14
- end
15
-
16
- let(:attrs) do
17
- {
18
- email: 'jane@doe.org',
19
- age: 19,
20
- address: { city: 'NYC', street: 'Street 1/2', country: { code: 'US', name: 'USA' } },
21
- phone_numbers: [
22
- '123456', '234567'
23
- ]
24
- }.freeze
25
- end
26
-
27
- describe '#messages' do
28
- it 'returns compiled error messages' do
29
- expect(validation.messages(attrs.merge(email: ''))).to eql([
30
- [:email, ["email can't be blank"]]
31
- ])
32
- end
33
- end
34
- end
35
- end
@@ -1,57 +0,0 @@
1
- RSpec.describe Dry::Validation do
2
- subject(:validation) { schema.new }
3
-
4
- shared_context 'uses custom predicates' do
5
- it 'uses provided custom predicates' do
6
- expect(validation.(email: 'jane@doe')).to be_empty
7
-
8
- expect(validation.(email: nil)).to match_array([
9
- [:error, [:input, [:email, nil, [[:val, [:email, [:predicate, [:filled?, []]]]]]]]]
10
- ])
11
-
12
- expect(validation.(email: 'jane')).to match_array([
13
- [:error, [:input, [:email, 'jane', [[:val, [:email, [:predicate, [:email?, []]]]]]]]]
14
- ])
15
- end
16
- end
17
-
18
- describe 'defining schema with custom predicates container' do
19
- let(:schema) do
20
- Class.new(Dry::Validation::Schema) do
21
- configure do |config|
22
- config.predicates = Test::Predicates
23
- end
24
-
25
- key(:email) { |value| value.filled? & value.email? }
26
- end
27
- end
28
-
29
- before do
30
- module Test
31
- module Predicates
32
- include Dry::Validation::Predicates
33
-
34
- predicate(:email?) do |input|
35
- input.include?('@') # for the lols
36
- end
37
- end
38
- end
39
- end
40
-
41
- include_context 'uses custom predicates'
42
- end
43
-
44
- 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? }
48
-
49
- def email?(value)
50
- value.include?('@')
51
- end
52
- end
53
- end
54
-
55
- include_context 'uses custom predicates'
56
- end
57
- end