servactory 2.16.1 → 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.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +38 -9
  3. data/config/locales/de.yml +134 -0
  4. data/config/locales/en.yml +15 -0
  5. data/config/locales/es.yml +134 -0
  6. data/config/locales/fr.yml +134 -0
  7. data/config/locales/it.yml +134 -0
  8. data/config/locales/ru.yml +15 -0
  9. data/lib/generators/README.md +45 -0
  10. data/lib/generators/servactory/base.rb +82 -0
  11. data/lib/generators/servactory/extension/USAGE +54 -0
  12. data/lib/generators/servactory/extension/extension_generator.rb +41 -0
  13. data/lib/generators/servactory/extension/templates/extension.rb.tt +62 -0
  14. data/lib/generators/servactory/install/USAGE +27 -0
  15. data/lib/generators/servactory/install/install_generator.rb +94 -0
  16. data/lib/generators/servactory/{templates/services/application_service/base.rb → install/templates/application_service/base.rb.tt} +29 -19
  17. data/lib/generators/servactory/{templates/services/application_service/exceptions.rb → install/templates/application_service/exceptions.rb.tt} +1 -1
  18. data/lib/generators/servactory/{templates/services/application_service/result.rb → install/templates/application_service/result.rb.tt} +1 -1
  19. data/lib/generators/servactory/rspec/USAGE +46 -0
  20. data/lib/generators/servactory/rspec/rspec_generator.rb +95 -0
  21. data/lib/generators/servactory/rspec/templates/service_spec.rb.tt +58 -0
  22. data/lib/generators/servactory/service/USAGE +51 -0
  23. data/lib/generators/servactory/service/service_generator.rb +56 -0
  24. data/lib/generators/servactory/service/templates/service.rb.tt +22 -0
  25. data/lib/servactory/actions/collection.rb +56 -1
  26. data/lib/servactory/actions/dsl.rb +11 -11
  27. data/lib/servactory/actions/tools/rules.rb +1 -1
  28. data/lib/servactory/actions/tools/runner.rb +111 -28
  29. data/lib/servactory/base.rb +1 -7
  30. data/lib/servactory/configuration/actions/aliases/collection.rb +5 -0
  31. data/lib/servactory/configuration/actions/rescue_handlers/collection.rb +5 -0
  32. data/lib/servactory/configuration/actions/shortcuts/collection.rb +5 -0
  33. data/lib/servactory/configuration/collection_mode/class_names_collection.rb +5 -0
  34. data/lib/servactory/configuration/config.rb +36 -0
  35. data/lib/servactory/configuration/configurable.rb +95 -0
  36. data/lib/servactory/configuration/dsl.rb +3 -27
  37. data/lib/servactory/configuration/factory.rb +20 -20
  38. data/lib/servactory/configuration/hash_mode/class_names_collection.rb +5 -0
  39. data/lib/servactory/configuration/option_helpers/option_helpers_collection.rb +5 -0
  40. data/lib/servactory/context/warehouse/inputs.rb +2 -2
  41. data/lib/servactory/context/workspace/inputs.rb +2 -2
  42. data/lib/servactory/context/workspace/internals.rb +2 -2
  43. data/lib/servactory/context/workspace/outputs.rb +2 -2
  44. data/lib/servactory/context/workspace.rb +11 -7
  45. data/lib/servactory/dsl.rb +10 -8
  46. data/lib/servactory/maintenance/attributes/tools/validation.rb +1 -1
  47. data/lib/servactory/result.rb +2 -2
  48. data/lib/servactory/stroma/dsl.rb +118 -0
  49. data/lib/servactory/stroma/entry.rb +32 -0
  50. data/lib/servactory/stroma/exceptions/base.rb +45 -0
  51. data/lib/servactory/stroma/exceptions/invalid_hook_type.rb +29 -0
  52. data/lib/servactory/stroma/exceptions/key_already_registered.rb +32 -0
  53. data/lib/servactory/stroma/exceptions/registry_frozen.rb +33 -0
  54. data/lib/servactory/stroma/exceptions/registry_not_finalized.rb +33 -0
  55. data/lib/servactory/stroma/exceptions/unknown_hook_target.rb +39 -0
  56. data/lib/servactory/stroma/hooks/applier.rb +63 -0
  57. data/lib/servactory/stroma/hooks/collection.rb +103 -0
  58. data/lib/servactory/stroma/hooks/factory.rb +80 -0
  59. data/lib/servactory/stroma/hooks/hook.rb +74 -0
  60. data/lib/servactory/stroma/registry.rb +94 -0
  61. data/lib/servactory/stroma/settings/collection.rb +90 -0
  62. data/lib/servactory/stroma/settings/registry_settings.rb +88 -0
  63. data/lib/servactory/stroma/settings/setting.rb +113 -0
  64. data/lib/servactory/stroma/state.rb +59 -0
  65. data/lib/servactory/test_kit/fake_type.rb +23 -0
  66. data/lib/servactory/test_kit/result.rb +45 -0
  67. data/lib/servactory/test_kit/rspec/helpers/argument_matchers.rb +97 -0
  68. data/lib/servactory/test_kit/rspec/helpers/concerns/error_messages.rb +179 -0
  69. data/lib/servactory/test_kit/rspec/helpers/concerns/service_class_validation.rb +74 -0
  70. data/lib/servactory/test_kit/rspec/helpers/fluent.rb +110 -0
  71. data/lib/servactory/test_kit/rspec/helpers/input_validator.rb +149 -0
  72. data/lib/servactory/test_kit/rspec/helpers/legacy.rb +228 -0
  73. data/lib/servactory/test_kit/rspec/helpers/mock_executor.rb +256 -0
  74. data/lib/servactory/test_kit/rspec/helpers/output_validator.rb +121 -0
  75. data/lib/servactory/test_kit/rspec/helpers/service_mock_builder.rb +422 -0
  76. data/lib/servactory/test_kit/rspec/helpers/service_mock_config.rb +129 -0
  77. data/lib/servactory/test_kit/rspec/helpers.rb +51 -84
  78. data/lib/servactory/test_kit/rspec/matchers/base/attribute_matcher.rb +324 -0
  79. data/lib/servactory/test_kit/rspec/matchers/base/submatcher.rb +133 -0
  80. data/lib/servactory/test_kit/rspec/matchers/base/submatcher_context.rb +101 -0
  81. data/lib/servactory/test_kit/rspec/matchers/base/submatcher_registry.rb +205 -0
  82. data/lib/servactory/test_kit/rspec/matchers/concerns/attribute_data_access.rb +100 -0
  83. data/lib/servactory/test_kit/rspec/matchers/concerns/error_message_builder.rb +106 -0
  84. data/lib/servactory/test_kit/rspec/matchers/concerns/value_comparison.rb +97 -0
  85. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matcher.rb +89 -219
  86. data/lib/servactory/test_kit/rspec/matchers/have_service_internal_matcher.rb +74 -166
  87. data/lib/servactory/test_kit/rspec/matchers/have_service_output_matcher.rb +238 -0
  88. data/lib/servactory/test_kit/rspec/matchers/result/be_failure_service_matcher.rb +257 -0
  89. data/lib/servactory/test_kit/rspec/matchers/result/be_success_service_matcher.rb +185 -0
  90. data/lib/servactory/test_kit/rspec/matchers/submatchers/input/default_submatcher.rb +81 -0
  91. data/lib/servactory/test_kit/rspec/matchers/submatchers/input/optional_submatcher.rb +62 -0
  92. data/lib/servactory/test_kit/rspec/matchers/submatchers/input/required_submatcher.rb +93 -0
  93. data/lib/servactory/test_kit/rspec/matchers/submatchers/input/valid_with_submatcher.rb +271 -0
  94. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/consists_of_submatcher.rb +85 -0
  95. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/inclusion_submatcher.rb +120 -0
  96. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/message_submatcher.rb +115 -0
  97. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/must_submatcher.rb +82 -0
  98. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/schema_submatcher.rb +102 -0
  99. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/target_submatcher.rb +125 -0
  100. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/types_submatcher.rb +77 -0
  101. data/lib/servactory/test_kit/rspec/matchers.rb +126 -285
  102. data/lib/servactory/test_kit/utils/faker.rb +62 -2
  103. data/lib/servactory/tool_kit/dynamic_options/consists_of.rb +166 -0
  104. data/lib/servactory/tool_kit/dynamic_options/format.rb +195 -8
  105. data/lib/servactory/tool_kit/dynamic_options/inclusion.rb +219 -17
  106. data/lib/servactory/tool_kit/dynamic_options/max.rb +143 -0
  107. data/lib/servactory/tool_kit/dynamic_options/min.rb +143 -0
  108. data/lib/servactory/tool_kit/dynamic_options/multiple_of.rb +157 -8
  109. data/lib/servactory/tool_kit/dynamic_options/must.rb +194 -0
  110. data/lib/servactory/tool_kit/dynamic_options/schema.rb +226 -2
  111. data/lib/servactory/tool_kit/dynamic_options/target.rb +252 -0
  112. data/lib/servactory/version.rb +4 -4
  113. data/lib/servactory.rb +4 -0
  114. metadata +73 -19
  115. data/lib/generators/servactory/install_generator.rb +0 -21
  116. data/lib/generators/servactory/rspec_generator.rb +0 -88
  117. data/lib/generators/servactory/service_generator.rb +0 -49
  118. data/lib/servactory/configuration/setup.rb +0 -97
  119. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/consists_of_matcher.rb +0 -68
  120. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/inclusion_matcher.rb +0 -73
  121. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/message_matcher.rb +0 -91
  122. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/must_matcher.rb +0 -72
  123. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/schema_matcher.rb +0 -92
  124. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/types_matcher.rb +0 -72
  125. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/default_matcher.rb +0 -69
  126. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/optional_matcher.rb +0 -63
  127. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/required_matcher.rb +0 -81
  128. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/valid_with_matcher.rb +0 -199
