servus 0.1.3 → 0.1.5

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 (139) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/check-docs.md +1 -0
  3. data/.claude/commands/consistency-check.md +1 -0
  4. data/.claude/commands/fine-tooth-comb.md +1 -0
  5. data/.claude/commands/red-green-refactor.md +5 -0
  6. data/.claude/settings.json +15 -0
  7. data/.rubocop.yml +18 -2
  8. data/.yardopts +6 -0
  9. data/CHANGELOG.md +47 -0
  10. data/CLAUDE.md +10 -0
  11. data/IDEAS.md +5 -0
  12. data/READme.md +300 -47
  13. data/Rakefile +33 -0
  14. data/builds/servus-0.1.3.gem +0 -0
  15. data/builds/servus-0.1.4.gem +0 -0
  16. data/builds/servus-0.1.5.gem +0 -0
  17. data/docs/core/1_overview.md +77 -0
  18. data/docs/core/2_architecture.md +120 -0
  19. data/docs/core/3_service_objects.md +121 -0
  20. data/docs/current_focus.md +569 -0
  21. data/docs/features/1_schema_validation.md +119 -0
  22. data/docs/features/2_error_handling.md +121 -0
  23. data/docs/features/3_async_execution.md +81 -0
  24. data/docs/features/4_logging.md +64 -0
  25. data/docs/features/5_event_bus.md +244 -0
  26. data/docs/guides/1_common_patterns.md +90 -0
  27. data/docs/guides/2_migration_guide.md +175 -0
  28. data/docs/integration/1_configuration.md +104 -0
  29. data/docs/integration/2_testing.md +287 -0
  30. data/docs/integration/3_rails_integration.md +99 -0
  31. data/docs/yard/Servus/Base.html +1645 -0
  32. data/docs/yard/Servus/Config.html +582 -0
  33. data/docs/yard/Servus/Extensions/Async/Call.html +400 -0
  34. data/docs/yard/Servus/Extensions/Async/Errors/AsyncError.html +140 -0
  35. data/docs/yard/Servus/Extensions/Async/Errors/JobEnqueueError.html +154 -0
  36. data/docs/yard/Servus/Extensions/Async/Errors/ServiceNotFoundError.html +154 -0
  37. data/docs/yard/Servus/Extensions/Async/Errors.html +128 -0
  38. data/docs/yard/Servus/Extensions/Async/Ext.html +119 -0
  39. data/docs/yard/Servus/Extensions/Async/Job.html +310 -0
  40. data/docs/yard/Servus/Extensions/Async.html +141 -0
  41. data/docs/yard/Servus/Extensions.html +117 -0
  42. data/docs/yard/Servus/Generators/ServiceGenerator.html +261 -0
  43. data/docs/yard/Servus/Generators.html +115 -0
  44. data/docs/yard/Servus/Helpers/ControllerHelpers.html +457 -0
  45. data/docs/yard/Servus/Helpers.html +115 -0
  46. data/docs/yard/Servus/Railtie.html +134 -0
  47. data/docs/yard/Servus/Support/Errors/AuthenticationError.html +287 -0
  48. data/docs/yard/Servus/Support/Errors/BadRequestError.html +283 -0
  49. data/docs/yard/Servus/Support/Errors/ForbiddenError.html +284 -0
  50. data/docs/yard/Servus/Support/Errors/InternalServerError.html +283 -0
  51. data/docs/yard/Servus/Support/Errors/NotFoundError.html +284 -0
  52. data/docs/yard/Servus/Support/Errors/ServiceError.html +489 -0
  53. data/docs/yard/Servus/Support/Errors/ServiceUnavailableError.html +290 -0
  54. data/docs/yard/Servus/Support/Errors/UnauthorizedError.html +200 -0
  55. data/docs/yard/Servus/Support/Errors/UnprocessableEntityError.html +288 -0
  56. data/docs/yard/Servus/Support/Errors/ValidationError.html +200 -0
  57. data/docs/yard/Servus/Support/Errors.html +140 -0
  58. data/docs/yard/Servus/Support/Logger.html +856 -0
  59. data/docs/yard/Servus/Support/Rescuer/BlockContext.html +585 -0
  60. data/docs/yard/Servus/Support/Rescuer/CallOverride.html +257 -0
  61. data/docs/yard/Servus/Support/Rescuer/ClassMethods.html +343 -0
  62. data/docs/yard/Servus/Support/Rescuer.html +267 -0
  63. data/docs/yard/Servus/Support/Response.html +574 -0
  64. data/docs/yard/Servus/Support/Validator.html +1150 -0
  65. data/docs/yard/Servus/Support.html +119 -0
  66. data/docs/yard/Servus/Testing/ExampleBuilders.html +523 -0
  67. data/docs/yard/Servus/Testing/ExampleExtractor.html +578 -0
  68. data/docs/yard/Servus/Testing.html +142 -0
  69. data/docs/yard/Servus.html +343 -0
  70. data/docs/yard/_index.html +535 -0
  71. data/docs/yard/class_list.html +54 -0
  72. data/docs/yard/css/common.css +1 -0
  73. data/docs/yard/css/full_list.css +58 -0
  74. data/docs/yard/css/style.css +503 -0
  75. data/docs/yard/file.1_common_patterns.html +154 -0
  76. data/docs/yard/file.1_configuration.html +115 -0
  77. data/docs/yard/file.1_overview.html +142 -0
  78. data/docs/yard/file.1_schema_validation.html +188 -0
  79. data/docs/yard/file.2_architecture.html +157 -0
  80. data/docs/yard/file.2_error_handling.html +190 -0
  81. data/docs/yard/file.2_migration_guide.html +242 -0
  82. data/docs/yard/file.2_testing.html +227 -0
  83. data/docs/yard/file.3_async_execution.html +145 -0
  84. data/docs/yard/file.3_rails_integration.html +160 -0
  85. data/docs/yard/file.3_service_objects.html +191 -0
  86. data/docs/yard/file.4_logging.html +135 -0
  87. data/docs/yard/file.ErrorHandling.html +190 -0
  88. data/docs/yard/file.READme.html +674 -0
  89. data/docs/yard/file.architecture.html +157 -0
  90. data/docs/yard/file.async_execution.html +145 -0
  91. data/docs/yard/file.common_patterns.html +154 -0
  92. data/docs/yard/file.configuration.html +115 -0
  93. data/docs/yard/file.error_handling.html +190 -0
  94. data/docs/yard/file.logging.html +135 -0
  95. data/docs/yard/file.migration_guide.html +242 -0
  96. data/docs/yard/file.overview.html +142 -0
  97. data/docs/yard/file.rails_integration.html +160 -0
  98. data/docs/yard/file.schema_validation.html +188 -0
  99. data/docs/yard/file.service_objects.html +191 -0
  100. data/docs/yard/file.testing.html +227 -0
  101. data/docs/yard/file_list.html +119 -0
  102. data/docs/yard/frames.html +22 -0
  103. data/docs/yard/index.html +674 -0
  104. data/docs/yard/js/app.js +344 -0
  105. data/docs/yard/js/full_list.js +242 -0
  106. data/docs/yard/js/jquery.js +4 -0
  107. data/docs/yard/method_list.html +542 -0
  108. data/docs/yard/top-level-namespace.html +110 -0
  109. data/lib/generators/servus/event_handler/event_handler_generator.rb +59 -0
  110. data/lib/generators/servus/event_handler/templates/handler.rb.erb +86 -0
  111. data/lib/generators/servus/event_handler/templates/handler_spec.rb.erb +48 -0
  112. data/lib/generators/servus/service/service_generator.rb +68 -1
  113. data/lib/generators/servus/service/templates/arguments.json.erb +19 -10
  114. data/lib/generators/servus/service/templates/result.json.erb +8 -2
  115. data/lib/generators/servus/service/templates/service.rb.erb +102 -5
  116. data/lib/generators/servus/service/templates/service_spec.rb.erb +67 -6
  117. data/lib/servus/base.rb +275 -58
  118. data/lib/servus/config.rb +83 -17
  119. data/lib/servus/event_handler.rb +275 -0
  120. data/lib/servus/events/bus.rb +137 -0
  121. data/lib/servus/events/emitter.rb +162 -0
  122. data/lib/servus/events/errors.rb +10 -0
  123. data/lib/servus/extensions/async/call.rb +50 -18
  124. data/lib/servus/extensions/async/errors.rb +23 -3
  125. data/lib/servus/extensions/async/ext.rb +10 -2
  126. data/lib/servus/extensions/async/job.rb +30 -9
  127. data/lib/servus/helpers/controller_helpers.rb +73 -37
  128. data/lib/servus/railtie.rb +16 -0
  129. data/lib/servus/support/errors.rb +135 -45
  130. data/lib/servus/support/rescuer.rb +189 -36
  131. data/lib/servus/support/response.rb +49 -7
  132. data/lib/servus/support/validator.rb +147 -19
  133. data/lib/servus/testing/example_builders.rb +133 -0
  134. data/lib/servus/testing/example_extractor.rb +309 -0
  135. data/lib/servus/testing/matchers.rb +88 -0
  136. data/lib/servus/testing.rb +19 -0
  137. data/lib/servus/version.rb +1 -1
  138. data/lib/servus.rb +6 -0
  139. metadata +135 -19
