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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f404944fef3e958464a2287b2aa1ca008cd5c6a04b0de7dbf3099f871878eee2
|
|
4
|
+
data.tar.gz: fd55568c5b5a48003ce84f1cb9aa64adbae0140b707517f99186ab2064127d94
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a2a7b707d93c5e210c9c0eefb54bbfd3374ac48fda5afbcce8669ec4eda8f1358c063a9a7c8121b3ea6c651fc4c8748e380f72c8a33ca13ab586d39b89fbe505
|
|
7
|
+
data.tar.gz: 36f8a525ca8a622771136ed8955a74091fd2a17a3aa444871c711048c45f60c6e612eaec28cd7220f1663762095fac203d9cb7996b7630448cc03d2964763cd7
|
data/README.md
CHANGED
|
@@ -96,6 +96,11 @@ class SessionsController < ApplicationController
|
|
|
96
96
|
end
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
+
## 🧩 Ecosystem
|
|
100
|
+
|
|
101
|
+
- [**Stroma**](https://github.com/servactory/stroma) — A foundation for building modular, extensible DSLs in Ruby
|
|
102
|
+
- [**OpenTelemetry Instrumentation**](https://github.com/servactory/opentelemetry-instrumentation-servactory) — Automatic tracing of service calls and actions with span attributes and failure detection
|
|
103
|
+
|
|
99
104
|
## 🤝 Contributing
|
|
100
105
|
|
|
101
106
|
We love contributions! Check out our [Contributing Guide](https://servactory.com/CONTRIBUTING) to get started.
|
data/lib/generators/README.md
CHANGED
|
@@ -19,7 +19,7 @@ rails generate servactory:service NAME [inputs] [options]
|
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
**Arguments:**
|
|
22
|
-
- `NAME` — Service name (e.g., `Users::Create`, `
|
|
22
|
+
- `NAME` — Service name (e.g., `Users::Create`, `Orders::Process`)
|
|
23
23
|
- `inputs` — Input attributes with optional types (e.g., `email:string name:String user:User`)
|
|
24
24
|
|
|
25
25
|
**Options:**
|
|
@@ -6,7 +6,7 @@ Usage:
|
|
|
6
6
|
|
|
7
7
|
Options:
|
|
8
8
|
--namespace=NAMESPACE Base namespace for services (default: ApplicationService)
|
|
9
|
-
--locales=LOCALES Locales to install (available: en, ru, de, fr, es, it)
|
|
9
|
+
--locales=LOCALES Locales to install (available: en, ru, de, fr, es, it, ja)
|
|
10
10
|
--minimal Generate minimal setup without configuration examples
|
|
11
11
|
--path=PATH Path to install service files (default: app/services)
|
|
12
12
|
|
|
@@ -21,7 +21,7 @@ Examples:
|
|
|
21
21
|
Creates app/services/my_app/services/ with all files
|
|
22
22
|
|
|
23
23
|
rails generate servactory:install --locales=en,de,fr
|
|
24
|
-
Also copies locale files to config/locales/ (available: en, ru, de, fr, es, it)
|
|
24
|
+
Also copies locale files to config/locales/ (available: en, ru, de, fr, es, it, ja)
|
|
25
25
|
|
|
26
26
|
rails generate servactory:install --minimal
|
|
27
27
|
Creates minimal setup without configuration examples
|
|
@@ -5,7 +5,7 @@ Usage:
|
|
|
5
5
|
rails generate servactory:rspec NAME [inputs] [options]
|
|
6
6
|
|
|
7
7
|
Arguments:
|
|
8
|
-
NAME The name of the service (e.g., Users::Create,
|
|
8
|
+
NAME The name of the service (e.g., Users::Create, Orders::Process)
|
|
9
9
|
inputs Input attributes with optional types (e.g., email:string, user:User)
|
|
10
10
|
|
|
11
11
|
Options:
|
|
@@ -33,14 +33,14 @@ Examples:
|
|
|
33
33
|
rails generate servactory:rspec Users::Create email:String name:String
|
|
34
34
|
Creates spec/services/users/create_spec.rb with Ruby class types
|
|
35
35
|
|
|
36
|
-
rails generate servactory:rspec
|
|
36
|
+
rails generate servactory:rspec Orders::Process user:User event:Event
|
|
37
37
|
Creates spec with custom model/class type inputs
|
|
38
38
|
|
|
39
39
|
rails generate servactory:rspec Users::Create --path=lib/my_gem/services
|
|
40
40
|
Creates spec/my_gem/services/users/create_spec.rb for gem/library use
|
|
41
41
|
|
|
42
|
-
rails generate servactory:rspec
|
|
42
|
+
rails generate servactory:rspec Orders::Process --call-method=call
|
|
43
43
|
Creates spec using .call instead of .call!
|
|
44
44
|
|
|
45
|
-
rails generate servactory:rspec
|
|
45
|
+
rails generate servactory:rspec Caches::Write --skip-pending
|
|
46
46
|
Creates spec without pending placeholder
|
|
@@ -78,8 +78,8 @@ module Servactory
|
|
|
78
78
|
when "Float" then "1.0"
|
|
79
79
|
when "TrueClass", "[TrueClass, FalseClass]" then "true"
|
|
80
80
|
when "FalseClass" then "false"
|
|
81
|
-
when "Array" then "
|
|
82
|
-
when "Hash" then "
|
|
81
|
+
when "Array" then '["example"]'
|
|
82
|
+
when "Hash" then '{ key: "value" }'
|
|
83
83
|
when "Symbol" then ":example"
|
|
84
84
|
when "Date" then "Date.current"
|
|
85
85
|
when "DateTime" then "DateTime.current"
|
|
@@ -5,7 +5,7 @@ Usage:
|
|
|
5
5
|
rails generate servactory:service NAME [inputs] [options]
|
|
6
6
|
|
|
7
7
|
Arguments:
|
|
8
|
-
NAME The name of the service (e.g., Users::Create,
|
|
8
|
+
NAME The name of the service (e.g., Users::Create, Orders::Process)
|
|
9
9
|
inputs Input attributes with optional types (e.g., email:string, user:User)
|
|
10
10
|
|
|
11
11
|
Options:
|
|
@@ -38,14 +38,14 @@ Examples:
|
|
|
38
38
|
rails generate servactory:service Users::Create email:String name:String
|
|
39
39
|
Creates service with typed inputs (capitalized Ruby class names)
|
|
40
40
|
|
|
41
|
-
rails generate servactory:service
|
|
41
|
+
rails generate servactory:service Orders::Process user:User event:Event
|
|
42
42
|
Creates service with custom model/class type inputs
|
|
43
43
|
|
|
44
|
-
rails generate servactory:service
|
|
44
|
+
rails generate servactory:service Orders::Process role:Admin::Users::Role
|
|
45
45
|
Creates service with nested namespace type
|
|
46
46
|
|
|
47
|
-
rails generate servactory:service
|
|
47
|
+
rails generate servactory:service Emails::Send recipient
|
|
48
48
|
Creates service with untyped input (defaults to String)
|
|
49
49
|
|
|
50
|
-
rails generate servactory:service
|
|
50
|
+
rails generate servactory:service Caches::Write key:String --skip-output
|
|
51
51
|
Creates service without default output declaration
|
|
@@ -30,10 +30,6 @@ module Servactory
|
|
|
30
30
|
class Collection
|
|
31
31
|
extend Forwardable
|
|
32
32
|
|
|
33
|
-
# @!method <<(action)
|
|
34
|
-
# Adds an action to the collection.
|
|
35
|
-
# @param action [Action] The action to add
|
|
36
|
-
# @return [Set] The updated collection
|
|
37
33
|
# @!method each
|
|
38
34
|
# Iterates over all actions in the collection.
|
|
39
35
|
# @yield [Action] Each action in the collection
|
|
@@ -50,7 +46,7 @@ module Servactory
|
|
|
50
46
|
# @!method empty?
|
|
51
47
|
# Checks if the collection is empty.
|
|
52
48
|
# @return [Boolean] true if no actions registered
|
|
53
|
-
def_delegators :@collection,
|
|
49
|
+
def_delegators :@collection, :each, :to_h, :sort_by, :size, :empty?
|
|
54
50
|
|
|
55
51
|
# Creates a new actions collection.
|
|
56
52
|
#
|
|
@@ -59,6 +55,26 @@ module Servactory
|
|
|
59
55
|
@collection = collection
|
|
60
56
|
end
|
|
61
57
|
|
|
58
|
+
# Duplicates the collection, resetting memoized caches.
|
|
59
|
+
#
|
|
60
|
+
# @param original [Collection] The collection being duplicated
|
|
61
|
+
# @return [void]
|
|
62
|
+
def initialize_dup(original)
|
|
63
|
+
super
|
|
64
|
+
@collection = original.instance_variable_get(:@collection).dup
|
|
65
|
+
@sorted_by_position = nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Adds an action to the collection, invalidating the sorted cache.
|
|
69
|
+
#
|
|
70
|
+
# @param action [Action] The action to add
|
|
71
|
+
# @return [Collection] self
|
|
72
|
+
def <<(action)
|
|
73
|
+
@collection << action
|
|
74
|
+
@sorted_by_position = nil
|
|
75
|
+
self
|
|
76
|
+
end
|
|
77
|
+
|
|
62
78
|
# Returns a new collection with actions sorted by position.
|
|
63
79
|
#
|
|
64
80
|
# @return [Collection] New collection with actions in position order
|
|
@@ -6,12 +6,30 @@ module Servactory
|
|
|
6
6
|
class Collection
|
|
7
7
|
extend Forwardable
|
|
8
8
|
|
|
9
|
-
def_delegators :@collection,
|
|
9
|
+
def_delegators :@collection, :each, :to_h, :sort_by, :size, :empty?
|
|
10
10
|
|
|
11
11
|
def initialize(collection = Set.new)
|
|
12
12
|
@collection = collection
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
def initialize_dup(original)
|
|
16
|
+
super
|
|
17
|
+
@collection = original.instance_variable_get(:@collection).dup
|
|
18
|
+
@sorted_by_position = nil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def <<(stage)
|
|
22
|
+
@collection << stage
|
|
23
|
+
@sorted_by_position = nil
|
|
24
|
+
self
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def merge(other)
|
|
28
|
+
@collection.merge(other)
|
|
29
|
+
@sorted_by_position = nil
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
|
|
15
33
|
def sorted_by_position
|
|
16
34
|
@sorted_by_position ||= Collection.new(sort_by(&:position))
|
|
17
35
|
end
|
|
@@ -56,7 +56,7 @@ module Servactory
|
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
def raise_message_with!(formatted_text)
|
|
59
|
-
raise @context.config.failure_class.new(
|
|
59
|
+
raise @context.class.config.failure_class.new(
|
|
60
60
|
type: :base,
|
|
61
61
|
message: @context.send(:servactory_service_info).translate(
|
|
62
62
|
"methods.cannot_be_overwritten",
|
|
@@ -179,7 +179,7 @@ module Servactory
|
|
|
179
179
|
# @param exception [Exception] The exception to handle
|
|
180
180
|
# @return [Exception, nil] The exception if handled, nil otherwise
|
|
181
181
|
def rescue_with_handler(exception) # rubocop:disable Metrics/MethodLength
|
|
182
|
-
_, handler = @context.config.action_rescue_handlers.reverse_each.detect do |class_or_name, _|
|
|
182
|
+
_, handler = @context.class.config.action_rescue_handlers.reverse_each.detect do |class_or_name, _|
|
|
183
183
|
if (detected_exception = Servactory::Utils.constantize_class(class_or_name))
|
|
184
184
|
detected_exception === exception # rubocop:disable Style/CaseEquality
|
|
185
185
|
end
|
|
@@ -63,7 +63,7 @@ module Servactory
|
|
|
63
63
|
|
|
64
64
|
def default_input_option_helpers(config)
|
|
65
65
|
Set[
|
|
66
|
-
Servactory::Maintenance::
|
|
66
|
+
Servactory::Maintenance::Options::Helper
|
|
67
67
|
.new(name: :optional, equivalent: { required: false }),
|
|
68
68
|
Servactory::ToolKit::DynamicOptions::ConsistsOf
|
|
69
69
|
.use(collection_mode_class_names: config.collection_mode_class_names),
|
|
@@ -60,15 +60,15 @@ module Servactory
|
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
def input_option_helpers(input_option_helpers)
|
|
63
|
-
@config.input_option_helpers
|
|
63
|
+
register_option_helpers_with(:input_option_helpers, @config.input_option_helpers, input_option_helpers)
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
def internal_option_helpers(internal_option_helpers)
|
|
67
|
-
@config.internal_option_helpers
|
|
67
|
+
register_option_helpers_with(:internal_option_helpers, @config.internal_option_helpers, internal_option_helpers)
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def output_option_helpers(output_option_helpers)
|
|
71
|
-
@config.output_option_helpers
|
|
71
|
+
register_option_helpers_with(:output_option_helpers, @config.output_option_helpers, output_option_helpers)
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
def collection_mode_class_names(collection_mode_class_names)
|
|
@@ -113,6 +113,19 @@ module Servactory
|
|
|
113
113
|
value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
|
114
114
|
end
|
|
115
115
|
|
|
116
|
+
def register_option_helpers_with(config_name, collection, option_helpers)
|
|
117
|
+
option_helpers.each do |option_helper|
|
|
118
|
+
case collection.register(option_helper)
|
|
119
|
+
when :reserved
|
|
120
|
+
raise_error_about_reserved_option_helper_with(config_name, option_helper.name)
|
|
121
|
+
when :duplicated
|
|
122
|
+
raise_error_about_duplicated_option_helper_with(config_name, option_helper.name)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
collection
|
|
127
|
+
end
|
|
128
|
+
|
|
116
129
|
##########################################################################
|
|
117
130
|
|
|
118
131
|
def raise_error_about_wrong_exception_class_with(config_name, value)
|
|
@@ -142,6 +155,20 @@ module Servactory
|
|
|
142
155
|
"The `#{value.inspect}` value must be `TrueClass` or `FalseClass`. " \
|
|
143
156
|
"See configuration example here: https://servactory.com/guide/configuration"
|
|
144
157
|
end
|
|
158
|
+
|
|
159
|
+
def raise_error_about_reserved_option_helper_with(config_name, option_helper_name)
|
|
160
|
+
raise ArgumentError,
|
|
161
|
+
"Error in `#{config_name}` configuration. " \
|
|
162
|
+
"The `#{option_helper_name}` option helper name is reserved by a built-in option helper. " \
|
|
163
|
+
"See configuration example here: https://servactory.com/guide/configuration"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def raise_error_about_duplicated_option_helper_with(config_name, option_helper_name)
|
|
167
|
+
raise ArgumentError,
|
|
168
|
+
"Error in `#{config_name}` configuration. " \
|
|
169
|
+
"The `#{option_helper_name}` option helper is already defined in this configuration. " \
|
|
170
|
+
"See configuration example here: https://servactory.com/guide/configuration"
|
|
171
|
+
end
|
|
145
172
|
end
|
|
146
173
|
end
|
|
147
174
|
end
|
|
@@ -9,7 +9,20 @@ module Servactory
|
|
|
9
9
|
#
|
|
10
10
|
# OptionHelpersCollection provides a unified interface for storing and
|
|
11
11
|
# querying Helper instances used by the configuration system.
|
|
12
|
-
# It
|
|
12
|
+
# It keeps helpers in an insertion-ordered Hash keyed by helper name,
|
|
13
|
+
# so every name resolves to exactly one helper.
|
|
14
|
+
#
|
|
15
|
+
# ## Built-in Helpers
|
|
16
|
+
#
|
|
17
|
+
# Helpers passed to the constructor are built-in: their names are
|
|
18
|
+
# reserved, and only `replace` may swap them (used to rebind built-in
|
|
19
|
+
# dynamic options to the configuration of a subclass).
|
|
20
|
+
#
|
|
21
|
+
# ## Inheritance
|
|
22
|
+
#
|
|
23
|
+
# A collection accepts one helper per name. A duplicate, made when
|
|
24
|
+
# a service class is inherited, may replace a helper registered before
|
|
25
|
+
# duplication in place without affecting the original.
|
|
13
26
|
#
|
|
14
27
|
# ## Usage
|
|
15
28
|
#
|
|
@@ -17,84 +30,184 @@ module Servactory
|
|
|
17
30
|
# to manage registered option helpers:
|
|
18
31
|
#
|
|
19
32
|
# ```ruby
|
|
20
|
-
# collection = OptionHelpersCollection.new
|
|
21
|
-
# collection
|
|
33
|
+
# collection = OptionHelpersCollection.new(builtin_helpers)
|
|
34
|
+
# collection.register(helper) # => :registered
|
|
35
|
+
# collection << other_helper # => collection, or raises ArgumentError
|
|
36
|
+
# collection.merge([third_helper]) # => collection, or raises ArgumentError
|
|
22
37
|
#
|
|
23
38
|
# collection.find_by(name: :must) # => helper instance
|
|
24
39
|
# collection.dynamic_options # => filtered OptionHelpersCollection
|
|
25
40
|
# ```
|
|
26
41
|
#
|
|
42
|
+
# ## Enumeration
|
|
43
|
+
#
|
|
44
|
+
# The collection includes Enumerable and yields helpers in registration
|
|
45
|
+
# order. Enumerable methods return plain values rather than collections:
|
|
46
|
+
# `filter` and `map` return Arrays, `find` returns a helper or nil, and
|
|
47
|
+
# `each_with_object` returns the memo object. Use `dynamic_options`
|
|
48
|
+
# to get a filtered OptionHelpersCollection.
|
|
49
|
+
#
|
|
27
50
|
# ## Performance
|
|
28
51
|
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
52
|
+
# Registration and lookup by name are O(1) Hash operations without
|
|
53
|
+
# cached indexes, so helpers registered after attributes are declared
|
|
54
|
+
# are visible to subsequent declarations.
|
|
31
55
|
#
|
|
32
56
|
class OptionHelpersCollection
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def_delegators :@collection,
|
|
36
|
-
:<<,
|
|
37
|
-
:filter,
|
|
38
|
-
:each_with_object,
|
|
39
|
-
:map,
|
|
40
|
-
:merge
|
|
57
|
+
include Enumerable
|
|
41
58
|
|
|
42
|
-
# Initializes the collection with
|
|
59
|
+
# Initializes the collection with built-in helpers.
|
|
43
60
|
#
|
|
44
|
-
# @param
|
|
61
|
+
# @param builtin_helpers [Enumerable<Maintenance::Options::Helper>] helpers whose names are reserved
|
|
45
62
|
# @return [OptionHelpersCollection]
|
|
46
|
-
def initialize(
|
|
47
|
-
@
|
|
63
|
+
def initialize(builtin_helpers = [])
|
|
64
|
+
@helpers = builtin_helpers.to_h { |helper| [helper.name, helper] }
|
|
65
|
+
@builtin_names = @helpers.keys.to_set.freeze
|
|
66
|
+
@own_names = Set.new
|
|
48
67
|
end
|
|
49
68
|
|
|
50
|
-
# Duplicates the collection
|
|
69
|
+
# Duplicates the collection so that registrations do not leak into the original.
|
|
70
|
+
#
|
|
71
|
+
# Helpers of the original may be replaced once in the duplicate.
|
|
51
72
|
#
|
|
52
73
|
# @param original [OptionHelpersCollection] the collection being duplicated
|
|
53
74
|
# @return [void]
|
|
54
75
|
def initialize_dup(original)
|
|
55
76
|
super
|
|
56
|
-
@
|
|
57
|
-
@
|
|
77
|
+
@helpers = original.instance_variable_get(:@helpers).dup
|
|
78
|
+
@own_names = Set.new
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Iterates over helpers in registration order.
|
|
82
|
+
#
|
|
83
|
+
# @yieldparam helper [Maintenance::Options::Helper] each registered helper
|
|
84
|
+
# @return [OptionHelpersCollection, Enumerator] self, or an enumerator without a block
|
|
85
|
+
def each(&block)
|
|
86
|
+
return enum_for(:each) unless block_given?
|
|
87
|
+
|
|
88
|
+
@helpers.each_value(&block)
|
|
89
|
+
self
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Registers a helper under its name.
|
|
93
|
+
#
|
|
94
|
+
# A helper with a new name is appended; a helper replacing one registered
|
|
95
|
+
# before duplication keeps its position. The helpers are left unchanged
|
|
96
|
+
# unless the result is `:registered`.
|
|
97
|
+
#
|
|
98
|
+
# @param helper [Maintenance::Options::Helper] the helper to register
|
|
99
|
+
# @return [Symbol] `:registered`, `:skipped` when this very helper is
|
|
100
|
+
# already registered (the name then counts as registered by this collection
|
|
101
|
+
# itself), `:reserved` when the name belongs to a built-in helper,
|
|
102
|
+
# or `:duplicated` when this collection already registered the name itself
|
|
103
|
+
# since it was created or duplicated
|
|
104
|
+
def register(helper)
|
|
105
|
+
name = helper.name
|
|
106
|
+
|
|
107
|
+
if @helpers[name].equal?(helper)
|
|
108
|
+
@own_names << name
|
|
109
|
+
return :skipped
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
return :reserved if @builtin_names.include?(name)
|
|
113
|
+
return :duplicated if @own_names.include?(name)
|
|
114
|
+
|
|
115
|
+
@own_names << name
|
|
116
|
+
@helpers[name] = helper
|
|
117
|
+
:registered
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Registers a helper under its name, raising when the name is rejected.
|
|
121
|
+
#
|
|
122
|
+
# Follows `register`: a helper registered before duplication may be
|
|
123
|
+
# replaced once. Re-adding this very helper leaves the helpers unchanged
|
|
124
|
+
# but still counts as this collection registering the name itself, so
|
|
125
|
+
# after re-adding an inherited helper a different helper with that name
|
|
126
|
+
# raises instead of replacing it.
|
|
127
|
+
#
|
|
128
|
+
# @param helper [Maintenance::Options::Helper] the helper to register
|
|
129
|
+
# @return [OptionHelpersCollection] self
|
|
130
|
+
# @raise [ArgumentError] when the name belongs to a built-in helper
|
|
131
|
+
# or this collection already registered the name itself
|
|
132
|
+
def <<(helper)
|
|
133
|
+
case register(helper)
|
|
134
|
+
when :reserved
|
|
135
|
+
raise_error_about_reserved_name_with(helper.name)
|
|
136
|
+
when :duplicated
|
|
137
|
+
raise_error_about_duplicated_name_with(helper.name)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
self
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Registers every helper of the given enumerables in order with `<<`.
|
|
144
|
+
#
|
|
145
|
+
# Helpers preceding a rejected one stay registered.
|
|
146
|
+
#
|
|
147
|
+
# A built-in helper coming from another service's collection counts as
|
|
148
|
+
# a reserved name unless it is the very helper of this collection. Each
|
|
149
|
+
# service rebinds the built-in dynamic options (`consists_of`, `schema`)
|
|
150
|
+
# to its own configuration, so merging another service's whole
|
|
151
|
+
# collection raises.
|
|
152
|
+
#
|
|
153
|
+
# @param enums [Array<Enumerable<Maintenance::Options::Helper>>] the helpers to register
|
|
154
|
+
# @return [OptionHelpersCollection] self
|
|
155
|
+
# @raise [ArgumentError] when `<<` rejects a helper
|
|
156
|
+
def merge(*enums)
|
|
157
|
+
enums.each { |helpers| helpers.each { |helper| self << helper } }
|
|
158
|
+
self
|
|
58
159
|
end
|
|
59
160
|
|
|
60
161
|
# Returns a new collection containing only dynamic option helpers.
|
|
61
162
|
#
|
|
62
163
|
# @return [OptionHelpersCollection] filtered collection of dynamic helpers
|
|
63
164
|
def dynamic_options
|
|
64
|
-
OptionHelpersCollection.new
|
|
165
|
+
each_with_object(OptionHelpersCollection.new) do |helper, collection|
|
|
166
|
+
collection.register(helper) if helper.dynamic_option?
|
|
167
|
+
end
|
|
65
168
|
end
|
|
66
169
|
|
|
67
|
-
# Finds a helper by its name
|
|
170
|
+
# Finds a helper by its name with a direct Hash lookup.
|
|
68
171
|
#
|
|
69
172
|
# @param name [Symbol] the helper name to find
|
|
70
173
|
# @return [Maintenance::Options::Helper, nil] the found helper or nil
|
|
71
174
|
def find_by(name:)
|
|
72
|
-
|
|
175
|
+
@helpers[name]
|
|
73
176
|
end
|
|
74
177
|
|
|
75
|
-
# Replaces a helper by name with a new one,
|
|
178
|
+
# Replaces a built-in helper by name with a new one, keeping its position.
|
|
179
|
+
#
|
|
180
|
+
# Names that do not belong to a built-in helper are ignored.
|
|
76
181
|
#
|
|
77
|
-
# @param name [Symbol] the helper name to replace
|
|
78
|
-
# @param with [Maintenance::
|
|
182
|
+
# @param name [Symbol] the built-in helper name to replace
|
|
183
|
+
# @param with [Maintenance::Options::Helper] the replacement helper
|
|
79
184
|
# @return [void]
|
|
80
185
|
def replace(name:, with:)
|
|
81
|
-
|
|
82
|
-
return unless old
|
|
186
|
+
return unless @builtin_names.include?(name)
|
|
83
187
|
|
|
84
|
-
@
|
|
85
|
-
@collection.add(with)
|
|
86
|
-
@helpers_index = nil
|
|
188
|
+
@helpers[name] = with
|
|
87
189
|
end
|
|
88
190
|
|
|
89
191
|
private
|
|
90
192
|
|
|
91
|
-
#
|
|
193
|
+
# Raises an error about a name that belongs to a built-in helper.
|
|
92
194
|
#
|
|
93
|
-
# @
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
195
|
+
# @param name [Symbol] the rejected helper name
|
|
196
|
+
# @raise [ArgumentError] always
|
|
197
|
+
def raise_error_about_reserved_name_with(name)
|
|
198
|
+
raise ArgumentError,
|
|
199
|
+
"The `#{name}` option helper name is reserved by a built-in option helper. " \
|
|
200
|
+
"See configuration example here: https://servactory.com/guide/configuration"
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Raises an error about a name this collection already registered itself.
|
|
204
|
+
#
|
|
205
|
+
# @param name [Symbol] the rejected helper name
|
|
206
|
+
# @raise [ArgumentError] always
|
|
207
|
+
def raise_error_about_duplicated_name_with(name)
|
|
208
|
+
raise ArgumentError,
|
|
209
|
+
"The `#{name}` option helper is already registered in this collection. " \
|
|
210
|
+
"See configuration example here: https://servactory.com/guide/configuration"
|
|
98
211
|
end
|
|
99
212
|
end
|
|
100
213
|
end
|
|
@@ -45,7 +45,7 @@ module Servactory
|
|
|
45
45
|
# @param _args [Array] Method arguments (unused)
|
|
46
46
|
# @return [Object] Input value or predicate result
|
|
47
47
|
def method_missing(name, *_args)
|
|
48
|
-
predicate = @context.config.predicate_methods_enabled && name.end_with?("?")
|
|
48
|
+
predicate = @context.class.config.predicate_methods_enabled && name.end_with?("?")
|
|
49
49
|
|
|
50
50
|
input_name = predicate ? name.to_s.chomp("?").to_sym : name
|
|
51
51
|
|
|
@@ -38,7 +38,7 @@ module Servactory
|
|
|
38
38
|
private
|
|
39
39
|
|
|
40
40
|
def fetch_with(name:, &_block)
|
|
41
|
-
predicate = @context.config.predicate_methods_enabled && name.end_with?("?")
|
|
41
|
+
predicate = @context.class.config.predicate_methods_enabled && name.end_with?("?")
|
|
42
42
|
|
|
43
43
|
input_name = predicate ? name.to_s.chomp("?").to_sym : name
|
|
44
44
|
|
|
@@ -67,7 +67,7 @@ module Servactory
|
|
|
67
67
|
def resolve_input(name)
|
|
68
68
|
return true if @collection_of_inputs.find_by(name:)
|
|
69
69
|
|
|
70
|
-
@context.config.predicate_methods_enabled &&
|
|
70
|
+
@context.class.config.predicate_methods_enabled &&
|
|
71
71
|
name.to_s.end_with?("?") &&
|
|
72
72
|
@collection_of_inputs.find_by(name: name.to_s.chomp("?").to_sym)
|
|
73
73
|
end
|
|
@@ -60,7 +60,7 @@ module Servactory
|
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
def fetch_with(name:, &_block)
|
|
63
|
-
predicate = @context.config.predicate_methods_enabled && name.end_with?("?")
|
|
63
|
+
predicate = @context.class.config.predicate_methods_enabled && name.end_with?("?")
|
|
64
64
|
|
|
65
65
|
internal_name = predicate ? name.to_s.chomp("?").to_sym : name
|
|
66
66
|
internal = @collection_of_internals.find_by(name: internal_name)
|
|
@@ -89,7 +89,7 @@ module Servactory
|
|
|
89
89
|
def resolve_internal(name)
|
|
90
90
|
return true if @collection_of_internals.find_by(name:)
|
|
91
91
|
|
|
92
|
-
@context.config.predicate_methods_enabled &&
|
|
92
|
+
@context.class.config.predicate_methods_enabled &&
|
|
93
93
|
name.to_s.end_with?("?") &&
|
|
94
94
|
@collection_of_internals.find_by(name: name.to_s.chomp("?").to_sym)
|
|
95
95
|
end
|
|
@@ -52,7 +52,7 @@ module Servactory
|
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
def fetch_with(name:, &_block)
|
|
55
|
-
predicate = @context.config.predicate_methods_enabled && name.end_with?("?")
|
|
55
|
+
predicate = @context.class.config.predicate_methods_enabled && name.end_with?("?")
|
|
56
56
|
|
|
57
57
|
output_name = predicate ? name.to_s.chomp("?").to_sym : name
|
|
58
58
|
output = @collection_of_outputs.find_by(name: output_name)
|
|
@@ -67,7 +67,7 @@ module Servactory
|
|
|
67
67
|
def resolve_output(name)
|
|
68
68
|
return true if @collection_of_outputs.find_by(name:)
|
|
69
69
|
|
|
70
|
-
@context.config.predicate_methods_enabled &&
|
|
70
|
+
@context.class.config.predicate_methods_enabled &&
|
|
71
71
|
name.to_s.end_with?("?") &&
|
|
72
72
|
@collection_of_outputs.find_by(name: name.to_s.chomp("?").to_sym)
|
|
73
73
|
end
|