servactory 3.1.0.rc3 → 3.1.0.rc4
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/README.md +5 -0
- data/lib/generators/README.md +1 -1
- data/lib/generators/servactory/extension/USAGE +1 -1
- data/lib/generators/servactory/install/USAGE +2 -2
- data/lib/generators/servactory/rspec/USAGE +4 -4
- data/lib/generators/servactory/rspec/rspec_generator.rb +2 -2
- data/lib/generators/servactory/service/USAGE +5 -5
- data/lib/servactory/actions/collection.rb +21 -5
- data/lib/servactory/actions/stages/collection.rb +19 -1
- data/lib/servactory/actions/tools/rules.rb +1 -1
- data/lib/servactory/actions/tools/runner.rb +1 -1
- data/lib/servactory/configuration/collection_mode/class_names_collection.rb +1 -1
- data/lib/servactory/configuration/configurable.rb +1 -1
- data/lib/servactory/configuration/factory.rb +30 -3
- data/lib/servactory/configuration/hash_mode/class_names_collection.rb +2 -1
- data/lib/servactory/configuration/option_helpers/option_helpers_collection.rb +150 -37
- data/lib/servactory/context/warehouse/inputs.rb +1 -1
- data/lib/servactory/context/workspace/inputs.rb +2 -2
- data/lib/servactory/context/workspace/internals.rb +2 -2
- data/lib/servactory/context/workspace/outputs.rb +2 -2
- data/lib/servactory/context/workspace.rb +7 -7
- data/lib/servactory/info/builder.rb +4 -3
- data/lib/servactory/inputs/dsl.rb +5 -1
- data/lib/servactory/inputs/input.rb +1 -2
- data/lib/servactory/internals/dsl.rb +7 -1
- data/lib/servactory/maintenance/attributes/base.rb +4 -1
- data/lib/servactory/maintenance/attributes/collection.rb +61 -6
- data/lib/servactory/maintenance/options/collection.rb +46 -15
- data/lib/servactory/maintenance/options/option.rb +3 -12
- data/lib/servactory/maintenance/options/registrar.rb +2 -12
- data/lib/servactory/maintenance/validations/checkers/type.rb +23 -5
- data/lib/servactory/outputs/dsl.rb +7 -1
- data/lib/servactory/result.rb +21 -8
- data/lib/servactory/test_kit/rspec/helpers/exception_validator.rb +112 -0
- data/lib/servactory/test_kit/rspec/helpers/input_validator.rb +3 -1
- data/lib/servactory/test_kit/rspec/helpers/mock_executor.rb +125 -129
- data/lib/servactory/test_kit/rspec/helpers/service_inputs_guard.rb +98 -0
- data/lib/servactory/test_kit/rspec/helpers/service_inputs_matcher.rb +75 -0
- data/lib/servactory/test_kit/rspec/helpers/service_mock_builder.rb +110 -45
- data/lib/servactory/test_kit/rspec/helpers/service_mock_config.rb +26 -8
- data/lib/servactory/test_kit/rspec/helpers/wrap_block_adapter.rb +74 -0
- data/lib/servactory/test_kit/rspec/helpers.rb +4 -0
- data/lib/servactory/test_kit/rspec/matchers/base/attribute_matcher.rb +72 -16
- data/lib/servactory/test_kit/rspec/matchers/base/message_expectation.rb +194 -0
- data/lib/servactory/test_kit/rspec/matchers/base/submatcher.rb +8 -0
- data/lib/servactory/test_kit/rspec/matchers/concerns/option_helper_rules.rb +148 -0
- data/lib/servactory/test_kit/rspec/matchers/concerns/proc_message.rb +117 -0
- data/lib/servactory/test_kit/rspec/matchers/concerns/required_message.rb +92 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_input_matcher.rb +27 -4
- data/lib/servactory/test_kit/rspec/matchers/have_service_internal_matcher.rb +10 -3
- data/lib/servactory/test_kit/rspec/matchers/submatchers/input/required_submatcher.rb +58 -23
- data/lib/servactory/test_kit/rspec/matchers/submatchers/input/valid_with_submatcher.rb +124 -59
- data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/message_submatcher.rb +133 -39
- data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/must_submatcher.rb +192 -17
- data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/target_submatcher.rb +4 -2
- data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/types_submatcher.rb +6 -0
- data/lib/servactory/test_kit/rspec/matchers.rb +15 -0
- data/lib/servactory/tool_kit/dynamic_options/consists_of.rb +29 -8
- data/lib/servactory/tool_kit/dynamic_options/format.rb +2 -3
- data/lib/servactory/tool_kit/dynamic_options/inclusion.rb +1 -1
- data/lib/servactory/tool_kit/dynamic_options/max.rb +1 -1
- data/lib/servactory/tool_kit/dynamic_options/min.rb +1 -1
- data/lib/servactory/tool_kit/dynamic_options/multiple_of.rb +33 -5
- data/lib/servactory/tool_kit/dynamic_options/must.rb +2 -3
- data/lib/servactory/tool_kit/dynamic_options/schema.rb +34 -21
- data/lib/servactory/tool_kit/dynamic_options/target.rb +1 -1
- data/lib/servactory/utils.rb +13 -3
- data/lib/servactory/version.rb +1 -1
- data/lib/servactory.rb +3 -0
- metadata +10 -2
|
@@ -8,7 +8,7 @@ module Servactory
|
|
|
8
8
|
|
|
9
9
|
def initialize(context)
|
|
10
10
|
@class_name = context.class.name
|
|
11
|
-
@i18n_root_key = context.config.i18n_root_key
|
|
11
|
+
@i18n_root_key = context.class.config.i18n_root_key
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def translate(key, **options)
|
|
@@ -42,11 +42,11 @@ module Servactory
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def success!
|
|
45
|
-
raise config.success_class.new(context: self)
|
|
45
|
+
raise self.class.config.success_class.new(context: self)
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def fail_input!(input_name, message:, meta: nil)
|
|
49
|
-
raise config.input_exception_class.new(
|
|
49
|
+
raise self.class.config.input_exception_class.new(
|
|
50
50
|
context: self,
|
|
51
51
|
input_name:,
|
|
52
52
|
message:,
|
|
@@ -55,7 +55,7 @@ module Servactory
|
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def fail_internal!(internal_name, message:, meta: nil)
|
|
58
|
-
raise config.internal_exception_class.new(
|
|
58
|
+
raise self.class.config.internal_exception_class.new(
|
|
59
59
|
context: self,
|
|
60
60
|
internal_name:,
|
|
61
61
|
message:,
|
|
@@ -64,7 +64,7 @@ module Servactory
|
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
def fail_output!(output_name, message:, meta: nil)
|
|
67
|
-
raise config.output_exception_class.new(
|
|
67
|
+
raise self.class.config.output_exception_class.new(
|
|
68
68
|
context: self,
|
|
69
69
|
output_name:,
|
|
70
70
|
message:,
|
|
@@ -73,7 +73,7 @@ module Servactory
|
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
def fail!(type = :base, message:, meta: nil)
|
|
76
|
-
raise config.failure_class.new(type:, message:, meta:)
|
|
76
|
+
raise self.class.config.failure_class.new(type:, message:, meta:)
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
def fail_result!(service_result)
|
|
@@ -118,7 +118,7 @@ module Servactory
|
|
|
118
118
|
end
|
|
119
119
|
|
|
120
120
|
def call
|
|
121
|
-
raise config.failure_class.new(
|
|
121
|
+
raise self.class.config.failure_class.new(
|
|
122
122
|
type: :base,
|
|
123
123
|
message: servactory_service_info.translate(
|
|
124
124
|
"methods.call.not_used"
|
|
@@ -26,7 +26,7 @@ module Servactory
|
|
|
26
26
|
|
|
27
27
|
private
|
|
28
28
|
|
|
29
|
-
# rubocop:disable Metrics/MethodLength
|
|
29
|
+
# rubocop:disable-next Metrics/MethodLength
|
|
30
30
|
def build_all_attributes(inputs:, internals:, outputs:, stages:, config:)
|
|
31
31
|
build_input_attributes_with(
|
|
32
32
|
collection: inputs,
|
|
@@ -45,7 +45,6 @@ module Servactory
|
|
|
45
45
|
|
|
46
46
|
build_action_stages_with(collection: stages)
|
|
47
47
|
end
|
|
48
|
-
# rubocop:enable Metrics/MethodLength
|
|
49
48
|
|
|
50
49
|
def build_input_attributes_with(collection:, dynamic_options:)
|
|
51
50
|
@inputs = build_attributes_with(
|
|
@@ -142,11 +141,13 @@ module Servactory
|
|
|
142
141
|
end
|
|
143
142
|
|
|
144
143
|
def enrich_options_with_common_fields(attribute:, options:)
|
|
144
|
+
types = attribute.collection_of_options.find_by(name: :types)
|
|
145
145
|
must = attribute.collection_of_options.find_by(name: :must)
|
|
146
146
|
|
|
147
147
|
options.merge(
|
|
148
148
|
actor: attribute.actor,
|
|
149
|
-
types:
|
|
149
|
+
type: { is: types.value, message: types.body[:message] },
|
|
150
|
+
types: types.value,
|
|
150
151
|
must: must&.value
|
|
151
152
|
)
|
|
152
153
|
end
|
|
@@ -18,12 +18,16 @@ module Servactory
|
|
|
18
18
|
private
|
|
19
19
|
|
|
20
20
|
def input(name, *helpers, **options)
|
|
21
|
-
|
|
21
|
+
input = Input.new(
|
|
22
22
|
name,
|
|
23
23
|
*helpers,
|
|
24
24
|
option_helpers: config.input_option_helpers,
|
|
25
25
|
**options
|
|
26
26
|
)
|
|
27
|
+
|
|
28
|
+
raise ArgumentError, "[#{self.name}] Input `#{name}` must have the `type` option" if input.types.empty?
|
|
29
|
+
|
|
30
|
+
collection_of_inputs << input
|
|
27
31
|
end
|
|
28
32
|
|
|
29
33
|
def collection_of_inputs
|
|
@@ -52,7 +52,7 @@ module Servactory
|
|
|
52
52
|
|
|
53
53
|
attr_reader :internal_name
|
|
54
54
|
|
|
55
|
-
# rubocop:disable Style/KeywordParametersOrder
|
|
55
|
+
# rubocop:disable-next Style/KeywordParametersOrder
|
|
56
56
|
def initialize(
|
|
57
57
|
name,
|
|
58
58
|
*helpers,
|
|
@@ -64,7 +64,6 @@ module Servactory
|
|
|
64
64
|
|
|
65
65
|
super(name, *helpers, option_helpers:, **options)
|
|
66
66
|
end
|
|
67
|
-
# rubocop:enable Style/KeywordParametersOrder
|
|
68
67
|
|
|
69
68
|
def conflict_code
|
|
70
69
|
@collection_of_options.defined_conflict_code
|
|
@@ -17,12 +17,18 @@ module Servactory
|
|
|
17
17
|
private
|
|
18
18
|
|
|
19
19
|
def internal(name, *helpers, **options)
|
|
20
|
-
|
|
20
|
+
internal = Internal.new(
|
|
21
21
|
name,
|
|
22
22
|
*helpers,
|
|
23
23
|
option_helpers: config.internal_option_helpers,
|
|
24
24
|
**options
|
|
25
25
|
)
|
|
26
|
+
|
|
27
|
+
if internal.types.empty?
|
|
28
|
+
raise ArgumentError, "[#{self.name}] Internal attribute `#{name}` must have the `type` option"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
collection_of_internals << internal
|
|
26
32
|
end
|
|
27
33
|
|
|
28
34
|
def collection_of_internals
|
|
@@ -39,8 +39,11 @@ module Servactory
|
|
|
39
39
|
@collection_of_options = options_registrar.collection
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
+
# @deprecated Use collection_of_options.validations_for_checks instead.
|
|
42
43
|
def options_for_checks
|
|
43
|
-
|
|
44
|
+
warn "[DEPRECATION] Servactory::Maintenance::Attributes::Base#options_for_checks is deprecated. " \
|
|
45
|
+
"Use collection_of_options.validations_for_checks instead."
|
|
46
|
+
@collection_of_options.send(:check_options_by_key)
|
|
44
47
|
end
|
|
45
48
|
|
|
46
49
|
def system_name
|
|
@@ -13,6 +13,10 @@ module Servactory
|
|
|
13
13
|
# how attributes are indexed and filtered (e.g., inputs use `internal_name`
|
|
14
14
|
# for the `as:` parameter).
|
|
15
15
|
#
|
|
16
|
+
# An attribute is declared once per name: adding an attribute whose
|
|
17
|
+
# `name` is already declared (e.g., redeclaring a parent's input in
|
|
18
|
+
# a child service) replaces the earlier declaration in place.
|
|
19
|
+
#
|
|
16
20
|
# ## Usage
|
|
17
21
|
#
|
|
18
22
|
# Subclasses are used internally by Input, Internal, and Output DSL modules
|
|
@@ -32,16 +36,15 @@ module Servactory
|
|
|
32
36
|
#
|
|
33
37
|
# The collection uses memoization for frequently accessed data:
|
|
34
38
|
# - `attributes_index` - cached hash for O(1) lookups by name
|
|
39
|
+
# - `declarations_index` - cached hash for O(1) redeclaration checks
|
|
35
40
|
#
|
|
36
41
|
class Collection
|
|
37
42
|
extend Forwardable
|
|
38
43
|
|
|
39
44
|
def_delegators :@collection,
|
|
40
|
-
:<<,
|
|
41
45
|
:filter,
|
|
42
46
|
:each, :each_with_object,
|
|
43
|
-
:map, :to_h
|
|
44
|
-
:merge
|
|
47
|
+
:map, :to_h
|
|
45
48
|
|
|
46
49
|
# Initializes the collection with an optional pre-built Set.
|
|
47
50
|
#
|
|
@@ -51,6 +54,50 @@ module Servactory
|
|
|
51
54
|
@collection = collection
|
|
52
55
|
end
|
|
53
56
|
|
|
57
|
+
# Duplicates the collection, resetting memoized caches.
|
|
58
|
+
#
|
|
59
|
+
# @param original [Collection] the collection being duplicated
|
|
60
|
+
# @return [void]
|
|
61
|
+
def initialize_dup(original)
|
|
62
|
+
super
|
|
63
|
+
@collection = original.instance_variable_get(:@collection).dup
|
|
64
|
+
@attributes_index = nil
|
|
65
|
+
@declarations_index = nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Adds an attribute to the collection, invalidating the index cache.
|
|
69
|
+
#
|
|
70
|
+
# An attribute with an already declared `name` replaces
|
|
71
|
+
# the earlier declaration in place, keeping its position.
|
|
72
|
+
#
|
|
73
|
+
# @param attribute [Object] the attribute to add
|
|
74
|
+
# @return [Collection] self
|
|
75
|
+
def <<(attribute)
|
|
76
|
+
redeclared = declarations_index[attribute.name]
|
|
77
|
+
|
|
78
|
+
if redeclared.nil?
|
|
79
|
+
@collection << attribute
|
|
80
|
+
else
|
|
81
|
+
@collection = Set.new(@collection) { |declared| declared.equal?(redeclared) ? attribute : declared }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
declarations_index[attribute.name] = attribute
|
|
85
|
+
@attributes_index = nil
|
|
86
|
+
self
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Merges attributes from another collection, invalidating the index cache.
|
|
90
|
+
#
|
|
91
|
+
# Each attribute is added as with `<<`, so redeclarations replace
|
|
92
|
+
# the earlier declarations in place.
|
|
93
|
+
#
|
|
94
|
+
# @param other [Collection, Enumerable] attributes to merge
|
|
95
|
+
# @return [Collection] self
|
|
96
|
+
def merge(other)
|
|
97
|
+
other.each { |attribute| self << attribute }
|
|
98
|
+
self
|
|
99
|
+
end
|
|
100
|
+
|
|
54
101
|
# Returns names of all attributes in the collection.
|
|
55
102
|
#
|
|
56
103
|
# @return [Array<Symbol>] list of attribute names
|
|
@@ -88,9 +135,17 @@ module Servactory
|
|
|
88
135
|
#
|
|
89
136
|
# @return [Hash{Symbol => Object}] attribute names mapped to attribute instances
|
|
90
137
|
def attributes_index
|
|
91
|
-
@attributes_index ||=
|
|
92
|
-
|
|
93
|
-
|
|
138
|
+
@attributes_index ||= to_h { |attribute| [lookup_name(attribute), attribute] }
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Builds and caches a hash index of declared attributes by `name`.
|
|
142
|
+
#
|
|
143
|
+
# Unlike `attributes_index`, it is keyed by the declared name
|
|
144
|
+
# (the external name for inputs), which identifies a redeclaration.
|
|
145
|
+
#
|
|
146
|
+
# @return [Hash{Symbol => Object}] declared names mapped to attribute instances
|
|
147
|
+
def declarations_index
|
|
148
|
+
@declarations_index ||= to_h { |attribute| [attribute.name, attribute] }
|
|
94
149
|
end
|
|
95
150
|
|
|
96
151
|
# Returns the name used for indexing and filtering a given attribute.
|
|
@@ -28,15 +28,13 @@ module Servactory
|
|
|
28
28
|
# ## Performance
|
|
29
29
|
#
|
|
30
30
|
# The collection uses memoization for frequently accessed data:
|
|
31
|
-
# - `
|
|
32
|
-
# - `options_for_checks` - cached hash for validation pipeline
|
|
31
|
+
# - `validations_for_checks` - cached tuples for validation pipeline
|
|
33
32
|
# - `options_index` - cached hash for O(1) lookups by name
|
|
34
33
|
#
|
|
35
34
|
class Collection
|
|
36
35
|
extend Forwardable
|
|
37
36
|
|
|
38
37
|
def_delegators :@collection,
|
|
39
|
-
:<<,
|
|
40
38
|
:filter,
|
|
41
39
|
:each, :each_with_object,
|
|
42
40
|
:map,
|
|
@@ -50,6 +48,26 @@ module Servactory
|
|
|
50
48
|
@collection = Set.new
|
|
51
49
|
end
|
|
52
50
|
|
|
51
|
+
# Duplicates the collection, resetting memoized caches.
|
|
52
|
+
#
|
|
53
|
+
# @param original [Collection] the collection being duplicated
|
|
54
|
+
# @return [void]
|
|
55
|
+
def initialize_dup(original)
|
|
56
|
+
super
|
|
57
|
+
@collection = original.instance_variable_get(:@collection).dup
|
|
58
|
+
reset_memoized_caches
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Adds an option to the collection, invalidating memoized caches.
|
|
62
|
+
#
|
|
63
|
+
# @param option [Option] the option to add
|
|
64
|
+
# @return [Collection] self
|
|
65
|
+
def <<(option)
|
|
66
|
+
@collection << option
|
|
67
|
+
reset_memoized_caches
|
|
68
|
+
self
|
|
69
|
+
end
|
|
70
|
+
|
|
53
71
|
# Returns all option names in the collection.
|
|
54
72
|
#
|
|
55
73
|
# @return [Array<Symbol>] list of option names
|
|
@@ -57,23 +75,22 @@ module Servactory
|
|
|
57
75
|
map(&:name)
|
|
58
76
|
end
|
|
59
77
|
|
|
60
|
-
#
|
|
78
|
+
# @deprecated Use {#validations_for_checks} instead.
|
|
61
79
|
#
|
|
62
80
|
# @return [Array<Class>] deduplicated list of validation classes
|
|
63
81
|
def validation_classes
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
.uniq
|
|
82
|
+
warn "[DEPRECATION] Servactory::Maintenance::Options::Collection#validation_classes is deprecated. " \
|
|
83
|
+
"Use #validations_for_checks instead."
|
|
84
|
+
validations_for_checks.map(&:last).uniq
|
|
68
85
|
end
|
|
69
86
|
|
|
70
|
-
#
|
|
87
|
+
# @deprecated Use {#validations_for_checks} instead.
|
|
71
88
|
#
|
|
72
89
|
# @return [Hash{Symbol => Object}] option names mapped to normalized bodies
|
|
73
90
|
def options_for_checks
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
91
|
+
warn "[DEPRECATION] Servactory::Maintenance::Options::Collection#options_for_checks is deprecated. " \
|
|
92
|
+
"Use #validations_for_checks instead."
|
|
93
|
+
check_options_by_key
|
|
77
94
|
end
|
|
78
95
|
|
|
79
96
|
# Returns options that need validation checks as an array of tuples.
|
|
@@ -115,13 +132,27 @@ module Servactory
|
|
|
115
132
|
|
|
116
133
|
private
|
|
117
134
|
|
|
135
|
+
# Builds the result of the deprecated `options_for_checks` methods
|
|
136
|
+
# without emitting a deprecation warning.
|
|
137
|
+
#
|
|
138
|
+
# @return [Hash{Symbol => Object}] option names mapped to normalized bodies
|
|
139
|
+
def check_options_by_key
|
|
140
|
+
validations_for_checks.to_h { |check_key, check_options, _| [check_key, check_options] }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Resets memoized data derived from the collection contents.
|
|
144
|
+
#
|
|
145
|
+
# @return [void]
|
|
146
|
+
def reset_memoized_caches
|
|
147
|
+
@validations_for_checks = nil
|
|
148
|
+
@options_index = nil
|
|
149
|
+
end
|
|
150
|
+
|
|
118
151
|
# Builds and caches a hash index for O(1) option lookups.
|
|
119
152
|
#
|
|
120
153
|
# @return [Hash{Symbol => Option}] option names mapped to Option instances
|
|
121
154
|
def options_index
|
|
122
|
-
@options_index ||=
|
|
123
|
-
index[option.name] = option
|
|
124
|
-
end
|
|
155
|
+
@options_index ||= @collection.to_h { |option| [option.name, option] }
|
|
125
156
|
end
|
|
126
157
|
|
|
127
158
|
# Extracts the normalized body value from an option.
|
|
@@ -17,7 +17,7 @@ module Servactory
|
|
|
17
17
|
:body_key,
|
|
18
18
|
:return_value_on_access
|
|
19
19
|
|
|
20
|
-
# rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
|
|
20
|
+
# rubocop:disable-next Metrics/MethodLength, Metrics/ParameterLists
|
|
21
21
|
def initialize(
|
|
22
22
|
name:,
|
|
23
23
|
attribute:,
|
|
@@ -54,7 +54,6 @@ module Servactory
|
|
|
54
54
|
|
|
55
55
|
apply_dynamic_methods_to(attribute:)
|
|
56
56
|
end
|
|
57
|
-
# rubocop:enable Metrics/MethodLength, Metrics/ParameterLists
|
|
58
57
|
|
|
59
58
|
def value
|
|
60
59
|
return body unless body.is_a?(Hash)
|
|
@@ -72,7 +71,7 @@ module Servactory
|
|
|
72
71
|
|
|
73
72
|
private
|
|
74
73
|
|
|
75
|
-
# rubocop:disable Metrics/MethodLength
|
|
74
|
+
# rubocop:disable-next Metrics/MethodLength
|
|
76
75
|
def construct_body(
|
|
77
76
|
original_value:,
|
|
78
77
|
options:,
|
|
@@ -81,9 +80,7 @@ module Servactory
|
|
|
81
80
|
body_fallback:,
|
|
82
81
|
detect_advanced_mode:
|
|
83
82
|
)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
raw_value = options.fetch(@name, body_fallback)
|
|
83
|
+
raw_value = original_value.nil? ? options.fetch(@name, body_fallback) : original_value
|
|
87
84
|
|
|
88
85
|
result = if detect_advanced_mode
|
|
89
86
|
use_advanced_mode(
|
|
@@ -98,12 +95,6 @@ module Servactory
|
|
|
98
95
|
|
|
99
96
|
apply_normalizer(result)
|
|
100
97
|
end
|
|
101
|
-
# rubocop:enable Metrics/MethodLength
|
|
102
|
-
|
|
103
|
-
def wrap_and_normalize(value)
|
|
104
|
-
result = wrap_value(value)
|
|
105
|
-
apply_normalizer(result)
|
|
106
|
-
end
|
|
107
98
|
|
|
108
99
|
def apply_normalizer(body)
|
|
109
100
|
return body unless @normalizer && body.is_a?(Hash)
|
|
@@ -78,9 +78,10 @@ module Servactory
|
|
|
78
78
|
create_option(
|
|
79
79
|
name: :types,
|
|
80
80
|
validation_class:,
|
|
81
|
-
original_value:
|
|
81
|
+
original_value: @options[:type],
|
|
82
82
|
need_for_checks: true,
|
|
83
83
|
body_key: :is,
|
|
84
|
+
body_value: nil,
|
|
84
85
|
body_fallback: nil,
|
|
85
86
|
detect_advanced_mode: true,
|
|
86
87
|
return_value_on_access: true,
|
|
@@ -142,17 +143,6 @@ module Servactory
|
|
|
142
143
|
)
|
|
143
144
|
end
|
|
144
145
|
|
|
145
|
-
def extract_types_value
|
|
146
|
-
type_option = @options[:type]
|
|
147
|
-
return nil if type_option.nil?
|
|
148
|
-
|
|
149
|
-
# Advanced Mode: type: { is: String, message: "..." }
|
|
150
|
-
return nil if type_option.is_a?(Hash) && type_option.key?(:is)
|
|
151
|
-
|
|
152
|
-
# Simple Mode: type: String or type: [String, Integer]
|
|
153
|
-
Array(type_option).uniq
|
|
154
|
-
end
|
|
155
|
-
|
|
156
146
|
########################################################################
|
|
157
147
|
|
|
158
148
|
def required_define_methods
|
|
@@ -21,11 +21,19 @@ module Servactory
|
|
|
21
21
|
# input :id, type: Integer
|
|
22
22
|
# input :data, type: [Hash, Array] # union type
|
|
23
23
|
# input :status, type: String, required: false
|
|
24
|
+
# input :email, type: { is: String, message: "Email must be a String" }
|
|
24
25
|
#
|
|
25
26
|
# internal :result, type: CustomType
|
|
26
27
|
# output :response, type: Hash
|
|
27
28
|
# end
|
|
28
29
|
# ```
|
|
30
|
+
#
|
|
31
|
+
# ## Custom Message
|
|
32
|
+
#
|
|
33
|
+
# Advanced mode `type: { is:, message: }` replaces the default error
|
|
34
|
+
# message for every type failure of the attribute. A Proc message
|
|
35
|
+
# receives `service:`, `input:`/`internal:`/`output:`, `value:`,
|
|
36
|
+
# `expected_type:` and `given_type:`.
|
|
29
37
|
class Type
|
|
30
38
|
extend Concerns::ErrorBuilder
|
|
31
39
|
|
|
@@ -51,7 +59,7 @@ module Servactory
|
|
|
51
59
|
|
|
52
60
|
return if error_data.nil?
|
|
53
61
|
|
|
54
|
-
build_error_message(error_data)
|
|
62
|
+
build_error_message(attribute:, error_data:)
|
|
55
63
|
end
|
|
56
64
|
|
|
57
65
|
# Determines if type validation should run for given attribute.
|
|
@@ -67,7 +75,7 @@ module Servactory
|
|
|
67
75
|
# @param value [Object] Current value (for optional input check)
|
|
68
76
|
# @param check_key [Symbol] Current validation check key
|
|
69
77
|
# @return [Boolean] true if type validation should run
|
|
70
|
-
# rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
78
|
+
# rubocop:disable-next Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
71
79
|
def self.should_be_checked_for?(attribute, value, check_key)
|
|
72
80
|
check_key == :types && (
|
|
73
81
|
(
|
|
@@ -81,7 +89,6 @@ module Servactory
|
|
|
81
89
|
) || attribute.internal? || attribute.output?
|
|
82
90
|
)
|
|
83
91
|
end
|
|
84
|
-
# rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
85
92
|
private_class_method :should_be_checked_for?
|
|
86
93
|
|
|
87
94
|
# Computes prepared value with default substitution for optional inputs.
|
|
@@ -100,10 +107,21 @@ module Servactory
|
|
|
100
107
|
|
|
101
108
|
# Builds error message from validation error data.
|
|
102
109
|
#
|
|
110
|
+
# Uses the custom message from `type: { is:, message: }` if provided,
|
|
111
|
+
# otherwise falls back to the default message.
|
|
112
|
+
#
|
|
113
|
+
# @param attribute [Inputs::Input, Internals::Internal, Outputs::Output] Failed attribute
|
|
103
114
|
# @param error_data [Hash] Error data from TypeValidator.validate
|
|
104
115
|
# @return [String] Processed error message
|
|
105
|
-
def self.build_error_message(error_data)
|
|
106
|
-
|
|
116
|
+
def self.build_error_message(attribute:, error_data:)
|
|
117
|
+
message = attribute.collection_of_options.find_by(name: :types).body[:message]
|
|
118
|
+
return process_message(error_data[:message], **error_data) if message.blank?
|
|
119
|
+
|
|
120
|
+
process_message(
|
|
121
|
+
message,
|
|
122
|
+
**attribute.typed_actor_kwargs,
|
|
123
|
+
**error_data.slice(:service, :value, :expected_type, :given_type)
|
|
124
|
+
)
|
|
107
125
|
end
|
|
108
126
|
private_class_method :build_error_message
|
|
109
127
|
end
|
|
@@ -17,12 +17,18 @@ module Servactory
|
|
|
17
17
|
private
|
|
18
18
|
|
|
19
19
|
def output(name, *helpers, **options)
|
|
20
|
-
|
|
20
|
+
output = Output.new(
|
|
21
21
|
name,
|
|
22
22
|
*helpers,
|
|
23
23
|
option_helpers: config.output_option_helpers,
|
|
24
24
|
**options
|
|
25
25
|
)
|
|
26
|
+
|
|
27
|
+
if output.types.empty?
|
|
28
|
+
raise ArgumentError, "[#{self.name}] Output attribute `#{name}` must have the `type` option"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
collection_of_outputs << output
|
|
26
32
|
end
|
|
27
33
|
|
|
28
34
|
def collection_of_outputs
|
data/lib/servactory/result.rb
CHANGED
|
@@ -41,7 +41,7 @@ module Servactory
|
|
|
41
41
|
# ```ruby
|
|
42
42
|
# result.active? # Equivalent to Utils.query_attribute(result.active)
|
|
43
43
|
# ```
|
|
44
|
-
class Result
|
|
44
|
+
class Result # rubocop:disable Metrics/ClassLength
|
|
45
45
|
# Internal container for service output values.
|
|
46
46
|
#
|
|
47
47
|
# Provides dynamic method access to output values.
|
|
@@ -120,6 +120,10 @@ module Servactory
|
|
|
120
120
|
|
|
121
121
|
private_constant :Outputs
|
|
122
122
|
|
|
123
|
+
# Result state keys exposed by {#deconstruct_keys}.
|
|
124
|
+
STATE_KEYS = %i[success failure error].freeze
|
|
125
|
+
private_constant :STATE_KEYS
|
|
126
|
+
|
|
123
127
|
############################################################################
|
|
124
128
|
|
|
125
129
|
# Creates a success result for the given context.
|
|
@@ -198,20 +202,27 @@ module Servactory
|
|
|
198
202
|
|
|
199
203
|
# Converts outputs to hash.
|
|
200
204
|
#
|
|
201
|
-
#
|
|
205
|
+
# Returns only outputs that were assigned during the call, including
|
|
206
|
+
# outputs explicitly assigned nil. Declared outputs that were never
|
|
207
|
+
# assigned are absent. Excludes predicate methods from the result hash.
|
|
202
208
|
#
|
|
203
|
-
# @return [Hash<Symbol, Object>]
|
|
209
|
+
# @return [Hash<Symbol, Object>] Assigned output name-value pairs
|
|
204
210
|
#
|
|
205
211
|
# @example
|
|
206
|
-
# result.to_h # => { user: #<User>, token:
|
|
212
|
+
# result.to_h # => { user: #<User>, token: nil }
|
|
207
213
|
def to_h
|
|
208
|
-
|
|
214
|
+
hash = {}
|
|
215
|
+
outputs.send(:output_names).each { |key| hash[key] = outputs.public_send(key) }
|
|
216
|
+
hash
|
|
209
217
|
end
|
|
210
218
|
|
|
211
219
|
# Pattern matching support.
|
|
212
220
|
#
|
|
213
221
|
# Returns hash of result state and output values for use with case/in.
|
|
214
|
-
#
|
|
222
|
+
# Output keys match {#to_h}: only assigned outputs are present.
|
|
223
|
+
# State keys (:success, :failure, :error) take priority over output names:
|
|
224
|
+
# outputs with these names are not included, and :error is present only
|
|
225
|
+
# for failures.
|
|
215
226
|
#
|
|
216
227
|
# @param keys [Array<Symbol>, nil] Keys to include, or nil for all
|
|
217
228
|
# @return [Hash<Symbol, Object>] Hash of state and outputs for pattern matching
|
|
@@ -234,6 +245,8 @@ module Servactory
|
|
|
234
245
|
available[:error] = error if failure?
|
|
235
246
|
|
|
236
247
|
outputs.send(:output_names).each do |name|
|
|
248
|
+
next if STATE_KEYS.include?(name)
|
|
249
|
+
|
|
237
250
|
available[name] = outputs.public_send(name)
|
|
238
251
|
end
|
|
239
252
|
|
|
@@ -347,7 +360,7 @@ module Servactory
|
|
|
347
360
|
Outputs.new(
|
|
348
361
|
outputs: build_outputs_hash,
|
|
349
362
|
predicate_methods_enabled:
|
|
350
|
-
@context.is_a?(Servactory::TestKit::Result) || @context.config.predicate_methods_enabled
|
|
363
|
+
@context.is_a?(Servactory::TestKit::Result) || @context.class.config.predicate_methods_enabled
|
|
351
364
|
)
|
|
352
365
|
end
|
|
353
366
|
|
|
@@ -374,7 +387,7 @@ module Servactory
|
|
|
374
387
|
def rescue_no_method_error_with(exception:) # rubocop:disable Metrics/MethodLength
|
|
375
388
|
raise exception if @context.blank? || @context.instance_of?(Servactory::TestKit::Result)
|
|
376
389
|
|
|
377
|
-
raise @context.config.failure_class.new(
|
|
390
|
+
raise @context.class.config.failure_class.new(
|
|
378
391
|
type: :base,
|
|
379
392
|
message: @context.send(:servactory_service_info).translate(
|
|
380
393
|
"common.undefined_method.missing_name",
|