data/lib/servus/base.rb CHANGED
@@ -1,94 +1,311 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Servus
4
- # Base class for all services
4
+ # Base class for all service objects in the Servus framework.
5
+ #
6
+ # This class provides the foundational functionality for implementing the Service Object pattern,
7
+ # including automatic validation, logging, benchmarking, and error handling.
8
+ #
9
+ # @abstract Subclass and implement initialize and call methods to create a service
10
+ #
11
+ # @example Creating a basic service
12
+ # class Services::ProcessPayment::Service < Servus::Base
13
+ # def initialize(user:, amount:, payment_method:)
14
+ # @user = user
15
+ # @amount = amount
16
+ # @payment_method = payment_method
17
+ # end
18
+ #
19
+ # def call
20
+ # return failure("Invalid amount") if @amount <= 0
21
+ #
22
+ # transaction = charge_payment
23
+ # success({ transaction_id: transaction.id })
24
+ # end
25
+ #
26
+ # private
27
+ #
28
+ # def charge_payment
29
+ # # Payment processing logic
30
+ # end
31
+ # end
32
+ #
33
+ # @example Using a service
34
+ # result = Services::ProcessPayment::Service.call(
35
+ # user: current_user,
36
+ # amount: 100,
37
+ # payment_method: "credit_card"
38
+ # )
39
+ #
40
+ # if result.success?
41
+ # puts "Transaction ID: #{result.data[:transaction_id]}"
42
+ # else
43
+ # puts "Error: #{result.error.message}"
44
+ # end
45
+ #
46
+ # @see Servus::Support::Response
47
+ # @see Servus::Support::Errors
5
48
  class Base
