service_actor 3.5.0 → 3.6.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/service_actor/base.rb +2 -5
- data/lib/service_actor/checkable.rb +62 -0
- data/lib/service_actor/checks/base.rb +17 -0
- data/lib/service_actor/checks/default_check.rb +90 -0
- data/lib/service_actor/checks/inclusion_check.rb +75 -0
- data/lib/service_actor/checks/must_check.rb +83 -0
- data/lib/service_actor/checks/nil_check.rb +110 -0
- data/lib/service_actor/checks/type_check.rb +101 -0
- data/lib/service_actor/{raisable.rb → configurable.rb} +1 -1
- data/lib/service_actor/core.rb +4 -10
- data/lib/service_actor/defaultable.rb +7 -0
- data/lib/service_actor/version.rb +1 -1
- metadata +10 -7
- data/lib/service_actor/collectionable.rb +0 -69
- data/lib/service_actor/conditionable.rb +0 -75
- data/lib/service_actor/nil_checkable.rb +0 -88
- data/lib/service_actor/type_checkable.rb +0 -91
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd30f97e96402732d11711e040cd4a65ab96962a60338a5358bcae13c9578b06
|
4
|
+
data.tar.gz: 42694afe490d30e236c3a42bde46547759fd5adf7d8a52556dc7838a45be937e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d5d01ce6b421df89f2074753c84ee84c9d166219316ee92c69a2d35e186f28faad4482e020c1144cac05caf1ca0ee2e56ee567dd1793f8bbf33c5b87dea31df
|
7
|
+
data.tar.gz: '038d365e4f8cff50b7fbc71ba757da5095d62487cd1e043338f386e9c04dd49fd1fe99081d84f7c4ac94f30b3bc35774ea6460825ae98c6380aa17760f8f133d'
|
data/lib/service_actor/base.rb
CHANGED
@@ -6,15 +6,12 @@ module ServiceActor::Base
|
|
6
6
|
def self.included(base)
|
7
7
|
# Essential mechanics
|
8
8
|
base.include(ServiceActor::Core)
|
9
|
-
base.include(ServiceActor::
|
9
|
+
base.include(ServiceActor::Configurable)
|
10
10
|
base.include(ServiceActor::Attributable)
|
11
11
|
base.include(ServiceActor::Playable)
|
12
12
|
|
13
13
|
# Extra concerns
|
14
|
-
base.include(ServiceActor::
|
15
|
-
base.include(ServiceActor::NilCheckable)
|
16
|
-
base.include(ServiceActor::Conditionable)
|
17
|
-
base.include(ServiceActor::Collectionable)
|
14
|
+
base.include(ServiceActor::Checkable)
|
18
15
|
base.include(ServiceActor::Defaultable)
|
19
16
|
base.include(ServiceActor::Failable)
|
20
17
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ServiceActor::Checkable
|
4
|
+
def self.included(base)
|
5
|
+
base.prepend(PrependedMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module PrependedMethods
|
9
|
+
def _call
|
10
|
+
self.service_actor_argument_errors = []
|
11
|
+
|
12
|
+
service_actor_checks_for(:input)
|
13
|
+
super
|
14
|
+
service_actor_checks_for(:output)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_accessor :service_actor_argument_errors
|
20
|
+
|
21
|
+
# rubocop:disable Metrics/MethodLength
|
22
|
+
def service_actor_checks_for(origin)
|
23
|
+
self.class.public_send("#{origin}s").each do |input_key, input_options|
|
24
|
+
input_options.each do |check_name, check_conditions|
|
25
|
+
check_classes.each do |check_class|
|
26
|
+
argument_errors = check_class.check(
|
27
|
+
check_name: check_name,
|
28
|
+
origin: origin,
|
29
|
+
input_key: input_key,
|
30
|
+
actor: self.class,
|
31
|
+
conditions: check_conditions,
|
32
|
+
result: result,
|
33
|
+
input_options: input_options,
|
34
|
+
)
|
35
|
+
|
36
|
+
service_actor_argument_errors.push(*argument_errors)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
raise_actor_argument_errors
|
41
|
+
end
|
42
|
+
end
|
43
|
+
# rubocop:enable Metrics/MethodLength
|
44
|
+
|
45
|
+
def raise_actor_argument_errors
|
46
|
+
return if service_actor_argument_errors.empty?
|
47
|
+
|
48
|
+
raise self.class.argument_error_class,
|
49
|
+
service_actor_argument_errors.first
|
50
|
+
end
|
51
|
+
|
52
|
+
def check_classes
|
53
|
+
[
|
54
|
+
ServiceActor::Checks::TypeCheck,
|
55
|
+
ServiceActor::Checks::MustCheck,
|
56
|
+
ServiceActor::Checks::InclusionCheck,
|
57
|
+
ServiceActor::Checks::NilCheck,
|
58
|
+
ServiceActor::Checks::DefaultCheck
|
59
|
+
]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ServiceActor::Checks::Base
|
4
|
+
def initialize
|
5
|
+
@argument_errors = []
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :argument_errors
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
def add_argument_error(message, **arguments)
|
13
|
+
message = message.call(**arguments) if message.is_a?(Proc)
|
14
|
+
|
15
|
+
argument_errors.push(message)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Adds the `default:` option to inputs. Accepts regular values and lambdas.
|
4
|
+
# If no default is set and the value has not been given, raises an error.
|
5
|
+
#
|
6
|
+
# Example:
|
7
|
+
#
|
8
|
+
# class MultiplyThing < Actor
|
9
|
+
# input :counter, default: 1
|
10
|
+
# input :multiplier, default: -> { rand(1..10) }
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# class MultiplyThing < Actor
|
14
|
+
# input :counter,
|
15
|
+
# default: {
|
16
|
+
# is: 1,
|
17
|
+
# message: "Counter is required"
|
18
|
+
# }
|
19
|
+
#
|
20
|
+
# input :multiplier,
|
21
|
+
# default: {
|
22
|
+
# is: -> { rand(1..10) },
|
23
|
+
# message: (lambda do |input_key:, actor:|
|
24
|
+
# "Input \"#{input_key}\" is required"
|
25
|
+
# end)
|
26
|
+
# }
|
27
|
+
# end
|
28
|
+
class ServiceActor::Checks::DefaultCheck < ServiceActor::Checks::Base
|
29
|
+
def self.check(result:, input_key:, input_options:, actor:, **)
|
30
|
+
new(
|
31
|
+
result: result,
|
32
|
+
input_key: input_key,
|
33
|
+
input_options: input_options,
|
34
|
+
actor: actor,
|
35
|
+
).check
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(result:, input_key:, input_options:, actor:)
|
39
|
+
super()
|
40
|
+
|
41
|
+
@result = result
|
42
|
+
@input_key = input_key
|
43
|
+
@input_options = input_options
|
44
|
+
@actor = actor
|
45
|
+
end
|
46
|
+
|
47
|
+
def check # rubocop:disable Metrics/MethodLength
|
48
|
+
return if @result.key?(@input_key)
|
49
|
+
|
50
|
+
unless @input_options.key?(:default)
|
51
|
+
return add_argument_error(
|
52
|
+
"The \"#{@input_key}\" input on \"#{@actor}\" is missing",
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
default = @input_options[:default]
|
57
|
+
|
58
|
+
if default.is_a?(Hash)
|
59
|
+
default_for_advanced_mode_with(default)
|
60
|
+
else
|
61
|
+
default_for_normal_mode_with(default)
|
62
|
+
end
|
63
|
+
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def default_for_normal_mode_with(default)
|
70
|
+
default = default.call if default.is_a?(Proc)
|
71
|
+
@result[@input_key] = default
|
72
|
+
end
|
73
|
+
|
74
|
+
def default_for_advanced_mode_with(content)
|
75
|
+
default, message = content.values_at(:is, :message)
|
76
|
+
|
77
|
+
unless default
|
78
|
+
return add_argument_error(
|
79
|
+
message,
|
80
|
+
input_key: @input_key,
|
81
|
+
actor: self.class,
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
default = default.call if default.is_a?(Proc)
|
86
|
+
@result[@input_key] = default
|
87
|
+
|
88
|
+
message.call(@input_key, self.class)
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Add checks to your inputs, by specifying what values are authorized under the
|
4
|
+
# "in" key.
|
5
|
+
#
|
6
|
+
# Example:
|
7
|
+
#
|
8
|
+
# class Pay < Actor
|
9
|
+
# input :provider, inclusion: ["MANGOPAY", "PayPal", "Stripe"]
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# class Pay < Actor
|
13
|
+
# input :provider,
|
14
|
+
# inclusion: {
|
15
|
+
# in: ["MANGOPAY", "PayPal", "Stripe"],
|
16
|
+
# message: (lambda do |input_key:, actor:, inclusion_in:, value:|
|
17
|
+
# "Payment system \"#{value}\" is not supported"
|
18
|
+
# end)
|
19
|
+
# }
|
20
|
+
# end
|
21
|
+
class ServiceActor::Checks::InclusionCheck < ServiceActor::Checks::Base
|
22
|
+
DEFAULT_MESSAGE = lambda do |input_key:, actor:, inclusion_in:, value:|
|
23
|
+
"The \"#{input_key}\" input must be included " \
|
24
|
+
"in #{inclusion_in.inspect} on \"#{actor}\" " \
|
25
|
+
"instead of #{value.inspect}"
|
26
|
+
end
|
27
|
+
|
28
|
+
private_constant :DEFAULT_MESSAGE
|
29
|
+
|
30
|
+
def self.check(check_name:, input_key:, actor:, conditions:, result:, **) # rubocop:disable Metrics/ParameterLists
|
31
|
+
# DEPRECATED: `in` is deprecated in favor of `inclusion`.
|
32
|
+
return unless %i[inclusion in].include?(check_name)
|
33
|
+
|
34
|
+
new(
|
35
|
+
input_key: input_key,
|
36
|
+
actor: actor,
|
37
|
+
inclusion: conditions,
|
38
|
+
value: result[input_key],
|
39
|
+
).check
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(input_key:, actor:, inclusion:, value:)
|
43
|
+
super()
|
44
|
+
|
45
|
+
@input_key = input_key
|
46
|
+
@actor = actor
|
47
|
+
@inclusion = inclusion
|
48
|
+
@value = value
|
49
|
+
end
|
50
|
+
|
51
|
+
def check
|
52
|
+
inclusion_in, message = define_inclusion_and_message
|
53
|
+
|
54
|
+
return if inclusion_in.nil?
|
55
|
+
return if inclusion_in.include?(@value)
|
56
|
+
|
57
|
+
add_argument_error(
|
58
|
+
message,
|
59
|
+
input_key: @input_key,
|
60
|
+
actor: @actor,
|
61
|
+
inclusion_in: inclusion_in,
|
62
|
+
value: @value,
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def define_inclusion_and_message
|
69
|
+
if @inclusion.is_a?(Hash)
|
70
|
+
@inclusion.values_at(:in, :message)
|
71
|
+
else
|
72
|
+
[@inclusion, DEFAULT_MESSAGE]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Add checks to your inputs, by calling lambdas with the name of you choice
|
4
|
+
# under the "must" key.
|
5
|
+
#
|
6
|
+
# Will raise an error if any check returns a truthy value.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# class Pay < Actor
|
11
|
+
# input :provider,
|
12
|
+
# must: {
|
13
|
+
# exist: -> provider { PROVIDERS.include?(provider) },
|
14
|
+
# }
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# class Pay < Actor
|
18
|
+
# input :provider,
|
19
|
+
# must: {
|
20
|
+
# exist: {
|
21
|
+
# is: -> provider { PROVIDERS.include?(provider) },
|
22
|
+
# message: (lambda do |input_key:, check_name:, actor:, value:|
|
23
|
+
# "The specified provider \"#{value}\" was not found."
|
24
|
+
# end)
|
25
|
+
# }
|
26
|
+
# }
|
27
|
+
# end
|
28
|
+
class ServiceActor::Checks::MustCheck < ServiceActor::Checks::Base
|
29
|
+
DEFAULT_MESSAGE = lambda do |input_key:, actor:, check_name:, value:|
|
30
|
+
"The \"#{input_key}\" input on \"#{actor}\" must \"#{check_name}\" " \
|
31
|
+
"but was #{value.inspect}"
|
32
|
+
end
|
33
|
+
|
34
|
+
private_constant :DEFAULT_MESSAGE
|
35
|
+
|
36
|
+
def self.check(check_name:, input_key:, actor:, conditions:, result:, **) # rubocop:disable Metrics/ParameterLists
|
37
|
+
return unless check_name == :must
|
38
|
+
|
39
|
+
new(
|
40
|
+
input_key: input_key,
|
41
|
+
actor: actor,
|
42
|
+
nested_checks: conditions,
|
43
|
+
value: result[input_key],
|
44
|
+
).check
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(input_key:, actor:, nested_checks:, value:)
|
48
|
+
super()
|
49
|
+
|
50
|
+
@input_key = input_key
|
51
|
+
@actor = actor
|
52
|
+
@nested_checks = nested_checks
|
53
|
+
@value = value
|
54
|
+
end
|
55
|
+
|
56
|
+
def check
|
57
|
+
@nested_checks.each do |nested_check_name, nested_check_conditions|
|
58
|
+
check, message = define_check_and_message_from(nested_check_conditions)
|
59
|
+
|
60
|
+
next if check.call(@value)
|
61
|
+
|
62
|
+
add_argument_error(
|
63
|
+
message,
|
64
|
+
input_key: @input_key,
|
65
|
+
actor: @actor,
|
66
|
+
check_name: nested_check_name,
|
67
|
+
value: @value,
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
@argument_errors
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def define_check_and_message_from(nested_check_conditions)
|
77
|
+
if nested_check_conditions.is_a?(Hash)
|
78
|
+
nested_check_conditions.values_at(:is, :message)
|
79
|
+
else
|
80
|
+
[nested_check_conditions, DEFAULT_MESSAGE]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Ensure your inputs and outputs are not nil by adding `allow_nil: false`.
|
4
|
+
#
|
5
|
+
# Example:
|
6
|
+
#
|
7
|
+
# class CreateUser < Actor
|
8
|
+
# input :name, allow_nil: false
|
9
|
+
# output :user, allow_nil: false
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# class CreateUser < Actor
|
13
|
+
# input :name,
|
14
|
+
# allow_nil: {
|
15
|
+
# is: false,
|
16
|
+
# message: (lambda do |origin:, input_key:, actor:|
|
17
|
+
# "The value \"#{input_key}\" cannot be empty"
|
18
|
+
# end)
|
19
|
+
# }
|
20
|
+
#
|
21
|
+
# input :phone, allow_nil: { is: false, message: "Phone must be present" }
|
22
|
+
#
|
23
|
+
# output :user,
|
24
|
+
# allow_nil: {
|
25
|
+
# is: false,
|
26
|
+
# message: (lambda do |origin:, input_key:, actor:|
|
27
|
+
# "The value \"#{input_key}\" cannot be empty"
|
28
|
+
# end)
|
29
|
+
# }
|
30
|
+
# end
|
31
|
+
class ServiceActor::Checks::NilCheck < ServiceActor::Checks::Base
|
32
|
+
DEFAULT_MESSAGE = lambda do |origin:, input_key:, actor:|
|
33
|
+
"The \"#{input_key}\" #{origin} on \"#{actor}\" does not allow nil values"
|
34
|
+
end
|
35
|
+
|
36
|
+
private_constant :DEFAULT_MESSAGE
|
37
|
+
|
38
|
+
def self.check( # rubocop:disable Metrics/ParameterLists
|
39
|
+
origin:,
|
40
|
+
input_key:,
|
41
|
+
input_options:,
|
42
|
+
actor:,
|
43
|
+
conditions:,
|
44
|
+
result:,
|
45
|
+
**
|
46
|
+
) # do
|
47
|
+
new(
|
48
|
+
origin: origin,
|
49
|
+
input_key: input_key,
|
50
|
+
input_options: input_options,
|
51
|
+
actor: actor,
|
52
|
+
allow_nil: conditions,
|
53
|
+
value: result[input_key],
|
54
|
+
).check
|
55
|
+
end
|
56
|
+
|
57
|
+
def initialize( # rubocop:disable Metrics/ParameterLists
|
58
|
+
origin:,
|
59
|
+
input_key:,
|
60
|
+
input_options:,
|
61
|
+
actor:,
|
62
|
+
allow_nil:,
|
63
|
+
value:
|
64
|
+
) # do
|
65
|
+
super()
|
66
|
+
|
67
|
+
@origin = origin
|
68
|
+
@input_key = input_key
|
69
|
+
@input_options = input_options
|
70
|
+
@actor = actor
|
71
|
+
@allow_nil = allow_nil
|
72
|
+
@value = value
|
73
|
+
end
|
74
|
+
|
75
|
+
def check
|
76
|
+
return unless @value.nil?
|
77
|
+
|
78
|
+
allow_nil, message =
|
79
|
+
define_allow_nil_and_message_from(@input_options[:allow_nil])
|
80
|
+
|
81
|
+
return if allow_nil?(allow_nil)
|
82
|
+
|
83
|
+
add_argument_error(
|
84
|
+
message,
|
85
|
+
origin: @origin,
|
86
|
+
input_key: @input_key,
|
87
|
+
actor: @actor,
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def define_allow_nil_and_message_from(allow_nil)
|
94
|
+
if allow_nil.is_a?(Hash)
|
95
|
+
allow_nil.values_at(:is, :message)
|
96
|
+
else
|
97
|
+
[allow_nil, DEFAULT_MESSAGE]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def allow_nil?(tmp_allow_nil)
|
102
|
+
return tmp_allow_nil unless tmp_allow_nil.nil?
|
103
|
+
|
104
|
+
if @input_options.key?(:default) && @input_options[:default].nil?
|
105
|
+
return true
|
106
|
+
end
|
107
|
+
|
108
|
+
!@input_options[:type]
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Adds `type:` checking to inputs and outputs. Accepts class names or classes
|
4
|
+
# that should match an ancestor. Also accepts arrays.
|
5
|
+
#
|
6
|
+
# Example:
|
7
|
+
#
|
8
|
+
# class ReduceOrderAmount < Actor
|
9
|
+
# input :order, type: "Order"
|
10
|
+
# input :amount, type: [Integer, Float]
|
11
|
+
# input :bonus_applied, type: [TrueClass, FalseClass]
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# class ReduceOrderAmount < Actor
|
15
|
+
# input :order, type: { is: Order, message: "Order is required" }
|
16
|
+
# input :amount, type: { is: Integer, message: "Incorrect amount" }
|
17
|
+
#
|
18
|
+
# input :bonus_applied,
|
19
|
+
# type: {
|
20
|
+
# is: [TrueClass, FalseClass],
|
21
|
+
# message: (lambda do |origin:, input_key:, actor:, expected_type:, given_type:| # rubocop:disable Layout/LineLength
|
22
|
+
# "Wrong type \"#{given_type}\" for \"#{input_key}\". " \
|
23
|
+
# "Expected: \"#{expected_type}\""
|
24
|
+
# end)
|
25
|
+
# }
|
26
|
+
# end
|
27
|
+
class ServiceActor::Checks::TypeCheck < ServiceActor::Checks::Base
|
28
|
+
DEFAULT_MESSAGE = lambda do
|
29
|
+
|origin:, input_key:, actor:, expected_type:, given_type:|
|
30
|
+
|
31
|
+
"The \"#{input_key}\" #{origin} on \"#{actor}\" must be of type " \
|
32
|
+
"\"#{expected_type}\" but was \"#{given_type}\""
|
33
|
+
end
|
34
|
+
|
35
|
+
private_constant :DEFAULT_MESSAGE
|
36
|
+
|
37
|
+
def self.check( # rubocop:disable Metrics/ParameterLists
|
38
|
+
check_name:,
|
39
|
+
origin:,
|
40
|
+
input_key:,
|
41
|
+
actor:,
|
42
|
+
conditions:,
|
43
|
+
result:,
|
44
|
+
**
|
45
|
+
) # do
|
46
|
+
return unless check_name == :type
|
47
|
+
|
48
|
+
new(
|
49
|
+
origin: origin,
|
50
|
+
input_key: input_key,
|
51
|
+
actor: actor,
|
52
|
+
type_definition: conditions,
|
53
|
+
given_type: result[input_key],
|
54
|
+
).check
|
55
|
+
end
|
56
|
+
|
57
|
+
def initialize(origin:, input_key:, actor:, type_definition:, given_type:)
|
58
|
+
super()
|
59
|
+
|
60
|
+
@origin = origin
|
61
|
+
@input_key = input_key
|
62
|
+
@actor = actor
|
63
|
+
@type_definition = type_definition
|
64
|
+
@given_type = given_type
|
65
|
+
end
|
66
|
+
|
67
|
+
def check
|
68
|
+
return if @type_definition.nil?
|
69
|
+
return if @given_type.nil?
|
70
|
+
|
71
|
+
types, message = define_types_and_message
|
72
|
+
|
73
|
+
return if types.any? { |type| @given_type.is_a?(type) }
|
74
|
+
|
75
|
+
add_argument_error(
|
76
|
+
message,
|
77
|
+
origin: @origin,
|
78
|
+
input_key: @input_key,
|
79
|
+
actor: @actor,
|
80
|
+
expected_type: types.join(", "),
|
81
|
+
given_type: @given_type.class,
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def define_types_and_message
|
88
|
+
if @type_definition.is_a?(Hash)
|
89
|
+
@type_definition, message =
|
90
|
+
@type_definition.values_at(:is, :message)
|
91
|
+
else
|
92
|
+
message = DEFAULT_MESSAGE
|
93
|
+
end
|
94
|
+
|
95
|
+
types = Array(@type_definition).map do |name|
|
96
|
+
name.is_a?(String) ? Object.const_get(name) : name
|
97
|
+
end
|
98
|
+
|
99
|
+
[types, message]
|
100
|
+
end
|
101
|
+
end
|
data/lib/service_actor/core.rb
CHANGED
@@ -11,7 +11,10 @@ module ServiceActor::Core
|
|
11
11
|
# CreateUser.call(name: "Joe")
|
12
12
|
def call(result = nil, **arguments)
|
13
13
|
result = ServiceActor::Result.to_result(result).merge!(arguments)
|
14
|
-
|
14
|
+
|
15
|
+
instance = new(result)
|
16
|
+
instance._call
|
17
|
+
|
15
18
|
result
|
16
19
|
end
|
17
20
|
|
@@ -54,13 +57,4 @@ module ServiceActor::Core
|
|
54
57
|
def fail!(**arguments)
|
55
58
|
result.fail!(self.class.failure_class, **arguments)
|
56
59
|
end
|
57
|
-
|
58
|
-
private
|
59
|
-
|
60
|
-
# Raises an error depending on the mode
|
61
|
-
def raise_error_with(message, **arguments)
|
62
|
-
message = message.call(**arguments) if message.is_a?(Proc)
|
63
|
-
|
64
|
-
raise self.class.argument_error_class, message
|
65
|
-
end
|
66
60
|
end
|
@@ -72,5 +72,12 @@ module ServiceActor::Defaultable
|
|
72
72
|
|
73
73
|
message.call(key, self.class)
|
74
74
|
end
|
75
|
+
|
76
|
+
# Raises an error depending on the mode
|
77
|
+
def raise_error_with(message, **arguments)
|
78
|
+
message = message.call(**arguments) if message.is_a?(Proc)
|
79
|
+
|
80
|
+
raise self.class.argument_error_class, message
|
81
|
+
end
|
75
82
|
end
|
76
83
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: service_actor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sunny Ripert
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-12-
|
11
|
+
date: 2022-12-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: zeitwerk
|
@@ -171,19 +171,22 @@ files:
|
|
171
171
|
- lib/service_actor/argument_error.rb
|
172
172
|
- lib/service_actor/attributable.rb
|
173
173
|
- lib/service_actor/base.rb
|
174
|
-
- lib/service_actor/
|
175
|
-
- lib/service_actor/
|
174
|
+
- lib/service_actor/checkable.rb
|
175
|
+
- lib/service_actor/checks/base.rb
|
176
|
+
- lib/service_actor/checks/default_check.rb
|
177
|
+
- lib/service_actor/checks/inclusion_check.rb
|
178
|
+
- lib/service_actor/checks/must_check.rb
|
179
|
+
- lib/service_actor/checks/nil_check.rb
|
180
|
+
- lib/service_actor/checks/type_check.rb
|
181
|
+
- lib/service_actor/configurable.rb
|
176
182
|
- lib/service_actor/core.rb
|
177
183
|
- lib/service_actor/defaultable.rb
|
178
184
|
- lib/service_actor/error.rb
|
179
185
|
- lib/service_actor/failable.rb
|
180
186
|
- lib/service_actor/failure.rb
|
181
|
-
- lib/service_actor/nil_checkable.rb
|
182
187
|
- lib/service_actor/playable.rb
|
183
|
-
- lib/service_actor/raisable.rb
|
184
188
|
- lib/service_actor/result.rb
|
185
189
|
- lib/service_actor/support/loader.rb
|
186
|
-
- lib/service_actor/type_checkable.rb
|
187
190
|
- lib/service_actor/version.rb
|
188
191
|
homepage: https://github.com/sunny/actor
|
189
192
|
licenses:
|
@@ -1,69 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Add checks to your inputs, by specifying what values are authorized under the
|
4
|
-
# "in" key.
|
5
|
-
#
|
6
|
-
# Example:
|
7
|
-
#
|
8
|
-
# class Pay < Actor
|
9
|
-
# input :provider, inclusion: ["MANGOPAY", "PayPal", "Stripe"]
|
10
|
-
# end
|
11
|
-
#
|
12
|
-
# class Pay < Actor
|
13
|
-
# input :provider,
|
14
|
-
# inclusion: {
|
15
|
-
# in: ["MANGOPAY", "PayPal", "Stripe"],
|
16
|
-
# message: (lambda do |input_key:, actor:, inclusion_in:, value:|
|
17
|
-
# "Payment system \"#{value}\" is not supported"
|
18
|
-
# end)
|
19
|
-
# }
|
20
|
-
# end
|
21
|
-
module ServiceActor::Collectionable
|
22
|
-
def self.included(base)
|
23
|
-
base.prepend(PrependedMethods)
|
24
|
-
end
|
25
|
-
|
26
|
-
module PrependedMethods
|
27
|
-
DEFAULT_MESSAGE = lambda do |input_key:, actor:, inclusion_in:, value:|
|
28
|
-
"The \"#{input_key}\" input must be included " \
|
29
|
-
"in #{inclusion_in.inspect} on \"#{actor}\" " \
|
30
|
-
"instead of #{value.inspect}"
|
31
|
-
end
|
32
|
-
|
33
|
-
private_constant :DEFAULT_MESSAGE
|
34
|
-
|
35
|
-
def _call # rubocop:disable Metrics/MethodLength
|
36
|
-
self.class.inputs.each do |key, options|
|
37
|
-
value = result[key]
|
38
|
-
|
39
|
-
# DEPRECATED: `in` is deprecated in favor of `inclusion`.
|
40
|
-
inclusion = options[:inclusion] || options[:in]
|
41
|
-
|
42
|
-
inclusion_in, message = define_inclusion_from(inclusion)
|
43
|
-
|
44
|
-
next if inclusion_in.nil?
|
45
|
-
next if inclusion_in.include?(value)
|
46
|
-
|
47
|
-
raise_error_with(
|
48
|
-
message,
|
49
|
-
input_key: key,
|
50
|
-
actor: self.class,
|
51
|
-
inclusion_in: inclusion_in,
|
52
|
-
value: value,
|
53
|
-
)
|
54
|
-
end
|
55
|
-
|
56
|
-
super
|
57
|
-
end
|
58
|
-
|
59
|
-
private
|
60
|
-
|
61
|
-
def define_inclusion_from(inclusion)
|
62
|
-
if inclusion.is_a?(Hash)
|
63
|
-
inclusion.values_at(:in, :message)
|
64
|
-
else
|
65
|
-
[inclusion, DEFAULT_MESSAGE]
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
@@ -1,75 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Add checks to your inputs, by calling lambdas with the name of you choice
|
4
|
-
# under the "must" key.
|
5
|
-
#
|
6
|
-
# Will raise an error if any check returns a truthy value.
|
7
|
-
#
|
8
|
-
# Example:
|
9
|
-
#
|
10
|
-
# class Pay < Actor
|
11
|
-
# input :provider,
|
12
|
-
# must: {
|
13
|
-
# exist: -> provider { PROVIDERS.include?(provider) },
|
14
|
-
# }
|
15
|
-
# end
|
16
|
-
#
|
17
|
-
# class Pay < Actor
|
18
|
-
# input :provider,
|
19
|
-
# must: {
|
20
|
-
# exist: {
|
21
|
-
# is: -> provider { PROVIDERS.include?(provider) },
|
22
|
-
# message: (lambda do |input_key:, check_name:, actor:, value:|
|
23
|
-
# "The specified provider \"#{value}\" was not found."
|
24
|
-
# end)
|
25
|
-
# }
|
26
|
-
# }
|
27
|
-
# end
|
28
|
-
module ServiceActor::Conditionable
|
29
|
-
def self.included(base)
|
30
|
-
base.prepend(PrependedMethods)
|
31
|
-
end
|
32
|
-
|
33
|
-
module PrependedMethods
|
34
|
-
DEFAULT_MESSAGE = lambda do |input_key:, check_name:, actor:, value:|
|
35
|
-
"The \"#{input_key}\" input on \"#{actor}\" must \"#{check_name}\" " \
|
36
|
-
"but was #{value.inspect}"
|
37
|
-
end
|
38
|
-
|
39
|
-
private_constant :DEFAULT_MESSAGE
|
40
|
-
|
41
|
-
def _call # rubocop:disable Metrics/MethodLength
|
42
|
-
self.class.inputs.each do |key, options|
|
43
|
-
next unless options[:must]
|
44
|
-
|
45
|
-
options[:must].each do |check_name, check|
|
46
|
-
value = result[key]
|
47
|
-
|
48
|
-
check, message = define_check_from(check)
|
49
|
-
|
50
|
-
next if check.call(value)
|
51
|
-
|
52
|
-
raise_error_with(
|
53
|
-
message,
|
54
|
-
input_key: key,
|
55
|
-
check_name: check_name,
|
56
|
-
actor: self.class,
|
57
|
-
value: value,
|
58
|
-
)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
super
|
63
|
-
end
|
64
|
-
|
65
|
-
private
|
66
|
-
|
67
|
-
def define_check_from(check)
|
68
|
-
if check.is_a?(Hash)
|
69
|
-
check.values_at(:is, :message)
|
70
|
-
else
|
71
|
-
[check, DEFAULT_MESSAGE]
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
@@ -1,88 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Ensure your inputs and outputs are not nil by adding `allow_nil: false`.
|
4
|
-
#
|
5
|
-
# Example:
|
6
|
-
#
|
7
|
-
# class CreateUser < Actor
|
8
|
-
# input :name, allow_nil: false
|
9
|
-
# output :user, allow_nil: false
|
10
|
-
# end
|
11
|
-
#
|
12
|
-
# class CreateUser < Actor
|
13
|
-
# input :name,
|
14
|
-
# allow_nil: {
|
15
|
-
# is: false,
|
16
|
-
# message: (lambda do |origin:, input_key:, actor:|
|
17
|
-
# "The value \"#{input_key}\" cannot be empty"
|
18
|
-
# end)
|
19
|
-
# }
|
20
|
-
#
|
21
|
-
# input :phone, allow_nil: { is: false, message: "Phone must be present" }
|
22
|
-
#
|
23
|
-
# output :user,
|
24
|
-
# allow_nil: {
|
25
|
-
# is: false,
|
26
|
-
# message: (lambda do |origin:, input_key:, actor:|
|
27
|
-
# "The value \"#{input_key}\" cannot be empty"
|
28
|
-
# end)
|
29
|
-
# }
|
30
|
-
# end
|
31
|
-
module ServiceActor::NilCheckable
|
32
|
-
def self.included(base)
|
33
|
-
base.prepend(PrependedMethods)
|
34
|
-
end
|
35
|
-
|
36
|
-
module PrependedMethods
|
37
|
-
DEFAULT_MESSAGE = lambda do |origin:, input_key:, actor:|
|
38
|
-
"The \"#{input_key}\" #{origin} on \"#{actor}\" does not allow " \
|
39
|
-
"nil values"
|
40
|
-
end
|
41
|
-
|
42
|
-
private_constant :DEFAULT_MESSAGE
|
43
|
-
|
44
|
-
def _call
|
45
|
-
check_context_for_nil(self.class.inputs, origin: "input")
|
46
|
-
|
47
|
-
super
|
48
|
-
|
49
|
-
check_context_for_nil(self.class.outputs, origin: "output")
|
50
|
-
end
|
51
|
-
|
52
|
-
private
|
53
|
-
|
54
|
-
def check_context_for_nil(definitions, origin:)
|
55
|
-
definitions.each do |key, options|
|
56
|
-
value = result[key]
|
57
|
-
|
58
|
-
next unless value.nil?
|
59
|
-
|
60
|
-
allow_nil, message = define_allow_nil_from(options[:allow_nil])
|
61
|
-
|
62
|
-
next if allow_nil?(allow_nil, options)
|
63
|
-
|
64
|
-
raise_error_with(
|
65
|
-
message,
|
66
|
-
origin: origin,
|
67
|
-
input_key: key,
|
68
|
-
actor: self.class,
|
69
|
-
)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def define_allow_nil_from(allow_nil)
|
74
|
-
if allow_nil.is_a?(Hash)
|
75
|
-
allow_nil.values_at(:is, :message)
|
76
|
-
else
|
77
|
-
[allow_nil, DEFAULT_MESSAGE]
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def allow_nil?(allow_nil, options)
|
82
|
-
return allow_nil unless allow_nil.nil?
|
83
|
-
return true if options.key?(:default) && options[:default].nil?
|
84
|
-
|
85
|
-
!options[:type]
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
@@ -1,91 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Adds `type:` checking to inputs and outputs. Accepts class names or classes
|
4
|
-
# that should match an ancestor. Also accepts arrays.
|
5
|
-
#
|
6
|
-
# Example:
|
7
|
-
#
|
8
|
-
# class ReduceOrderAmount < Actor
|
9
|
-
# input :order, type: "Order"
|
10
|
-
# input :amount, type: [Integer, Float]
|
11
|
-
# input :bonus_applied, type: [TrueClass, FalseClass]
|
12
|
-
# end
|
13
|
-
#
|
14
|
-
# class ReduceOrderAmount < Actor
|
15
|
-
# input :order, type: { is: Order, message: "Order is required" }
|
16
|
-
# input :amount, type: { is: Integer, message: "Incorrect amount" }
|
17
|
-
#
|
18
|
-
# input :bonus_applied,
|
19
|
-
# type: {
|
20
|
-
# is: [TrueClass, FalseClass],
|
21
|
-
# message: (lambda do |origin:, input_key:, actor:, expected_type:, given_type:| # rubocop:disable Layout/LineLength
|
22
|
-
# "Wrong type \"#{given_type}\" for \"#{input_key}\". " \
|
23
|
-
# "Expected: \"#{expected_type}\""
|
24
|
-
# end)
|
25
|
-
# }
|
26
|
-
# end
|
27
|
-
module ServiceActor::TypeCheckable
|
28
|
-
def self.included(base)
|
29
|
-
base.prepend(PrependedMethods)
|
30
|
-
end
|
31
|
-
|
32
|
-
module PrependedMethods
|
33
|
-
DEFAULT_MESSAGE = lambda do
|
34
|
-
|origin:, input_key:, actor:, expected_type:, given_type:|
|
35
|
-
|
36
|
-
"The \"#{input_key}\" #{origin} on \"#{actor}\" must be of type " \
|
37
|
-
"\"#{expected_type}\" but was \"#{given_type}\""
|
38
|
-
end
|
39
|
-
|
40
|
-
private_constant :DEFAULT_MESSAGE
|
41
|
-
|
42
|
-
def _call
|
43
|
-
check_type_definitions(self.class.inputs, origin: "input")
|
44
|
-
|
45
|
-
super
|
46
|
-
|
47
|
-
check_type_definitions(self.class.outputs, origin: "output")
|
48
|
-
end
|
49
|
-
|
50
|
-
private
|
51
|
-
|
52
|
-
def check_type_definitions(definitions, origin:) # rubocop:disable Metrics/MethodLength
|
53
|
-
definitions.each do |key, options|
|
54
|
-
type_definition = options[:type] || next
|
55
|
-
value = result[key] || next
|
56
|
-
|
57
|
-
types, message = define_types_with(type_definition)
|
58
|
-
|
59
|
-
next if types.any? { |type| value.is_a?(type) }
|
60
|
-
|
61
|
-
raise_error_with(
|
62
|
-
message,
|
63
|
-
origin: origin,
|
64
|
-
input_key: key,
|
65
|
-
actor: self.class,
|
66
|
-
expected_type: types.join(", "),
|
67
|
-
given_type: value.class,
|
68
|
-
)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def define_types_with(type_definition)
|
73
|
-
if type_definition.is_a?(Hash)
|
74
|
-
type_definition, message =
|
75
|
-
type_definition.values_at(:is, :message)
|
76
|
-
else
|
77
|
-
message = DEFAULT_MESSAGE
|
78
|
-
end
|
79
|
-
|
80
|
-
types = types_for_definition(type_definition)
|
81
|
-
|
82
|
-
[types, message]
|
83
|
-
end
|
84
|
-
|
85
|
-
def types_for_definition(type_definition)
|
86
|
-
Array(type_definition).map do |name|
|
87
|
-
name.is_a?(String) ? Object.const_get(name) : name
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|