remap 2.0.3 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/remap/base.rb +229 -75
- data/lib/remap/compiler.rb +127 -37
- data/lib/remap/constructor/argument.rb +20 -6
- data/lib/remap/constructor/keyword.rb +20 -4
- data/lib/remap/constructor/none.rb +3 -4
- data/lib/remap/constructor.rb +12 -5
- data/lib/remap/contract.rb +27 -0
- data/lib/remap/extensions/enumerable.rb +48 -0
- data/lib/remap/extensions/hash.rb +13 -0
- data/lib/remap/extensions/object.rb +37 -0
- data/lib/remap/failure.rb +25 -15
- data/lib/remap/iteration/array.rb +20 -11
- data/lib/remap/iteration/hash.rb +21 -13
- data/lib/remap/iteration/other.rb +7 -7
- data/lib/remap/iteration.rb +8 -2
- data/lib/remap/mapper/and.rb +29 -7
- data/lib/remap/mapper/binary.rb +3 -6
- data/lib/remap/mapper/or.rb +29 -6
- data/lib/remap/mapper/support/operations.rb +40 -0
- data/lib/remap/mapper/xor.rb +29 -7
- data/lib/remap/mapper.rb +1 -48
- data/lib/remap/notice/traced.rb +19 -0
- data/lib/remap/notice/untraced.rb +11 -0
- data/lib/remap/notice.rb +34 -0
- data/lib/remap/operation.rb +26 -13
- data/lib/remap/path/input.rb +37 -0
- data/lib/remap/path/output.rb +26 -0
- data/lib/remap/path.rb +22 -0
- data/lib/remap/path_error.rb +13 -0
- data/lib/remap/proxy.rb +18 -0
- data/lib/remap/rule/each.rb +25 -24
- data/lib/remap/rule/embed.rb +33 -28
- data/lib/remap/rule/map/optional.rb +42 -0
- data/lib/remap/rule/map/required.rb +35 -0
- data/lib/remap/rule/map.rb +176 -55
- data/lib/remap/rule/set.rb +23 -33
- data/lib/remap/rule/support/collection/empty.rb +7 -7
- data/lib/remap/rule/support/collection/filled.rb +21 -8
- data/lib/remap/rule/support/collection.rb +11 -3
- data/lib/remap/rule/support/enum.rb +44 -21
- data/lib/remap/rule/void.rb +17 -18
- data/lib/remap/rule/wrap.rb +25 -17
- data/lib/remap/rule.rb +8 -1
- data/lib/remap/selector/all.rb +29 -7
- data/lib/remap/selector/index.rb +24 -16
- data/lib/remap/selector/key.rb +31 -16
- data/lib/remap/selector.rb +17 -0
- data/lib/remap/state/extension.rb +182 -208
- data/lib/remap/state/schema.rb +1 -1
- data/lib/remap/state.rb +30 -4
- data/lib/remap/static/fixed.rb +14 -3
- data/lib/remap/static/option.rb +21 -6
- data/lib/remap/static.rb +13 -0
- data/lib/remap/struct.rb +1 -0
- data/lib/remap/types.rb +13 -28
- data/lib/remap.rb +15 -19
- metadata +91 -89
- data/lib/remap/result.rb +0 -11
- data/lib/remap/rule/support/path.rb +0 -45
- data/lib/remap/state/types.rb +0 -11
- data/lib/remap/success.rb +0 -29
- data/lib/remap/version.rb +0 -5
@@ -2,16 +2,27 @@
|
|
2
2
|
|
3
3
|
module Remap
|
4
4
|
class Constructor
|
5
|
-
|
6
|
-
using State::Extension
|
5
|
+
using State::Extension
|
7
6
|
|
7
|
+
# Allows a class (target) to be called with a regular argument
|
8
|
+
class Argument < Concrete
|
9
|
+
# @return [:argument]
|
8
10
|
attribute :strategy, Value(:argument), default: :argument
|
9
11
|
|
10
|
-
# Uses the {#method} method to initialize {#target} with
|
11
|
-
# Target is only called if
|
12
|
+
# Uses the {#method} method to initialize {#target} with state
|
13
|
+
# Target is only called if state is defined
|
14
|
+
#
|
15
|
+
# Used by {Remap::Base} to define constructors for mapped data
|
12
16
|
#
|
13
17
|
# Fails if {#target} does not respond to {#method}
|
14
|
-
# Fails if {#target} cannot be called with
|
18
|
+
# Fails if {#target} cannot be called with state
|
19
|
+
#
|
20
|
+
# @example Initialize a target with a state
|
21
|
+
# target = ::Struct.new(:foo)
|
22
|
+
# constructor = Remap::Constructor.call(strategy: :argument, target: target, method: :new)
|
23
|
+
# state = Remap::State.call(:bar)
|
24
|
+
# new_state = constructor.call(state)
|
25
|
+
# new_state.fetch(:value).foo # => :bar
|
15
26
|
#
|
16
27
|
# @param state [State]
|
17
28
|
#
|
@@ -20,7 +31,10 @@ module Remap
|
|
20
31
|
super.fmap do |input|
|
21
32
|
target.public_send(id, input)
|
22
33
|
rescue ArgumentError => e
|
23
|
-
raise e.exception("
|
34
|
+
raise e.exception("Failed to create [%p] with input [%s] (%s)" % [
|
35
|
+
target, input,
|
36
|
+
input.class
|
37
|
+
])
|
24
38
|
end
|
25
39
|
end
|
26
40
|
end
|
@@ -2,15 +2,26 @@
|
|
2
2
|
|
3
3
|
module Remap
|
4
4
|
class Constructor
|
5
|
-
|
6
|
-
using State::Extension
|
5
|
+
using State::Extension
|
7
6
|
|
7
|
+
# Allows a class (target) to be called with keyword arguments
|
8
|
+
class Keyword < Concrete
|
9
|
+
# @return [:keyword]
|
8
10
|
attribute :strategy, Value(:keyword)
|
9
11
|
|
10
12
|
# Calls {#target} as with keyword arguments
|
11
13
|
#
|
12
14
|
# Fails if {#target} does not respond to {#method}
|
13
|
-
# Fails if {#target} cannot be called with
|
15
|
+
# Fails if {#target} cannot be called with state
|
16
|
+
#
|
17
|
+
# Used by {Remap::Base} to define constructors for mapped data
|
18
|
+
#
|
19
|
+
# @example Initialize a target with a state
|
20
|
+
# target = OpenStruct
|
21
|
+
# constructor = Remap::Constructor.call(strategy: :keyword, target: target, method: :new)
|
22
|
+
# state = Remap::State.call({ foo: :bar })
|
23
|
+
# new_state = constructor.call(state)
|
24
|
+
# new_state.fetch(:value).foo # => :bar
|
14
25
|
#
|
15
26
|
# @param state [State]
|
16
27
|
#
|
@@ -23,7 +34,12 @@ module Remap
|
|
23
34
|
|
24
35
|
target.public_send(id, **input)
|
25
36
|
rescue ArgumentError => e
|
26
|
-
raise e.exception("
|
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
|
+
])
|
27
43
|
end
|
28
44
|
end
|
29
45
|
end
|
@@ -2,15 +2,14 @@
|
|
2
2
|
|
3
3
|
module Remap
|
4
4
|
class Constructor
|
5
|
+
# Default type used by {Remap::Base}
|
5
6
|
class None < Concrete
|
6
7
|
attribute :target, Types::Nothing
|
7
8
|
attribute :strategy, Types::Any
|
8
9
|
attribute :method, Types::Any
|
9
10
|
|
10
|
-
#
|
11
|
-
#
|
12
|
-
# Fails if {#target} does not respond to {#method}
|
13
|
-
# Fails if {#target} cannot be called with {state}
|
11
|
+
# Used by {Remap::Base} as a default constructor
|
12
|
+
# Using it does nothing but return its input state
|
14
13
|
#
|
15
14
|
# @param state [State]
|
16
15
|
#
|
data/lib/remap/constructor.rb
CHANGED
@@ -2,8 +2,11 @@
|
|
2
2
|
|
3
3
|
module Remap
|
4
4
|
class Constructor < Dry::Interface
|
5
|
+
# @return [Any]
|
6
|
+
attribute :target, Types::Any, not_eql: Nothing
|
7
|
+
|
8
|
+
# @return [Symbol]
|
5
9
|
attribute :method, Symbol, default: :new
|
6
|
-
attribute :target, Types::Any.constrained(not_eql: Nothing)
|
7
10
|
|
8
11
|
# Ensures {#target} responds to {#method}
|
9
12
|
# Returns an error state unless above is true
|
@@ -19,12 +22,16 @@ module Remap
|
|
19
22
|
end
|
20
23
|
end
|
21
24
|
|
22
|
-
|
23
|
-
attributes.fetch(:method)
|
24
|
-
end
|
25
|
-
|
25
|
+
# @return [Proc]
|
26
26
|
def to_proc
|
27
27
|
method(:call).to_proc
|
28
28
|
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# @return [Symbol]
|
33
|
+
def id
|
34
|
+
attributes.fetch(:method)
|
35
|
+
end
|
29
36
|
end
|
30
37
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Contract < Dry::Validation::Contract
|
5
|
+
# Constructs a contract used to validate mapper input
|
6
|
+
#
|
7
|
+
# @param rules [Array<Proc>]
|
8
|
+
# @param options [Hash]
|
9
|
+
# @param contract [Proc]
|
10
|
+
# @param attributes [Hash]
|
11
|
+
#
|
12
|
+
# @return [Contract]
|
13
|
+
def self.call(rules:, options:, contract:, attributes:)
|
14
|
+
Class.new(self) do
|
15
|
+
rules.each do |rule|
|
16
|
+
class_eval(&rule)
|
17
|
+
end
|
18
|
+
|
19
|
+
options.each do |option|
|
20
|
+
class_eval(&option)
|
21
|
+
end
|
22
|
+
|
23
|
+
schema(contract)
|
24
|
+
end.new(**attributes)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
module Extensions
|
5
|
+
using Object
|
6
|
+
|
7
|
+
module Enumerable
|
8
|
+
refine ::Enumerable do
|
9
|
+
# Creates a hash using {self} as the {path} and {value} as the hash value
|
10
|
+
#
|
11
|
+
# @param value [Any] Hash value
|
12
|
+
#
|
13
|
+
# @example A hash from path
|
14
|
+
# [:a, :b].hide('value') # => { a: { b: 'value' } }
|
15
|
+
#
|
16
|
+
# @return [Hash]
|
17
|
+
def hide(value)
|
18
|
+
reverse.reduce(value) do |element, key|
|
19
|
+
{ key => element }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Fetches value at {path}
|
24
|
+
#
|
25
|
+
# @example Fetch value at path
|
26
|
+
# [[:a, :b], [:c, :d]].get(0, 1) # => :b
|
27
|
+
#
|
28
|
+
# @return [Any]
|
29
|
+
#
|
30
|
+
# @raise When path cannot be found
|
31
|
+
def get(*path, &error)
|
32
|
+
_, result = path.reduce([
|
33
|
+
EMPTY_ARRAY,
|
34
|
+
self
|
35
|
+
]) do |(current_path, element), key|
|
36
|
+
value = element.fetch(key) do
|
37
|
+
raise PathError, current_path + [key]
|
38
|
+
end
|
39
|
+
|
40
|
+
[current_path + [key], value]
|
41
|
+
end
|
42
|
+
|
43
|
+
result
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
module Extensions
|
5
|
+
module Object
|
6
|
+
refine ::Object do
|
7
|
+
# Fallback validation method
|
8
|
+
#
|
9
|
+
# @yield if block is provided
|
10
|
+
#
|
11
|
+
# @raise unless block is provided
|
12
|
+
def _(&block)
|
13
|
+
unless block
|
14
|
+
return _ { raise _1 }
|
15
|
+
end
|
16
|
+
|
17
|
+
block["Expected a state, got [#{self}] (#{self.class})"]
|
18
|
+
end
|
19
|
+
|
20
|
+
# Fallback method used when #get is called on an object that does not respond to #get
|
21
|
+
#
|
22
|
+
# Block is invoked, if provided
|
23
|
+
# Otherwise a symbol is thrown
|
24
|
+
#
|
25
|
+
# @param path [Array<Key>]
|
26
|
+
def get(*path, &block)
|
27
|
+
raise PathError, []
|
28
|
+
end
|
29
|
+
alias_method :fetch, :get
|
30
|
+
|
31
|
+
def formated
|
32
|
+
self
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/remap/failure.rb
CHANGED
@@ -1,27 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Remap
|
4
|
-
|
5
|
-
attribute :reasons, Types::Hash
|
4
|
+
using Extensions::Hash
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
class Failure < Dry::Concrete
|
7
|
+
attribute :failures, [Notice], min_size: 1
|
8
|
+
attribute? :notices, [Notice], default: EMPTY_ARRAY
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
# Merges two failures
|
11
|
+
#
|
12
|
+
# @param other [Failure]
|
13
|
+
#
|
14
|
+
# @return [Failure]
|
15
|
+
def merge(other)
|
16
|
+
unless other.is_a?(self.class)
|
17
|
+
raise ArgumentError, "can't merge #{self.class} with #{other.class}"
|
18
|
+
end
|
14
19
|
|
15
|
-
|
16
|
-
|
17
|
-
|
20
|
+
failure = attributes.deep_merge(other.attributes) do |_, value1, value2|
|
21
|
+
case [value1, value2]
|
22
|
+
in [Array, Array]
|
23
|
+
value1 + value2
|
24
|
+
else
|
25
|
+
raise ArgumentError, "can't merge #{self.class} with #{other.class}"
|
26
|
+
end
|
27
|
+
end
|
18
28
|
|
19
|
-
|
20
|
-
false
|
29
|
+
new(failure)
|
21
30
|
end
|
22
31
|
|
23
|
-
|
24
|
-
|
32
|
+
# @return [String]
|
33
|
+
def exception
|
34
|
+
Error.new(attributes.formated)
|
25
35
|
end
|
26
36
|
end
|
27
37
|
end
|
@@ -2,28 +2,37 @@
|
|
2
2
|
|
3
3
|
module Remap
|
4
4
|
class Iteration
|
5
|
+
using State::Extension
|
6
|
+
|
7
|
+
# Implements an array iterator which defines index in state
|
5
8
|
class Array < Concrete
|
6
|
-
|
9
|
+
# @return [Array<T>]
|
10
|
+
attribute :value, Types::Array, alias: :array
|
7
11
|
|
8
|
-
|
12
|
+
# @return [State<Array<T>>]
|
9
13
|
attribute :state, Types::State
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
input_state.merged(new_array_state)
|
17
|
-
end
|
18
|
-
end._
|
15
|
+
# @see Iteration#map
|
16
|
+
def call(&block)
|
17
|
+
array.each_with_index.reduce(init) do |state, (value, index)|
|
18
|
+
reduce(state, value, index, &block)
|
19
|
+
end
|
19
20
|
end
|
20
|
-
alias call map
|
21
21
|
|
22
22
|
private
|
23
23
|
|
24
24
|
def init
|
25
25
|
state.set(EMPTY_ARRAY)
|
26
26
|
end
|
27
|
+
|
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)
|
35
|
+
end
|
27
36
|
end
|
28
37
|
end
|
29
38
|
end
|
data/lib/remap/iteration/hash.rb
CHANGED
@@ -2,26 +2,34 @@
|
|
2
2
|
|
3
3
|
module Remap
|
4
4
|
class Iteration
|
5
|
+
using State::Extension
|
6
|
+
|
7
|
+
# Implements a hash iterator which defines key in state
|
5
8
|
class Hash < Concrete
|
6
|
-
|
9
|
+
# @return [Hash]
|
10
|
+
attribute :value, Types::Hash, alias: :hash
|
11
|
+
|
12
|
+
# @return [State<Hash>]
|
7
13
|
attribute :state, Types::State
|
8
14
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
block[value, key: key]._.then do |new_state|
|
15
|
-
new_state.fmap { { key => _1 } }
|
16
|
-
end.then do |new_hash_state|
|
17
|
-
input_state.merged(new_hash_state)
|
18
|
-
end
|
19
|
-
end._
|
15
|
+
# @see Iteration#map
|
16
|
+
def call(&block)
|
17
|
+
hash.reduce(init) do |state, (key, value)|
|
18
|
+
reduce(state, key, value, &block)
|
19
|
+
end
|
20
20
|
end
|
21
|
-
alias call map
|
22
21
|
|
23
22
|
private
|
24
23
|
|
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)
|
31
|
+
end
|
32
|
+
|
25
33
|
def init
|
26
34
|
state.set(EMPTY_HASH)
|
27
35
|
end
|
@@ -2,17 +2,17 @@
|
|
2
2
|
|
3
3
|
module Remap
|
4
4
|
class Iteration
|
5
|
+
using State::Extension
|
6
|
+
|
7
|
+
# Default iterator which doesn't do anything
|
5
8
|
class Other < Concrete
|
9
|
+
attribute :value, Types::Any, alias: :other
|
6
10
|
attribute :state, Types::State
|
7
|
-
attribute :value, Types::Any
|
8
|
-
|
9
|
-
using State::Extension
|
10
11
|
|
11
|
-
# @see
|
12
|
-
def
|
13
|
-
|
12
|
+
# @see Iteration#map
|
13
|
+
def call(&block)
|
14
|
+
state.fatal!("Expected an enumerable, got %s (%s)", value, value.class)
|
14
15
|
end
|
15
|
-
alias call map
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
data/lib/remap/iteration.rb
CHANGED
@@ -2,10 +2,13 @@
|
|
2
2
|
|
3
3
|
module Remap
|
4
4
|
class Iteration < Dry::Interface
|
5
|
-
|
5
|
+
# @return [State<T>]
|
6
6
|
attribute :state, Types::State
|
7
7
|
|
8
|
-
#
|
8
|
+
# @return [T]
|
9
|
+
attribute :value, Types::Any
|
10
|
+
|
11
|
+
# Maps every element in {#value}
|
9
12
|
#
|
10
13
|
# @abstract
|
11
14
|
#
|
@@ -14,6 +17,9 @@ module Remap
|
|
14
17
|
# @yieldreturn [Array<V>, Hash<V, K>]
|
15
18
|
#
|
16
19
|
# @return [Array<V>, Hash<V, K>]
|
20
|
+
def call(state)
|
21
|
+
raise NotImplementedError, "#{self.class}#call not implemented"
|
22
|
+
end
|
17
23
|
|
18
24
|
order :Hash, :Array, :Other
|
19
25
|
end
|
data/lib/remap/mapper/and.rb
CHANGED
@@ -2,14 +2,36 @@
|
|
2
2
|
|
3
3
|
module Remap
|
4
4
|
class Mapper
|
5
|
-
|
6
|
-
using State::Extension
|
5
|
+
using State::Extension
|
7
6
|
|
7
|
+
# Represents two mappers that are combined with the & operator
|
8
|
+
#
|
9
|
+
# @example Combine two mappers
|
10
|
+
# class Mapper1 < Remap::Base
|
11
|
+
# contract do
|
12
|
+
# required(:a1)
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# class Mapper2 < Remap::Base
|
17
|
+
# contract do
|
18
|
+
# required(:a2)
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# state = Remap::State.call({ a2: 2, a1: 1 })
|
23
|
+
# output = (Mapper1 & Mapper2).call!(state)
|
24
|
+
# output.fetch(:value) # => { a2: 2, a1: 1 }
|
25
|
+
class And < Binary
|
26
|
+
# Succeeds if both left and right succeed
|
27
|
+
# Returns the combined result of left and right
|
28
|
+
#
|
29
|
+
# @param state [State]
|
30
|
+
#
|
31
|
+
# @yield [Failure] if mapper fails
|
32
|
+
#
|
33
|
+
# @return [Result]
|
8
34
|
def call!(state, &error)
|
9
|
-
unless error
|
10
|
-
return call!(state, &exception)
|
11
|
-
end
|
12
|
-
|
13
35
|
state1 = left.call!(state) do |failure1|
|
14
36
|
right.call!(state) do |failure2|
|
15
37
|
return error[failure1.merge(failure2)]
|
@@ -22,7 +44,7 @@ module Remap
|
|
22
44
|
return error[failure]
|
23
45
|
end
|
24
46
|
|
25
|
-
state1.
|
47
|
+
state1.combine(state2)
|
26
48
|
end
|
27
49
|
end
|
28
50
|
end
|
data/lib/remap/mapper/binary.rb
CHANGED
@@ -2,15 +2,12 @@
|
|
2
2
|
|
3
3
|
module Remap
|
4
4
|
class Mapper
|
5
|
+
# @abstract
|
5
6
|
class Binary < self
|
7
|
+
include Operation
|
8
|
+
|
6
9
|
attribute :left, Types::Mapper
|
7
10
|
attribute :right, Types::Mapper
|
8
|
-
|
9
|
-
def exception
|
10
|
-
->(error) { raise error }
|
11
|
-
end
|
12
|
-
|
13
|
-
include Operation
|
14
11
|
end
|
15
12
|
end
|
16
13
|
end
|
data/lib/remap/mapper/or.rb
CHANGED
@@ -2,14 +2,37 @@
|
|
2
2
|
|
3
3
|
module Remap
|
4
4
|
class Mapper
|
5
|
-
|
6
|
-
using State::Extension
|
5
|
+
using State::Extension
|
7
6
|
|
7
|
+
# Represents two mappers that are combined with the | operator
|
8
|
+
#
|
9
|
+
# @example Combine two mappers
|
10
|
+
# class Mapper1 < Remap::Base
|
11
|
+
# contract do
|
12
|
+
# required(:a1)
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# class Mapper2 < Remap::Base
|
17
|
+
# contract do
|
18
|
+
# required(:a2)
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# state = Remap::State.call({ a2: 2 })
|
23
|
+
# result = (Mapper1 | Mapper2).call!(state)
|
24
|
+
# result.fetch(:value) # => { a2: 2 }
|
25
|
+
class Or < Binary
|
26
|
+
# Succeeds if left or right succeeds
|
27
|
+
# Returns which ever succeeds first
|
28
|
+
#
|
29
|
+
# @param state [State]
|
30
|
+
#
|
31
|
+
# @yieldparam [Failure] if mapper fails
|
32
|
+
# @yieldreturn [Failure]
|
33
|
+
#
|
34
|
+
# @return [Result]
|
8
35
|
def call!(state, &error)
|
9
|
-
unless error
|
10
|
-
return call!(state, &exception)
|
11
|
-
end
|
12
|
-
|
13
36
|
left.call!(state) do |failure1|
|
14
37
|
return right.call!(state) do |failure2|
|
15
38
|
return error[failure1.merge(failure2)]
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remap
|
4
|
+
class Mapper
|
5
|
+
module Operations
|
6
|
+
# Tries self and other and returns the first successful result
|
7
|
+
#
|
8
|
+
# @param other [Mapper]
|
9
|
+
#
|
10
|
+
# @return [Mapper::Or]
|
11
|
+
def |(other)
|
12
|
+
Or.new(left: self, right: other)
|
13
|
+
rescue Dry::Struct::Error => e
|
14
|
+
raise ArgumentError, e.message
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns a successful result when self & other are successful
|
18
|
+
#
|
19
|
+
# @param other [Mapper]
|
20
|
+
#
|
21
|
+
# @return [Mapper::And]
|
22
|
+
def &(other)
|
23
|
+
And.new(left: self, right: other)
|
24
|
+
rescue Dry::Struct::Error => e
|
25
|
+
raise ArgumentError, e.message
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns a successful result when only one of self & other are successful
|
29
|
+
#
|
30
|
+
# @param other [Mapper]
|
31
|
+
#
|
32
|
+
# @return [Mapper:Xor]
|
33
|
+
def ^(other)
|
34
|
+
Xor.new(left: self, right: other)
|
35
|
+
rescue Dry::Struct::Error => e
|
36
|
+
raise ArgumentError, e.message
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/remap/mapper/xor.rb
CHANGED
@@ -2,14 +2,36 @@
|
|
2
2
|
|
3
3
|
module Remap
|
4
4
|
class Mapper
|
5
|
-
|
6
|
-
using State::Extension
|
5
|
+
using State::Extension
|
7
6
|
|
7
|
+
# Represents two mappers that are combined with the ^ operator
|
8
|
+
#
|
9
|
+
# @example Combine two mappers
|
10
|
+
# class Mapper1 < Remap::Base
|
11
|
+
# contract do
|
12
|
+
# required(:a1)
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# class Mapper2 < Remap::Base
|
17
|
+
# contract do
|
18
|
+
# required(:a2)
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# state = Remap::State.call({ a2: 2 })
|
23
|
+
# output = (Mapper1 ^ Mapper2).call!(state)
|
24
|
+
# output.fetch(:value) # => { a2: 2 }
|
25
|
+
class Xor < Binary
|
26
|
+
# Succeeds if left or right succeeds, but not both
|
27
|
+
#
|
28
|
+
# @param state [State]
|
29
|
+
#
|
30
|
+
# @yieldparam [Failure] if mapper fails
|
31
|
+
# @yieldreturn [Failure]
|
32
|
+
#
|
33
|
+
# @return [Result]
|
8
34
|
def call!(state, &error)
|
9
|
-
unless error
|
10
|
-
return call!(state, &exception)
|
11
|
-
end
|
12
|
-
|
13
35
|
state1 = left.call!(state) do |failure1|
|
14
36
|
return right.call!(state) do |failure2|
|
15
37
|
return error[failure1.merge(failure2)]
|
@@ -20,7 +42,7 @@ module Remap
|
|
20
42
|
return state1
|
21
43
|
end
|
22
44
|
|
23
|
-
|
45
|
+
state1.combine(state2).failure("Both left and right passed xor operation").then(&error)
|
24
46
|
end
|
25
47
|
end
|
26
48
|
end
|