dry-schema 0.4.0 → 0.5.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: af5d9afb30c697c7ff8e028dff66ed3a1e28a8d0ed9c6f03ac4bd0e0ff73a364
4
- data.tar.gz: 94c6bb4243dfc12bd478e715e716deb1fd9b278761d1822324ed7b8920b0de55
3
+ metadata.gz: 91cccc18d1227997d8a7d14d0e73372625e11f3180bf0dede6ec38dc60e9acde
4
+ data.tar.gz: d900bd76107b6ac3825a32ead9e0a9818426a96d7ee4b591c55a49e53b84e79f
5
5
  SHA512:
6
- metadata.gz: cf8a06bf211c79bed5ccfc9e02800e47318535036a47b626c29438cae91e3388c380bd9b8b936391718f88c784318f9ef5e88d41a19d06240d9773d2ee7b8270
7
- data.tar.gz: 7eaa15a99a94bdcd301fed7d636879a0ef7241d7cf5721bd87ef053c8684ece4b65a0a6aa6fb2f10d7d0947bb66b27548f575749b83190ea2db3bab04f4f08d2
6
+ metadata.gz: e7457d70d6e82e2691d067ebf33c9fa1512938856bfda50304f20010e2b3313804b37215acd972e659968af60dc317d2771e8efa017dc0b43072d3aef7008a9b
7
+ data.tar.gz: 81467419fdc896a52e9013677d17b0c9cfd91e16ea4323efb007554899eb69e43a7c20b5426eb5213687631ed5e04e34f79fbf780e9e9d4748a1971faa4385c0
@@ -1,3 +1,43 @@
1
+ # 0.5.0 2019-04-04
2
+
3
+ ### Added
4
+
5
+ * Support for arbitrary meta-data in messages, ie:
6
+
7
+ ```yaml
8
+ en:
9
+ dry_schema:
10
+ errors:
11
+ filled?:
12
+ text: "cannot be blank"
13
+ code: 123
14
+ ```
15
+
16
+ Now your error hash will include `{ foo: [{ text: 'cannot be blank', code: 123 }] }` (solnic + flash-gordon)
17
+
18
+ * Support for type specs in `array` macro, ie `required(:tags).array(:integer)` (solnic)
19
+ * Support for type specs in `each` macro, ie `required(:tags).each(:integer)` (solnic)
20
+ * Shortcut for defining an array with hash as its member, ie:
21
+
22
+ ```ruby
23
+ Dry::Schema.Params do
24
+ required(:tags).array(:hash) do
25
+ required(:name).filled(:string)
26
+ end
27
+ end
28
+ ```
29
+
30
+ ### Fixed
31
+
32
+ * Inferring type specs when type is already set works correctly (solnic)
33
+
34
+ ### Changed
35
+
36
+ * [BREAKING] `:monads` extension wraps entire result objects in `Success` or `Failure` (flash-gordon)
37
+ * When `:hints` are disabled, result AST will not include hint nodes (solnic)
38
+
39
+ [Compare v0.4.0...v0.5.0](https://github.com/dry-rb/dry-schema/compare/v0.4.0...v0.5.0)
40
+
1
41
  # 0.4.0 2019-03-26
2
42
 
3
43
  ### Added
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  [gem]: https://rubygems.org/gems/dry-schema
2
2
  [travis]: https://travis-ci.org/dry-rb/dry-schema
3
3
  [codeclimate]: https://codeclimate.com/github/dry-rb/dry-schema
4
- [coveralls]: https://coveralls.io/r/dry-rb/dry-schema
4
+ [chat]: https://dry-rb.zulipchat.com
5
5
  [inchpages]: http://inch-ci.org/github/dry-rb/dry-schema
6
6
 
7
- # dry-schema [![Join the chat at https://gitter.im/dry-rb/chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dry-rb/chat)
7
+ # dry-schema [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat]
8
8
 
9
9
  [![Gem Version](https://badge.fury.io/rb/dry-schema.svg)][gem]
10
10
  [![Build Status](https://travis-ci.org/dry-rb/dry-schema.svg?branch=master)][travis]
@@ -1,6 +1,7 @@
1
1
  en:
2
2
  dry_schema:
3
3
  or: "or"
4
+
4
5
  errors:
5
6
  array?: "must be an array"
6
7
 
@@ -12,6 +13,7 @@ en:
12
13
  arg:
13
14
  default: "must not be one of: %{list}"
14
15
  range: "must not be one of: %{list_left} - %{list_right}"
16
+
15
17
  exclusion?: "must not be one of: %{list}"
16
18
 
17
19
  eql?: "must be equal to %{left}"
@@ -38,6 +40,7 @@ en:
38
40
  arg:
39
41
  default: "must be one of: %{list}"
40
42
  range: "must be one of: %{list_left} - %{list_right}"
43
+
41
44
  inclusion?: "must be one of: %{list}"
42
45
 
43
46
  includes?: "must include %{value}"
@@ -88,5 +91,6 @@ en:
88
91
  arg:
89
92
  default: "length must be %{size}"
90
93
  range: "length must be within %{size_left} - %{size_right}"
94
+
91
95
  not:
92
96
  empty?: "cannot be empty"
@@ -19,6 +19,11 @@ module Dry
19
19
  super
20
20
  end
21
21
 
22
+ # @api private
23
+ def visit_and(node)
24
+ super.with(hints: false)
25
+ end
26
+
22
27
  # Build a special rule that will produce namespaced failures
23
28
  #
24
29
  # This is needed for schemas that are namespaced and they are
@@ -247,6 +247,22 @@ module Dry
247
247
  types[name] = type.meta(meta)
248
248
  end
249
249
 
250
+ # Resolve type object from the provided spec
251
+ #
252
+ # @param [Symbol, Array<Symbol>, Dry::Types::Type]
253
+ #
254
+ # @return [Dry::Types::Type]
255
+ #
256
+ # @api private
257
+ def resolve_type(spec)
258
+ case spec
259
+ when ::Dry::Types::Type then spec
260
+ when ::Array then spec.map { |s| resolve_type(s) }.reduce(:|)
261
+ else
262
+ type_registry[spec]
263
+ end
264
+ end
265
+
250
266
  protected
251
267
 
252
268
  # Build a rule applier
@@ -345,22 +361,6 @@ module Dry
345
361
  end
346
362
  end
347
363
 
348
- # Resolve type object from the provided spec
349
- #
350
- # @param [Symbol, Array<Symbol>, Dry::Types::Type]
351
- #
352
- # @return [Dry::Types::Type]
353
- #
354
- # @api private
355
- def resolve_type(spec)
356
- case spec
357
- when ::Dry::Types::Type then spec
358
- when ::Array then spec.map { |s| resolve_type(s) }.reduce(:|)
359
- else
360
- type_registry[spec]
361
- end
362
- end
363
-
364
364
  # @api private
365
365
  def parent_rules
366
366
  parent&.rules || EMPTY_HASH
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'dry/schema/compiler'
3
4
  require 'dry/schema/message'
4
5
  require 'dry/schema/message_compiler'
5
6
 
7
+ require 'dry/schema/extensions/hints/compiler_methods'
6
8
  require 'dry/schema/extensions/hints/message_compiler_methods'
7
9
  require 'dry/schema/extensions/hints/message_set_methods'
8
10
  require 'dry/schema/extensions/hints/result_methods'
@@ -35,10 +37,6 @@ module Dry
35
37
  #
36
38
  # @api private
37
39
  class Hint < Message
38
- def self.[](predicate, path, text, options)
39
- Hint.new(predicate, path, text, options)
40
- end
41
-
42
40
  # @api private
43
41
  def hint?
44
42
  true
@@ -46,6 +44,7 @@ module Dry
46
44
  end
47
45
 
48
46
  module Extensions
47
+ Compiler.prepend(Hints::CompilerMethods)
49
48
  MessageCompiler.prepend(Hints::MessageCompilerMethods)
50
49
  MessageSet.prepend(Hints::MessageSetMethods)
51
50
  Result.prepend(Hints::ResultMethods)
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module Schema
5
+ module Extensions
6
+ module Hints
7
+ # Tweaks AND visitor to enable :hints
8
+ #
9
+ # @api private
10
+ module CompilerMethods
11
+ # @api private
12
+ def visit_and(node)
13
+ super.with(hints: true)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -16,7 +16,7 @@ module Dry
16
16
 
17
17
  # @api public
18
18
  def to_h
19
- failures ? messages_map : messages_map(hints)
19
+ @to_h ||= failures ? messages_map : messages_map(hints)
20
20
  end
21
21
  alias_method :to_hash, :to_h
22
22
  end
@@ -7,11 +7,11 @@ module Dry
7
7
  class Result
8
8
  include Dry::Monads::Result::Mixin
9
9
 
10
- def to_monad(options = EMPTY_HASH)
10
+ def to_monad
11
11
  if success?
12
- Success(output)
12
+ Success(self)
13
13
  else
14
- Failure(message_set(options).to_h)
14
+ Failure(self)
15
15
  end
16
16
  end
17
17
  end
@@ -10,9 +10,22 @@ module Dry
10
10
  # @api public
11
11
  class Array < DSL
12
12
  # @api private
13
- def value(*args, &block)
14
- schema_dsl.set_type(name, :array)
15
- super
13
+ def value(*args, **opts, &block)
14
+ type(:array)
15
+
16
+ extract_type_spec(*args, set_type: false) do |*predicates, type_spec:|
17
+ type(schema_dsl.array[type_spec]) if type_spec
18
+
19
+ is_hash_block = type_spec.equal?(:hash)
20
+
21
+ if predicates.any? || opts.any? || !is_hash_block
22
+ super(*predicates, type_spec: type_spec, **opts, &(is_hash_block ? nil : block))
23
+ end
24
+
25
+ hash(&block) if is_hash_block
26
+ end
27
+
28
+ self
16
29
  end
17
30
 
18
31
  # @api private
@@ -19,6 +19,11 @@ module Dry
19
19
  # @api private
20
20
  option :chain, default: -> { true }
21
21
 
22
+ # @!attribute [r] predicate_inferrer
23
+ # @return [PredicateInferrer]
24
+ # @api private
25
+ option :predicate_inferrer, default: proc { PredicateInferrer.new(compiler.predicates) }
26
+
22
27
  # Specify predicates that should be applied to a value
23
28
  #
24
29
  # @api public
@@ -75,10 +80,22 @@ module Dry
75
80
  end
76
81
  end
77
82
 
83
+ # Set type spec
84
+ #
85
+ # @param [Symbol, Array, Dry::Types::Type]
86
+ #
87
+ # @return [Macros::Key]
88
+ #
89
+ # @api public
90
+ def type(spec)
91
+ schema_dsl.set_type(name, spec)
92
+ self
93
+ end
94
+
78
95
  private
79
96
 
80
97
  # @api private
81
- def append_macro(macro_type, &block)
98
+ def append_macro(macro_type)
82
99
  macro = macro_type.new(schema_dsl: schema_dsl, name: name)
83
100
 
84
101
  yield(macro)
@@ -90,6 +107,39 @@ module Dry
90
107
  macro
91
108
  end
92
109
  end
110
+
111
+ # @api private
112
+ def extract_type_spec(*args, nullable: false, set_type: true)
113
+ type_spec = args[0]
114
+
115
+ is_type_spec = type_spec.is_a?(Dry::Schema::Processor) ||
116
+ type_spec.is_a?(Symbol) &&
117
+ type_spec.to_s.end_with?(QUESTION_MARK)
118
+
119
+ type_spec = nil if is_type_spec
120
+
121
+ predicates = Array(type_spec ? args[1..-1] : args)
122
+
123
+ if type_spec
124
+ resolved_type = schema_dsl.resolve_type(
125
+ nullable && !type_spec.is_a?(::Array) ? [:nil, type_spec] : type_spec
126
+ )
127
+
128
+ type(resolved_type) if set_type
129
+
130
+ type_predicates = predicate_inferrer[resolved_type]
131
+
132
+ unless predicates.include?(type_predicates)
133
+ if type_predicates.is_a?(::Array) && type_predicates.size.equal?(1)
134
+ predicates.unshift(type_predicates[0])
135
+ else
136
+ predicates.unshift(type_predicates)
137
+ end
138
+ end
139
+ end
140
+
141
+ yield(*predicates, type_spec: type_spec)
142
+ end
93
143
  end
94
144
  end
95
145
  end
@@ -9,6 +9,15 @@ module Dry
9
9
  #
10
10
  # @api public
11
11
  class Each < DSL
12
+ # @api private
13
+ def value(*args, **opts)
14
+ extract_type_spec(*args, set_type: false) do |*predicates, type_spec:|
15
+ type(schema_dsl.array[type_spec]) if type_spec
16
+
17
+ super(*predicates, type_spec: type_spec, **opts)
18
+ end
19
+ end
20
+
12
21
  # @api private
13
22
  def to_ast(*)
14
23
  [:each, trace.to_ast]
@@ -18,8 +18,8 @@ module Dry
18
18
  raise ::Dry::Schema::InvalidSchemaError, 'Using filled with filled? is redundant'
19
19
  end
20
20
 
21
- if opts[:type_spec].equal?(true)
22
- value(predicates[0], :filled?, *predicates[1..predicates.size-1], **opts, &block)
21
+ if opts[:type_spec]
22
+ value(predicates[0], :filled?, *predicates[1..predicates.size - 1], **opts, &block)
23
23
  else
24
24
  value(:filled?, *predicates, **opts, &block)
25
25
  end
@@ -19,13 +19,6 @@ module Dry
19
19
  # @api private
20
20
  option :filter_schema, optional: true, default: proc { schema_dsl&.new }
21
21
 
22
- # @!attribute [r] predicate_inferrer
23
- # @return [PredicateInferrer]
24
- # @api private
25
- option :predicate_inferrer, default: proc {
26
- PredicateInferrer.new(compiler.predicates)
27
- }
28
-
29
22
  # Specify predicates that should be used to filter out values
30
23
  # before coercion is applied
31
24
  #
@@ -80,18 +73,6 @@ module Dry
80
73
  end
81
74
  end
82
75
 
83
- # Set type spec
84
- #
85
- # @param [Symbol, Array, Dry::Types::Type]
86
- #
87
- # @return [Macros::Key]
88
- #
89
- # @api public
90
- def type(spec)
91
- schema_dsl.set_type(name, spec)
92
- self
93
- end
94
-
95
76
  # Coerce macro to a rule
96
77
  #
97
78
  # @return [Dry::Logic::Rule]
@@ -109,37 +90,6 @@ module Dry
109
90
  def to_ast
110
91
  [:predicate, [:key?, [[:name, name], [:input, Undefined]]]]
111
92
  end
112
-
113
- private
114
-
115
- # @api private
116
- def extract_type_spec(*args, nullable: false)
117
- type_spec = args[0]
118
-
119
- is_type_spec = type_spec.kind_of?(Dry::Schema::Processor) ||
120
- type_spec.is_a?(Symbol) &&
121
- type_spec.to_s.end_with?(QUESTION_MARK)
122
-
123
- type_spec = nil if is_type_spec
124
-
125
- predicates = Array(type_spec ? args[1..-1] : args)
126
-
127
- if type_spec
128
- type(nullable && !type_spec.is_a?(::Array) ? [:nil, type_spec] : type_spec)
129
-
130
- type_predicates = predicate_inferrer[schema_dsl.types[name]]
131
-
132
- unless predicates.include?(type_predicates)
133
- if type_predicates.is_a?(::Array) && type_predicates.size.equal?(1)
134
- predicates.unshift(type_predicates[0])
135
- else
136
- predicates.unshift(type_predicates)
137
- end
138
- end
139
- end
140
-
141
- yield(*predicates, type_spec: !type_spec.nil?)
142
- end
143
93
  end
144
94
  end
145
95
  end
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'dry/initializer'
3
4
  require 'dry/equalizer'
5
+
4
6
  require 'dry/schema/path'
7
+ require 'dry/schema/message/or'
5
8
 
6
9
  module Dry
7
10
  module Schema
@@ -9,76 +12,31 @@ module Dry
9
12
  #
10
13
  # @api public
11
14
  class Message
12
- include Dry::Equalizer(:predicate, :path, :text, :options)
15
+ include Dry::Equalizer(:text, :path, :predicate, :input)
13
16
 
14
- attr_reader :predicate, :path, :text, :args, :options
17
+ extend Dry::Initializer
15
18
 
16
- # A message sub-type used by OR operations
17
- #
18
- # @api public
19
- class Or
20
- include Enumerable
19
+ option :text
21
20
 
22
- # @api private
23
- attr_reader :left
21
+ option :path
24
22
 
25
- # @api private
26
- attr_reader :right
23
+ option :predicate
27
24
 
28
- # @api private
29
- attr_reader :path
25
+ option :args, default: proc { EMPTY_ARRAY }
30
26
 
31
- # @api private
32
- attr_reader :messages
27
+ option :input
33
28
 
34
- # @api private
35
- def initialize(left, right, messages)
36
- @left = left
37
- @right = right
38
- @messages = messages
39
- @path = left.path
40
- end
29
+ option :meta, optional: true, default: proc { EMPTY_HASH }
41
30
 
42
- # Return a string representation of the message
43
- #
44
- # @api public
45
- def to_s
46
- uniq.join(" #{messages[:or]} ")
47
- end
48
-
49
- # @api private
50
- def each(&block)
51
- to_a.each(&block)
52
- end
53
-
54
- # @api private
55
- def to_a
56
- [left, right]
57
- end
58
- end
59
-
60
- # Build a new message object
31
+ # Dump the message to a representation suitable for the message set hash
61
32
  #
62
- # @api private
63
- def self.[](predicate, path, text, options)
64
- Message.new(predicate, path, text, options)
65
- end
66
-
67
- # @api private
68
- def initialize(predicate, path, text, options)
69
- @predicate = predicate
70
- @path = path
71
- @text = text
72
- @options = options
73
- @args = options[:args] || EMPTY_ARRAY
74
- end
75
-
76
- # Return a string representation of the message
33
+ # @return [String,Hash]
77
34
  #
78
35
  # @api public
79
- def to_s
80
- text
36
+ def dump
37
+ @dump ||= meta.empty? ? text : { text: text, **meta }
81
38
  end
39
+ alias to_s dump
82
40
 
83
41
  # @api private
84
42
  def eql?(other)
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/equalizer'
4
+
5
+ module Dry
6
+ module Schema
7
+ # Message objects used by message sets
8
+ #
9
+ # @api public
10
+ class Message
11
+ # A message sub-type used by OR operations
12
+ #
13
+ # @api public
14
+ class Or
15
+ # @api private
16
+ attr_reader :left
17
+
18
+ # @api private
19
+ attr_reader :right
20
+
21
+ # @api private
22
+ attr_reader :path
23
+
24
+ # @api private
25
+ attr_reader :messages
26
+
27
+ # @api private
28
+ def initialize(left, right, messages)
29
+ @left = left
30
+ @right = right
31
+ @messages = messages
32
+ @path = left.path
33
+ end
34
+
35
+ # @see Message#dump
36
+ #
37
+ # @return [String]
38
+ #
39
+ # @api public
40
+ def dump
41
+ to_a.map(&:dump).join(" #{messages[:or][:text]} ")
42
+ end
43
+ alias to_s dump
44
+
45
+ # @api private
46
+ def to_a
47
+ [left, right]
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -28,6 +28,7 @@ module Dry
28
28
  .new(resolve_predicate).update(key?: resolve_key_predicate).freeze
29
29
 
30
30
  EMPTY_OPTS = VisitorOpts.new
31
+ EMPTY_MESSAGE_SET = MessageSet.new(EMPTY_ARRAY).freeze
31
32
 
32
33
  param :messages
33
34
 
@@ -50,11 +51,17 @@ module Dry
50
51
  def with(new_options)
51
52
  return self if new_options.empty?
52
53
 
53
- self.class.new(messages, options.merge(new_options))
54
+ updated_opts = options.merge(new_options)
55
+
56
+ return self if updated_opts.eql?(options)
57
+
58
+ self.class.new(messages, updated_opts)
54
59
  end
55
60
 
56
61
  # @api private
57
62
  def call(ast)
63
+ return EMPTY_MESSAGE_SET if ast.empty?
64
+
58
65
  current_messages = EMPTY_ARRAY.dup
59
66
  compiled_messages = ast.map { |node| visit(node, EMPTY_OPTS.dup(current_messages)) }
60
67
 
@@ -123,15 +130,13 @@ module Dry
123
130
  path: path.last, **tokens, **lookup_options(arg_vals: arg_vals, input: input)
124
131
  ).to_h
125
132
 
126
- template = messages[predicate, options] || raise(MissingMessageError, path)
133
+ template, meta = messages[predicate, options] || raise(MissingMessageError, path)
127
134
 
128
135
  text = message_text(template, tokens, options)
129
136
 
130
- message_type(options)[
131
- predicate, path, text,
132
- args: arg_vals,
133
- input: input
134
- ]
137
+ message_type(options).new(
138
+ text: text, path: path, predicate: predicate, args: arg_vals, input: input, meta: meta
139
+ )
135
140
  end
136
141
 
137
142
  # @api private
@@ -29,13 +29,15 @@ module Dry
29
29
 
30
30
  # @api public
31
31
  def each(&block)
32
+ return self if empty?
32
33
  return to_enum unless block
34
+
33
35
  messages.each(&block)
34
36
  end
35
37
 
36
38
  # @api public
37
39
  def to_h
38
- messages_map
40
+ @to_h ||= messages_map
39
41
  end
40
42
  alias_method :to_hash, :to_h
41
43
 
@@ -51,13 +53,22 @@ module Dry
51
53
 
52
54
  # @api private
53
55
  def empty?
54
- messages.empty?
56
+ @empty ||= messages.empty?
57
+ end
58
+
59
+ # @api private
60
+ def freeze
61
+ to_h
62
+ empty?
63
+ super
55
64
  end
56
65
 
57
66
  private
58
67
 
59
68
  # @api private
60
69
  def messages_map(messages = self.messages)
70
+ return EMPTY_HASH if empty?
71
+
61
72
  messages.group_by(&:path).reduce(placeholders) do |hash, (path, msgs)|
62
73
  node = path.reduce(hash) { |a, e| a[e] }
63
74
 
@@ -65,7 +76,7 @@ module Dry
65
76
  node << msg
66
77
  end
67
78
 
68
- node.map!(&:to_s)
79
+ node.map!(&:dump)
69
80
 
70
81
  hash
71
82
  end
@@ -78,12 +89,14 @@ module Dry
78
89
 
79
90
  # @api private
80
91
  def initialize_placeholders!
81
- @placeholders = messages.map(&:path).uniq.reduce({}) do |hash, path|
92
+ return @placeholders = EMPTY_HASH if empty?
93
+
94
+ @placeholders = paths.reduce(EMPTY_HASH.dup) do |hash, path|
82
95
  curr_idx = 0
83
96
  last_idx = path.size - 1
84
97
  node = hash
85
98
 
86
- while curr_idx <= last_idx do
99
+ while curr_idx <= last_idx
87
100
  key = path[curr_idx]
88
101
  node = (node[key] || node[key] = curr_idx < last_idx ? {} : [])
89
102
  curr_idx += 1
@@ -27,7 +27,6 @@ module Dry
27
27
  '%<root>s.rules.%<path>s.%<predicate>s.arg.%<arg_type>s',
28
28
  '%<root>s.rules.%<path>s.%<predicate>s',
29
29
  '%<root>s.%<predicate>s.%<message_type>s',
30
- '%<root>s.%<predicate>s.value.%<path>s.arg.%<arg_type>s',
31
30
  '%<root>s.%<predicate>s.value.%<path>s',
32
31
  '%<root>s.%<predicate>s.value.%<val_type>s.arg.%<arg_type>s',
33
32
  '%<root>s.%<predicate>s.value.%<val_type>s',
@@ -87,7 +86,8 @@ module Dry
87
86
  tokens = { name: name, locale: options.fetch(:locale, default_locale) }
88
87
  path = rule_lookup_paths(tokens).detect { |key| key?(key, options) }
89
88
 
90
- get(path, options) if path
89
+ rule = get(path, options) if path
90
+ rule.is_a?(Hash) ? rule[:text] : rule
91
91
  end
92
92
 
93
93
  # Retrieve a message template
@@ -97,8 +97,8 @@ module Dry
97
97
  # @api public
98
98
  def call(*args)
99
99
  cache.fetch_or_store(args.hash) do
100
- path, opts = lookup(*args)
101
- Template[get(path, opts)] if path
100
+ text, meta = lookup(*args)
101
+ [Template[text], meta] if text
102
102
  end
103
103
  end
104
104
  alias_method :[], :call
@@ -106,10 +106,12 @@ module Dry
106
106
  # Try to find a message for the given predicate and its options
107
107
  #
108
108
  # @api private
109
- def lookup(predicate, options = {})
109
+ #
110
+ # rubocop:disable Metrics/AbcSize
111
+ def lookup(predicate, options)
110
112
  tokens = options.merge(
111
- root: options[:not] ? "#{root}.not" : root,
112
113
  predicate: predicate,
114
+ root: options[:not] ? "#{root}.not" : root,
113
115
  arg_type: config.arg_types[options[:arg_type]],
114
116
  val_type: config.val_types[options[:val_type]],
115
117
  message_type: options[:message_type] || :failure
@@ -117,12 +119,19 @@ module Dry
117
119
 
118
120
  opts = options.reject { |k, _| config.lookup_options.include?(k) }
119
121
 
120
- path = lookup_paths(tokens).detect do |key|
121
- key?(key, opts) && get(key, opts).is_a?(String)
122
- end
122
+ path = lookup_paths(tokens).detect { |key| key?(key, opts) }
123
123
 
124
- [path, opts]
124
+ return unless path
125
+
126
+ text = get(path, opts)
127
+
128
+ if text.is_a?(Hash)
129
+ text.values_at(:text, :meta)
130
+ else
131
+ [text, EMPTY_HASH]
132
+ end
125
133
  end
134
+ # rubocop:enable Metrics/AbcSize
126
135
 
127
136
  # @api private
128
137
  def lookup_paths(tokens)
@@ -31,11 +31,23 @@ module Dry
31
31
  end
32
32
 
33
33
  # @api private
34
- def self.flat_hash(hash, acc = [], result = {})
35
- return result.update(acc.join(DOT) => hash) unless hash.is_a?(Hash)
36
-
37
- hash.each { |k, v| flat_hash(v, acc + [k], result) }
38
- result
34
+ def self.flat_hash(hash, path = [], keys = {})
35
+ hash.each do |key, value|
36
+ flat_hash(value, [*path, key], keys) if value.is_a?(Hash)
37
+
38
+ if value.is_a?(String) && hash['text'] != value
39
+ keys[[*path, key].join(DOT)] = {
40
+ text: value,
41
+ meta: EMPTY_HASH
42
+ }
43
+ elsif value.is_a?(Hash) && value['text'].is_a?(String)
44
+ keys[[*path, key].join(DOT)] = {
45
+ text: value['text'],
46
+ meta: value.dup.delete_if { |k| k == 'text' }.map { |k, v| [k.to_sym, v] }.to_h
47
+ }
48
+ end
49
+ end
50
+ keys
39
51
  end
40
52
 
41
53
  # @api private
@@ -107,6 +107,11 @@ module Dry
107
107
  other, * = node
108
108
  visit(other)
109
109
  end
110
+
111
+ # @api private
112
+ def visit_any(_)
113
+ nil
114
+ end
110
115
  end
111
116
 
112
117
  # @!attribute [r] compiler
@@ -139,7 +139,7 @@ module Dry
139
139
  #
140
140
  # @api private
141
141
  def result_ast
142
- @__result__ast ||= results.map(&:to_ast)
142
+ @result_ast ||= results.map(&:to_ast)
143
143
  end
144
144
  end
145
145
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Dry
4
4
  module Schema
5
- VERSION = '0.4.0'
5
+ VERSION = '0.5.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-schema
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Solnica
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-03-26 00:00:00.000000000 Z
11
+ date: 2019-04-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -98,20 +98,20 @@ dependencies:
98
98
  requirements:
99
99
  - - ">="
100
100
  - !ruby/object:Gem::Version
101
- version: 0.5.0
101
+ version: 0.6.0
102
102
  - - "~>"
103
103
  - !ruby/object:Gem::Version
104
- version: '0.5'
104
+ version: '0.6'
105
105
  type: :runtime
106
106
  prerelease: false
107
107
  version_requirements: !ruby/object:Gem::Requirement
108
108
  requirements:
109
109
  - - ">="
110
110
  - !ruby/object:Gem::Version
111
- version: 0.5.0
111
+ version: 0.6.0
112
112
  - - "~>"
113
113
  - !ruby/object:Gem::Version
114
- version: '0.5'
114
+ version: '0.6'
115
115
  - !ruby/object:Gem::Dependency
116
116
  name: dry-types
117
117
  requirement: !ruby/object:Gem::Requirement
@@ -193,6 +193,7 @@ files:
193
193
  - lib/dry/schema/dsl.rb
194
194
  - lib/dry/schema/extensions.rb
195
195
  - lib/dry/schema/extensions/hints.rb
196
+ - lib/dry/schema/extensions/hints/compiler_methods.rb
196
197
  - lib/dry/schema/extensions/hints/message_compiler_methods.rb
197
198
  - lib/dry/schema/extensions/hints/message_set_methods.rb
198
199
  - lib/dry/schema/extensions/hints/result_methods.rb
@@ -215,6 +216,7 @@ files:
215
216
  - lib/dry/schema/macros/schema.rb
216
217
  - lib/dry/schema/macros/value.rb
217
218
  - lib/dry/schema/message.rb
219
+ - lib/dry/schema/message/or.rb
218
220
  - lib/dry/schema/message_compiler.rb
219
221
  - lib/dry/schema/message_compiler/visitor_opts.rb
220
222
  - lib/dry/schema/message_set.rb
@@ -250,7 +252,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
250
252
  requirements:
251
253
  - - ">="
252
254
  - !ruby/object:Gem::Version
253
- version: '2.3'
255
+ version: '2.4'
254
256
  required_rubygems_version: !ruby/object:Gem::Requirement
255
257
  requirements:
256
258
  - - ">="