remap 2.2.37 → 2.2.41

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: e0c1f52a49e1b423fdf9e5eb2ffc98da0b4ba1941d6243998e033d30d4aae344
4
- data.tar.gz: f9c476e98e76f6e9fd82a6df8ffdf42ad20e15db775b5108346e94ab44956e01
3
+ metadata.gz: 5a8ee7751985c548b272cb12bb0b08b655f52de3143075afafd8578f9e604eba
4
+ data.tar.gz: f333d2922e7d5ef7a6bd050eb2fc0261446097fdd17f2745eef1a3bc35eb96ee
5
5
  SHA512:
6
- metadata.gz: 3f8db44f46bc9ec9e5b286213f34dedd05d0d8d39d816eb367d62712591eb901bd09e1534710de153ce4e776e53b2e6bad53aa7cdcc8cf5c7f3fb94b0ed8c25e
7
- data.tar.gz: 1eb289f6d77f57b8d2c5e5396e2d2c727bfdce97fa5a8878a89b69f2d52c5a1e59262a33e6cf8a7ae23dbf1870f4b1c59f28cbe03d2499e88a4051a0b4d1c250
6
+ metadata.gz: 69b98a323c17562a06d4b83f3455c2b1c7e6a482280f5afe283f0d9570411391bb2d0de353d96250436d8aa51ad8443f1a3e25805dde0d6baebbc5286d019a9e
7
+ data.tar.gz: 86f03041d4cf3ef2ebd1c597269645ae83c55e5d3326077a9d55f51f3f0a47aea131523e9e314d635f34d60487dbba20350a28ffa3eca9b032f0aecd01bf006f
data/lib/remap/base.rb CHANGED
@@ -106,8 +106,10 @@ module Remap
106
106
  class Base < Mapper
107
107
  include ActiveSupport::Configurable
108
108
  include Dry::Core::Constants
109
+
110
+ include Catchable
111
+
109
112
  using State::Extension
110
- extend Operation
111
113
 
112
114
  with_options instance_accessor: true do |scope|
113
115
  scope.config_accessor(:contract) { Dry::Schema.define {} }
@@ -120,6 +122,31 @@ module Remap
120
122
 
121
123
  schema schema.strict(false)
122
124
 
125
+ # extend Operation
126
+
127
+ def self.call(input, backtrace: caller, **options, &error)
128
+ unless block_given?
129
+ return call(input, **options) do |failure|
130
+ raise failure.exception(backtrace)
131
+ end
132
+ end
133
+
134
+ s0 = State.call(input, options: options, mapper: self)._
135
+
136
+ s1 = call!(s0) do |failure|
137
+ return error[failure]
138
+ end
139
+
140
+ case s1
141
+ in { value: value }
142
+ value
143
+ in { notices: [] }
144
+ error[s1.failure("No data could be mapped")]
145
+ in { notices: }
146
+ error[Failure.new(failures: notices)]
147
+ end
148
+ end
149
+
123
150
  # Defines a schema for the mapper
124
151
  # If the schema fail, the mapper will fail
125
152
  #
@@ -235,26 +262,31 @@ module Remap
235
262
  # Mapper.call({name: "John"}).first_name # => "John"
236
263
  #
237
264
  # @return [void]
238
- def self.define(target = Nothing, method: :new, strategy: :argument, &context)
265
+ # rubocop:disable Layout/LineLength
266
+ def self.define(target = Nothing, method: :new, strategy: :argument, backtrace: caller, &context)
239
267
  unless block_given?
240
268
  raise ArgumentError, "#{self}.define requires a block"
241
269
  end
242
270
 
243
271
  self.constructor = Constructor.call(method: method, strategy: strategy, target: target)
244
- self.context = Compiler.call(&context)
272
+ self.context = Compiler.call(backtrace: backtrace, &context)
245
273
  end
274
+ # rubocop:enable Layout/LineLength
246
275
 
247
- # Similar to {::call}, but takes a state
248
- #
249
276
  # @param state [State]
250
277
  #
251
- # @yield [Failure] if mapper fails
278
+ # @yield [Failure]
279
+ # when a non-critical error occurs
280
+ # @yieldreturn T
252
281
  #
253
- # @return [Result] if mapper succeeds
282
+ # @return [State, T]
283
+ # when request is a success
284
+ # @raise [Remap::Error]
285
+ # when a fatal error occurs
254
286
  #
255
287
  # @private
256
288
  def self.call!(state, &error)
257
- new(state.options).call(state._.set(mapper: self), &error)
289
+ new(state.options).call(state, &error)
258
290
  end
259
291
 
260
292
  # Mappers state according to the mapper rules
@@ -267,8 +299,8 @@ module Remap
267
299
  #
268
300
  # @private
269
301
  def call(state, &error)
270
- unless block_given?
271
- raise ArgumentError, "Base#call(state, &error) requires block"
302
+ state._ do |reason|
303
+ raise ArgumentError, "Invalid state due to #{reason.formatted}"
272
304
  end
273
305
 
274
306
  state.tap do |input|
@@ -279,13 +311,11 @@ module Remap
279
311
  end
280
312
  end