6
49
  include Servus::Support::Errors
7
50
  include Servus::Support::Rescuer
51
+ include Servus::Events::Emitter
8
52
 
9
53
  # Support class aliases
10
54
  Logger = Servus::Support::Logger
55
+ Emitter = Servus::Events::Emitter
11
56
  Response = Servus::Support::Response
12
57
  Validator = Servus::Support::Validator
13
58
 
14
- # Calls the service and returns a response
15
- #
16
- # @param args [Hash] The arguments to pass to the service
17
- # @return [Servus::Support::Response] The response
18
- # @raise [StandardError] If an exception is raised
19
- # @raise [Servus::Support::Errors::ValidationError] If result is invalid
20
- # @raise [Servus::Support::Errors::ValidationError] If arguments are invalid
21
- def self.call(**args)
22
- before_call(args)
23
- result = benchmark(**args) { new(**args).call }
24
- after_call(result)
25
-
26
- result
27
- rescue ValidationError => e
28
- Logger.log_validation_error(self, e)
29
- raise e
30
- rescue StandardError => e
31
- Logger.log_exception(self, e)
32
- raise e
33
- end
34
-
35
- # Returns a success response
59
+ # Creates a successful response with the provided data.
60
+ #
61
+ # Use this method to return successful results from your service's call method.
62
+ # The data will be validated against the RESULT_SCHEMA if one is defined.
63
+ #
64
+ # @param data [Object] the data to return in the response (typically a Hash)
65
+ # @return [Servus::Support::Response] response with success: true and the provided data
36
66
  #
