remap 2.0.3 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|