281
313
 
282
- notice = catch :fatal do
283
- return context.call(state) do |failure|
284
- return error[failure]
285
- end.then(&constructor)
314
+ s1 = catch_ignored do |id|
315
+ return context.call(state.set(id: id)).then(&constructor).remove_id
286
316
  end
287
317
 
288
- error[state.failure(notice)]
318
+ Failure.new(failures: s1.notices).then(&error)
289
319
  end
290
320
 
291
321
  private
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ module Catchable
5
+ # @yieldparam id [Symbol]
6
+ # @yieldreturn [T]
7
+ #
8
+ # @return [T]
9
+ def catch_ignored(&block)
10
+ catch(to_id(:ignored), &block)
11
+ end
12
+
13
+ # @yieldparam id [Symbol]
14
+ # @yieldreturn [T]
15
+ #
16
+ # @return [T]
17
+ def catch_fatal(&block)
18
+ catch(to_id(:fatal), &block)
19
+ end
20
+
21
+ private
22
+
23
+ def to_id(value)
24
+ [value, self.class.name&.downcase || :unknown].join("::")
25
+ end
26
+ end
27
+ end
@@ -5,6 +5,8 @@ module Remap
5
5
 
6
6
  # Constructs a {Rule} from the block passed to {Remap::Base.define}
7
7
  class Compiler < Proxy
8
+ extend Catchable
9
+ include Catchable
8
10
  # @return [Array<Rule>]
9
11
  param :rules, type: Types.Array(Rule)
10
12
 
@@ -29,7 +31,7 @@ module Remap
29
31
  # rule.call(state, &error).fetch(:value) # => { name: "John", age: 50 }
30
32
  #
31
33
  # @return [Rule]
32
- def self.call(&block)
34
+ def self.call(backtrace: caller, &block)
33
35
  unless block_given?
34
36
  return Rule::VOID
35
37
  end
@@ -38,7 +40,7 @@ module Remap
38
40
  compiler.instance_exec(&block)
39
41
  end.rules
40
42
 
41
- Rule::Block.new(rules)
43
+ Rule::Block.new(backtrace: backtrace, rules: rules)
42
44
  end
43
45
 
44
46
  # Maps input path [input] to output path [to]
@@ -176,18 +178,20 @@ module Remap
176
178
  # output.fetch(:value) # => { car: "Volvo" }
177
179
  #
178
180
  # @return [Rule::Embed]
179
- def embed(mapper)
181
+ def embed(mapper, backtrace: caller)
180
182
  if block_given?
181
183
  raise ArgumentError, "#embed does not take a block"
182
184
  end
183
185
 
184
- embeding = rule.add do |state, &error|
185
- mapper.call!(state.set(mapper: mapper)) do |failure|
186
- next error[failure]
187
- end.except(:mapper, :scope)
186
+ Types::Mapper[mapper] do
187
+ raise ArgumentError, "Argument to #embed must be a mapper, got #{mapper.class}"
188
188
  end
189
189
 
190
- add embeding
190
+ result = rule(backtrace: backtrace).add do |s0|
191
+ build_embed(s0, mapper, backtrace)
192
+ end
193
+
194
+ add result
191
195
  end
192
196
 
193
197
  # Set a static value
@@ -225,12 +229,16 @@ module Remap
225
229
  # @raise [ArgumentError]
226
230
  # if no path given
227
231
  # if path is not a Symbol or Array<Symbol>
228
- def set(*path, to:)
232
+ def set(*path, to:, backtrace: caller)
229
233
  if block_given?
230
234
  raise ArgumentError, "#set does not take a block"
231
235
  end
232
236
 
233
- add rule(to: path).add { to.call(_1) }
237
+ unless to.is_a?(Static)
238
+ raise ArgumentError, "Argument to #set must be a static value, got #{to.class}"
239
+ end
240
+
241
+ add rule(to: path, backtrace: backtrace).add { to.call(_1) }
234
242
  end
235
243
 
236
244
  # Maps to path from map with block in between
@@ -280,8 +288,8 @@ module Remap
280
288
  # @see #to
281
289
  #
282
290
  # @return [Rule::Map::Optional]
283
- def to?(*path, map: EMPTY_ARRAY, &block)
284
- add rule?(*map, to: path, &block)
291
+ def to?(*path, map: EMPTY_ARRAY, backtrace: caller, &block)
292
+ add rule?(*map, to: path, backtrace: backtrace, &block)
285
293
  end
286
294
 
287
295
  # Iterates over the input value, passes each value
@@ -308,12 +316,12 @@ module Remap
308
316
  #
309
317
  # @return [Rule::Each]]
310
318
  # @raise [ArgumentError] if no block given
311
- def each(&block)
319
+ def each(backtrace: caller, &block)
312
320
  unless block_given?
313
321
  raise ArgumentError, "#each requires a block"
314
322
  end
315
323
 
316
- add rule(all, &block)
324
+ add rule(all, backtrace: backtrace, &block)
317
325
  end
318
326
 
319
327
  # Wraps output in type
@@ -341,12 +349,16 @@ module Remap
341
349
  #
342
350
  # @return [Rule::Wrap]
