remap 2.2.39 → 2.2.43

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: 63e86ddbef0fd288ba558768cd947639c566f4db3789b19163c0ead17782635f
4
- data.tar.gz: 2e3359e5d2ca7f78e1c2540c9289d489d132be91456493eaf2e4c6f8542b9b4a
3
+ metadata.gz: 88fc0978315944ad791a089536840916db814b0fc7dad18cda8c19d1c9901d9c
4
+ data.tar.gz: 2b59e22a02ac486b04758c772082731b2e91cb0ba698e40e3502794236515ff5
5
5
  SHA512:
6
- metadata.gz: 8f2f4134ae97e0e00e9e346e271a2d07e4ded2e4e4e1c8b2a80efdbfd0a41d39c615bee75e61c582d9fa5cda514f74fb164a197f136ea7b1bf170b618b2b383d
7
- data.tar.gz: e88fe7b9c0c8aef4a03f630315b9813ec1bbe5cda5851cadda400b9ea110903cb62732ccf6b89e3b0d35aebe982280c34d49558259e1b40de2d385d599e0565c
6
+ metadata.gz: 41a5d60aa1fa66fb254162d1313611e441e239de54e59c4d7ebaa8a5334a6930317cb32703bd837e85212826b23286afa57e9d8662d8e9c78ab4fc3d569bcddd
7
+ data.tar.gz: 3c8b89c3a3cb5a4fae156a0757b2130afc95ced6911b2707e239e7a3dd791ed5036b46711fb4262b94006b027bc0e98f0882e5a2e4131a12c7730542699e7068
data/lib/remap/base.rb CHANGED
@@ -106,11 +106,13 @@ module Remap
106
106
  class Base < Mapper
107
107
  include ActiveSupport::Configurable
108
108
  include Dry::Core::Constants
109
+ include Catchable
110
+ extend Mapper::API
109
111
  using State::Extension
110
- extend Operation
111
112
 
112
113
  with_options instance_accessor: true do |scope|
113
114
  scope.config_accessor(:contract) { Dry::Schema.define {} }
115
+ scope.config_accessor(:config_options) { Config.new }
114
116
  scope.config_accessor(:constructor) { IDENTITY }
115
117
  scope.config_accessor(:options) { EMPTY_ARRAY }
116
118
  scope.config_accessor(:option) { EMPTY_HASH }
@@ -235,26 +237,50 @@ module Remap
235
237
  # Mapper.call({name: "John"}).first_name # => "John"
236
238
  #
237
239
  # @return [void]
238
- def self.define(target = Nothing, method: :new, strategy: :argument, &context)
240
+ # rubocop:disable Layout/LineLength
241
+ def self.define(target = Nothing, method: :new, strategy: :argument, backtrace: caller, &context)
239
242
  unless block_given?
240
243
  raise ArgumentError, "#{self}.define requires a block"
241
244
  end
242
245
 
243
246
  self.constructor = Constructor.call(method: method, strategy: strategy, target: target)
244
- self.context = Compiler.call(&context)
247
+ self.context = Compiler.call(backtrace: backtrace, &context)
245
248
  end
249
+ # rubocop:enable Layout/LineLength
246
250
 
247
- # Similar to {::call}, but takes a state
248
- #
249
251
  # @param state [State]
250
252
  #
251
- # @yield [Failure] if mapper fails
253
+ # @yield [Failure]
254
+ # when a non-critical error occurs
255
+ # @yieldreturn T
252
256
  #
253
- # @return [Result] if mapper succeeds
257
+ # @return [State, T]
258
+ # when request is a success
259
+ # @raise [Remap::Error]
260
+ # when a fatal error occurs
254
261
  #
255
262
  # @private
256
263
  def self.call!(state, &error)
257
- new(state.options).call(state._.set(mapper: self), &error)
264
+ new(state.options).call(state, &error)
265
+ end
266
+
267
+ # Configuration options for the mapper
268
+ #
269
+ # @yield [Config]
270
+ # @yieldreturn [void]
271
+ #
272
+ # @return [void]
273
+ def self.configuration(&block)
274
+ config = Config.new
275
+ block[config]
276
+ self.config_options = config
277
+ end
278
+
279
+ # @see Mapper::API
280
+ #
281
+ # @private
282
+ def self.validate?
283
+ config_options.validation
258
284
  end
