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
@@ -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