343
351
  # @raise [ArgumentError] if type is not :array
344
- def wrap(type, &block)
352
+ def wrap(type, backtrace: caller, &block)
345
353
  unless block_given?
346
354
  raise ArgumentError, "#wrap requires a block"
347
355
  end
348
356
 
349
- add rule(&block).then { Array.wrap(_1) }
357
+ unless type == :array
358
+ raise ArgumentError, "Argument to #wrap must equal :array, got [#{type}] (#{type.class})"
359
+ end
360
+
361
+ add rule(backtrace: backtrace, &block).then { Array.wrap(_1) }
350
362
  end
351
363
 
352
364
  # Selects all elements
@@ -394,12 +406,12 @@ module Remap
394
406
  # output.fetch(:value) # => { api_key: "<SECRET>" }
395
407
  #
396
408
  # @return [Rule::Static::Fixed]
397
- def value(value)
409
+ def value(value, backtrace: caller)
398
410
  if block_given?
399
411
  raise ArgumentError, "option selector does not take a block"
400
412
  end
401
413
 
402
- Static::Fixed.new(value: value)
414
+ Static::Fixed.new(value: value, backtrace: backtrace)
403
415
  end
404
416
 
405
417
  # Static option to be selected
@@ -526,7 +538,7 @@ module Remap
526
538
  input: path.flatten
527
539
  },
528
540
  backtrace: backtrace,
529
- rule: call(&block)
541
+ rule: call(backtrace: backtrace, &block)
530
542
  })
531
543
  end
532
544
 
@@ -536,8 +548,23 @@ module Remap
536
548
  output: [to].flatten,
537
549
  input: path.flatten
538
550
  },
539
- rule: call(&block)
551
+ rule: call(backtrace: backtrace, &block)
540
552
  })
541
553
  end
554
+
555
+ def build_embed(s0, mapper, backtrace)
556
+ f0 = catch_fatal do |fatal_id|
557
+ s1 = s0.set(fatal_id: fatal_id)
558
+ s2 = s1.set(mapper: mapper)
559
+ old_mapper = s0.fetch(:mapper)
560
+
561
+ return mapper.call!(s2) do |f1|
562
+ s3 = s2.set(notices: f1.notices + f1.failures)
563
+ s3.return!
564
+ end.except(:scope).merge(mapper: old_mapper)
565
+ end
566
+
567
+ raise f0.exception(backtrace)
568
+ end
542
569
  end
543
570
  end
@@ -27,19 +27,12 @@ module Remap
27
27
  #
28
28
  # @return [State]
29
29
  def call(state)
30
- super.fmap do |input, &error|
30
+ super.fmap do |input|
31
31
  unless input.is_a?(Hash)
32
- return error["Input is not a hash"]
32
+ raise ArgumentError, "Expected Hash, got #{input.class}"
33
33
  end
34
34
 
35
35
  target.public_send(id, **input)
36
- rescue ArgumentError => e
37
- raise e.exception("Failed to create [%p] with input [%s] (%s}) using method %s" % [
38
- target,
39
- input,
40
- input.class,
41
- id
42
- ])
43
36
  end
44
37
  end
45
38
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ module Extensions
5
+ module Array
6
+ refine ::Array do
7
+ # @return [Array<Hash>]
8
+ def to_hash
9
+ map(&:to_hash)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -4,6 +4,11 @@ module Remap
4
4
  module Extensions
5
5
  module Object
6
6
  refine ::Object do
7
+ # @return [Any]
8
+ def to_hash
9
+ self
10
+ end
11
+
7
12
  # Fallback validation method
8
13
  #
9
14
  # @yield if block is provided
@@ -43,6 +48,7 @@ module Remap
43
48
  end
44
49
  alias_method :fetch, :get
45
50
 
51
+ # return [Any]
46
52
  def formatted
47
53
  self
48
54
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Failure
5
+ using Extensions::Hash
6
+
7
+ class Error < Error
8
+ extend Dry::Initializer
9
+
10
+ option :failure, type: Types.Instance(Failure)
11
+ delegate_missing_to :failure
12
+
13
+ # @return [String]
14
+ def inspect
15
+ "#<%s %s>" % [self.class, to_hash.formatted]
16
+ end
17
+ alias to_s inspect
18
+ end
19
+ end
20
+ end
data/lib/remap/failure.rb CHANGED
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Remap
4
- using Extensions::Hash
5
-
6
4
  class Failure < Dry::Concrete
7
5
  attribute :failures, Types.Array(Types::Notice), min_size: 1
8
6
  attribute? :notices, Types.Array(Types::Notice), default: EMPTY_ARRAY
@@ -29,9 +27,13 @@ module Remap
29
27
  new(failure)
30
28
  end
31
29
 
32
- # @return [Error]
33
- def exception
34
- Error.new(attributes)
30
+ # @param backtrace [Array<String>] Backtrace from Kernel.caller
31
+ #
32
+ # @return [Failure::Error]
33
+ def exception(backtrace)
34
+ e = Error.new(failure: self)
35
+ e.set_backtrace(backtrace)
36
+ e
35
37
  end
36
38
  end
37
39
  end
