remap 2.2.40 → 2.2.44

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: f9c58b85124adc855131d9efee5ce1ff8653cf4828882a5ad1b60ebb0d1ea99b
4
+ data.tar.gz: ace57dfce51c7b820d2db5c2f742e5e44828bc998cc719a817bb040e4d08f139
5
5
  SHA512:
6
- metadata.gz: 1f9b976bca1187592db2ae0370e8ef33064ace68e15353d55c8e1bf16ea3d4c30afdac56e487ce663dc0d12241329a4484e9ea72ec0b8700c93654678e70b465
7
- data.tar.gz: d8b580437f7c42ab3578fdf14b8f902d27c084438668f8dfdc67f9fbc74a73e16b0712bca50450f62212650955b9f5f53a59edda15243acb130a0e77e9ee2417
6
+ metadata.gz: d868e87397958d9c68f2048a7888e306c3764dbac799ff5c34c2d8d00e08463fe5abacc708dcc232605c33f319b1cca0b34252108cfb8f4ca1f7831718956d9e
7
+ data.tar.gz: 3880cde868fbfa68702db58d6db914825f34614b140c3284c264f6b4303338dfda22f1a0f151419d610225c4cd029a776c149b4901607915988acf3c6598386a
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,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Remap
4
+ module Extensions
5
+ module Array
6
+ refine ::Array do
7
+ using Object
8
+
9
+ # @return [Array<Hash>]
10
+ def to_hash
11
+ map(&:to_hash)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -5,7 +5,7 @@ module Remap
5
5
  module Hash
6
6
  refine ::Hash do
7
7
  def formatted
8
- JSON.neat_generate(self, sort: true, wrap: 40, aligned: true, around_colon: 1)
8
+ JSON.neat_generate(compact_blank, sort: true, wrap: 40, aligned: true, around_colon: 1)
9
9
  end
10
10
  end
11
11
  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
@@ -11,27 +11,6 @@ module Remap
11
11
  delegate_missing_to :notice
12
12
  delegate :inspect, :to_s, to: :notice
13
13
  option :notice, type: Types.Instance(Notice)
14
-
15
- def inspect
16
- "#<%s %s>" % [self.class, to_hash.formatted]
17
- end
18
-
19
- def undefined(state)
20
- state.set(notice: notice).except(:value)
21
- end
22
-
23
- def failure(state)
24
- Failure.new(failures: [notice], notices: state.fetch(:notices))
25
- end
26
-
27
- def traced(backtrace)
28
- e = Traced.new(notice: notice)
29
- e.set_backtrace(backtrace)
30
- e
31
- end
32
- end
33
-
34
- class Traced < Error
35
14
  end
36
15
  end
37
16
  end
data/lib/remap/notice.rb CHANGED
@@ -20,19 +20,5 @@ module Remap
20
20
  def to_hash
21
21
  super.except(:backtrace).compact_blank
22
22
  end
23
-
24
- # Used by State to skip mapping rules
25
- #
26
- # @raise [Notice::Ignore]
27
- def ignore!
28
- raise Ignore.new(notice: self)
29
- end
30
-
31
- # Used by the state to halt mappers
32
- #
33
- # @raise [Notice::Fatal]
34
- def fatal!
35
- raise Fatal.new(notice: self)
36
- end
37
23
  end
38
24
  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
@@ -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.40
4
+ version: 2.2.44
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