remap 2.2.40 → 2.2.41

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: 5237c926e4f9d2101746dedf0c6287cf81e1e757000f45ff80a346852474ca1c
4
- data.tar.gz: 18e5bde7388e9cf14a518153f0d47271bf6d09ad9b09a6dd4da98a2a61169668
3
+ metadata.gz: 5a8ee7751985c548b272cb12bb0b08b655f52de3143075afafd8578f9e604eba
4
+ data.tar.gz: f333d2922e7d5ef7a6bd050eb2fc0261446097fdd17f2745eef1a3bc35eb96ee
5
5
  SHA512:
6
- metadata.gz: 1f9b976bca1187592db2ae0370e8ef33064ace68e15353d55c8e1bf16ea3d4c30afdac56e487ce663dc0d12241329a4484e9ea72ec0b8700c93654678e70b465
7
- data.tar.gz: d8b580437f7c42ab3578fdf14b8f902d27c084438668f8dfdc67f9fbc74a73e16b0712bca50450f62212650955b9f5f53a59edda15243acb130a0e77e9ee2417
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]
@@ -181,13 +183,15 @@ module Remap
181
183
  raise ArgumentError, "#embed does not take a block"
182
184
  end
183
185
 
184
- embeding = rule(backtrace: backtrace).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
@@ -230,6 +234,10 @@ module Remap
230
234
  raise ArgumentError, "#set does not take a block"
231
235
  end
232
236
 
237
+ unless to.is_a?(Static)
238
+ raise ArgumentError, "Argument to #set must be a static value, got #{to.class}"
239
+ end
240
+
233
241
  add rule(to: path, backtrace: backtrace).add { to.call(_1) }
234
242
  end
235
243
 
@@ -346,6 +354,10 @@ module Remap
346
354
  raise ArgumentError, "#wrap requires a block"
347
355
  end
348
356
 
357
+ unless type == :array
358
+ raise ArgumentError, "Argument to #wrap must equal :array, got [#{type}] (#{type.class})"
359
+ end
360
+
349
361
  add rule(backtrace: backtrace, &block).then { Array.wrap(_1) }
350
362
  end
351
363
 
@@ -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
@@ -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,24 +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
24
23
  end
25
24
 
26
- rules.reduce(state.except(:value)) do |s1, rule|
27
- result = rule.call(state) do |failure|
28
- return error[failure]
29
- end
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)
30
31
 
31
- s1.combine(result)
32
- rescue Notice::Fatal => e
33
- raise e.traced(rule.backtrace)
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
34
39
  end
40
+
41
+ raise failure.exception(backtrace)
35
42
  end
36
43
  end
37
44
  end
@@ -30,14 +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)
33
+ def call(state)
34
+ catch { super(state.set(id: _1)).except(:id) }
41
35
  end
42
36
  end
43
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,16 +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
200
+ def callback(state)
201
+ fn.reduce(state) { |s1, f1| f1[s1] }
211
202
  end
212
203
  end
213
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>]
@@ -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,7 +138,7 @@ module Remap
131
138
  in [:value, Array => list1, Array => list2]
132
139
  list1 + list2
133
140
  in [:value, left, right]
134
- fatal!(
141
+ other.fatal!(
135
142
  "Could not merge [%s] (%s) with [%s] (%s)",
136
143
  left.formatted,
137
144
  left.class,
@@ -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,
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.40
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