37
- # @param data [Object] The data to return
38
- # @return [Servus::Support::Response] The success response
67
+ # @example Returning simple data
68
+ # def call
69
+ # success({ user_id: 123, status: "active" })
70
+ # end
71
+ #
72
+ # @example Returning nil for operations without data
73
+ # def call
74
+ # perform_action
75
+ # success(nil)
76
+ # end
77
+ #
78
+ # @see #failure
79
+ # @see Servus::Support::Response
39
80
  def success(data)
40
81
  Response.new(true, data, nil)
41
82
  end
42
83
 
43
- # Returns a failure response
84
+ # Creates a failure response with an error.
85
+ #
86
+ # Use this method to return failure results from your service's call method.
87
+ # The failure is logged automatically and returns a response containing the error.
88
+ #
89
+ # @param message [String, nil] custom error message (uses error type's default if nil)
90
+ # @param type [Class] error class to instantiate (must inherit from ServiceError)
91
+ # @return [Servus::Support::Response] response with success: false and the error
44
92
  #
45
- # @param message [String] The error message
46
- # @param type [Class] The error type
47
- # @return [Servus::Support::Response] The failure response
93
+ # @example Using default error type with custom message
94
+ # def call
95
+ # return failure("User not found") unless user_exists?
96
+ # # ...
97
+ # end
98
+ #
99
+ # @example Using custom error type
100
+ # def call
101
+ # return failure("Invalid payment", type: Servus::Support::Errors::BadRequestError)
102
+ # # ...
103
+ # end
104
+ #
105
+ # @example Using error type's default message
106
+ # def call
107
+ # return failure(type: Servus::Support::Errors::NotFoundError)
108
+ # # Uses "Not found" as the message
109
+ # end
110
+ #
111
+ # @see #success
112
+ # @see #error!
113
+ # @see Servus::Support::Errors
48
114
  def failure(message = nil, type: Servus::Support::Errors::ServiceError)
49
115
  error = type.new(message)
50
116
  Response.new(false, nil, error)
51
117
  end
52
118
 
53
- # Raises an error and logs it
119
+ # Logs an error and raises an exception, halting service execution.
120
+ #
121
+ # Use this method when you need to immediately halt execution with an exception
122
+ # rather than returning a failure response. The error is automatically logged before
123
+ # the exception is raised.
54
124
  #
55
- # @param message [String] The error message
56
- # @param type [Class] The error type
125
+ # @param message [String, nil] error message for the exception (uses default if nil)
126
+ # @param type [Class] error class to raise (must inherit from ServiceError)
57
127
  # @return [void]
128
+ # @raise [Servus::Support::Errors::ServiceError] the specified error type
129
+ #
130
+ # @example Raising an error with custom message
131
+ # def call
132
+ # error!("Critical system failure") if system_down?
133
+ # end
134
+ #
135
+ # @example Raising with specific error type
136
+ # def call
137
+ # error!("Unauthorized access", type: Servus::Support::Errors::UnauthorizedError)
138
+ # end
139
+ #
140
+ # @note Prefer {#failure} for expected error conditions. Use this for exceptional cases.
141
+ # @see #failure
58
142
  def error!(message = nil, type: Servus::Support::Errors::ServiceError)
59
- Logger.log_exception(self.class, type.new(message))
143
+ error = type.new(message)
144
+ Logger.log_exception(self.class, error)
145
+
146
+ # Emit error! events before raising
147
+ emit_events_for(:error!, Response.new(false, nil, error))
148
+
60
149
  raise type, message
61
150
  end
62
151
 