@@ -26,12 +26,9 @@ module Remap
26
26
  end
27
27
 
28
28
  def reduce(state, value, index, &block)
29
- notice = catch :ignore do
30
- other = block[value, index: index]
31
- return state.combine(other.fmap { [_1] })
32
- end
33
-
34
- state.set(notice: notice)
29
+ s0 = block[value, index: index]
30
+ s1 = s0.set(**state.only(:ids, :fatal_id))
31
+ state.combine(s1.fmap { [_1] })
35
32
  end
36
33
  end
37
34
  end
@@ -22,12 +22,9 @@ module Remap
22
22
  private
23
23
 
24
24
  def reduce(state, key, value, &block)
25
- notice = catch :ignore do
26
- other = block[value, key: key]
27
- return state.combine(other.fmap { { key => _1 } })
28
- end
29
-
30
- state.set(notice: notice)
25
+ s0 = block[value, key: key]
26
+ s1 = s0.set(fatal_id: state.fatal_id, ids: state.ids)
27
+ state.combine(s1.fmap { { key => _1 } })
31
28
  end
32
29
 
33
30
  def init
@@ -25,8 +25,13 @@ module Remap
25
25
  end
26
26
 
27
27
  def traced(backtrace)
28
- tap { _1.set_backtrace(backtrace) }
28
+ e = Traced.new(notice: notice)
29
+ e.set_backtrace(backtrace)
30
+ e
29
31
  end
30
32
  end
33
+
34
+ class Traced < Error
35
+ end
31
36
  end
32
37
  end
@@ -7,32 +7,37 @@ module Remap
7
7
  module Operation
8
8
  # Public interface for mappers
9
9
  #
10
- # @param input [Any] Data to be mapped
11
- # @param options [Hash] Mapper arguments
10
+ # @param input [Any] data to be mapped
11
+ # @param options [Hash] mapper options
12
12
  #
13
- # @yield [Failure] if mapper fails
13
+ # @yield [Failure]
14
+ # when a non-critical error occurs
15
+ # @yieldreturn T
14
16
  #
15
- # @return [Success] if mapper succeeds
16
- def call(input, **options, &error)
17
- unless error
17
+ # @return [Any, T]
18
+ # when request is a success
19
+ # @raise [Remap::Error]
20
+ # when a fatal error occurs
21
+ def call(input, backtrace: caller, **options, &error)
22
+ unless block_given?
18
23
  return call(input, **options) do |failure|
19
- raise failure.exception
24
+ raise failure.exception(backtrace)
20
25
  end
21
26
  end
22
27
 
23
- other = State.call(input, options: options, mapper: self).then do |state|
24
- call!(state) do |failure|
25
- return error[failure]
26
- end
28
+ s0 = State.call(input, options: options, mapper: self)._
29
+
30
+ s1 = call!(s0) do |failure|
31
+ return error[failure]
27
32
  end
28
33
 
29
- case other
30
- in { value: }
34
+ case s1
35
+ in { value: value }
31
36
  value
32
37
  in { notices: [] }
33
- error[other.failure("No return value")]
38
+ error[s1.failure("No data could be mapped")]
34
39
  in { notices: }
35
- error[Failure.call(failures: notices)]
40
+ error[Failure.new(failures: notices)]
36
41
  end
37
42
  end
38
43
  end
@@ -6,6 +6,7 @@ module Remap
6
6
 
7
7
  class Block < Unit
8
8
  # @return [Array<Rule>]
9
+ attribute :backtrace, [String], min_size: 1
9
10
  attribute :rules, [Types::Rule]
10
11
 
11
12
  # Represents a non-empty define block with one or more rules
@@ -14,20 +15,30 @@ module Remap
14
15
  # @param state [State]
15
16
  #
16
17
  # @return [State]
17
- def call(state, &error)
18
- unless block_given?
19
- raise ArgumentError, "Block#call(state, &error) requires a block"
20
- end
18
+ def call(state)
19
+ s0 = state.except(:value)
21
20
 
22
21
  if rules.empty?
23
- return state.except(:value)
22
+ return s0
23
+ end
24
+
25
+ failure = catch_fatal do |fatal_id|
26
+ s1 = s0.set(fatal_id: fatal_id)
27
+ s4 = state.set(fatal_id: fatal_id)
28
+
29
+ return catch_ignored do |id|
30
+ s2 = s1.set(id: id)
31
+
32
+ rules.reduce(s2) do |s3, rule|
33
+ s5 = s3
34
+ s6 = rule.call(s4)
35
+ s7 = s6.set(id: id)
36
+ s5.combine(s7)
37
+ end
38
+ end.remove_id.remove_fatal_id
24
39
  end
25
40
 
26
- rules.map do |rule|
27
- rule.call(state) do |failure|
28
- return error[failure]
29
- end
30
- end.reduce(&:combine)
41
+ raise failure.exception(backtrace)
31
42
  end
32
43
  end
33
44
  end
@@ -30,25 +30,8 @@ module Remap
30
30
  # @param state [State]
31
31
  #
32
32
  # @return [State]