259
285
 
260
286
  # Mappers state according to the mapper rules
@@ -267,8 +293,8 @@ module Remap
267
293
  #
268
294
  # @private
269
295
  def call(state, &error)
270
- unless block_given?
271
- raise ArgumentError, "Base#call(state, &error) requires block"
296
+ state._ do |reason|
297
+ raise ArgumentError, "Invalid state due to #{reason.formatted}"
272
298
  end
273
299
 
274
300
  state.tap do |input|
@@ -279,13 +305,11 @@ module Remap
279
305
  end
280
306
  end
281
307
 
282
- notice = catch :fatal do
283
- return context.call(state) do |failure|
284
- return error[failure]
285
- end.then(&constructor)
308
+ s1 = catch_ignored do |id|
309
+ return context.call(state.set(id: id)).then(&constructor).remove_id
286
310
  end
287
311
 
288
- error[state.failure(notice)]
312
+ Failure.new(failures: s1.notices).then(&error)
289
313
  end
290
314
 
291
315
  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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Config < OpenStruct
5
+ def initialize
6
+ super(validation: false)
7
+ end
8
+ end
9
+ 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
@@ -46,6 +46,12 @@ module Remap
46
46
 
47
47
  state1.combine(state2)
48
48
  end
49
+
50
+ # @return [String]
51
+ def inspect
52
+ "%s & %s" % [left, right]
53
+ end
54
+ alias to_s inspect
49
55
  end
50
56
  end
51
57
  end
@@ -4,10 +4,15 @@ module Remap
4
4
  class Mapper
5
5
  # @abstract
6
6
  class Binary < self
7
- include Operation
7
+ include API
8
8
 
9
9
  attribute :left, Types::Mapper
10
10
  attribute :right, Types::Mapper
11
+
12
+ # @return [Bool]
13
+ def validate?
14
+ left.validate? && right.validate?
15
+ end
11
16
  end
12
17
  end
13
18
  end
@@ -39,6 +39,12 @@ module Remap
39
39
  end
40
40
  end
41
41
  end
42
+
43
+ # @return [String]
44
+ def inspect
45
+ "%s | %s" % [left, right]
46
+ end
47
+ alias to_s inspect
42
48
  end
43
49
  end
44
50
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ class Mapper
5
+ using State::Extension
6
+
7
+ module API
8
+ # @return [Boolean]
9
+ #
10
+ # @abstract
11
+ # @private
12
+ def validate?
13
+ raise NotImplementedError, "`validate?` is not implemented for #{self}"
14
+ end
15
+
16
+ # @param input [Any]
17
+ # @param backtrace [Array<String>]
18
+ # @param options [Hash]
19
+ #
20
+ # @yieldparam [Failure]
21
+ # @yieldreturn [T]
22
+ #
23
+ # @return [Any, T]
24
+ def call(input, backtrace: caller, **options, &error)
25
+ unless block_given?
26
+ return call(input, **options) do |failure|
27
+ raise failure.exception(backtrace)
28
+ end
29
+ end
30
+
31
+ s0 = State.call(input, options: options, mapper: self)._
32
+
33
+ s1 = call!(s0) do |failure|
34
+ return error[failure]
35
+ end
36
+
37
+ case s1
38
+ in { value: value }
39
+ value
40
+ in { notices: [] }
41
+ error[s1.failure("No data could be mapped")]
42
+ in { notices: }
43
+ error[Failure.new(failures: notices)]
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -44,6 +44,12 @@ module Remap
44
44
 
45
45
  state1.combine(state2).failure("Both left and right passed xor operation").then(&error)
46
46
  end
47
+
48
+ # @return [String]
49
+ def inspect
50
+ "%s ^ %s" % [left, right]
51
+ end
52
+ alias to_s inspect
47
53
  end
48
54
  end
49
55
  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,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>]