@@ -16,17 +16,19 @@ module Servactory
16
16
  end
17
17
  end
18
18
 
19
+ Stroma::Registry.register(:configuration, Configuration::DSL)
20
+ Stroma::Registry.register(:info, Info::DSL)
21
+ Stroma::Registry.register(:context, Context::DSL)
22
+ Stroma::Registry.register(:inputs, Inputs::DSL)
23
+ Stroma::Registry.register(:internals, Internals::DSL)
24
+ Stroma::Registry.register(:outputs, Outputs::DSL)
25
+ Stroma::Registry.register(:actions, Actions::DSL)
26
+ Stroma::Registry.finalize!
27
+
19
28
  def self.included(base)
20
- base.include(Configuration::DSL)
21
- base.include(Info::DSL)
22
- base.include(Context::DSL)
23
- base.include(Inputs::DSL)
24
- base.include(Internals::DSL)
25
- base.include(Outputs::DSL)
29
+ base.include(Stroma::DSL)
26
30
 
27
31
  Extensions.registry.each { |extension| base.include(extension) }
28
-
29
- base.include(Actions::DSL)
30
32
  end
31
33
 
32
34
  def self.with_extensions(*extensions)
@@ -74,7 +74,7 @@ module Servactory
74
74
  def raise_errors
75
75
  return if (tmp_errors = errors.not_blank).empty?