33
- def call(state, &error)
34
- unless block_given?
35
- raise ArgumentError, "map.call(state, &error) requires a block"
36
- end
37
-
38
- super
39
- rescue Notice::Ignore => e
40
- e.undefined(state)
41
- end
42
-
43
- private
44
-
45
- # Catches :ignore exceptions and re-package them as a state
46
- #
47
- # @param state [State]
48
- #
49
- # @return [State]
50
- def ignore(state, &block)
51
- state.set(notice: catch(:ignore, &block).traced(backtrace)).except(:value)
33
+ def call(state)
34
+ catch { super(state.set(id: _1)).except(:id) }
52
35
  end
53
36
  end
54
37
  end
@@ -8,40 +8,7 @@ module Remap
8
8
  class Required < Concrete
9
9
  attribute :backtrace, Types::Backtrace
10
10
 
11
- # Represents a required mapping rule
12
- # When it fails, the entire mapping is marked as failed
13
- #
14
- # @example Map [:name] to [:nickname]
15
- # map = Map::Required.call({
16
- # backtrace: caller,
17
- # path: {
18
- # input: [:name],
19
- # output: [:nickname]
20
- # }
21
- # })
22
- #
23
- # state = Remap::State.call({
24
- # name: "John"
25
- # })
26
- #
27
- # output = map.call(state) do |failure|
28
- # # ...
29
- # end
30
- #
31
- # output.fetch(:value) # => { nickname: "John" }
32
- #
33
- # @param state [State]
34
- #
35
- # @return [State]
36
- def call(state, &error)
37
- unless block_given?
38
- raise ArgumentError, "Required.call(state, &error) requires a block"
39
- end
40
-
41
- super
42
- rescue Notice::Ignore => e
43
- error[e.failure(state)]
44
- end
11
+ # TODO: Remove
45
12
  end
46
13
  end
47
14
  end
@@ -42,29 +42,28 @@ module Remap
42
42
  # @return [State]
43
43
  #
44
44
  # @abstract
45
- def call(state, &error)
46
- unless block_given?
47
- raise ArgumentError, "Map#call(state, &error) requires error handler block"
48
- end
45
+ def call(state)
46
+ failure = catch_fatal do |fatal_id|
47
+ s0 = state.set(fatal_id: fatal_id)
49
48
 
50
- s1 = path.input.call(state) do |inner_state|
51
- other_state = rule.call(inner_state) do |failure|
52
- return error[failure]
49
+ s2 = path.input.call(s0) do |s1|
50
+ s2 = rule.call(s1)
51
+ callback(s2)
53
52
  end
54
53
 
55
- callback(other_state) do |failure|
56
- return error[failure]
57
- end
58
- end.then(&path.output)
54
+ s3 = s2.then(&path.output)
55
+ s4 = s3.set(path: state.path)
56
+ s5 = s4.except(:key)
57
+
58
+ return s5.remove_fatal_id
59
+ end
59
60
 
60
- s1.set(path: state.path).except(:key)
61
- rescue Notice::Fatal => e
62
- raise e.traced(backtrace)
61
+ raise failure.exception(backtrace)
63
62
  end
64
63
 
65
64
  # A post-processor method
66
65
  #
67
- # @example Upcase mapped value
66
+ # @example Up-case mapped value
68
67
  # state = Remap::State.call("Hello World")
69
68
  # map = Remap::Rule::Map.call({})
70
69
  # upcase = map.adjust(&:upcase)
@@ -198,40 +197,8 @@ module Remap
198
197
  end
199
198
 
200
199
  # @return [Proc]
201
- def callback(state, &error)
202
- unless block_given?
203
- raise ArgumentError, "Map#callback(state, &error) requires error handler block"
204
- end
205
-
206
- fn.reduce(state) do |inner, fn|
207
- fn[inner] do |failure|
208
- return error[failure]
209
- end
210
- end
211
- end
212
-
213
- # Catches :fatal and raises {Notice::Error}
214
- #
215
- # @param state [State]
216
- # @param id (:fatal) [:fatal, :notice, :ignore]
217
- #
218
- # raise [Notice::Error]
219
- def fatal(state, id: :fatal, &block)
220
- raise catch(id, &block).traced(backtrace).exception
221
- end
222
-
223
- # Catches :notice exceptions and repackages them as a state
224
- #
225
- # @param state [State]
226
- #
227
- # @return [State]
228
- def notice(state, &block)
229
- state.set(notice: catch(:notice, &block).traced(backtrace)).except(:value)
230
- end
231
-
232
- # @abstract
233
- def ignore(...)
234
- raise NotImplementedError, "#{self.class}#ignore"
200
+ def callback(state)
201
+ fn.reduce(state) { |s1, f1| f1[s1] }
235
202
  end
236
203
  end
237
204
  end
@@ -19,9 +19,7 @@ module Remap
19
19
  #
20
20
  # @return [State<T>]
21
21
  def call(state)
22
- state.bind do |value, inner_state|
23
- inner_state.set(value)
24
- end
22
+ state
25
23
  end
26
24
  end
27
25
  end
data/lib/remap/rule.rb CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  module Remap
4
4
  class Rule < Dry::Interface
