servactory 2.16.0 → 3.0.0.rc1
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 +38 -9
- data/config/locales/de.yml +134 -0
- data/config/locales/en.yml +15 -0
- data/config/locales/es.yml +134 -0
- data/config/locales/fr.yml +134 -0
- data/config/locales/it.yml +134 -0
- data/config/locales/ru.yml +15 -0
- data/lib/generators/README.md +45 -0
- data/lib/generators/servactory/base.rb +82 -0
- data/lib/generators/servactory/extension/USAGE +54 -0
- data/lib/generators/servactory/extension/extension_generator.rb +41 -0
- data/lib/generators/servactory/extension/templates/extension.rb.tt +62 -0
- data/lib/generators/servactory/install/USAGE +27 -0
- data/lib/generators/servactory/install/install_generator.rb +94 -0
- data/lib/generators/servactory/{templates/services/application_service/base.rb → install/templates/application_service/base.rb.tt} +29 -19
- data/lib/generators/servactory/{templates/services/application_service/exceptions.rb → install/templates/application_service/exceptions.rb.tt} +1 -1
- data/lib/generators/servactory/{templates/services/application_service/result.rb → install/templates/application_service/result.rb.tt} +1 -1
- data/lib/generators/servactory/rspec/USAGE +46 -0
- data/lib/generators/servactory/rspec/rspec_generator.rb +95 -0
- data/lib/generators/servactory/rspec/templates/service_spec.rb.tt +58 -0
- data/lib/generators/servactory/service/USAGE +51 -0
- data/lib/generators/servactory/service/service_generator.rb +56 -0
- data/lib/generators/servactory/service/templates/service.rb.tt +22 -0
- data/lib/servactory/actions/collection.rb +56 -1
- data/lib/servactory/actions/dsl.rb +11 -11
- data/lib/servactory/actions/tools/rules.rb +1 -1
- data/lib/servactory/actions/tools/runner.rb +111 -28
- data/lib/servactory/base.rb +1 -7
- data/lib/servactory/configuration/actions/aliases/collection.rb +5 -0
- data/lib/servactory/configuration/actions/rescue_handlers/collection.rb +5 -0
- data/lib/servactory/configuration/actions/shortcuts/collection.rb +5 -0
- data/lib/servactory/configuration/collection_mode/class_names_collection.rb +5 -0
- data/lib/servactory/configuration/config.rb +36 -0
- data/lib/servactory/configuration/configurable.rb +95 -0
- data/lib/servactory/configuration/dsl.rb +3 -27
- data/lib/servactory/configuration/factory.rb +20 -20
- data/lib/servactory/configuration/hash_mode/class_names_collection.rb +5 -0
- data/lib/servactory/configuration/option_helpers/option_helpers_collection.rb +5 -0
- data/lib/servactory/context/warehouse/inputs.rb +2 -2
- 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 +11 -7
- data/lib/servactory/dsl.rb +10 -8
- data/lib/servactory/maintenance/attributes/tools/validation.rb +1 -1
- data/lib/servactory/result.rb +2 -2
- data/lib/servactory/stroma/dsl.rb +118 -0
- data/lib/servactory/stroma/entry.rb +32 -0
- data/lib/servactory/stroma/exceptions/base.rb +45 -0
- data/lib/servactory/stroma/exceptions/invalid_hook_type.rb +29 -0
- data/lib/servactory/stroma/exceptions/key_already_registered.rb +32 -0
- data/lib/servactory/stroma/exceptions/registry_frozen.rb +33 -0
- data/lib/servactory/stroma/exceptions/registry_not_finalized.rb +33 -0
- data/lib/servactory/stroma/exceptions/unknown_hook_target.rb +39 -0
- data/lib/servactory/stroma/hooks/applier.rb +63 -0
- data/lib/servactory/stroma/hooks/collection.rb +103 -0
- data/lib/servactory/stroma/hooks/factory.rb +80 -0
- data/lib/servactory/stroma/hooks/hook.rb +74 -0
- data/lib/servactory/stroma/registry.rb +94 -0
- data/lib/servactory/stroma/settings/collection.rb +90 -0
- data/lib/servactory/stroma/settings/registry_settings.rb +88 -0
- data/lib/servactory/stroma/settings/setting.rb +113 -0
- data/lib/servactory/stroma/state.rb +59 -0
- data/lib/servactory/test_kit/fake_type.rb +23 -0
- data/lib/servactory/test_kit/result.rb +45 -0
- data/lib/servactory/test_kit/rspec/helpers/argument_matchers.rb +97 -0
- data/lib/servactory/test_kit/rspec/helpers/concerns/error_messages.rb +179 -0
- data/lib/servactory/test_kit/rspec/helpers/concerns/service_class_validation.rb +74 -0
- data/lib/servactory/test_kit/rspec/helpers/fluent.rb +110 -0
- data/lib/servactory/test_kit/rspec/helpers/input_validator.rb +149 -0
- data/lib/servactory/test_kit/rspec/helpers/legacy.rb +228 -0
- data/lib/servactory/test_kit/rspec/helpers/mock_executor.rb +256 -0
- data/lib/servactory/test_kit/rspec/helpers/output_validator.rb +121 -0
- data/lib/servactory/test_kit/rspec/helpers/service_mock_builder.rb +422 -0
- data/lib/servactory/test_kit/rspec/helpers/service_mock_config.rb +129 -0
- data/lib/servactory/test_kit/rspec/helpers.rb +51 -84
- data/lib/servactory/test_kit/rspec/matchers/base/attribute_matcher.rb +324 -0
- data/lib/servactory/test_kit/rspec/matchers/base/submatcher.rb +133 -0
- data/lib/servactory/test_kit/rspec/matchers/base/submatcher_context.rb +101 -0
- data/lib/servactory/test_kit/rspec/matchers/base/submatcher_registry.rb +205 -0
- data/lib/servactory/test_kit/rspec/matchers/concerns/attribute_data_access.rb +100 -0
- data/lib/servactory/test_kit/rspec/matchers/concerns/error_message_builder.rb +106 -0
- data/lib/servactory/test_kit/rspec/matchers/concerns/value_comparison.rb +97 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_input_matcher.rb +89 -219
- data/lib/servactory/test_kit/rspec/matchers/have_service_internal_matcher.rb +74 -166
- data/lib/servactory/test_kit/rspec/matchers/have_service_output_matcher.rb +238 -0
- data/lib/servactory/test_kit/rspec/matchers/result/be_failure_service_matcher.rb +257 -0
- data/lib/servactory/test_kit/rspec/matchers/result/be_success_service_matcher.rb +185 -0
- data/lib/servactory/test_kit/rspec/matchers/submatchers/input/default_submatcher.rb +81 -0
- data/lib/servactory/test_kit/rspec/matchers/submatchers/input/optional_submatcher.rb +62 -0
- data/lib/servactory/test_kit/rspec/matchers/submatchers/input/required_submatcher.rb +93 -0
- data/lib/servactory/test_kit/rspec/matchers/submatchers/input/valid_with_submatcher.rb +271 -0
- data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/consists_of_submatcher.rb +85 -0
- data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/inclusion_submatcher.rb +120 -0
- data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/message_submatcher.rb +115 -0
- data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/must_submatcher.rb +82 -0
- data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/schema_submatcher.rb +102 -0
- data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/target_submatcher.rb +125 -0
- data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/types_submatcher.rb +77 -0
- data/lib/servactory/test_kit/rspec/matchers.rb +126 -285
- data/lib/servactory/test_kit/utils/faker.rb +62 -2
- data/lib/servactory/tool_kit/dynamic_options/consists_of.rb +166 -0
- data/lib/servactory/tool_kit/dynamic_options/format.rb +195 -8
- data/lib/servactory/tool_kit/dynamic_options/inclusion.rb +219 -17
- data/lib/servactory/tool_kit/dynamic_options/max.rb +143 -0
- data/lib/servactory/tool_kit/dynamic_options/min.rb +143 -0
- data/lib/servactory/tool_kit/dynamic_options/multiple_of.rb +157 -8
- data/lib/servactory/tool_kit/dynamic_options/must.rb +194 -0
- data/lib/servactory/tool_kit/dynamic_options/schema.rb +226 -2
- data/lib/servactory/tool_kit/dynamic_options/target.rb +252 -0
- data/lib/servactory/version.rb +3 -3
- data/lib/servactory.rb +4 -0
- metadata +73 -25
- data/lib/generators/servactory/install_generator.rb +0 -21
- data/lib/generators/servactory/rspec_generator.rb +0 -88
- data/lib/generators/servactory/service_generator.rb +0 -49
- data/lib/servactory/configuration/setup.rb +0 -97
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/consists_of_matcher.rb +0 -68
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/inclusion_matcher.rb +0 -73
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/message_matcher.rb +0 -91
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/must_matcher.rb +0 -72
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/schema_matcher.rb +0 -92
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/types_matcher.rb +0 -72
- data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/default_matcher.rb +0 -69
- data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/optional_matcher.rb +0 -63
- data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/required_matcher.rb +0 -81
- data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/valid_with_matcher.rb +0 -199
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servactory
|
|
4
|
+
module Stroma
|
|
5
|
+
module Hooks
|
|
6
|
+
# Valid hook types for Hook validation.
|
|
7
|
+
VALID_HOOK_TYPES = %i[before after].freeze
|
|
8
|
+
private_constant :VALID_HOOK_TYPES
|
|
9
|
+
|
|
10
|
+
# Immutable value object representing a hook configuration.
|
|
11
|
+
#
|
|
12
|
+
# ## Purpose
|
|
13
|
+
#
|
|
14
|
+
# Defines when and where an extension module should be included
|
|
15
|
+
# relative to a registered DSL module. Hook is immutable - once
|
|
16
|
+
# created, it cannot be modified.
|
|
17
|
+
#
|
|
18
|
+
# ## Attributes
|
|
19
|
+
#
|
|
20
|
+
# @!attribute [r] type
|
|
21
|
+
# @return [Symbol] Either :before or :after
|
|
22
|
+
# @!attribute [r] target_key
|
|
23
|
+
# @return [Symbol] Key of the DSL module to hook into (:inputs, :actions, etc.)
|
|
24
|
+
# @!attribute [r] extension
|
|
25
|
+
# @return [Module] The module to include at the hook point
|
|
26
|
+
#
|
|
27
|
+
# ## Usage
|
|
28
|
+
#
|
|
29
|
+
# ```ruby
|
|
30
|
+
# hook = Servactory::Stroma::Hooks::Hook.new(
|
|
31
|
+
# type: :before,
|
|
32
|
+
# target_key: :actions,
|
|
33
|
+
# extension: MyExtension
|
|
34
|
+
# )
|
|
35
|
+
# hook.before? # => true
|
|
36
|
+
# hook.after? # => false
|
|
37
|
+
# ```
|
|
38
|
+
#
|
|
39
|
+
# ## Immutability
|
|
40
|
+
#
|
|
41
|
+
# Hook is a Data object - frozen and immutable after creation.
|
|
42
|
+
Hook = Data.define(:type, :target_key, :extension) do
|
|
43
|
+
# Initializes a new Hook with validation.
|
|
44
|
+
#
|
|
45
|
+
# @param type [Symbol] Hook type (:before or :after)
|
|
46
|
+
# @param target_key [Symbol] Registry key to hook into
|
|
47
|
+
# @param extension [Module] Extension module to include
|
|
48
|
+
# @raise [Exceptions::InvalidHookType] If type is invalid
|
|
49
|
+
def initialize(type:, target_key:, extension:)
|
|
50
|
+
if VALID_HOOK_TYPES.exclude?(type)
|
|
51
|
+
raise Exceptions::InvalidHookType,
|
|
52
|
+
"Invalid hook type: #{type.inspect}. Valid types: #{VALID_HOOK_TYPES.map(&:inspect).join(', ')}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
super
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Checks if this is a before hook.
|
|
59
|
+
#
|
|
60
|
+
# @return [Boolean] true if type is :before
|
|
61
|
+
def before?
|
|
62
|
+
type == :before
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Checks if this is an after hook.
|
|
66
|
+
#
|
|
67
|
+
# @return [Boolean] true if type is :after
|
|
68
|
+
def after?
|
|
69
|
+
type == :after
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servactory
|
|
4
|
+
module Stroma
|
|
5
|
+
# Manages global registration of DSL modules for Servactory.
|
|
6
|
+
#
|
|
7
|
+
# ## Purpose
|
|
8
|
+
#
|
|
9
|
+
# Singleton registry that stores all DSL modules that will be included
|
|
10
|
+
# in service classes. Implements two-phase lifecycle: registration
|
|
11
|
+
# followed by finalization.
|
|
12
|
+
#
|
|
13
|
+
# ## Usage
|
|
14
|
+
#
|
|
15
|
+
# ```ruby
|
|
16
|
+
# # During gem initialization:
|
|
17
|
+
# Servactory::Stroma::Registry.register(:inputs, Servactory::Inputs::DSL)
|
|
18
|
+
# Servactory::Stroma::Registry.register(:outputs, Servactory::Outputs::DSL)
|
|
19
|
+
# Servactory::Stroma::Registry.finalize!
|
|
20
|
+
#
|
|
21
|
+
# # After finalization:
|
|
22
|
+
# Servactory::Stroma::Registry.keys # => [:inputs, :outputs]
|
|
23
|
+
# Servactory::Stroma::Registry.key?(:inputs) # => true
|
|
24
|
+
# ```
|
|
25
|
+
#
|
|
26
|
+
# ## Integration
|
|
27
|
+
#
|
|
28
|
+
# Used by Servactory::Stroma::DSL to include all registered modules in service classes.
|
|
29
|
+
# Used by Servactory::Stroma::Hooks::Factory to validate hook target keys.
|
|
30
|
+
#
|
|
31
|
+
# ## Thread Safety
|
|
32
|
+
#
|
|
33
|
+
# Registration must occur during single-threaded boot phase.
|
|
34
|
+
# After finalization, all read operations are thread-safe.
|
|
35
|
+
class Registry
|
|
36
|
+
include Singleton
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
delegate :register,
|
|
40
|
+
:finalize!,
|
|
41
|
+
:entries,
|
|
42
|
+
:keys,
|
|
43
|
+
:key?,
|
|
44
|
+
to: :instance
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def initialize
|
|
48
|
+
@entries = []
|
|
49
|
+
@finalized = false
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def register(key, extension)
|
|
53
|
+
raise Exceptions::RegistryFrozen, "Registry is finalized" if @finalized
|
|
54
|
+
|
|
55
|
+
if @entries.any? { |e| e.key == key }
|
|
56
|
+
raise Exceptions::KeyAlreadyRegistered, "Key #{key.inspect} already registered"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
@entries << Entry.new(key:, extension:)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def finalize!
|
|
63
|
+
return if @finalized
|
|
64
|
+
|
|
65
|
+
@entries.freeze
|
|
66
|
+
@finalized = true
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def entries
|
|
70
|
+
ensure_finalized!
|
|
71
|
+
@entries
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def keys
|
|
75
|
+
ensure_finalized!
|
|
76
|
+
@entries.map(&:key)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def key?(key)
|
|
80
|
+
ensure_finalized!
|
|
81
|
+
@entries.any? { |e| e.key == key }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def ensure_finalized!
|
|
87
|
+
return if @finalized
|
|
88
|
+
|
|
89
|
+
raise Exceptions::RegistryNotFinalized,
|
|
90
|
+
"Registry not finalized. Call Stroma::Registry.finalize! after registration."
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servactory
|
|
4
|
+
module Stroma
|
|
5
|
+
module Settings
|
|
6
|
+
# Top-level hierarchical container for extension settings.
|
|
7
|
+
#
|
|
8
|
+
# ## Purpose
|
|
9
|
+
#
|
|
10
|
+
# Provides two-level hierarchical access to extension settings:
|
|
11
|
+
# registry_key -> extension_name -> setting values.
|
|
12
|
+
# Auto-vivifies RegistrySettings on first access.
|
|
13
|
+
#
|
|
14
|
+
# ## Usage
|
|
15
|
+
#
|
|
16
|
+
# ```ruby
|
|
17
|
+
# settings = Servactory::Stroma::Settings::Collection.new
|
|
18
|
+
# settings[:actions][:authorization][:method_name] = :authorize
|
|
19
|
+
# settings[:actions][:transactional][:enabled] = true
|
|
20
|
+
#
|
|
21
|
+
# settings.keys # => [:actions]
|
|
22
|
+
# settings.empty? # => false
|
|
23
|
+
# settings.to_h # => { actions: { authorization: { method_name: :authorize }, ... } }
|
|
24
|
+
# ```
|
|
25
|
+
#
|
|
26
|
+
# ## Integration
|
|
27
|
+
#
|
|
28
|
+
# Stored in Servactory::Stroma::State alongside Hooks::Collection.
|
|
29
|
+
# Accessed via `stroma.settings` in service classes.
|
|
30
|
+
# Properly duplicated during class inheritance via initialize_dup.
|
|
31
|
+
class Collection
|
|
32
|
+
extend Forwardable
|
|
33
|
+
|
|
34
|
+
# @!method each
|
|
35
|
+
# Iterates over all registry key settings.
|
|
36
|
+
# @yield [key, settings] Each registry key and its RegistrySettings
|
|
37
|
+
# @!method keys
|
|
38
|
+
# Returns all registry keys with settings.
|
|
39
|
+
# @return [Array<Symbol>] List of registry keys
|
|
40
|
+
# @!method size
|
|
41
|
+
# Returns the number of registry keys configured.
|
|
42
|
+
# @return [Integer] Number of registry keys
|
|
43
|
+
# @!method empty?
|
|
44
|
+
# Checks if no settings are configured.
|
|
45
|
+
# @return [Boolean] true if empty
|
|
46
|
+
# @!method map
|
|
47
|
+
# Maps over all registry key settings.
|
|
48
|
+
# @yield [key, settings] Each registry key and its RegistrySettings
|
|
49
|
+
# @return [Array] Mapped results
|
|
50
|
+
def_delegators :@storage, :each, :keys, :size, :empty?, :map
|
|
51
|
+
|
|
52
|
+
# Creates a new settings collection.
|
|
53
|
+
#
|
|
54
|
+
# @param storage [Hash] Initial storage (default: empty Hash)
|
|
55
|
+
def initialize(storage = {})
|
|
56
|
+
@storage = storage
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Creates a deep copy during inheritance.
|
|
60
|
+
#
|
|
61
|
+
# @param original [Collection] The original collection being duplicated
|
|
62
|
+
# @return [void]
|
|
63
|
+
def initialize_dup(original)
|
|
64
|
+
super
|
|
65
|
+
@storage = original.instance_variable_get(:@storage).transform_values(&:dup)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Accesses or creates RegistrySettings for a registry key.
|
|
69
|
+
#
|
|
70
|
+
# Auto-vivifies a new RegistrySettings on first access.
|
|
71
|
+
#
|
|
72
|
+
# @param registry_key [Symbol] The registry key (e.g., :actions)
|
|
73
|
+
# @return [RegistrySettings] Settings for that registry key
|
|
74
|
+
#
|
|
75
|
+
# @example
|
|
76
|
+
# settings[:actions][:authorization][:method_name] = :authorize
|
|
77
|
+
def [](registry_key)
|
|
78
|
+
@storage[registry_key.to_sym] ||= RegistrySettings.new
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Converts to a nested Hash.
|
|
82
|
+
#
|
|
83
|
+
# @return [Hash] Deep nested hash of all settings
|
|
84
|
+
def to_h
|
|
85
|
+
@storage.transform_values(&:to_h)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servactory
|
|
4
|
+
module Stroma
|
|
5
|
+
module Settings
|
|
6
|
+
# Collection of Setting objects for one registry key.
|
|
7
|
+
#
|
|
8
|
+
# ## Purpose
|
|
9
|
+
#
|
|
10
|
+
# Groups extension settings by registry key (e.g., :actions).
|
|
11
|
+
# Provides auto-vivifying access to individual Setting objects.
|
|
12
|
+
# This is the middle layer in the settings hierarchy.
|
|
13
|
+
#
|
|
14
|
+
# ## Usage
|
|
15
|
+
#
|
|
16
|
+
# ```ruby
|
|
17
|
+
# settings = Servactory::Stroma::Settings::RegistrySettings.new
|
|
18
|
+
# settings[:authorization][:method_name] = :authorize
|
|
19
|
+
# settings[:transactional][:enabled] = true
|
|
20
|
+
#
|
|
21
|
+
# settings.keys # => [:authorization, :transactional]
|
|
22
|
+
# settings.empty? # => false
|
|
23
|
+
# ```
|
|
24
|
+
#
|
|
25
|
+
# ## Integration
|
|
26
|
+
#
|
|
27
|
+
# Used by Collection as second-level container.
|
|
28
|
+
# Properly duplicated during class inheritance via initialize_dup.
|
|
29
|
+
class RegistrySettings
|
|
30
|
+
extend Forwardable
|
|
31
|
+
|
|
32
|
+
# @!method each
|
|
33
|
+
# Iterates over all extension settings.
|
|
34
|
+
# @yield [name, setting] Each extension name and its Setting
|
|
35
|
+
# @!method keys
|
|
36
|
+
# Returns all extension names.
|
|
37
|
+
# @return [Array<Symbol>] List of extension names
|
|
38
|
+
# @!method size
|
|
39
|
+
# Returns the number of extensions configured.
|
|
40
|
+
# @return [Integer] Number of extensions
|
|
41
|
+
# @!method empty?
|
|
42
|
+
# Checks if no extensions are configured.
|
|
43
|
+
# @return [Boolean] true if empty
|
|
44
|
+
# @!method map
|
|
45
|
+
# Maps over all extension settings.
|
|
46
|
+
# @yield [name, setting] Each extension name and its Setting
|
|
47
|
+
# @return [Array] Mapped results
|
|
48
|
+
def_delegators :@storage, :each, :keys, :size, :empty?, :map
|
|
49
|
+
|
|
50
|
+
# Creates a new registry settings container.
|
|
51
|
+
#
|
|
52
|
+
# @param storage [Hash] Initial storage (default: empty Hash)
|
|
53
|
+
def initialize(storage = {})
|
|
54
|
+
@storage = storage
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Creates a deep copy during inheritance.
|
|
58
|
+
#
|
|
59
|
+
# @param original [RegistrySettings] The original being duplicated
|
|
60
|
+
# @return [void]
|
|
61
|
+
def initialize_dup(original)
|
|
62
|
+
super
|
|
63
|
+
@storage = original.instance_variable_get(:@storage).transform_values(&:dup)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Accesses or creates a Setting for an extension.
|
|
67
|
+
#
|
|
68
|
+
# Auto-vivifies a new Setting on first access.
|
|
69
|
+
#
|
|
70
|
+
# @param extension_name [Symbol] The extension name
|
|
71
|
+
# @return [Setting] The extension's setting container
|
|
72
|
+
#
|
|
73
|
+
# @example
|
|
74
|
+
# settings[:authorization][:method_name] = :authorize
|
|
75
|
+
def [](extension_name)
|
|
76
|
+
@storage[extension_name.to_sym] ||= Setting.new
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Converts to a nested Hash.
|
|
80
|
+
#
|
|
81
|
+
# @return [Hash] Nested hash of all settings
|
|
82
|
+
def to_h
|
|
83
|
+
@storage.transform_values(&:to_h)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servactory
|
|
4
|
+
module Stroma
|
|
5
|
+
module Settings
|
|
6
|
+
# Dynamic key-value storage for extension configuration.
|
|
7
|
+
#
|
|
8
|
+
# ## Purpose
|
|
9
|
+
#
|
|
10
|
+
# Provides a Hash-based container for storing extension-specific
|
|
11
|
+
# configuration data. Uses Forwardable delegation for consistent API.
|
|
12
|
+
# This is the leaf-level container in the settings hierarchy.
|
|
13
|
+
#
|
|
14
|
+
# ## Usage
|
|
15
|
+
#
|
|
16
|
+
# ```ruby
|
|
17
|
+
# setting = Servactory::Stroma::Settings::Setting.new
|
|
18
|
+
# setting[:method_name] = :authorize
|
|
19
|
+
# setting[:method_name] # => :authorize
|
|
20
|
+
# setting.key?(:method_name) # => true
|
|
21
|
+
# ```
|
|
22
|
+
#
|
|
23
|
+
# ## Integration
|
|
24
|
+
#
|
|
25
|
+
# Used by RegistrySettings to store individual extension settings.
|
|
26
|
+
# Properly duplicated during class inheritance via initialize_dup.
|
|
27
|
+
class Setting
|
|
28
|
+
extend Forwardable
|
|
29
|
+
|
|
30
|
+
# @!method [](key)
|
|
31
|
+
# Retrieves a value by key.
|
|
32
|
+
# @param key [Symbol] The key to look up
|
|
33
|
+
# @return [Object, nil] The stored value or nil
|
|
34
|
+
# @!method []=(key, value)
|
|
35
|
+
# Stores a value by key.
|
|
36
|
+
# @param key [Symbol] The key to store under
|
|
37
|
+
# @param value [Object] The value to store
|
|
38
|
+
# @!method key?(key)
|
|
39
|
+
# Checks if a key exists.
|
|
40
|
+
# @param key [Symbol] The key to check
|
|
41
|
+
# @return [Boolean] true if key exists
|
|
42
|
+
# @!method keys
|
|
43
|
+
# Returns all stored keys.
|
|
44
|
+
# @return [Array<Symbol>] List of keys
|
|
45
|
+
# @!method each
|
|
46
|
+
# Iterates over all key-value pairs.
|
|
47
|
+
# @yield [key, value] Each stored pair
|
|
48
|
+
# @!method empty?
|
|
49
|
+
# Checks if no values are stored.
|
|
50
|
+
# @return [Boolean] true if empty
|
|
51
|
+
# @!method size
|
|
52
|
+
# Returns the number of stored values.
|
|
53
|
+
# @return [Integer] Number of entries
|
|
54
|
+
# @!method map
|
|
55
|
+
# Maps over all key-value pairs.
|
|
56
|
+
# @yield [key, value] Each stored pair
|
|
57
|
+
# @return [Array] Mapped results
|
|
58
|
+
def_delegators :@data, :[], :[]=, :key?, :keys, :each, :empty?, :size, :map
|
|
59
|
+
|
|
60
|
+
# Creates a new setting container.
|
|
61
|
+
#
|
|
62
|
+
# @param data [Hash] Initial data (default: empty Hash)
|
|
63
|
+
def initialize(data = {})
|
|
64
|
+
@data = data
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Creates a deep copy during inheritance.
|
|
68
|
+
#
|
|
69
|
+
# @param original [Setting] The original setting being duplicated
|
|
70
|
+
# @return [void]
|
|
71
|
+
def initialize_dup(original)
|
|
72
|
+
super
|
|
73
|
+
@data = deep_dup(original.instance_variable_get(:@data))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Converts to a plain Hash.
|
|
77
|
+
#
|
|
78
|
+
# @return [Hash] Deep copy of internal data
|
|
79
|
+
def to_h
|
|
80
|
+
deep_dup(@data)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Fetches a value with optional default.
|
|
84
|
+
#
|
|
85
|
+
# @param key [Symbol] The key to fetch
|
|
86
|
+
# @param args [Array] Optional default value
|
|
87
|
+
# @yield Optional block for default value
|
|
88
|
+
# @return [Object] The fetched value or default
|
|
89
|
+
#
|
|
90
|
+
# @example
|
|
91
|
+
# setting.fetch(:method_name, :default_method)
|
|
92
|
+
# setting.fetch(:method_name) { :computed_default }
|
|
93
|
+
def fetch(key, *args, &block)
|
|
94
|
+
@data.fetch(key.to_sym, *args, &block)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
# Recursively duplicates nested Hash and Array structures.
|
|
100
|
+
#
|
|
101
|
+
# @param obj [Object] The object to duplicate
|
|
102
|
+
# @return [Object] Deep copy of the object
|
|
103
|
+
def deep_dup(obj)
|
|
104
|
+
case obj
|
|
105
|
+
when Hash then obj.transform_values { |v| deep_dup(v) }
|
|
106
|
+
when Array then obj.map { |v| deep_dup(v) }
|
|
107
|
+
else obj.respond_to?(:dup) ? obj.dup : obj
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servactory
|
|
4
|
+
module Stroma
|
|
5
|
+
# Holds the complete Stroma state for a service class.
|
|
6
|
+
#
|
|
7
|
+
# ## Purpose
|
|
8
|
+
#
|
|
9
|
+
# Central container that stores:
|
|
10
|
+
# - Hooks collection for before/after extension points
|
|
11
|
+
# - Settings collection for extension-specific configuration
|
|
12
|
+
#
|
|
13
|
+
# Each service class has its own State instance, duplicated during
|
|
14
|
+
# inheritance to ensure independent configuration.
|
|
15
|
+
#
|
|
16
|
+
# ## Usage
|
|
17
|
+
#
|
|
18
|
+
# Accessed via `stroma` method in service classes:
|
|
19
|
+
#
|
|
20
|
+
# ```ruby
|
|
21
|
+
# class MyService < ApplicationService::Base
|
|
22
|
+
# # In ClassMethods:
|
|
23
|
+
# stroma.hooks.before(:actions)
|
|
24
|
+
# stroma.settings[:actions][:authorization][:method_name]
|
|
25
|
+
# end
|
|
26
|
+
# ```
|
|
27
|
+
#
|
|
28
|
+
# ## Integration
|
|
29
|
+
#
|
|
30
|
+
# Stored as @stroma instance variable on each service class.
|
|
31
|
+
# Duplicated in DSL.inherited to provide inheritance isolation.
|
|
32
|
+
class State
|
|
33
|
+
# @!attribute [r] hooks
|
|
34
|
+
# @return [Hooks::Collection] The hooks collection for this class
|
|
35
|
+
# @!attribute [r] settings
|
|
36
|
+
# @return [Settings::Collection] The settings collection for this class
|
|
37
|
+
attr_reader :hooks, :settings
|
|
38
|
+
|
|
39
|
+
# Creates a new State with empty collections.
|
|
40
|
+
def initialize
|
|
41
|
+
@hooks = Hooks::Collection.new
|
|
42
|
+
@settings = Settings::Collection.new
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Creates a deep copy during inheritance.
|
|
46
|
+
#
|
|
47
|
+
# Ensures child classes have independent hooks and settings
|
|
48
|
+
# that don't affect the parent class.
|
|
49
|
+
#
|
|
50
|
+
# @param original [State] The original state being duplicated
|
|
51
|
+
# @return [void]
|
|
52
|
+
def initialize_dup(original)
|
|
53
|
+
super
|
|
54
|
+
@hooks = original.instance_variable_get(:@hooks).dup
|
|
55
|
+
@settings = original.instance_variable_get(:@settings).dup
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
module Servactory
|
|
4
4
|
module TestKit
|
|
5
|
+
# Dummy class used as a type mismatch value in tests.
|
|
6
|
+
#
|
|
7
|
+
# ## Purpose
|
|
8
|
+
#
|
|
9
|
+
# FakeType is an empty class that doesn't match any expected service
|
|
10
|
+
# input types. It's used by ValidWithSubmatcher to test type validation
|
|
11
|
+
# by providing a value guaranteed to fail type checks.
|
|
12
|
+
#
|
|
13
|
+
# ## Usage
|
|
14
|
+
#
|
|
15
|
+
# Used internally by valid_with tests:
|
|
16
|
+
#
|
|
17
|
+
# ```ruby
|
|
18
|
+
# # In valid_with submatcher
|
|
19
|
+
# prepared_attributes[attribute_name] = Servactory::TestKit::FakeType.new
|
|
20
|
+
# # This triggers type validation failure
|
|
21
|
+
# ```
|
|
22
|
+
#
|
|
23
|
+
# ## Note
|
|
24
|
+
#
|
|
25
|
+
# The class is intentionally empty - its sole purpose is to be a type
|
|
26
|
+
# that doesn't match String, Integer, Hash, Array, or any other type
|
|
27
|
+
# that a service might expect.
|
|
5
28
|
class FakeType; end # rubocop:disable Lint/EmptyClass
|
|
6
29
|
end
|
|
7
30
|
end
|
|
@@ -2,7 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
module Servactory
|
|
4
4
|
module TestKit
|
|
5
|
+
# Factory for creating mock Servactory result objects.
|
|
6
|
+
#
|
|
7
|
+
# ## Purpose
|
|
8
|
+
#
|
|
9
|
+
# Provides factory methods for creating success and failure result objects
|
|
10
|
+
# in tests without actually calling a service. Used internally by the
|
|
11
|
+
# service mocking helpers to generate return values.
|
|
12
|
+
#
|
|
13
|
+
# ## Usage
|
|
14
|
+
#
|
|
15
|
+
# ```ruby
|
|
16
|
+
# # Create success result with outputs
|
|
17
|
+
# result = Servactory::TestKit::Result.as_success(
|
|
18
|
+
# service_class: MyService,
|
|
19
|
+
# user: user_object,
|
|
20
|
+
# status: :active
|
|
21
|
+
# )
|
|
22
|
+
#
|
|
23
|
+
# # Create failure result with exception
|
|
24
|
+
# result = Servactory::TestKit::Result.as_failure(
|
|
25
|
+
# service_class: MyService,
|
|
26
|
+
# exception: Servactory::Exceptions::Failure.new(message: "Error")
|
|
27
|
+
# )
|
|
28
|
+
# ```
|
|
29
|
+
#
|
|
30
|
+
# ## Service Class Resolution
|
|
31
|
+
#
|
|
32
|
+
# When `service_class` is provided, uses that service's configured
|
|
33
|
+
# `result_class` for proper result type. Otherwise, uses the default
|
|
34
|
+
# `Servactory::Result` class.
|
|
5
35
|
class Result
|
|
36
|
+
# Creates a successful mock result with given outputs.
|
|
37
|
+
#
|
|
38
|
+
# @param attributes [Hash] Output attributes and optional :service_class
|
|
39
|
+
# @return [Servactory::Result] Success result with outputs as methods
|
|
6
40
|
def self.as_success(**attributes)
|
|
7
41
|
service_class = attributes.delete(:service_class) || self
|
|
8
42
|
|
|
@@ -15,6 +49,10 @@ module Servactory
|
|
|
15
49
|
end
|
|
16
50
|
end
|
|
17
51
|
|
|
52
|
+
# Creates a failed mock result with given exception.
|
|
53
|
+
#
|
|
54
|
+
# @param attributes [Hash] Output attributes, :exception, and optional :service_class
|
|
55
|
+
# @return [Servactory::Result] Failure result with error accessor
|
|
18
56
|
def self.as_failure(**attributes)
|
|
19
57
|
service_class = attributes.delete(:service_class) || self
|
|
20
58
|
exception = attributes.delete(:exception)
|
|
@@ -28,6 +66,10 @@ module Servactory
|
|
|
28
66
|
end
|
|
29
67
|
end
|
|
30
68
|
|
|
69
|
+
# Initializes result with attribute accessors.
|
|
70
|
+
#
|
|
71
|
+
# @param attributes [Hash] Output name-value pairs
|
|
72
|
+
# @return [Result] New result instance
|
|
31
73
|
def initialize(attributes = {})
|
|
32
74
|
attributes.each_pair do |name, value|
|
|
33
75
|
servactory_service_warehouse.assign_output(name, value)
|
|
@@ -38,6 +80,9 @@ module Servactory
|
|
|
38
80
|
|
|
39
81
|
private
|
|
40
82
|
|
|
83
|
+
# Internal warehouse for storing output values.
|
|
84
|
+
#
|
|
85
|
+
# @return [Servactory::Context::Warehouse::Setup] Warehouse instance
|
|
41
86
|
def servactory_service_warehouse
|
|
42
87
|
@servactory_service_warehouse ||= Servactory::Context::Warehouse::Setup.new(self)
|
|
43
88
|
end
|