63
- # Runs call setup before call
64
- #
65
- # @param args [Hash] The arguments to pass to the service
66
- # @return [Object] The result of the call
67
- def self.before_call(args)
68
- Logger.log_call(self, args)
69
- Validator.validate_arguments!(self, args)
70
- end
152
+ class << self
153
+ # Executes the service with automatic validation, logging, and benchmarking.
154
+ #
155
+ # This is the primary entry point for executing services. It handles the complete
156
+ # service lifecycle including:
157
+ # - Input argument validation against schema
158
+ # - Service instantiation
159
+ # - Execution timing/benchmarking
160
+ # - Result validation against schema
161
+ # - Automatic logging of calls, results, and errors
162
+ #
163
+ # @param args [Hash] keyword arguments passed to the service's initialize method
164
+ # @return [Servus::Support::Response] response object with success status and data or error
165
+ #
166
+ # @raise [Servus::Support::Errors::ValidationError] if input arguments fail schema validation
167
+ # @raise [Servus::Support::Errors::ValidationError] if result data fails schema validation
168
+ # @raise [StandardError] if an uncaught exception occurs during execution
169
+ #
170
+ # @example Successful execution
171
+ # result = MyService.call(user_id: 123, amount: 50)
172
+ # result.success? # => true
173
+ # result.data # => { transaction_id: "abc123" }
174
+ #
175
+ # @example Failed execution
176
+ # result = MyService.call(user_id: 123, amount: -10)
177
+ # result.success? # => false
178
+ # result.error.message # => "Amount must be positive"
179
+ #
180
+ # @see #initialize
181
+ # @see #call
182
+ #
183
+ # rubocop:disable Metrics/MethodLength
184
+ def call(**args)
185
+ before_call(args)
71
186
 
72
- # Runs after call
73
- #
74
- # @param args [Hash] The arguments to pass to the service
75
- # @return [Object] The result of the call
76
- def self.after_call(args)
77
- Validator.validate_result!(self, args)
78
- end
187
+ instance = new(**args)
188
+ result = benchmark(**args) { instance.call }
79
189
 