5
+ using Extensions::Hash
6
+ using Extensions::Array
7
+ using Extensions::Object
8
+ include Catchable
5
9
  defines :requirement
6
10
  requirement Types::Any
7
11
 
@@ -13,5 +17,16 @@ module Remap
13
17
  def call(state)
14
18
  raise NotImplementedError, "#{self.class}#call not implemented"
15
19
  end
20
+
21
+ # @return [String]
22
+ def inspect
23
+ "#<#{self.class} #{to_hash.formatted}>"
24
+ end
25
+ alias to_s inspect
26
+
27
+ # @return [Hash]
28
+ def to_hash
29
+ attributes.transform_values(&:to_hash).except(:backtrace)
30
+ end
16
31
  end
17
32
  end
@@ -24,7 +24,7 @@ module Remap
24
24
 
25
25
  # Selects the {#index}th element from state and passes it to block
26
26
  #
27
- # @param outer_state [State<Array<T>>]
27
+ # @param state [State<Array<T>>]
28
28
  #
29
29
  # @yieldparam [State<T>]
30
30
  # @yieldreturn [State<U>]
@@ -41,7 +41,7 @@ module Remap
41
41
  end
42
42
 
43
43
  element = array.fetch(index) do
44
- s.ignore!("Index %s not found", index)
44
+ s.ignore!("Index not found")
45
45
  end
46
46
 
47
47
  state.set(element, index: index).then(&block)
@@ -21,7 +21,7 @@ module Remap
21
21
 
22
22
  # Selects {#key} from state and passes it to block
23
23
  #
24
- # @param outer_state [State<Hash<K, V>>]
24
+ # @param state [State<Hash<K, V>>]
25
25
  #
26
26
  # @yieldparam [State<V>]
27
27
  # @yieldreturn [State<U>]
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Remap
4
4
  module State
5
+ # @api private
5
6
  module Extension
6
7
  using Extensions::Enumerable
7
8
  using Extensions::Object
@@ -54,7 +55,11 @@ module Remap
54
55
 
55
56
  # Throws :fatal containing a Notice
56
57
  def fatal!(...)
57
- notice(...).fatal!
58
+ fatal_id = fetch(:fatal_id) do
59
+ raise ArgumentError, "Missing :fatal_id in %s" % formatted
60
+ end
61
+
62
+ throw fatal_id, Failure.new(failures: [notice(...)], notices: notices)
58
63
  end
59
64
 
60
65
  # Throws :warn containing a Notice
@@ -64,7 +69,7 @@ module Remap
64
69
 
65
70
  # Throws :ignore containing a Notice
66
71
  def ignore!(...)
67
- notice(...).ignore!
72
+ set(notice: notice(...)).except(:value).return!
68
73
  end
69
74
 
70
75
  # Creates a notice containing the given message
@@ -88,7 +93,9 @@ module Remap
88
93
  # @return [self]
89
94
  def _(&block)
90
95
  unless block
91
- return _ { raise ArgumentError, "Input: #{self} output: #{_1.formatted}" }
96
+ return _ do |reason|
97
+ raise ArgumentError, "[BUG] State: #{formatted} reason: #{reason.formatted}"
98
+ end
92
99
  end
93
100
 
94
101
  unless (result = Schema.call(self)).success?
@@ -98,11 +105,11 @@ module Remap
98
105
  self
99
106
  end
100
107
 
101
- # Makes the state iterable
108
+ # Iterates over {#value}
102
109
  #
103
110
  # @yieldparam value [Any]
104
- # @yieldoption key [Symbol]
105
- # @yieldoption index [Integer]
111
+ # @yieldparam key [Symbol]
112
+ # @yieldparam index [Integer]
106
113
  #
107
114
  # @yieldreturn [State]
108
115
  #
@@ -131,8 +138,8 @@ module Remap
131
138
  in [:value, Array => list1, Array => list2]
132
139
  list1 + list2
133
140
  in [:value, left, right]
