remap 2.0.3 → 2.1.6

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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/lib/remap/base.rb +229 -75
  3. data/lib/remap/compiler.rb +403 -37
  4. data/lib/remap/constructor/argument.rb +20 -6
  5. data/lib/remap/constructor/keyword.rb +20 -4
  6. data/lib/remap/constructor/none.rb +3 -4
  7. data/lib/remap/constructor.rb +12 -5
  8. data/lib/remap/contract.rb +27 -0
  9. data/lib/remap/extensions/enumerable.rb +48 -0
  10. data/lib/remap/extensions/hash.rb +13 -0
  11. data/lib/remap/extensions/object.rb +37 -0
  12. data/lib/remap/failure.rb +25 -15
  13. data/lib/remap/iteration/array.rb +20 -11
  14. data/lib/remap/iteration/hash.rb +21 -13
  15. data/lib/remap/iteration/other.rb +7 -7
  16. data/lib/remap/iteration.rb +8 -2
  17. data/lib/remap/mapper/and.rb +29 -7
  18. data/lib/remap/mapper/binary.rb +3 -6
  19. data/lib/remap/mapper/or.rb +29 -6
  20. data/lib/remap/mapper/support/operations.rb +40 -0
  21. data/lib/remap/mapper/xor.rb +29 -7
  22. data/lib/remap/mapper.rb +1 -48
  23. data/lib/remap/notice/traced.rb +19 -0
  24. data/lib/remap/notice/untraced.rb +11 -0
  25. data/lib/remap/notice.rb +34 -0
  26. data/lib/remap/operation.rb +26 -13
  27. data/lib/remap/path/input.rb +37 -0
  28. data/lib/remap/path/output.rb +26 -0
  29. data/lib/remap/path.rb +22 -0
  30. data/lib/remap/path_error.rb +13 -0
  31. data/lib/remap/proxy.rb +18 -0
  32. data/lib/remap/rule/each.rb +25 -24
  33. data/lib/remap/rule/embed.rb +33 -28
  34. data/lib/remap/rule/map/optional.rb +42 -0
  35. data/lib/remap/rule/map/required.rb +35 -0
  36. data/lib/remap/rule/map.rb +176 -55
  37. data/lib/remap/rule/set.rb +23 -33
  38. data/lib/remap/rule/support/collection/empty.rb +7 -7
  39. data/lib/remap/rule/support/collection/filled.rb +21 -8
  40. data/lib/remap/rule/support/collection.rb +11 -3
  41. data/lib/remap/rule/support/enum.rb +44 -21
  42. data/lib/remap/rule/void.rb +17 -18
  43. data/lib/remap/rule/wrap.rb +25 -17
  44. data/lib/remap/rule.rb +8 -1
  45. data/lib/remap/selector/all.rb +29 -7
  46. data/lib/remap/selector/index.rb +24 -16
  47. data/lib/remap/selector/key.rb +31 -16
  48. data/lib/remap/selector.rb +17 -0
  49. data/lib/remap/state/extension.rb +182 -208
  50. data/lib/remap/state/schema.rb +1 -1
  51. data/lib/remap/state.rb +30 -4
  52. data/lib/remap/static/fixed.rb +14 -3
  53. data/lib/remap/static/option.rb +21 -6
  54. data/lib/remap/static.rb +13 -0
  55. data/lib/remap/struct.rb +1 -0
  56. data/lib/remap/types.rb +13 -28
  57. data/lib/remap.rb +15 -19
  58. metadata +91 -89
  59. data/lib/remap/result.rb +0 -11
  60. data/lib/remap/rule/support/path.rb +0 -45
  61. data/lib/remap/state/types.rb +0 -11
  62. data/lib/remap/success.rb +0 -29
  63. data/lib/remap/version.rb +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: feae800c3a05859a5cf0cefd1c080f3afa92109856b504aba89f81975f21dfc4
4
- data.tar.gz: 54c5b5b7dd28b67b67aed40019867d6293141edca668ea8d4de9b5b670dedaa4
3
+ metadata.gz: f155fc9609bac21c4f0c0d25567f5d214bd7f3e5417a71c06e44cea01f0fa409
4
+ data.tar.gz: e736316c91ed5763a9d028a457246f61628a1f2cd497df9dc86aabe0224c3f9c
5
5
  SHA512:
6
- metadata.gz: ecabf17439228869a9e14a2686cc38d92cd495a529efa2fc6114ffb10abf40837fe3fccf858123134f10c6a54f314e2464d8d309f854183b69c2875733cd48f4
7
- data.tar.gz: cc27691d7b1710d9642368559a25b7da4009c5a4346b5b44aa6166b81a8ec3ea053276ebd20b5d976548bf65290e9d2b4395395c2932edaaea8cea08bbdd285f
6
+ metadata.gz: b47bf7bd8de17faf5dfaa2425b566244fa54fa31fe448277e1d1ea2dedbb42f98e53840c7f5eedce2c3fe8036a94330f8723661d55a6a2b232ade3cdbc0a18cd
7
+ data.tar.gz: 86ff7e32e26f1a06f8c5840dcff37610e2991a79ef75f8495881c2ca629160bdc3c48af1580884f515d317021ae38d3b6cc7942d2fcbd8020b219c386a1806af
data/lib/remap/base.rb CHANGED
@@ -1,146 +1,300 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/monads/all"
3
+ require "active_support/configurable"
4
+ require "active_support/core_ext/object/with_options"
4
5
 
5
6
  module Remap
7
+ # @example Select all elements
8
+ # class Mapper < Remap::Base
9
+ # define do
10
+ # map [all, :name]
11
+ # end
12
+ # end
13
+ #
14
+ # Mapper.call([{ name: "John" }, { name: "Jane" }]) # => ["John", "Jane"]
15
+ #
16
+ # @example Given an option
17
+ # class Mapper < Remap::Base
18
+ # option :name
19
+ #
20
+ # define do
21
+ # set [:person, :name], to: option(:name)
22
+ # end
23
+ # end
24
+ #
25
+ # Mapper.call({}, name: "John") # => { person: { name: "John" } }
26
+ #
27
+ # @example Given a value
28
+ # class Mapper < Remap::Base
29
+ # define do
30
+ # set [:api_key], to: value("ABC-123")
31
+ # end
32
+ # end
33
+ #
34
+ # Mapper.call({}) # => { api_key: "ABC-123" }
35
+ #
36
+ # @example Maps ["A", "B", "C"] to ["A", "C"]
37
+ # class Mapper < Remap::Base
38
+ # define do
39
+ # each do
40
+ # map.if_not do
41
+ # value.include?("B")
42
+ # end
43
+ # end
44
+ # end
45
+ # end
46
+ #
47
+ # Mapper.call(["A", "B", "C"]) # => ["A", "C"]
48
+ #
49
+ # @example Maps ["A", "B", "C"] to ["B"]
50
+ # class Mapper < Remap::Base
51
+ # define do
52
+ # each do
53
+ # map.if do
54
+ # value.include?("B")
55
+ # end
56
+ # end
57
+ # end
58
+ # end
59
+ #
60
+ # Mapper.call(["A", "B", "C"]) # => ["B"]
61
+ #
62
+ # @example Maps { a: { b: "A" } } to "A"
63
+ # class Mapper < Remap::Base
64
+ # define do
65
+ # map(:a, :b).enum do
66
+ # value "A", "B"
67
+ # end
68
+ # end
69
+ # end
70
+ #
71
+ # Mapper.call({ a: { b: "A" } }) # => "A"
72
+ # Mapper.call({ a: { b: "B" } }) # => "B"
73
+ #
74
+ # @example Map { people: [{ name: "John" }] } to { names: ["John"] }
75
+ # class Mapper < Remap::Base
76
+ # define do
77
+ # map :people, to: :names do
78
+ # each do
79
+ # map :name
80
+ # end
81
+ # end
82
+ # end
83
+ # end
84
+ #
85
+ # Mapper.call({ people: [{ name: "John" }] }) # => { names: ["John"] }
86
+ #
87
+ # @example Map "Hello" to "Hello!"
88
+ # class Mapper < Remap::Base
89
+ # define do
90
+ # map.adjust do
91
+ # "#{value}!"
92
+ # end
93
+ # end
94
+ # end
95
+ #
96
+ # Mapper.call("Hello") # => "Hello!"
97
+ #
98
+ # @example Select the second element from an array
99
+ # class Mapper < Remap::Base
100
+ # define do
101
+ # map [at(1)]
102
+ # end
103
+ # end
104
+ #
105
+ # Mapper.call([1, 2, 3]) # => 2
6
106
  class Base < Mapper
7
- include Dry::Core::Memoizable
107
+ include ActiveSupport::Configurable
8
108
  include Dry::Core::Constants
9
- extend Dry::Monads[:result]
10
-
11
- extend Dry::Configurable
12
- extend Forwardable
13
-
14
109
  using State::Extension