76
76
 
77
- raise @context.class.config
77
+ raise @context.config
78
78
  .public_send(:"#{@attribute.system_name}_exception_class")
79
79
  .new(context: @context, message: tmp_errors.first)
80
80
  end
@@ -117,7 +117,7 @@ module Servactory
117
117
  @outputs ||= Outputs.new(
118
118
  outputs: @context.send(:servactory_service_warehouse).outputs,
119
119
  predicate_methods_enabled:
120
- @context.is_a?(Servactory::TestKit::Result) || @context.class.config.predicate_methods_enabled?
120
+ @context.is_a?(Servactory::TestKit::Result) || @context.config.predicate_methods_enabled
121
121
  )
122
122
  end
123
123
 
@@ -126,7 +126,7 @@ module Servactory
126
126
  def rescue_no_method_error_with(exception:) # rubocop:disable Metrics/MethodLength
127
127
  raise exception if @context.blank? || @context.instance_of?(Servactory::TestKit::Result)
128
128
 
129
- raise @context.class.config.failure_class.new(
129
+ raise @context.config.failure_class.new(
130
130
  type: :base,
131
131
  message: @context.send(:servactory_service_info).translate(
132
132
  "common.undefined_method.missing_name",
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module Stroma
5
+ # Main integration point between Stroma and service classes.
6
+ #
7
+ # ## Purpose
8
+ #
9
+ # Module that provides the core Stroma functionality to service classes:
10
+ # - Includes all registered DSL modules
11
+ # - Provides extensions block for hook registration
12
+ # - Handles inheritance with proper state copying
13
+ #
14
+ # ## Usage
15
+ #
16
+ # ```ruby
17
+ # class MyService
18
+ # include Servactory::Stroma::DSL
19
+ #
20
+ # extensions do
21
+ # before :actions, MyExtension
22
+ # end
23
+ # end
24
+ # ```
25
+ #
26
+ # ## Extension Settings Access
27
+ #
28
+ # Extensions access their settings through the stroma.settings hierarchy:
29
+ #
30
+ # ```ruby
31
+ # # In ClassMethods:
32
+ # stroma.settings[:actions][:authorization][:method_name] = :authorize
33
+ #
34
+ # # In InstanceMethods:
35
+ # self.class.stroma.settings[:actions][:authorization][:method_name]
36
+ # ```
37
+ #
38
+ # ## Integration
39
+ #
40
+ # Included by Servactory::DSL which is included by Servactory::Base.
41
+ # Provides ClassMethods with: stroma, inherited, extensions.
42
+ module DSL
43
+ def self.included(base)
44
+ base.extend(ClassMethods)
45
+
46
+ Registry.entries.each do |entry|
47
+ base.include(entry.extension)
48
+ end
49
+ end
50
+
51
+ # Class-level methods for Stroma integration.
52
+ #
53
+ # ## Purpose
54
+ #
55
+ # Provides access to Stroma state and hooks DSL at the class level.
56
+ # Handles proper duplication during inheritance.
57
+ #
58
+ # ## Key Methods
59
+ #
60
+ # - `stroma` - Access the State container
61
+ # - `inherited` - Copy state to child classes
62
+ # - `extensions` - DSL block for hook registration
63
+ module ClassMethods
64
+ def self.extended(base)
65
+ base.instance_variable_set(:@stroma, State.new)
66
+ end
67
+
68
+ # Handles inheritance by duplicating Stroma state.
69
+ #
70
+ # Creates an independent copy of hooks and settings for the child class,
71
+ # then applies all registered hooks to the child.
72
+ #
73
+ # @param child [Class] The child class being created
74
+ # @return [void]
75
+ def inherited(child)
76
+ super
77
+
78
+ child.instance_variable_set(:@stroma, stroma.dup)
79
+
80
+ Hooks::Applier.new(child, child.stroma.hooks).apply!
81
+ end
82
+
83
+ # Returns the Stroma state for this service class.
84
+ #
85
+ # @return [State] The Stroma state container
86
+ #
87
+ # @example Accessing hooks
88
+ # stroma.hooks.before(:actions)
89
+ #
90
+ # @example Accessing settings
91
+ # stroma.settings[:actions][:authorization][:method_name]
92
+ def stroma
93
+ @stroma ||= State.new
94
+ end
95
+
96
+ private
97
+
98
+ # DSL block for registering hooks.
99
+ #
100
+ # Evaluates the block in the context of a Hooks::Factory,
101
+ # allowing before/after hook registration.
102
+ #
103
+ # @yield Block with before/after DSL calls
104
+ # @return [void]
105
+ #
106
+ # @example
107
+ # extensions do
108
+ # before :actions, AuthorizationExtension
109
+ # after :outputs, LoggingExtension
110
+ # end
111
+ def extensions(&block)
112
+ @stroma_hooks_factory ||= Hooks::Factory.new(stroma.hooks)
113
+ @stroma_hooks_factory.instance_eval(&block)
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module Stroma
5
+ # Represents a registered DSL entry in the Stroma registry.
6
+ #
7
+ # ## Purpose
8
+ #
9
+ # Immutable value object that holds information about a DSL module
10
+ # registered in the Stroma system. Each entry has a unique key
11
+ # and references a Module that will be included in service classes.
12
+ #
13
+ # ## Attributes
14
+ #
15
+ # - `key` (Symbol): Unique identifier for the DSL module (:inputs, :outputs, :actions)
16
+ # - `extension` (Module): The actual DSL module to be included
17
+ #
18
+ # ## Usage
19
+ #
20
+ # Entries are created internally by Registry.register:
21
+ #
22
+ # ```ruby
23
+ # Stroma::Registry.register(:inputs, Servactory::Inputs::DSL)
24
+ # # Creates: Entry.new(key: :inputs, extension: Servactory::Inputs::DSL)
25
+ # ```
26
+ #
27
+ # ## Immutability
28
+ #
29
+ # Entry is immutable (Data object) - once created, it cannot be modified.
30
+ Entry = Data.define(:key, :extension)
31
+ end
32
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module Stroma
5
+ module Exceptions
6
+ # Base exception class for all Stroma-specific exceptions
7
+ #
8
+ # ## Purpose
9
+ #
10
+ # Serves as the parent class for all custom exceptions in the Stroma subsystem.
11
+ # Allows catching all Stroma-related exceptions with a single rescue clause.
12
+ #
13
+ # ## Usage
14
+ #
15
+ # All Stroma exceptions inherit from this base class:
16
+ #
17
+ # ```ruby
18
+ # begin
19
+ # Servactory::Stroma::Registry.register(:custom, CustomModule)
20
+ # rescue Servactory::Stroma::Exceptions::Base => e
21
+ # # Catches any Stroma-specific exception
22
+ # handle_stroma_error(e)
23
+ # end
24
+ # ```
25
+ #
26
+ # ## Integration
27
+ #
28
+ # Can be used in application error handlers for centralized error handling:
29
+ #
30
+ # ```ruby
31
+ # rescue_from Servactory::Stroma::Exceptions::Base, with: :handle_stroma_error
32
+ # ```
33
+ #
34
+ # ## Subclasses
35
+ #
36
+ # - RegistryFrozen - Raised when modifying a finalized registry
37
+ # - RegistryNotFinalized - Raised when accessing registry before finalization
38
+ # - KeyAlreadyRegistered - Raised when registering a duplicate key
39
+ # - UnknownHookTarget - Raised when using an invalid hook target key
40
+ # - InvalidHookType - Raised when using an invalid hook type (:before/:after)
41
+ class Base < StandardError
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module Stroma
5
+ module Exceptions
6
+ # Raised when an invalid hook type is provided.
7
+ #
8
+ # ## Purpose
9
+ #
10
+ # Ensures that only valid hook types (:before, :after) are used
11
+ # when creating Servactory::Stroma::Hooks::Hook objects. Provides fail-fast
12
+ # behavior during class definition rather than silent failures at runtime.
13
+ #
14
+ # ## Usage
15
+ #
16
+ # ```ruby
17
+ # # This will raise InvalidHookType:
18
+ # Servactory::Stroma::Hooks::Hook.new(
19
+ # type: :invalid,
20
+ # target_key: :actions,
21
+ # extension: MyModule
22
+ # )
23
+ # # => Servactory::Stroma::Exceptions::InvalidHookType:
24
+ # # Invalid hook type: :invalid. Valid types: :before, :after
25
+ # ```
26
+ class InvalidHookType < Base; end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module Stroma
5
+ module Exceptions
6
+ # Raised when registering a duplicate key in the registry
7
+ #
8
+ # ## Purpose
9
+ #
10
+ # Indicates that a DSL module key has already been registered.
11
+ # Each DSL module must have a unique key in the registry.
12
+ #
13
+ # ## Usage
14
+ #
15
+ # Raised when attempting to register a duplicate key:
16
+ #
17
+ # ```ruby
18
+ # Servactory::Stroma::Registry.register(:inputs, Inputs::DSL)
19
+ # Servactory::Stroma::Registry.register(:inputs, AnotherModule)
20
+ # # Raises: Servactory::Stroma::Exceptions::KeyAlreadyRegistered
21
+ # ```
22
+ #
23
+ # ## Integration
24
+ #
25
+ # This exception typically indicates a configuration error - each
26
+ # DSL module should only be registered once. Check for duplicate
27
+ # registrations in your initialization code.
28
+ class KeyAlreadyRegistered < Base
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module Stroma
5
+ module Exceptions
6
+ # Raised when attempting to modify a finalized registry
7
+ #
8
+ # ## Purpose
9
+ #
10
+ # Indicates that the Stroma::Registry has been finalized and cannot accept
11
+ # new module registrations. The registry is finalized once during gem
12
+ # initialization and remains immutable thereafter.
13
+ #
14
+ # ## Usage
15
+ #
16
+ # Raised when attempting to register modules after finalize!:
17
+ #
18
+ # ```ruby
19
+ # Servactory::Stroma::Registry.finalize!
20
+ # Servactory::Stroma::Registry.register(:custom, CustomModule)
21
+ # # Raises: Servactory::Stroma::Exceptions::RegistryFrozen
22
+ # ```
23
+ #
24
+ # ## Integration
25
+ #
26
+ # This exception typically indicates a programming error - module
27
+ # registration should only occur during application boot, before
28
+ # any service classes are defined.
29
+ class RegistryFrozen < Base
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module Stroma
5
+ module Exceptions
6
+ # Raised when accessing registry before finalization
7
+ #
8
+ # ## Purpose
9
+ #
10
+ # Indicates that the Stroma::Registry was accessed before finalize! was called.
11
+ # The registry must be finalized before it can be used to ensure all DSL modules
12
+ # are registered in the correct order.
13
+ #
14
+ # ## Usage
15
+ #
16
+ # Raised when accessing registry methods before finalization:
17
+ #
18
+ # ```ruby
19
+ # # Before finalize! is called
20
+ # Servactory::Stroma::Registry.entries
21
+ # # Raises: Servactory::Stroma::Exceptions::RegistryNotFinalized
22
+ # ```
23
+ #
24
+ # ## Integration
25
+ #
26
+ # This exception typically indicates that Servactory::DSL module was not
27
+ # properly loaded. Ensure servactory gem is properly required before
28
+ # defining service classes.
29
+ class RegistryNotFinalized < Base
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module Stroma
5
+ module Exceptions
6
+ # Raised when using an invalid hook target key
7
+ #
8
+ # ## Purpose
9
+ #
10
+ # Indicates that an unknown key was used as a hook target in the
11
+ # extensions block. Only registered DSL module keys can be used
12
+ # as hook targets.
13
+ #
14
+ # ## Usage
15
+ #
16
+ # Raised when using an invalid key in extensions block:
17
+ #
18
+ # ```ruby
19
+ # class ApplicationService::Base
20
+ # include Servactory::DSL
21
+ #
22
+ # extensions do
23
+ # before :unknown_key, SomeModule
24
+ # # Raises: Servactory::Stroma::Exceptions::UnknownHookTarget
25
+ # end
26
+ # end
27
+ # ```
28
+ #
29
+ # ## Integration
30
+ #
31
+ # Valid hook target keys are determined by registered DSL modules:
32
+ # :configuration, :info, :context, :inputs, :internals, :outputs, :actions
33
+ #
34
+ # Check Servactory::Stroma::Registry.keys for the list of valid targets.
35
+ class UnknownHookTarget < Base
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module Stroma
5
+ module Hooks
6
+ # Applies registered hooks to a target class.
7
+ #
8
+ # ## Purpose
9
+ #
10
+ # Iterates through all registered DSL modules and includes corresponding
11
+ # before/after hooks in the target class. For each registry entry,
12
+ # before hooks are included first, then after hooks.
13
+ #
14
+ # ## Usage
15
+ #
16
+ # ```ruby
17
+ # applier = Servactory::Stroma::Hooks::Applier.new(ChildService, hooks)
18
+ # applier.apply!
19
+ # # ChildService now includes all hook modules
20
+ # ```
21
+ #
22
+ # ## Integration
23
+ #
24
+ # Called by Servactory::Stroma::DSL.inherited after duplicating
25
+ # parent's configuration. Uses Registry.entries to determine
26
+ # hook application order.
27
+ class Applier
28
+ # Creates a new applier for applying hooks to a class.
29
+ #
30
+ # @param target_class [Class] The class to apply hooks to
31
+ # @param hooks [Collection] The hooks collection to apply
32
+ def initialize(target_class, hooks)
33
+ @target_class = target_class
34
+ @hooks = hooks
35
+ end
36
+
37
+ # Applies all registered hooks to the target class.
38
+ #
39
+ # For each registry entry, includes before hooks first,
40
+ # then after hooks. Does nothing if hooks collection is empty.
41
+ #
42
+ # @return [void]
43
+ #
44
+ # @example
45
+ # applier.apply!
46
+ # # Target class now includes all extension modules
47
+ def apply!
48
+ return if @hooks.empty?
49
+
50
+ Registry.entries.each do |entry|
51
+ @hooks.before(entry.key).each do |hook|
52
+ @target_class.include(hook.extension)
53
+ end
54
+
55
+ @hooks.after(entry.key).each do |hook|
56
+ @target_class.include(hook.extension)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module Stroma
5
+ module Hooks
6
+ # Mutable collection manager for Hook objects.
7
+ #
8
+ # ## Purpose
9
+ #
10
+ # Stores Hook objects and provides query methods to retrieve hooks
11
+ # by type and target key. Supports proper duplication during class
12
+ # inheritance to ensure configuration isolation.
13
+ #
14
+ # ## Usage
15
+ #
16
+ # ```ruby
17
+ # hooks = Servactory::Stroma::Hooks::Collection.new
18
+ # hooks.add(:before, :actions, MyModule)
19
+ # hooks.add(:after, :actions, AnotherModule)
20
+ #
21
+ # hooks.before(:actions) # => [Hook(...)]
22
+ # hooks.after(:actions) # => [Hook(...)]
23
+ # hooks.empty? # => false
24
+ # hooks.size # => 2
25
+ # ```
26
+ #
27
+ # ## Integration
28
+ #
29
+ # Stored in Servactory::Stroma::State and used by
30
+ # Servactory::Stroma::Hooks::Applier to apply hooks to classes.
31
+ # Properly duplicated during class inheritance via initialize_dup.
32
+ class Collection
33
+ extend Forwardable
34
+
35
+ # @!method each
36
+ # Iterates over all hooks in the collection.
37
+ # @yield [Hook] Each hook in the collection
38
+ # @!method map
39
+ # Maps over all hooks in the collection.
40
+ # @yield [Hook] Each hook in the collection
41
+ # @return [Array] Mapped results
42
+ # @!method size
43
+ # Returns the number of hooks in the collection.
44
+ # @return [Integer] Number of hooks
45
+ # @!method empty?
46
+ # Checks if the collection is empty.
47
+ # @return [Boolean] true if no hooks registered
48
+ def_delegators :@collection, :each, :map, :size, :empty?
49
+
50
+ # Creates a new hooks collection.
51
+ #
52
+ # @param collection [Set] Initial collection of hooks (default: empty Set)
53
+ def initialize(collection = Set.new)
54
+ @collection = collection
55
+ end
56
+
57
+ # Creates a deep copy during inheritance.
58
+ #
59
+ # @param original [Collection] The original collection being duplicated
60
+ # @return [void]
61
+ def initialize_dup(original)
62
+ super
63
+ @collection = original.instance_variable_get(:@collection).dup
64
+ end
65
+
66
+ # Adds a new hook to the collection.
67
+ #
68
+ # @param type [Symbol] Hook type (:before or :after)
69
+ # @param target_key [Symbol] Registry key to hook into
70
+ # @param extension [Module] Extension module to include
71
+ # @return [Set] The updated collection
72
+ #
73
+ # @example
74
+ # hooks.add(:before, :actions, ValidationModule)
75
+ def add(type, target_key, extension)
76
+ @collection << Hook.new(type:, target_key:, extension:)
77
+ end
78
+
79
+ # Returns all before hooks for a given key.
80
+ #
81
+ # @param key [Symbol] The target key to filter by
82
+ # @return [Array<Hook>] Hooks that run before the target
83
+ #
84
+ # @example
85
+ # hooks.before(:actions) # => [Hook(type: :before, ...)]
86
+ def before(key)
87
+ @collection.select { |hook| hook.before? && hook.target_key == key }
88
+ end
89
+
90
+ # Returns all after hooks for a given key.
91
+ #
92
+ # @param key [Symbol] The target key to filter by
93
+ # @return [Array<Hook>] Hooks that run after the target
94
+ #
95
+ # @example
96
+ # hooks.after(:actions) # => [Hook(type: :after, ...)]
97
+ def after(key)
98
+ @collection.select { |hook| hook.after? && hook.target_key == key }
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module Stroma
5
+ module Hooks
6
+ # DSL interface for registering hooks in extensions block.
7
+ #
8
+ # ## Purpose
9
+ #
10
+ # Provides the `before` and `after` methods used within the extensions
11
+ # block to register hooks. Validates that target keys exist in Registry
12
+ # before adding hooks.
13
+ #
14
+ # ## Usage
15
+ #
16
+ # ```ruby
17
+ # class ApplicationService::Base < Servactory::Base
18
+ # extensions do
19
+ # before :actions, ValidationModule
20
+ # after :outputs, LoggingModule
21
+ # end
22
+ # end
23
+ # ```
24
+ #
25
+ # ## Integration
26
+ #
27
+ # Created by DSL.extensions method and receives instance_eval of the block.
28
+ # Validates keys against Registry.keys and raises UnknownHookTarget
29
+ # for invalid keys.
30
+ class Factory
31
+ # Creates a new factory for registering hooks.
32
+ #
33
+ # @param hooks [Collection] The hooks collection to add to
34
+ def initialize(hooks)
35
+ @hooks = hooks
36
+ end
37
+
38
+ # Registers one or more before hooks for a target key.
39
+ #
40
+ # @param key [Symbol] The registry key to hook before
41
+ # @param extensions [Array<Module>] Extension modules to include
42
+ # @raise [Exceptions::UnknownHookTarget] If key is not registered
43
+ #
44
+ # @example
45
+ # before :actions, ValidationModule, AuthorizationModule
46
+ def before(key, *extensions)
47
+ validate_key!(key)
48
+ extensions.each { |extension| @hooks.add(:before, key, extension) }
49
+ end
50
+
51
+ # Registers one or more after hooks for a target key.
52
+ #
53
+ # @param key [Symbol] The registry key to hook after
54
+ # @param extensions [Array<Module>] Extension modules to include
55
+ # @raise [Exceptions::UnknownHookTarget] If key is not registered
56
+ #
57
+ # @example
58
+ # after :outputs, LoggingModule, AuditModule
59
+ def after(key, *extensions)
60
+ validate_key!(key)
61
+ extensions.each { |extension| @hooks.add(:after, key, extension) }
62
+ end
63
+
64
+ private
65
+
66
+ # Validates that the key exists in the Registry.
67
+ #
68
+ # @param key [Symbol] The key to validate
69
+ # @raise [Exceptions::UnknownHookTarget] If key is not registered
70
+ def validate_key!(key)
71
+ return if Registry.key?(key)
72
+
73
+ raise Exceptions::UnknownHookTarget,
74
+ "Unknown hook target: #{key.inspect}. " \
75
+ "Valid keys: #{Registry.keys.map(&:inspect).join(', ')}"
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end