134
- fatal!(
135
- "Could not merge [%p] (%s) with [%p] (%s)",
141
+ other.fatal!(
142
+ "Could not merge [%s] (%s) with [%s] (%s)",
136
143
  left.formatted,
137
144
  left.class,
138
145
  right.formatted,
@@ -140,10 +147,47 @@ module Remap
140
147
  )
141
148
  in [:notices, Array => n1, Array => n2]
142
149
  n1 + n2
150
+ in [:ids, i1, i2] if i1.all? { i2.include?(_1) }
151
+ i2
152
+ in [:ids, i1, i2] if i2.all? { i1.include?(_1) }
153
+ i1
154
+ in [:ids, i1, i2]
155
+ other.fatal!("Could not merge #ids [%s] (%s) with [%s] (%s)", i1, i1.class, i2,
156
+ i2.class)
143
157
  in [Symbol, _, value]
144
158
  value
145
159
  end
146
- end
160
+ end._
161
+ end
162
+
163
+ # @todo Merge with {#remove_fatal_id}
164
+ # @return [State]
165
+ def remove_id
166
+ case self
167
+ in { ids: [], id: }
168
+ except(:id)
169
+ in { ids:, id: }
170
+ merge(ids: ids[1...], id: ids[0])
171
+ in { ids: [] }
172
+ self
173
+ in { ids: }
174
+ raise ArgumentError, "[BUG] #ids for state are set, but not #id: %s" % formatted
175
+ end._
176
+ end
177
+
178
+ # @todo Merge with {#remove_id}
179
+ # @return [State]
180
+ def remove_fatal_id
181
+ case self
182
+ in { fatal_ids: [], fatal_id: }
183
+ except(:fatal_id)
184
+ in { fatal_ids: ids, fatal_id: id }
185
+ merge(fatal_ids: ids[1...], fatal_id: ids[0])
186
+ in { fatal_ids: [] }
187
+ self
188
+ in { fatal_ids: }
189
+ raise ArgumentError, "[BUG] #ids for state are set, but not #id: %s" % formatted
190
+ end._
147
191
  end
148
192
 
149
193
  # Creates a new state with params
@@ -168,6 +212,10 @@ module Remap
168
212
  merge(path: path + [index], element: value, index: index, value: value).set(**rest)
169
213
  in [{path:}, {index:, **rest}]
170
214
  merge(path: path + [index], index: index).set(**rest)
215
+ in [{ids:, id: old_id}, {id: new_id, **rest}]
216
+ merge(ids: [old_id] + ids, id: new_id).set(**rest)
217
+ in [{fatal_ids:, fatal_id: old_id}, {fatal_id: new_id, **rest}]
218
+ merge(fatal_ids: [old_id] + fatal_ids, fatal_id: new_id).set(**rest)
171
219
  else
172
220
  merge(options)
173
221
  end
@@ -185,8 +233,8 @@ module Remap
185
233
  #
186
234
  # @return [State<Y>]
187
235
  def fmap(**options, &block)
188
- bind(**options) do |input, state, &error|
189
- state.set(block[input, state, &error])
236
+ bind(**options) do |input, state:|
237
+ state.set(block[input, state, state: state])
190
238
  end
191
239
  end
192
240
 
@@ -203,28 +251,7 @@ module Remap
203
251
  # end
204
252
 
205
253
  def failure(reason = Undefined)
206
- failures = case [path, reason]
207
- in [_, Notice => notice]
208
- [notice]
209
- in [path, Array => reasons]
210
- reasons.map do |inner_reason|
211
- Notice.call(path: path, reason: inner_reason, **only(:value))
212
- end
213
- in [path, String => reason]
214
- [Notice.call(path: path, reason: reason, **only(:value))]
215
- in [path, Hash => errors]
216
- errors.paths.flat_map do |sufix|
217
- Array.wrap(errors.dig(*sufix)).map do |inner_reason|
218
- Notice.call(
219
- reason: inner_reason,
220
- path: path + sufix,
221
- **only(:value)
222
- )
223
- end
224
- end
225
- end
226
-
227
- Failure.new(failures: failures, notices: notices)
254
+ raise NotImplementedError, "Not implemented"
228
255
  end
229
256
 
230
257
  # Passes {#value} to block, if defined
@@ -242,12 +269,10 @@ module Remap
242
269
  raise ArgumentError, "State#bind requires a block"
243
270
  end
244
271
 
245
- s = set(**options)
272
+ s1 = set(**options)
246
273
 
247
- fetch(:value) { return s }.then do |value|
248
- block[value, s] do |reason, **other|
249
- return s.set(**other).ignore!(reason)
250
- end
274
+ fetch(:value) { return s1 }.then do |value|
275
+ block[value, s1, state: s1]
251
276
  end
252
277
  end
253
278
 
@@ -259,11 +284,11 @@ module Remap
259
284
  #
260
285
  # @return [State<U>]
261
286
  def execute(&block)
262
- bind do |value, &error|
263
- result = context(value, &error).instance_exec(value, &block)
287
+ bind do |value|
288
+ result = context(value).instance_exec(value, &block)
264
289
 
265
290
  if result.equal?(Dry::Core::Constants::Undefined)
266
- return error["Undefined returned, skipping!"]
291
+ ignore!("Undefined returned, skipping!")
267
292
  end
268
293
 
269
294
  set(result)
@@ -290,6 +315,21 @@ module Remap
290
315
  fetch(:path, EMPTY_ARRAY)
291
316
  end
292
317
 
318
+ # @return [Symbol]
319
+ def id
320
+ fetch(:id)
321
+ end
322
+
323
+ # @return [Array<Symbol>]
324
+ def ids
325
+ fetch(:ids)
326
+ end
327
+
328
+ # @return [Symbol]
329
+ def fatal_id
330
+ fetch(:fatal_id)
331
+ end
332
+
293
333
  # Represents options to a mapper
294
334
  #
295
335
  # @see Rule::Embed
@@ -303,7 +343,7 @@ module Remap
303
343
  #
304
344
  # @return [Hash]
305
345
  def to_hash
306
- super.except(:options, :notices, :value)
346
+ super.except(:options, :notices, :value, :id, :ids, :fatal_id, :fatal_ids)
307
347
  end
308
348
 
309
349
  # @return [Any]
@@ -331,6 +371,48 @@ module Remap
331
371
  fetch(:notices)
332
372
  end
333
373
 
374
+ # Creates a failure from the current state
375
+ #
376
+ # @param reason [String, Hash, Undefined]
377
+ #
378
+ # @return [Failure]
379
+ def failure(reason = Undefined)
380
+ failures = case [path, reason]
381
+ in [_, Notice => notice]
382
+ [notice]
383
+ in [path, Array => reasons]
384
+ reasons.map do |inner_reason|
385
+ Notice.call(path: path, reason: inner_reason, **only(:value))
386
+ end
387
+ in [path, String => reason]
388
+ [Notice.call(path: path, reason: reason, **only(:value))]
389
+ in [path, Hash => errors]
390
+ errors.paths.flat_map do |sufix|
391
+ Array.wrap(errors.dig(*sufix)).map do |inner_reason|
392
+ Notice.call(
393
+ reason: inner_reason,
394
+ path: path + sufix,
395
+ **only(:value)
396
+ )
397
+ end
398
+ end
399
+ end
400
+
401
+ Failure.new(failures: failures, notices: notices)
402
+ end
403
+
404
+ # @raise [ArgumentError]
405
+ # when {#id} is not defined
406
+ #
407
+ # @private
408
+ def return!
409
+ id = fetch(:id) do
410
+ raise ArgumentError, "#id not defined for state [%s]" % [formatted]
411
+ end
412
+
413
+ throw id, remove_id
414
+ end
415
+
334
416
  private
335
417
 
336
418
  # Creates a context containing {options} and {self}
@@ -340,13 +422,13 @@ module Remap
340
422
  # @yieldparam reason [T]
341
423
  #
342
424
  # @return [Struct]
343
- def context(value, context: self, &error)
344
- ::Struct.new(*keys, *options.keys, :state, keyword_init: true) do
425
+ def context(value, context: self)
426
+ ::Struct.new(*except(:id).keys, *options.keys, :state, keyword_init: true) do
345
427
  define_method :method_missing do |name, *|
346
- error["Method [#{name}] not defined"]
428
+ context.fatal!("Method [%s] not defined", name)
347
429
  end
348
430
 
349
- define_method :skip! do |message = "Manual skip!"|
431
+ define_method(:skip!) do |message = "Manual skip!"|
350
432
  context.ignore!(message)
351
433
  end
352
434
  end.new(**to_hash, **options, value: value, state: self)
@@ -9,6 +9,8 @@ module Remap
9
9
  required(:notices).array(Types.Instance(Notice))
10
10
  required(:options).value(:hash)
11
11
  required(:path).array(Types::Key)
12
+ required(:ids).value(:array)
13
+ required(:fatal_ids).value(:array)
12
14
 
13
15
  optional(:index).filled(:integer)
14
16
  optional(:element).filled
data/lib/remap/state.rb CHANGED
@@ -31,9 +31,11 @@ module Remap
31
31
  # @return [Hash] A valid state
32
32
  def self.call(value, mapper: Dummy, options: EMPTY_HASH)
33
33
  {
34
+ fatal_ids: EMPTY_ARRAY,
34
35
  notices: EMPTY_ARRAY,
35
36
  path: EMPTY_ARRAY,
36
37
  options: options,
38
+ ids: EMPTY_ARRAY,
37
39
  mapper: mapper,
38
40
  values: value,
39
41
  value: value,
@@ -26,11 +26,11 @@ module Remap
26
26
  #
27
27
  # @return [State]
28
28
  def call(state)
29
- state.set(state.options.fetch(name))
30
- rescue KeyError => e
31
- raise ArgumentError, e.exception("Option [%s] not found in input [%p]" % [
32
- name, state.options
33
- ])
29
+ value = state.options.fetch(name) do
30
+ raise ArgumentError, "Option [%s] not found" % [name], backtrace
31
+ end
32
+
33
+ state.set(value)
34
34
  end
35
35
  end
36
36
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: remap
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.37
4
+ version: 2.2.41
5
5
  platform: ruby
6
6
  authors:
7
7
  - Linus Oleander
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-12 00:00:00.000000000 Z
11
+ date: 2021-12-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -248,6 +248,7 @@ extra_rdoc_files: []
248
248
  files:
249
249
  - lib/remap.rb
250
250
  - lib/remap/base.rb
251
+ - lib/remap/catchable.rb
251
252
  - lib/remap/class_interface.rb
252
253
  - lib/remap/compiler.rb
253
254
  - lib/remap/constructor.rb
@@ -256,10 +257,12 @@ files:
256
257
  - lib/remap/constructor/none.rb
257
258
  - lib/remap/contract.rb
258
259
  - lib/remap/error.rb
260
+ - lib/remap/extensions/array.rb
259
261
  - lib/remap/extensions/enumerable.rb
260
262
  - lib/remap/extensions/hash.rb
261
263
  - lib/remap/extensions/object.rb
262
264
  - lib/remap/failure.rb
265
+ - lib/remap/failure/error.rb
263
266
  - lib/remap/iteration.rb
264
267
  - lib/remap/iteration/array.rb
265
268
  - lib/remap/iteration/hash.rb