@@ -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
@@ -87,8 +92,12 @@ module Remap
87
92
  #
88
93
  # @return [self]
89
94
  def _(&block)
95
+ return self unless mapper.validate?
96
+
90
97
  unless block
91
- return _ { raise ArgumentError, "Input: #{self} output: #{_1.formatted}" }
98
+ return _ do |reason|
99
+ raise ArgumentError, "[BUG] State: #{formatted} reason: #{reason.formatted}"
100
+ end
92
101
  end
93
102
 
94
103
  unless (result = Schema.call(self)).success?
@@ -98,11 +107,11 @@ module Remap
98
107
  self
99
108
  end
100
109
 
101
- # Makes the state iterable
110
+ # Iterates over {#value}
102
111
  #
103
112
  # @yieldparam value [Any]
104
- # @yieldoption key [Symbol]
105
- # @yieldoption index [Integer]
113
+ # @yieldparam key [Symbol]
114
+ # @yieldparam index [Integer]
106
115
  #
107
116
  # @yieldreturn [State]
108
117
  #
@@ -131,7 +140,7 @@ module Remap
131
140
  in [:value, Array => list1, Array => list2]
132
141
  list1 + list2
133
142
  in [:value, left, right]
134
- fatal!(
143
+ other.fatal!(
135
144
  "Could not merge [%s] (%s) with [%s] (%s)",
136
145
  left.formatted,
137
146
  left.class,
@@ -140,10 +149,47 @@ module Remap
140
149
  )
141
150
  in [:notices, Array => n1, Array => n2]
142
151
  n1 + n2
152
+ in [:ids, i1, i2] if i1.all? { i2.include?(_1) }
153
+ i2
154
+ in [:ids, i1, i2] if i2.all? { i1.include?(_1) }
155
+ i1
156
+ in [:ids, i1, i2]
157
+ other.fatal!("Could not merge #ids [%s] (%s) with [%s] (%s)", i1, i1.class, i2,
158
+ i2.class)
143
159
  in [Symbol, _, value]
144
160
  value
145
161
  end
146
- end
162
+ end._
163
+ end
164
+
165
+ # @todo Merge with {#remove_fatal_id}
166
+ # @return [State]
167
+ def remove_id
168
+ case self
169
+ in { ids: [], id: }
170
+ except(:id)
171
+ in { ids:, id: }
172
+ merge(ids: ids[1...], id: ids[0])
173
+ in { ids: [] }
174
+ self
175
+ in { ids: }
176
+ raise ArgumentError, "[BUG] #ids for state are set, but not #id: %s" % formatted
177
+ end._
178
+ end
179
+
180
+ # @todo Merge with {#remove_id}
181
+ # @return [State]
182
+ def remove_fatal_id
183
+ case self
184
+ in { fatal_ids: [], fatal_id: }
185
+ except(:fatal_id)
186
+ in { fatal_ids: ids, fatal_id: id }
187
+ merge(fatal_ids: ids[1...], fatal_id: ids[0])
188
+ in { fatal_ids: [] }
189
+ self
190
+ in { fatal_ids: }
191
+ raise ArgumentError, "[BUG] #ids for state are set, but not #id: %s" % formatted
192
+ end._
147
193
  end
148
194
 
149
195
  # Creates a new state with params
@@ -168,6 +214,10 @@ module Remap
168
214
  merge(path: path + [index], element: value, index: index, value: value).set(**rest)
169
215
  in [{path:}, {index:, **rest}]
170
216
  merge(path: path + [index], index: index).set(**rest)
217
+ in [{ids:, id: old_id}, {id: new_id, **rest}]
218
+ merge(ids: [old_id] + ids, id: new_id).set(**rest)
219
+ in [{fatal_ids:, fatal_id: old_id}, {fatal_id: new_id, **rest}]
220
+ merge(fatal_ids: [old_id] + fatal_ids, fatal_id: new_id).set(**rest)
171
221
  else
172
222
  merge(options)
173
223
  end
@@ -185,8 +235,8 @@ module Remap
185
235
  #
186
236
  # @return [State<Y>]
187
237
  def fmap(**options, &block)
188
- bind(**options) do |input, state, &error|
189
- state.set(block[input, state, &error])
238
+ bind(**options) do |input, state:|
239
+ state.set(block[input, state, state: state])
190
240
  end
191
241
  end
192
242
 
@@ -203,28 +253,7 @@ module Remap
203
253
  # end
204
254
 
205
255
  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)
