remap 2.0.2 → 2.1.5
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 +4 -4
- data/lib/remap/base.rb +229 -75
- data/lib/remap/compiler.rb +403 -37
- data/lib/remap/constructor/argument.rb +20 -6
- data/lib/remap/constructor/keyword.rb +20 -4
- data/lib/remap/constructor/none.rb +3 -4
- data/lib/remap/constructor.rb +12 -5
- data/lib/remap/contract.rb +27 -0
- data/lib/remap/extensions/enumerable.rb +48 -0
- data/lib/remap/extensions/hash.rb +13 -0
- data/lib/remap/extensions/object.rb +37 -0
- data/lib/remap/failure.rb +25 -15
- data/lib/remap/iteration/array.rb +20 -11
- data/lib/remap/iteration/hash.rb +21 -13
- data/lib/remap/iteration/other.rb +7 -7
- data/lib/remap/iteration.rb +8 -2
- data/lib/remap/mapper/and.rb +29 -7
- data/lib/remap/mapper/binary.rb +3 -6
- data/lib/remap/mapper/or.rb +29 -6
- data/lib/remap/mapper/support/operations.rb +40 -0
- data/lib/remap/mapper/xor.rb +29 -7
- data/lib/remap/mapper.rb +1 -48
- data/lib/remap/notice/traced.rb +19 -0
- data/lib/remap/notice/untraced.rb +11 -0
- data/lib/remap/notice.rb +34 -0
- data/lib/remap/operation.rb +26 -13
- data/lib/remap/path/input.rb +37 -0
- data/lib/remap/path/output.rb +26 -0
- data/lib/remap/path.rb +22 -0
- data/lib/remap/path_error.rb +13 -0
- data/lib/remap/proxy.rb +18 -0
- data/lib/remap/rule/each.rb +25 -24
- data/lib/remap/rule/embed.rb +33 -28
- data/lib/remap/rule/map/optional.rb +42 -0
- data/lib/remap/rule/map/required.rb +35 -0
- data/lib/remap/rule/map.rb +176 -55
- data/lib/remap/rule/set.rb +23 -33
- data/lib/remap/rule/support/collection/empty.rb +7 -7
- data/lib/remap/rule/support/collection/filled.rb +21 -8
- data/lib/remap/rule/support/collection.rb +11 -3
- data/lib/remap/rule/support/enum.rb +44 -21
- data/lib/remap/rule/void.rb +17 -18
- data/lib/remap/rule/wrap.rb +25 -17
- data/lib/remap/rule.rb +8 -1
- data/lib/remap/selector/all.rb +29 -7
- data/lib/remap/selector/index.rb +24 -16
- data/lib/remap/selector/key.rb +31 -16
- data/lib/remap/selector.rb +17 -0
- data/lib/remap/state/extension.rb +182 -208
- data/lib/remap/state/schema.rb +1 -1
- data/lib/remap/state.rb +30 -4
- data/lib/remap/static/fixed.rb +14 -3
- data/lib/remap/static/option.rb +21 -6
- data/lib/remap/static.rb +13 -0
- data/lib/remap/struct.rb +1 -0
- data/lib/remap/types.rb +13 -28
- data/lib/remap.rb +15 -19
- metadata +95 -93
- data/lib/remap/result.rb +0 -11
- data/lib/remap/rule/support/path.rb +0 -45
- data/lib/remap/state/types.rb +0 -11
- data/lib/remap/success.rb +0 -29
- data/lib/remap/version.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 67dff43a4d2c0b5b8d6885b7ffabc5172908d5802eb373f706a18d2452728bcd
|
4
|
+
data.tar.gz: f6c80d7e47b3e9a5e0fcccc6f0dab7f9797bf8a4aa6a2bc70ac1c18cfdef53a2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2332ff7485e86d2b677c07a361ea9782946685f9ce2f75afec9bbaf80e54fdf93831c845a57ec68b95fbbd3a58904763515f1e78f362727910fc0aaf36b7fa7
|
7
|
+
data.tar.gz: 1862064f9f782ab68dae1f907372ad2a5ae3977fd62b1b7101a464cd7d61223dde1b0f333a0ba2ff3f7cab3a080ebb6e4c95ccc7cdfe0969c00a031bb84c0358
|
data/lib/remap/base.rb
CHANGED
@@ -1,146 +1,300 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
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
|
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
|
110
|
+
extend Operation
|
16
111
|
|
17
|
-
|
18
|
-
|
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
|
-
#
|
32
|
-
#
|
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
|
-
|
146
|
+
self.contract = Dry::Schema.JSON(&context)
|
35
147
|
end
|
36
148
|
|
37
|
-
#
|
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
|
-
|
178
|
+
self.rules = rules + [-> * { rule(...) }]
|
40
179
|
end
|
41
180
|
|
42
|
-
# Defines a
|
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
|
-
#
|
45
|
-
#
|
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
|
-
|
205
|
+
self.options = options + [-> * { option(field, type: key) }]
|
54
206
|
end
|
55
207
|
|
56
|
-
#
|
208
|
+
# Defines a mapper rules and possible constructor
|
57
209
|
#
|
58
|
-
# @
|
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
|
-
# @
|
212
|
+
# @option method (:new) [Symbol]
|
213
|
+
# @option strategy (:argument) [:argument, :keywords, :none]
|
66
214
|
#
|
67
|
-
# @example A mapper
|
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
|
-
|
81
|
-
|
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
|
-
#
|
249
|
+
# Similar to {::call}, but takes a state
|
87
250
|
#
|
88
|
-
# @param
|
89
|
-
#
|
90
|
-
|
91
|
-
#
|
92
|
-
|
93
|
-
|
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
|
-
#
|
262
|
+
# Mappers state according to the mapper rules
|
102
263
|
#
|
103
|
-
# @
|
264
|
+
# @param state [State]
|
104
265
|
#
|
105
|
-
# @
|
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, "
|
273
|
+
raise ArgumentError, "Base#call(state, &error) requires block"
|
111
274
|
end
|
112
275
|
|
113
276
|
state.tap do |input|
|
114
|
-
|
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
|
-
|
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
|
-
|
127
|
-
|
128
|
-
|
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
|