dry-schema 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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'