256
+ raise NotImplementedError, "Not implemented"
228
257
  end
229
258
 
230
259
  # Passes {#value} to block, if defined
@@ -242,12 +271,10 @@ module Remap
242
271
  raise ArgumentError, "State#bind requires a block"
243
272
  end
244
273
 
245
- s = set(**options)
274
+ s1 = set(**options)
246
275
 
247
- fetch(:value) { return s }.then do |value|
248
- block[value, s] do |reason, **other|
249
- return s.set(**other).ignore!(reason)
250
- end
276
+ fetch(:value) { return s1 }.then do |value|
277
+ block[value, s1, state: s1]
251
278
  end
252
279
  end
253
280
 
@@ -259,11 +286,11 @@ module Remap
259
286
  #
260
287
  # @return [State<U>]
261
288
  def execute(&block)
262
- bind do |value, &error|
263
- result = context(value, &error).instance_exec(value, &block)
289
+ bind do |value|
290
+ result = context(value).instance_exec(value, &block)
264
291
 
265
292
  if result.equal?(Dry::Core::Constants::Undefined)
266
- return error["Undefined returned, skipping!"]
293
+ ignore!("Undefined returned, skipping!")
267
294
  end
268
295
 
269
296
  set(result)
@@ -290,6 +317,26 @@ module Remap
290
317
  fetch(:path, EMPTY_ARRAY)
291
318
  end
292
319
 
320
+ # @return [Symbol]
321
+ def id
322
+ fetch(:id)
323
+ end
324
+
325
+ # @return [Mapper::API]
326
+ def mapper
327
+ fetch(:mapper)
328
+ end
329
+
330
+ # @return [Array<Symbol>]
331
+ def ids
332
+ fetch(:ids)
333
+ end
334
+
335
+ # @return [Symbol]
336
+ def fatal_id
337
+ fetch(:fatal_id)
338
+ end
339
+
293
340
  # Represents options to a mapper
294
341
  #
295
342
  # @see Rule::Embed
@@ -303,7 +350,7 @@ module Remap
303
350
  #
304
351
  # @return [Hash]
305
352
  def to_hash
306
- super.except(:options, :notices, :value)
353
+ super.except(:options, :notices, :value, :id, :ids, :fatal_id, :fatal_ids)
307
354
  end
308
355
 
309
356
  # @return [Any]
@@ -331,6 +378,48 @@ module Remap
331
378
  fetch(:notices)
332
379
  end
333
380
 
381
+ # Creates a failure from the current state
382
+ #
383
+ # @param reason [String, Hash, Undefined]
384
+ #
385
+ # @return [Failure]
386
+ def failure(reason = Undefined)
387
+ failures = case [path, reason]
388
+ in [_, Notice => notice]
389
+ [notice]
390
+ in [path, Array => reasons]
391
+ reasons.map do |inner_reason|
392
+ Notice.call(path: path, reason: inner_reason, **only(:value))
393
+ end
394
+ in [path, String => reason]
395
+ [Notice.call(path: path, reason: reason, **only(:value))]
396
+ in [path, Hash => errors]
397
+ errors.paths.flat_map do |sufix|
398
+ Array.wrap(errors.dig(*sufix)).map do |inner_reason|
399
+ Notice.call(
400
+ reason: inner_reason,
401
+ path: path + sufix,
402
+ **only(:value)
403
+ )
404
+ end
405
+ end
406
+ end
407
+
408
+ Failure.new(failures: failures, notices: notices)
409
+ end
410
+
411
+ # @raise [ArgumentError]
412
+ # when {#id} is not defined
413
+ #
414
+ # @private
415
+ def return!
416
+ id = fetch(:id) do
417
+ raise ArgumentError, "#id not defined for state [%s]" % [formatted]
418
+ end
419
+
420
+ throw id, remove_id
421
+ end
422
+
334
423
  private