15
- extend State
110
+ extend Operation
16
111
 
17
- CONTRACT = Dry::Schema.JSON do
18
- # NOP
112
+ with_options instance_accessor: true do |scope|
113
+ scope.config_accessor(:contract) { Dry::Schema.JSON {} }
114
+ scope.config_accessor(:constructor) { IDENTITY }
115
+ scope.config_accessor(:options) { EMPTY_ARRAY }
116
+ scope.config_accessor(:option) { EMPTY_HASH }
117
+ scope.config_accessor(:rules) { EMPTY_ARRAY }
118
+ scope.config_accessor(:context) { IDENTITY }
19
119
  end
20
120
 
21
- setting :constructor, default: IDENTITY
22
- setting :options, default: EMPTY_ARRAY
23
- setting :rules, default: EMPTY_ARRAY
24
- setting :contract, default: CONTRACT
25
- setting :context, default: IDENTITY
26
-
27
- delegate [:config] => self
28
-
29
121
  schema schema.strict(false)
30
122
 
31
- # Holds the current context
32
- # @private
123
+ # Defines a schema for the mapper
124
+ # If the schema fail, the mapper will fail
125
+ #
126
+ # @example Guard against missing values
127
+ # class MapperWithAge < Remap::Base
128
+ # contract do
129
+ # required(:age).filled(:integer)
130
+ # end
131
+ #
132
+ # define do
133
+ # map :age, to: [:person, :age]
134
+ # end
135
+ # end
136
+ #
137
+ # MapperWithAge.call({age: 50}) # => { person: { age: 50 } }
138
+ # MapperWithAge.call({age: '10'}) do |failure|
139
+ # # ...
140
+ # end
141
+ #
142
+ # @see https://dry-rb.org/gems/dry-schema/1.5/
143
+ #
144
+ # @return [void]
33
145
  def self.contract(&context)
34
- config.contract = Dry::Schema.JSON(&context)
146
+ self.contract = Dry::Schema.JSON(&context)
35
147
  end
36
148
 
37
- # @see Dry::Validation::Contract.rule
149
+ # Defines a rule for the mapper
150
+ # If the rule fail, the mapper will fail
151
+ #
152
+ # @example Guard against values
153
+ # class MapperWithRule < Remap::Base
154
+ # contract do
155
+ # required(:age)
156
+ # end
157
+ #
158
+ # rule(:age) do
159
+ # unless value >= 18
160
+ # key.failure("must be at least 18 years old")
161
+ # end
162
+ # end
163
+ #
164
+ # define do
165
+ # map :age, to: [:person, :age]
166
+ # end
167
+ # end
168
+ #
169
+ # MapperWithRule.call({age: 50}) # => { person: { age: 50 } }
170
+ # MapperWithRule.call({age: 10}) do |failure|
171
+ # # ...
172
+ # end
173
+ #
174
+ # @see https://dry-rb.org/gems/dry-validation/1.6/rules/
175
+ #
176
+ # @return [void]
38
177
  def self.rule(...)
39
- config.rules << ->(*) { rule(...) }
178
+ self.rules = rules + [-> * { rule(...) }]
40
179
  end
41
180
 
42
- # Defines a a constructor argument for the mapper
181
+ # Defines a required option for the mapper
182
+ #
183
+ # @example A mapper that takes an argument name
184
+ # class MapperWithOption < Remap::Base
185
+ # option :name
186
+ #
187
+ # define do
188
+ # set :name, to: option(:name)
189
+ # end
190
+ # end
43
191
  #
44
- # @param name [Symbol]
45
- # @param type [#call]
192
+ # MapperWithOption.call({}, name: "John") # => { name: "John" }
193
+ #
194
+ # @param field [Symbol]
195
+ # @option type (Types::Any) [#call]
196
+ #
197
+ # @return [void]
46
198
  def self.option(field, type: Types::Any)
47
199
  attribute(field, type)
48
200
 
49
201
  unless (key = schema.keys.find { _1.name == field })
50
- raise ArgumentError, "Could not locate [#{field}] in [#{self}]"
202
+ raise ArgumentError, "[BUG] Could not locate [#{field}] in [#{self}]"
51
203
  end
52
204
 
53
- config.options << ->(*) { option(field, type: key) }
205
+ self.options = options + [-> * { option(field, type: key) }]
54
206
  end
55
207
 
56
- # Pretty print the mapper
208
+ # Defines a mapper rules and possible constructor
57
209
  #