80
- # Benchmarks the call
81
- #
82
- # @param args [Hash] The arguments to pass to the service
83
- # @return [Object] The result of the call
84
- def self.benchmark(**_args)
85
- start_time = Time.now.utc
86
- result = yield
87
- duration = Time.now.utc - start_time
190
+ after_call(result, instance)
191
+
192
+ result
193
+ rescue Servus::Support::Errors::ValidationError => e
194
+ Logger.log_validation_error(self, e)
195
+ raise e
196
+ rescue StandardError => e
197
+ Logger.log_exception(self, e)
198
+ raise e
199
+ end
200
+ # rubocop:enable Metrics/MethodLength
201
+
202
+ # Defines schema validation rules for the service's arguments and/or result.
203
+ #
204
+ # This method provides a clean DSL for specifying JSON schemas that will be used
205
+ # to validate service inputs and outputs. Schemas defined via this method take
206
+ # precedence over ARGUMENTS_SCHEMA and RESULT_SCHEMA constants. The next major
207
+ # version will deprecate those constants in favor of this DSL.
208
+ #
209
+ # @param arguments [Hash, nil] JSON schema for validating service arguments
210
+ # @param result [Hash, nil] JSON schema for validating service result data
211
+ # @return [void]
212
+ #
213
+ # @example Defining both arguments and result schemas
214
+ # class ProcessPayment::Service < Servus::Base
215
+ # schema(
216
+ # arguments: {
217
+ # type: 'object',
218
+ # required: ['user_id', 'amount'],
219
+ # properties: {
220
+ # user_id: { type: 'integer' },
221
+ # amount: { type: 'number', minimum: 0.01 }
222
+ # }
223
+ # },
224
+ # result: {
225
+ # type: 'object',
226
+ # required: ['transaction_id'],
227
+ # properties: {
228
+ # transaction_id: { type: 'string' }
229
+ # }
230
+ # }
231
+ # )
232
+ # end
233
+ #
234
+ # @example Defining only arguments schema
235
+ # class SendEmail::Service < Servus::Base
236
+ # schema arguments: { type: 'object', required: ['email', 'subject'] }
237
+ # end
238
+ #
239
+ # @see Servus::Support::Validator
240
+ def schema(arguments: nil, result: nil)
241
+ @arguments_schema = arguments.with_indifferent_access if arguments
242
+ @result_schema = result.with_indifferent_access if result
243
+ end
244
+
245
+ # Returns the arguments schema defined via the schema DSL method.
246
+ #
247
+ # @return [Hash, nil] the arguments schema or nil if not defined
248
+ # @api private
249
+ attr_reader :arguments_schema
250
+
251
+ # Returns the result schema defined via the schema DSL method.
252
+ #
253
+ # @return [Hash, nil] the result schema or nil if not defined
254
+ # @api private
255
+ attr_reader :result_schema
256
+
257
+ # Executes pre-call hooks including logging and argument validation.
258
+ #
259
+ # This method is automatically called before service execution and handles:
260
+ # - Logging the service call with arguments
261
+ # - Validating arguments against ARGUMENTS_SCHEMA (if defined)
262
+ #
263
+ # @param args [Hash] keyword arguments being passed to the service
264
+ # @return [void]
265
+ # @raise [Servus::Support::Errors::ValidationError] if arguments fail validation
266
+ #
267
+ # @api private
268
+ def before_call(args)
269
+ Logger.log_call(self, args)
270
+ Validator.validate_arguments!(self, args)
271
+ end
272
+
273
+ # Executes post-call hooks including result validation and event emission.
274
+ #
275
+ # This method is automatically called after service execution completes and handles:
276
+ # - Validating the result data against RESULT_SCHEMA (if defined)
277
+ # - Emitting events declared with the emits DSL
278
+ #
279
+ # @param result [Servus::Support::Response] the response returned from the service
280
+ # @param instance [Servus::Base] the service instance
281
+ # @return [void]
282
+ # @raise [Servus::Support::Errors::ValidationError] if result data fails validation
283
+ #
284
+ # @api private
285
+ def after_call(result, instance)
286
+ Validator.validate_result!(self, result)
287
+ Emitter.emit_result_events!(instance, result)
288
+ end
289
+
290
+ # Measures service execution time and logs the result.
291
+ #
292
+ # This method wraps the service execution to capture timing metrics.
293
+ # The duration is logged along with the success/failure status of the service.
294
+ #
295
+ # @param _args [Hash] keyword arguments (unused, kept for method signature compatibility)
296
+ # @yieldreturn [Servus::Support::Response] the result from executing the service
297
+ # @return [Servus::Support::Response] the service execution result
298
+ #
299
+ # @api private
300
+ def benchmark(**_args)
301
+ start_time = Time.now.utc
302
+ result = yield
303
+ duration = Time.now.utc - start_time
88
304
 
89
- Logger.log_result(self, result, duration)
305
+ Logger.log_result(self, result, duration)
90
306
 
91
- result
307
+ result
308
+ end
92
309
  end
93
310
  end
94
311
  end
data/lib/servus/config.rb CHANGED
@@ -2,38 +2,89 @@
2
2
 
3
3
  # Servus namespace
4
4
  module Servus
5
- # Configuration class for Servus
5
+ # Configuration settings for the Servus gem.
6
+ #
7
+ # Manages global configuration options including schema file locations.
8
+ # Access the configuration via {Servus.config} or modify via {Servus.configure}.
9
+ #
10
+ # @example Customizing schema location
11
+ # Servus.configure do |config|
12
+ # config.schema_root = Rails.root.join('lib/schemas')
13
+ # end
14
+ #
15
+ # @see Servus.config
16
+ # @see Servus.configure
6
17
  class Config