335
424
 
336
425
  # Creates a context containing {options} and {self}
@@ -340,13 +429,13 @@ module Remap
340
429
  # @yieldparam reason [T]
341
430
  #
342
431
  # @return [Struct]
343
- def context(value, context: self, &error)
344
- ::Struct.new(*keys, *options.keys, :state, keyword_init: true) do
432
+ def context(value, context: self)
433
+ ::Struct.new(*except(:id).keys, *options.keys, :state, keyword_init: true) do
345
434
  define_method :method_missing do |name, *|
346
- error["Method [#{name}] not defined"]
435
+ context.fatal!("Method [%s] not defined", name)
347
436
  end
348
437
 
349
- define_method :skip! do |message = "Manual skip!"|
438
+ define_method(:skip!) do |message = "Manual skip!"|
350
439
  context.ignore!(message)
351
440
  end
352
441
  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,
data/lib/remap.rb CHANGED
@@ -19,6 +19,7 @@ module Remap
19
19
  loader = Zeitwerk::Loader.for_gem
20
20
  loader.collapse("#{__dir__}/remap/mapper/support")
21
21
  loader.setup
22
+ loader.inflector.inflect "api" => "API"
22
23
  loader.eager_load
23
24
 
24
25
  include ClassInterface
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.39
4
+ version: 2.2.43
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-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -248,18 +248,22 @@ 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
254
+ - lib/remap/config.rb
253
255
  - lib/remap/constructor.rb
254
256
  - lib/remap/constructor/argument.rb
255
257
  - lib/remap/constructor/keyword.rb
256
258
  - lib/remap/constructor/none.rb
257
259
  - lib/remap/contract.rb
258
260
  - lib/remap/error.rb
261
+ - lib/remap/extensions/array.rb
259
262
  - lib/remap/extensions/enumerable.rb
260
263
  - lib/remap/extensions/hash.rb
261
264
  - lib/remap/extensions/object.rb
262
265
  - lib/remap/failure.rb
266
+ - lib/remap/failure/error.rb
263
267
  - lib/remap/iteration.rb
264
268
  - lib/remap/iteration/array.rb
265
269
  - lib/remap/iteration/hash.rb
@@ -268,6 +272,7 @@ files:
268
272
  - lib/remap/mapper/and.rb
269
273
  - lib/remap/mapper/binary.rb
270
274
  - lib/remap/mapper/or.rb
275
+ - lib/remap/mapper/support/api.rb
271
276
  - lib/remap/mapper/support/operations.rb
272
277
  - lib/remap/mapper/xor.rb
273
278
  - lib/remap/nothing.rb
@@ -275,7 +280,6 @@ files:
275
280
  - lib/remap/notice/error.rb
276
281
  - lib/remap/notice/fatal.rb
277
282
  - lib/remap/notice/ignore.rb
278
- - lib/remap/operation.rb
279
283
  - lib/remap/path.rb
280
284
  - lib/remap/path/input.rb
281
285
  - lib/remap/path/output.rb
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Remap
4
- using State::Extension
5
-
6
- # Class interface for {Remap::Base} and instance interface for {Mapper}
7
- module Operation
8
- # Public interface for mappers
9
- #
10
- # @param input [Any] Data to be mapped
11
- # @param options [Hash] Mapper arguments
12
- #
13
- # @yield [Failure] if mapper fails
14
- #
15
- # @return [Success] if mapper succeeds
16
- def call(input, **options, &error)
17
- unless error
18
- return call(input, **options) do |failure|
19
- raise failure.exception
20
- end
21
- end
22
-
23
- other = State.call(input, options: options, mapper: self).then do |state|
24
- call!(state) do |failure|
25
- return error[failure]
26
- end
27
- end
28
-
29
- case other
30
- in { value: }
31
- value
32
- in { notices: [] }
33
- error[other.failure("No return value")]
34
- in { notices: }
35
- error[Failure.call(failures: notices)]
36
- end
37
- end
38
- end
39
- end