dry-schema 0.1.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ebc7203aa64419493d5557a5228e8ae17da67a89b7dab86556897aad7856138e
4
- data.tar.gz: '083cad2ab1b35ecfac75a5d37a868f36145cce8ea31bf03dc10b3ad84233b498'
3
+ metadata.gz: bfbb04d5a9d9537294ec0e1d6fce172c2a6feb8a26ec13157ccc3f15120eee6f
4
+ data.tar.gz: 1c85541e6b71d1d44fc6abeb99350ef51abf7f8e2279556055f8f66cbb9e2373
5
5
  SHA512:
6
- metadata.gz: 0db3366b58c3fd06ad10958de4215ae5b2ff67f244873ff8b48b96da9bfcf08678a06ef3302099862891630b825596c6a4adfecac9140e8e5045ca7acc200b34
7
- data.tar.gz: 04e3fa2ee8d50aaac7c6bcdaacb1611252ce06af397499cf0fbc0ed21fb33e62c55b9ab0179b4df9160bbd0efe1c6bed1c60ac628c06f23f992879df8c6326ed
6
+ metadata.gz: c8b8b1023ed9256358d8ed96998be33070fe82c983e7d511e38f01d18da1fc1702ac2ea82f3fa02a83d9494d3822a3ce304662cc806ec6f4a16198d1db040444
7
+ data.tar.gz: fe668ebfb35ecee7ddc41c34d77fbde0abb9d3c8e98c192657036e8503bbde708c72b783a9f5ae1e6b9a366bee26b2217c78b4652e17d88219b3c4edaa74d6b9
data/CHANGELOG.md CHANGED
@@ -1,3 +1,32 @@
1
+ # 0.2.0 2019-02-26
2
+
3
+ ### Added
4
+
5
+ * New `hash` macro which prepends `hash?` type-check and allows nested schema definition (solnic)
6
+ * New `array` macro which works like `each` but prepends `array?` type-check (solnic)
7
+
8
+ ### Fixed
9
+
10
+ * Rule name translation works correctly with I18n (issue #52) (solnic)
11
+ * Rule name translation works correctly with namespaced messages (both I18n and plain YAML) (issue #57) (solnic)
12
+ * Error messages under namespaces are correctly resolved for overridden names (issue #53) (solnic)
13
+ * Namespaced error messages work correctly when schemas are reused within other schemas (issue #49) (solnic)
14
+ * Child schema can override inherited rules now (issue #66) (skryukov)
15
+ * Hints are correctly generated for disjunction that use type-check predicates (issue #24) (solnic)
16
+ * Hints are correctly generated for nested schemas (issue #26) (solnic)
17
+ * `filled` macro respects inferred type-check predicates and puts them in front (solnic)
18
+ * Value coercion works correctly with re-usable nested schemas (issue #25) (solnic)
19
+
20
+ ### Changed
21
+
22
+ * [BREAKING] **Messages are now configured under `dry_schema` namespace by default** (issue #38) (solnic)
23
+ * [BREAKING] Hints are now an optional feature provided by `:hints` extension, to load it do `Dry::Schema.load_extensions(:hints)` (solnic)
24
+ * [BREAKING] Hints generation was improved in general, output of `Result#messages` and `Result#hints` changed in some cases (solnic)
25
+ * [BREAKING] `schema` macro no longer prepends `hash?` check, for this behavior use the new `hash` macro (see #31) (solnic)
26
+ * [BREAKING] Support for MRI < 2.4 was dropped (solnic)
27
+
28
+ [Compare v0.1.1...v0.2.0](https://github.com/dry-rb/dry-schema/compare/v0.1.1...v0.2.0)
29
+
1
30
  # 0.1.1 2019-02-17
2
31
 
3
32
  ### Added
data/config/errors.yml CHANGED
@@ -1,91 +1,92 @@
1
1
  en:
2
- errors:
3
- or: "or"
4
- array?: "must be an array"
2
+ dry_schema:
3
+ errors:
4
+ or: "or"
5
+ array?: "must be an array"
5
6
 
6
- empty?: "must be empty"
7
+ empty?: "must be empty"
7
8
 
8
- excludes?: "must not include %{value}"
9
+ excludes?: "must not include %{value}"
9
10
 
10
- excluded_from?:
11
- arg:
12
- default: "must not be one of: %{list}"
13
- range: "must not be one of: %{list_left} - %{list_right}"
14
- exclusion?: "must not be one of: %{list}"
11
+ excluded_from?:
12
+ arg:
13
+ default: "must not be one of: %{list}"
14
+ range: "must not be one of: %{list_left} - %{list_right}"
15
+ exclusion?: "must not be one of: %{list}"
15
16
 
16
- eql?: "must be equal to %{left}"
17
+ eql?: "must be equal to %{left}"
17
18
 
18
- not_eql?: "must not be equal to %{left}"
19
+ not_eql?: "must not be equal to %{left}"
19
20
 
20
- filled?: "must be filled"
21
+ filled?: "must be filled"
21
22
 
22
- format?: "is in invalid format"
23
+ format?: "is in invalid format"
23
24
 
24
- number?: "must be a number"
25
+ number?: "must be a number"
25
26
 
26
- odd?: "must be odd"
27
+ odd?: "must be odd"
27
28
 
28
- even?: "must be even"
29
+ even?: "must be even"
29
30
 
30
- gt?: "must be greater than %{num}"
31
+ gt?: "must be greater than %{num}"
31
32
 
32
- gteq?: "must be greater than or equal to %{num}"
33
+ gteq?: "must be greater than or equal to %{num}"
33
34
 
34
- hash?: "must be a hash"
35
+ hash?: "must be a hash"
35
36
 
36
- included_in?:
37
- arg:
38
- default: "must be one of: %{list}"
39
- range: "must be one of: %{list_left} - %{list_right}"
40
- inclusion?: "must be one of: %{list}"
37
+ included_in?:
38
+ arg:
39
+ default: "must be one of: %{list}"
40
+ range: "must be one of: %{list_left} - %{list_right}"
41
+ inclusion?: "must be one of: %{list}"
41
42
 
42
- includes?: "must include %{value}"
43
+ includes?: "must include %{value}"
43
44
 
44
- bool?: "must be boolean"
45
+ bool?: "must be boolean"
45
46
 
46
- true?: "must be true"
47
+ true?: "must be true"
47
48
 
48
- false?: "must be false"
49
+ false?: "must be false"
49
50
 
50
- int?: "must be an integer"
51
+ int?: "must be an integer"
51
52
 
52
- float?: "must be a float"
53
+ float?: "must be a float"
53
54
 
54
- decimal?: "must be a decimal"
55
+ decimal?: "must be a decimal"
55
56
 
56
- date?: "must be a date"
57
+ date?: "must be a date"
57
58
 
58
- date_time?: "must be a date time"
59
+ date_time?: "must be a date time"
59
60
 
60
- time?: "must be a time"
61
+ time?: "must be a time"
61
62
 
62
- key?: "is missing"
63
+ key?: "is missing"
63
64
 
64
- attr?: "is missing"
65
+ attr?: "is missing"
65
66
 
66
- lt?: "must be less than %{num}"
67
+ lt?: "must be less than %{num}"
67
68
 
68
- lteq?: "must be less than or equal to %{num}"
69
+ lteq?: "must be less than or equal to %{num}"
69
70
 
70
- max_size?: "size cannot be greater than %{num}"
71
+ max_size?: "size cannot be greater than %{num}"
71
72
 
72
- min_size?: "size cannot be less than %{num}"
73
+ min_size?: "size cannot be less than %{num}"
73
74
 
74
- nil?: "cannot be defined"
75
+ nil?: "cannot be defined"
75
76
 
76
- str?: "must be a string"
77
+ str?: "must be a string"
77
78
 
78
- type?: "must be %{type}"
79
+ type?: "must be %{type}"
79
80
 
80
- size?:
81
- arg:
82
- default: "size must be %{size}"
83
- range: "size must be within %{size_left} - %{size_right}"
81
+ size?:
82
+ arg:
83
+ default: "size must be %{size}"
84
+ range: "size must be within %{size_left} - %{size_right}"
84
85
 
85
- value:
86
- string:
87
- arg:
88
- default: "length must be %{size}"
89
- range: "length must be within %{size_left} - %{size_right}"
90
- not:
91
- empty?: "cannot be empty"
86
+ value:
87
+ string:
88
+ arg:
89
+ default: "length must be %{size}"
90
+ range: "length must be within %{size_left} - %{size_right}"
91
+ not:
92
+ empty?: "cannot be empty"
@@ -1,4 +1,5 @@
1
1
  require 'dry/logic/rule_compiler'
2
+ require 'dry/schema/namespaced_rule'
2
3
  require 'dry/schema/predicate_registry'
3
4
 
4
5
  module Dry
@@ -16,6 +17,22 @@ module Dry
16
17
  super
17
18
  end
18
19
 
20
+ # Build a special rule that will produce namespaced failures
21
+ #
22
+ # This is needed for schemas that are namespaced and they are
23
+ # used as nested schemas
24
+ #
25
+ # @param [Array] node
26
+ # @param [Hash] opts
27
+ #
28
+ # @return [NamespacedRule]
29
+ #
30
+ # @api private
31
+ def visit_namespace(node, opts = EMPTY_HASH)
32
+ namespace, rest = node
33
+ NamespacedRule.new(namespace, visit(rest))
34
+ end
35
+
19
36
  # Return true if a given predicate is supported by this compiler
20
37
  #
21
38
  # @param [Symbol] predicate
@@ -263,7 +263,7 @@ module Dry
263
263
  #
264
264
  # @api protected
265
265
  def rules
266
- macros.map { |m| [m.name, m.to_rule] }.to_h.merge(parent_rules)
266
+ parent_rules.merge(macros.map { |m| [m.name, m.to_rule] }.to_h)
267
267
  end
268
268
 
269
269
  # Build a key map from defined types
@@ -1,3 +1,7 @@
1
1
  Dry::Schema.register_extension(:monads) do
2
2
  require 'dry/schema/extensions/monads'
3
3
  end
4
+
5
+ Dry::Schema.register_extension(:hints) do
6
+ require 'dry/schema/extensions/hints'
7
+ end
@@ -0,0 +1,52 @@
1
+ require 'dry/schema/message'
2
+ require 'dry/schema/message_compiler'
3
+
4
+ require 'dry/schema/extensions/hints/message_compiler_methods'
5
+ require 'dry/schema/extensions/hints/message_set_methods'
6
+ require 'dry/schema/extensions/hints/result_methods'
7
+
8
+ module Dry
9
+ module Schema
10
+ # Hint-specific Message extensions
11
+ #
12
+ # @see Message
13
+ #
14
+ # @api public
15
+ class Message
16
+ # @see Message::Or
17
+ #
18
+ # @api public
19
+ class Or
20
+ # @api private
21
+ def hint?
22
+ false
23
+ end
24
+ end
25
+
26
+ # @api private
27
+ def hint?
28
+ false
29
+ end
30
+ end
31
+
32
+ # A hint message sub-type
33
+ #
34
+ # @api private
35
+ class Hint < Message
36
+ def self.[](predicate, path, text, options)
37
+ Hint.new(predicate, path, text, options)
38
+ end
39
+
40
+ # @api private
41
+ def hint?
42
+ true
43
+ end
44
+ end
45
+
46
+ module Extensions
47
+ MessageCompiler.prepend(Hints::MessageCompilerMethods)
48
+ MessageSet.prepend(Hints::MessageSetMethods)
49
+ Result.prepend(Hints::ResultMethods)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,72 @@
1
+ module Dry
2
+ module Schema
3
+ module Extensions
4
+ module Hints
5
+ module MessageCompilerMethods
6
+ HINT_TYPE_EXCLUSION = %i[
7
+ key? nil? bool? str? int? float? decimal?
8
+ date? date_time? time? hash? array?
9
+ ].freeze
10
+
11
+ HINT_OTHER_EXCLUSION = %i[format? filled?].freeze
12
+
13
+ attr_reader :hints
14
+
15
+ def initialize(*args)
16
+ super
17
+ @hints = @options.fetch(:hints, true)
18
+ end
19
+
20
+ # @api private
21
+ def hints?
22
+ hints.equal?(true)
23
+ end
24
+
25
+ # @api private
26
+ def filter(messages, opts)
27
+ Array(messages).flatten.map { |msg| msg unless exclude?(msg, opts) }.compact.uniq
28
+ end
29
+
30
+ # @api private
31
+ def exclude?(messages, opts)
32
+ Array(messages).all? do |msg|
33
+ hints = opts.hints.reject { |hint| msg == hint }.reject { |hint| hint.predicate == :filled? }
34
+ key_failure = opts.key_failure?(msg.path)
35
+ predicate = msg.predicate
36
+
37
+ (HINT_TYPE_EXCLUSION.include?(predicate) && !key_failure) ||
38
+ (msg.predicate == :filled? && key_failure) ||
39
+ (!key_failure && HINT_TYPE_EXCLUSION.include?(predicate) && !hints.empty? && hints.any? { |hint| hint.path == msg.path }) ||
40
+ HINT_OTHER_EXCLUSION.include?(predicate)
41
+ end
42
+ end
43
+
44
+ # @api private
45
+ def message_type(options)
46
+ options[:message_type].equal?(:hint) ? Hint : Message
47
+ end
48
+
49
+ # @api private
50
+ def visit_hint(node, opts)
51
+ if hints?
52
+ filter(visit(node, opts.(message_type: :hint)), opts)
53
+ end
54
+ end
55
+
56
+ # @api private
57
+ def visit_predicate(node, opts)
58
+ message = super
59
+ opts.current_messages << message
60
+ message
61
+ end
62
+
63
+ # @api private
64
+ def visit_each(node, opts)
65
+ # TODO: we can still generate a hint for elements here!
66
+ []
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,29 @@
1
+ module Dry
2
+ module Schema
3
+ module Extensions
4
+ module Hints
5
+ module MessageSetMethods
6
+ attr_reader :hints, :failures
7
+
8
+ # @api private
9
+ def initialize(messages, options = EMPTY_HASH)
10
+ super
11
+ @hints = messages.select(&:hint?)
12
+ end
13
+
14
+ # @api public
15
+ def to_h
16
+ failures? ? messages_map : messages_map(hints)
17
+ end
18
+ alias_method :to_hash, :to_h
19
+ alias_method :dump, :to_h
20
+
21
+ # @api private
22
+ def failures?
23
+ options[:failures].equal?(true)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,38 @@
1
+ module Dry
2
+ module Schema
3
+ module Extensions
4
+ module Hints
5
+ module ResultMethods
6
+ # @see Result#errors
7
+ #
8
+ # @api public
9
+ def errors(options = EMPTY_HASH)
10
+ message_set(options.merge(hints: false)).dump
11
+ end
12
+
13
+ # Get all messages including hints
14
+ #
15
+ # @see #message_set
16
+ #
17
+ # @return [Hash<Symbol=>Array>]
18
+ #
19
+ # @api public
20
+ def messages(options = EMPTY_HASH)
21
+ message_set(options).dump
22
+ end
23
+
24
+ # Get hints exclusively without errors
25
+ #
26
+ # @see #message_set
27
+ #
28
+ # @return [Hash<Symbol=>Array>]
29
+ #
30
+ # @api public
31
+ def hints(options = EMPTY_HASH)
32
+ message_set(options.merge(failures: false)).dump
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,5 +1,6 @@
1
1
  require 'dry/schema/macros/each'
2
2
  require 'dry/schema/macros/filled'
3
+ require 'dry/schema/macros/schema'
3
4
  require 'dry/schema/macros/hash'
4
5
  require 'dry/schema/macros/maybe'
5
6
  require 'dry/schema/macros/optional'