dry-schema 0.4.0 → 0.5.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: 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
  - - ">="