7
- # The directory where schemas are loaded from, can be set by the user
8
- attr_reader :schema_root
18
+ # The directory where JSON schema files are located.
19
+ #
20
+ # Defaults to `Rails.root/app/schemas/services` in Rails applications.
21
+ #
22
+ # @return [String] the schemas directory path
23
+ attr_accessor :schemas_dir
24
+
25
+ # The directory where event handlers are located.
26
+ #
27
+ # Defaults to `Rails.root/app/events` in Rails applications.
28
+ #
29
+ # @return [String] the events directory path
30
+ attr_accessor :events_dir
31
+
32
+ # The directory where services are located.
33
+ #
34
+ # Defaults to `Rails.root/app/services` in Rails applications.
35
+ #
36
+ # @return [String] the services directory path
37
+ attr_accessor :services_dir
38
+
39
+ # Whether to validate that all event handlers subscribe to events that are actually emitted by services.
40
+ #
41
+ # When enabled, raises an error on boot if handlers subscribe to non-existent events.
42
+ # Helps catch typos and orphaned handlers.
43
+ #
44
+ # @return [Boolean] true to validate, false to skip validation
45
+ attr_accessor :strict_event_validation
9
46
 
47
+ # Initializes a new configuration with default values.
48
+ #
49
+ # @api private
10
50
  def initialize
11
- # Default to Rails.root if available, otherwise use current working directory
12
- @schema_root = File.join(root_path, 'app/schemas/services')
51
+ @events_dir = 'app/events'
52
+ @schemas_dir = 'app/schemas'
53
+ @services_dir = 'app/services'
54
+ @strict_event_validation = true
13
55
  end
14
56
 
15
- # Returns the path for a specific service's schema
57
+ # Returns the full path to a service's schema file.
58
+ #
59
+ # @param service_namespace [String] underscored service namespace (e.g., "process_payment")
60
+ # @param type [String] schema type ("arguments" or "result")
61
+ # @return [String] full path to the schema JSON file
16
62
  #
17
- # @param service_namespace [String] the namespace of the service
18
- # @param type [String] the type of the schema (e.g., "arguments", "result")
19
- # @return [String] the path for the service's schema type
63
+ # @example
64
+ # config.schema_path_for("process_payment", "arguments")
65
+ # # => "/full/path/app/schemas/process_payment/arguments.json"
20
66
  def schema_path_for(service_namespace, type)
21
- File.join(schema_root.to_s, service_namespace, "#{type}.json")
67
+ File.join(root_path, schemas_dir, service_namespace, "#{type}.json")
22
68
  end
23
69
 
24
- # Returns the directory for a specific service
70
+ # Returns the directory containing a service's schema files.
71
+ #
72
+ # @param service_namespace [String] underscored service namespace
73
+ # @return [String] directory path for the service's schemas
25
74
  #
26
- # @param service_namespace [String] the namespace of the service
27
- # @return [String] the directory for the service's schemas
75
+ # @example
76
+ # config.schema_dir_for("process_payment")
77
+ # # => "/full/path/app/schemas/process_payment"
28
78
  def schema_dir_for(service_namespace)
29
- File.join(schema_root.to_s, service_namespace)
79
+ File.join(root_path, schemas_dir, service_namespace)
30
80
  end
31
81
 
32
82
  private
33
83
 
34
- # Sets the schema root directory
84
+ # Determines the application root path.
35
85
  #
36
- # @param path [String] the new schema root directory
86
+ # @return [String] Rails.root in Rails apps, or gem's root directory otherwise
87
+ # @api private
37
88
  def root_path
38
89
  if defined?(Rails) && Rails.respond_to?(:root)
39
90
  Rails.root
@@ -43,11 +94,26 @@ module Servus
43
94
  end
44
95
  end
45
96
 
46
- # Singleton config instance
97
+ # Returns the singleton configuration instance.
98
+ #
99
+ # @return [Servus::Config] the global configuration object
100
+ #
101
+ # @example
102
+ # Servus.config.schema_root
103
+ # # => "/app/app/schemas/services"
47
104
  def self.config
48
105
  @config ||= Config.new
49
106
  end
50
107
 
108
+ # Yields the configuration for modification.
109
+ #
110
+ # @yieldparam config [Servus::Config] the configuration object to modify
111
+ # @return [void]
112
+ #
113
+ # @example
114
+ # Servus.configure do |config|
115
+ # config.schema_root = Rails.root.join('custom/schemas')
116
+ # end
51
117
  def self.configure
52
118
  yield(config)
53
119
  end