58
- # @return [String]
59
- def self.inspect
60
- "<#{self.class} #{rule}, #{self}>"
61
- end
62
-
63
- # Defines a mapper with a constructor used to wrap the output
210
+ # @param target (Nothing) [#call]
64
211
  #
65
- # @param constructor [#call]
212
+ # @option method (:new) [Symbol]
213
+ # @option strategy (:argument) [:argument, :keywords, :none]
66
214
  #
67
- # @example A mapper from path :a to path :b
68
- # class Mapper < Remap
215
+ # @example A mapper, which mapps a value at [:a] to [:b]
216
+ # class Mapper < Remap::Base
69
217
  # define do
70
218
  # map :a, to: :b
71
219
  # end
72
220
  # end
73
221
  #
74
- # Mapper.call(a: 1) # => { b: 1 }
222
+ # Mapper.call({a: 1}) # => { b: 1 }
223
+ #
224
+ # @example A mapper with an output constructor
225
+ # class Person < Dry::Struct
226
+ # attribute :first_name, Dry::Types['strict.string']
227
+ # end
228
+ #
229
+ # class Mapper < Remap::Base
230
+ # define(Person) do
231
+ # map :name, to: :first_name
232
+ # end
233
+ # end
234
+ #
235
+ # Mapper.call({name: "John"}).first_name # => "John"
236
+ #
237
+ # @return [void]
75
238
  def self.define(target = Nothing, method: :new, strategy: :argument, &context)
76
239
  unless context
77
240
  raise ArgumentError, "Missing block"
78
241
  end
79
242
 
80
- config.context = Compiler.call(&context)
81
- config.constructor = Constructor.call(method: method, strategy: strategy, target: target)
243
+ self.context = Compiler.call(&context)
244
+ self.constructor = Constructor.call(method: method, strategy: strategy, target: target)
82
245
  rescue Dry::Struct::Error => e
83
246
  raise ArgumentError, e.message
84
247
  end
85
248
 
86
- # Creates a new mapper
249
+ # Similar to {::call}, but takes a state
87
250
  #
88
- # @param input [Any]
89
- # @param params [Hash]
90
-
91
- # @return [Context]
92
-
93
- extend Operation
94
-
251
+ # @param state [State]
252
+ #
253
+ # @yield [Failure] if mapper fails
254
+ #
255
+ # @return [Result] if mapper succeeds
256
+ #
257
+ # @private
95
258
  def self.call!(state, &error)
96
259
  new(state.options).call(state._.set(mapper: self), &error)
97
- rescue Dry::Struct::Error => e
98
- raise ArgumentError, "Option missing to mapper [#{self}]: #{e}"
99
260
  end
100
261
 
101
- # Creates a mapper tree using {#context} and uses {#state} as argument
262
+ # Mappers state according to the mapper rules
102
263
  #
103
- # @return [State]
264
+ # @param state [State]
104
265
  #
105
- # @see .call!
266
+ # @yield [Failure] if mapper fails
267
+ #
268
+ # @return [State]
106
269
  #
107
270
  # @private
108
271
  def call(state, &error)
109
272
  unless error
110
- raise ArgumentError, "Missing block"
273
+ raise ArgumentError, "Base#call(state, &error) requires block"
111
274
  end
112
275
 
113
276
  state.tap do |input|
114
- contract.call(input, state.options).tap do |result|
277
+ validation.call(input, state.options).tap do |result|
115
278
  unless result.success?
116
279
  return error[state.failure(result.errors.to_h)]
117
280
  end
118
281
  end
119
282
  end
120
283
 
121
- state.then(&config.context).then(&config.constructor)
284
+ notice = catch :fatal do
285
+ return context.call(state) do |failure|
286
+ return error[failure]
287
+ end.then(&constructor)
288
+ end
289
+
290
+ error[state.failure(notice)]
122
291
  end
123
292
 
124
293
  private
125
294
 
126
- def contract(scope: self)
127
- Class.new(Dry::Validation::Contract) do |klass|
128
- config = scope.class.config
129
-
130
- config.rules.each do |rule|
131
- klass.class_eval(&rule)
132
- end
133
-
134
- config.options.each do |option|
135
- klass.class_eval(&option)
136
- end
137
-
138
- schema(config.contract)
139
- end.new(**attributes)
140
- end
141
-
142
- def config
143
- self.class.config
295
+ # @return [Contract]
296
+ def validation
297
+ Contract.call(attributes: attributes, contract: contract, options: options, rules: rules)
144
298
  end
145
299
  end
146
300
  end