servus 0.1.3 → 0.1.4

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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -0
  3. data/CHANGELOG.md +7 -0
  4. data/IDEAS.md +5 -0
  5. data/READme.md +147 -42
  6. data/Rakefile +33 -0
  7. data/builds/servus-0.1.3.gem +0 -0
  8. data/builds/servus-0.1.4.gem +0 -0
  9. data/docs/core/1_overview.md +77 -0
  10. data/docs/core/2_architecture.md +92 -0
  11. data/docs/core/3_service_objects.md +121 -0
  12. data/docs/features/1_schema_validation.md +119 -0
  13. data/docs/features/2_error_handling.md +121 -0
  14. data/docs/features/3_async_execution.md +81 -0
  15. data/docs/features/4_logging.md +64 -0
  16. data/docs/guides/1_common_patterns.md +90 -0
  17. data/docs/guides/2_migration_guide.md +175 -0
  18. data/docs/integration/1_configuration.md +51 -0
  19. data/docs/integration/2_testing.md +164 -0
  20. data/docs/integration/3_rails_integration.md +99 -0
  21. data/docs/yard/Servus/Base.html +1645 -0
  22. data/docs/yard/Servus/Config.html +582 -0
  23. data/docs/yard/Servus/Extensions/Async/Call.html +400 -0
  24. data/docs/yard/Servus/Extensions/Async/Errors/AsyncError.html +140 -0
  25. data/docs/yard/Servus/Extensions/Async/Errors/JobEnqueueError.html +154 -0
  26. data/docs/yard/Servus/Extensions/Async/Errors/ServiceNotFoundError.html +154 -0
  27. data/docs/yard/Servus/Extensions/Async/Errors.html +128 -0
  28. data/docs/yard/Servus/Extensions/Async/Ext.html +119 -0
  29. data/docs/yard/Servus/Extensions/Async/Job.html +310 -0
  30. data/docs/yard/Servus/Extensions/Async.html +141 -0
  31. data/docs/yard/Servus/Extensions.html +117 -0
  32. data/docs/yard/Servus/Generators/ServiceGenerator.html +261 -0
  33. data/docs/yard/Servus/Generators.html +115 -0
  34. data/docs/yard/Servus/Helpers/ControllerHelpers.html +457 -0
  35. data/docs/yard/Servus/Helpers.html +115 -0
  36. data/docs/yard/Servus/Railtie.html +134 -0
  37. data/docs/yard/Servus/Support/Errors/AuthenticationError.html +287 -0
  38. data/docs/yard/Servus/Support/Errors/BadRequestError.html +283 -0
  39. data/docs/yard/Servus/Support/Errors/ForbiddenError.html +284 -0
  40. data/docs/yard/Servus/Support/Errors/InternalServerError.html +283 -0
  41. data/docs/yard/Servus/Support/Errors/NotFoundError.html +284 -0
  42. data/docs/yard/Servus/Support/Errors/ServiceError.html +489 -0
  43. data/docs/yard/Servus/Support/Errors/ServiceUnavailableError.html +290 -0
  44. data/docs/yard/Servus/Support/Errors/UnauthorizedError.html +200 -0
  45. data/docs/yard/Servus/Support/Errors/UnprocessableEntityError.html +288 -0
  46. data/docs/yard/Servus/Support/Errors/ValidationError.html +200 -0
  47. data/docs/yard/Servus/Support/Errors.html +140 -0
  48. data/docs/yard/Servus/Support/Logger.html +856 -0
  49. data/docs/yard/Servus/Support/Rescuer/BlockContext.html +585 -0
  50. data/docs/yard/Servus/Support/Rescuer/CallOverride.html +257 -0
  51. data/docs/yard/Servus/Support/Rescuer/ClassMethods.html +343 -0
  52. data/docs/yard/Servus/Support/Rescuer.html +267 -0
  53. data/docs/yard/Servus/Support/Response.html +574 -0
  54. data/docs/yard/Servus/Support/Validator.html +1150 -0
  55. data/docs/yard/Servus/Support.html +119 -0
  56. data/docs/yard/Servus/Testing/ExampleBuilders.html +523 -0
  57. data/docs/yard/Servus/Testing/ExampleExtractor.html +578 -0
  58. data/docs/yard/Servus/Testing.html +142 -0
  59. data/docs/yard/Servus.html +343 -0
  60. data/docs/yard/_index.html +535 -0
  61. data/docs/yard/class_list.html +54 -0
  62. data/docs/yard/css/common.css +1 -0
  63. data/docs/yard/css/full_list.css +58 -0
  64. data/docs/yard/css/style.css +503 -0
  65. data/docs/yard/file.1_common_patterns.html +154 -0
  66. data/docs/yard/file.1_configuration.html +115 -0
  67. data/docs/yard/file.1_overview.html +142 -0
  68. data/docs/yard/file.1_schema_validation.html +188 -0
  69. data/docs/yard/file.2_architecture.html +157 -0
  70. data/docs/yard/file.2_error_handling.html +190 -0
  71. data/docs/yard/file.2_migration_guide.html +242 -0
  72. data/docs/yard/file.2_testing.html +227 -0
  73. data/docs/yard/file.3_async_execution.html +145 -0
  74. data/docs/yard/file.3_rails_integration.html +160 -0
  75. data/docs/yard/file.3_service_objects.html +191 -0
  76. data/docs/yard/file.4_logging.html +135 -0
  77. data/docs/yard/file.ErrorHandling.html +190 -0
  78. data/docs/yard/file.READme.html +674 -0
  79. data/docs/yard/file.architecture.html +157 -0
  80. data/docs/yard/file.async_execution.html +145 -0
  81. data/docs/yard/file.common_patterns.html +154 -0
  82. data/docs/yard/file.configuration.html +115 -0
  83. data/docs/yard/file.error_handling.html +190 -0
  84. data/docs/yard/file.logging.html +135 -0
  85. data/docs/yard/file.migration_guide.html +242 -0
  86. data/docs/yard/file.overview.html +142 -0
  87. data/docs/yard/file.rails_integration.html +160 -0
  88. data/docs/yard/file.schema_validation.html +188 -0
  89. data/docs/yard/file.service_objects.html +191 -0
  90. data/docs/yard/file.testing.html +227 -0
  91. data/docs/yard/file_list.html +119 -0
  92. data/docs/yard/frames.html +22 -0
  93. data/docs/yard/index.html +674 -0
  94. data/docs/yard/js/app.js +344 -0
  95. data/docs/yard/js/full_list.js +242 -0
  96. data/docs/yard/js/jquery.js +4 -0
  97. data/docs/yard/method_list.html +542 -0
  98. data/docs/yard/top-level-namespace.html +110 -0
  99. data/lib/generators/servus/service/service_generator.rb +64 -1
  100. data/lib/generators/servus/service/templates/service.rb.erb +1 -1
  101. data/lib/servus/base.rb +258 -57
  102. data/lib/servus/config.rb +58 -12
  103. data/lib/servus/extensions/async/call.rb +50 -18
  104. data/lib/servus/extensions/async/errors.rb +23 -3
  105. data/lib/servus/extensions/async/ext.rb +10 -2
  106. data/lib/servus/extensions/async/job.rb +30 -9
  107. data/lib/servus/helpers/controller_helpers.rb +73 -37
  108. data/lib/servus/support/errors.rb +135 -45
  109. data/lib/servus/support/rescuer.rb +189 -36
  110. data/lib/servus/support/response.rb +49 -7
  111. data/lib/servus/support/validator.rb +120 -19
  112. data/lib/servus/testing/example_builders.rb +133 -0
  113. data/lib/servus/testing/example_extractor.rb +309 -0
  114. data/lib/servus/testing.rb +17 -0
  115. data/lib/servus/version.rb +1 -1
  116. metadata +117 -19
@@ -3,14 +3,34 @@
3
3
  module Servus
4
4
  module Extensions
5
5
  module Async
6
+ # Error classes for asynchronous service execution.
7
+ #
8
+ # These errors are raised when async operations fail, such as job enqueueing
9
+ # failures or missing service classes during job execution.
6
10
  module Errors
7
- # Base error class for async extensions
11
+ # Base error class for all async extension errors.
12
+ #
13
+ # All async-related errors inherit from this class for easy rescue handling.
8
14
  class AsyncError < StandardError; end
9
15
 
10
- # Error raised when the job fails to enqueue
16
+ # Raised when enqueueing a background job fails.
17
+ #
18
+ # This typically occurs due to connection issues with the job backend
19
+ # (Redis, database, etc.) or configuration problems.
20
+ #
21
+ # @example
22
+ # Services::SendEmail::Service.call_async(user_id: 123)
23
+ # # => Servus::Extensions::Async::Errors::JobEnqueueError: Failed to enqueue async job
11
24
  class JobEnqueueError < AsyncError; end
12
25
 
13
- # Error raised when the service class cannot be found
26
+ # Raised when a service class name cannot be found.
27
+ #
28
+ # This occurs during job execution when the service class string
29
+ # cannot be constantized, usually due to typos or deleted classes.
30
+ #
31
+ # @example
32
+ # Job.perform_later(name: "NonExistent::Service", args: {})
33
+ # # => Servus::Extensions::Async::Errors::ServiceNotFoundError: Service class 'NonExistent::Service' not found
14
34
  class ServiceNotFoundError < AsyncError; end
15
35
  end
16
36
  end
@@ -2,13 +2,21 @@
2
2
 
3
3
  module Servus
4
4
  module Extensions
5
- # Async extensions for Servus
5
+ # Asynchronous execution extensions for Servus services.
6
+ #
7
+ # This module provides the infrastructure for running services in background jobs
8
+ # via ActiveJob. When loaded, it extends {Servus::Base} with the {Call#call_async} method.
9
+ #
10
+ # @see Servus::Extensions::Async::Call
11
+ # @see Servus::Extensions::Async::Job
6
12
  module Async
7
13
  require 'servus/extensions/async/errors'
8
14
  require 'servus/extensions/async/job'
9
15
  require 'servus/extensions/async/call'
10
16
 
11
- # Module providing async extensions for Servus
17
+ # Extension module for async functionality.
18
+ #
19
+ # @api private
12
20
  module Ext; end
13
21
  end
14
22
  end
@@ -3,21 +3,32 @@
3
3
  module Servus
4
4
  module Extensions
5
5
  module Async
6
- # Job to run a service class with given arguments.
6
+ # ActiveJob for executing Servus services asynchronously.
7
7
  #
8
- # This job will be migrated to Servus once it's stable as a .call_async method.
9
- # It takes the fully-qualified class name of the service as a string and any keyword arguments
10
- # required by the service's .call method.
8
+ # This job is used by {Call#call_async} to execute services in the background.
9
+ # It receives the service class name and arguments, instantiates the service,
10
+ # and executes it via {Servus::Base.call}.
11
11
  #
12
- # Example usage:
13
- # RunServiceJob.perform_later('SomeModule::SomeService', arg1: value1, arg2: value2)
12
+ # @example Enqueued by call_async
13
+ # Services::SendEmail::Service.call_async(user_id: 123)
14
+ # # Internally enqueues:
15
+ # # Job.perform_later(name: "Services::SendEmail::Service", args: { user_id: 123 })
14
16
  #
15
- # This will invoke SomeModule::SomeService.call(arg1: value1, arg2: value2) in a background job.
16
- #
17
- # Errors during service execution are logged.
17
+ # @api private
18
18
  class Job < ActiveJob::Base
19
19
  queue_as :default
20
20
 
21
+ # Executes the service with the provided arguments.
22
+ #
23
+ # Dynamically loads the service class by name and calls it with the
24
+ # provided keyword arguments.
25
+ #
26
+ # @param name [String] fully-qualified service class name
27
+ # @param args [Hash] keyword arguments to pass to the service
28
+ # @return [Servus::Support::Response] the service execution result
29
+ # @raise [Servus::Extensions::Async::Errors::ServiceNotFoundError] if service class doesn't exist
30
+ #
31
+ # @api private
21
32
  def perform(name:, args:)
22
33
  constantize!(name).call(**args)
23
34
  end
@@ -26,6 +37,16 @@ module Servus
26
37
 
27
38
  attr_reader :klass
28
39
 
40
+ # Safely constantizes a class name string.
41
+ #
42
+ # Converts a string class name to its corresponding class constant,
43
+ # raising an error if the class doesn't exist.
44
+ #
45
+ # @param class_name [String] the service class name
46
+ # @return [Class] the service class
47
+ # @raise [Servus::Extensions::Async::Errors::ServiceNotFoundError] if class not found
48
+ #
49
+ # @api private
29
50
  def constantize!(class_name)
30
51
  "::#{class_name}".safe_constantize ||
31
52
  (raise Errors::ServiceNotFoundError, "Service class '#{class_name}' not found.")
@@ -2,62 +2,98 @@
2
2
 
3
3
  module Servus
4
4
  module Helpers
5
- # Controller helpers
5
+ # Rails controller helper methods for service integration.
6
+ #
7
+ # Provides convenient methods for calling services from controllers and
8
+ # handling their responses. Automatically included in ActionController::Base
9
+ # when Servus is loaded in a Rails application.
10
+ #
11
+ # @example Including in a controller
12
+ # class ApplicationController < ActionController::Base
13
+ # include Servus::Helpers::ControllerHelpers
14
+ # end
15
+ #
16
+ # @see #run_service
17
+ # @see #render_service_object_error
6
18
  module ControllerHelpers
7
- # Run a service object and return the result
19
+ # Executes a service and handles success/failure automatically.
8
20
  #
9
- # This method is a helper method for controllers to run a service object and return the result.
10
- # Servus errors (Servus::Support::Errors::*) all impliment an api_error method that returns a hash with
11
- # a code and message. The service_object_error method and any custom implimentation, can be used to
12
- # automatically format and return an API error response.
21
+ # This method runs the service with the provided parameters. On success,
22
+ # it stores the result in @result for use in views. On failure, it
23
+ # automatically calls {#render_service_object_error} with the error details.
13
24
  #
14
- # @example:
15
- # class TestController < ApplicationController
16
- # def index
17
- # run_service MyService::Service, params
18
- # end
19
- # end
25
+ # The result is always stored in the @result instance variable, making it
26
+ # available in views for rendering successful responses.
20
27
  #
21
- # The result of the service is stored in the instance variable @result, which can be used
22
- # in views to template a response.
28
+ # @param klass [Class] service class to execute (must inherit from {Servus::Base})
29
+ # @param params [Hash] keyword arguments to pass to the service
30
+ # @return [Servus::Support::Response, nil] the service result, or nil if error rendered
23
31
  #
24
- # @example:
25
- # json.data do
26
- # json.some_key @result.data[:some_key]
32
+ # @example Basic usage
33
+ # class UsersController < ApplicationController
34
+ # def create
35
+ # run_service Services::CreateUser::Service, user_params
36
+ # # If successful, @result is available for rendering
37
+ # # If failed, error response is automatically rendered
38
+ # end
27
39
  # end
28
40
  #
29
- # When investigating the servus error classes, you can see the api_error method implimentation
30
- # for each error type. Below is an example implementation of the service_object_error method, which
31
- # could be overwritten to meet a specific applications needs.
41
+ # @example Using @result in views
42
+ # # In your Jbuilder view (create.json.jbuilder)
43
+ # json.user do
44
+ # json.id @result.data[:user_id]
45
+ # json.email @result.data[:email]
46
+ # end
32
47
  #
33
- # @example:
34
- # # Example implementation of api_error on Servus::Support::Errors::ServiceError
35
- # # def api_error
36
- # # { code: :bad_request, message: message }
37
- # # end
48
+ # @example Manual success handling
49
+ # class UsersController < ApplicationController
50
+ # def create
51
+ # run_service Services::CreateUser::Service, user_params
52
+ # return unless @result.success?
38
53
  #
39
- # Example implementation of service_object_error
40
- # def service_object_error(api_error)
41
- # render json: api_error, status: api_error[:code]
54
+ # # Custom success handling
55
+ # redirect_to user_path(@result.data[:user_id])
56
+ # end
42
57
  # end
43
58
  #
44
- # @param klass [Class] The service class
45
- # @param params [Hash] The parameters to pass to the service
46
- # @return [Servus::Support::Response] The result of the service
47
- #
48
- # @see Servus::Support::Errors::ServiceError
59
+ # @see #render_service_object_error
60
+ # @see Servus::Base.call
49
61
  def run_service(klass, params)
50
62
  @result = klass.call(**params)
51
63
  render_service_object_error(@result.error.api_error) unless @result.success?
52
64
  end
53
65
 
54
- # Service object error renderer
66
+ # Renders a service error as a JSON response.
55
67
  #
56
- # This method is a helper method for controllers to render service object errors.
68
+ # This method is called automatically by {#run_service} when a service fails,
69
+ # but can also be called manually for custom error handling. It renders the
70
+ # error's api_error hash with the appropriate HTTP status code.
57
71
  #
58
- # @param api_error [Hash] The API error response
72
+ # Override this method in your controller to customize error response format.
73
+ #
74
+ # @param api_error [Hash] error hash with :code and :message keys from {Servus::Support::Errors::ServiceError#api_error}
75
+ # @return [void]
76
+ #
77
+ # @example Default behavior
78
+ # # Renders: { code: :not_found, message: "User not found" }
79
+ # # With status: 404
80
+ # render_service_object_error(result.error.api_error)
81
+ #
82
+ # @example Custom error rendering
83
+ # class ApplicationController < ActionController::Base
84
+ # def render_service_object_error(api_error)
85
+ # render json: {
86
+ # error: {
87
+ # type: api_error[:code],
88
+ # details: api_error[:message],
89
+ # timestamp: Time.current
90
+ # }
91
+ # }, status: api_error[:code]
92
+ # end
93
+ # end
59
94
  #
60
- # @see Servus::Support::Errors::ServiceError
95
+ # @see Servus::Support::Errors::ServiceError#api_error
96
+ # @see #run_service
61
97
  def render_service_object_error(api_error)
62
98
  render json: api_error, status: api_error[:code]
63
99
  end
@@ -2,34 +2,80 @@
2
2
 
3
3
  module Servus
4
4
  module Support
5
+ # Contains all error classes used by Servus services.
6
+ #
7
+ # All error classes inherit from {ServiceError} and provide both a human-readable
8
+ # message and an API-friendly error response via {ServiceError#api_error}.
9
+ #
10
+ # @see ServiceError
5
11
  module Errors
6
- # Base error class for application services
12
+ # Base error class for all Servus service errors.
7
13
  #
8
- # @param message [String] The error message
9
- # @return [ServiceError] The error instance
14
+ # This class provides the foundation for all service-related errors, including:
15
+ # - Default error messages via DEFAULT_MESSAGE constant
16
+ # - API-friendly error responses via {#api_error}
17
+ # - Automatic message fallback to default if none provided
18
+ #
19
+ # @example Creating a custom error type
20
+ # class MyCustomError < Servus::Support::Errors::ServiceError
21
+ # DEFAULT_MESSAGE = 'Something went wrong'
22
+ #
23
+ # def api_error
24
+ # { code: :custom_error, message: message }
25
+ # end
26
+ # end
27
+ #
28
+ # @example Using with failure method
29
+ # def call
30
+ # return failure("User not found", type: Servus::Support::Errors::NotFoundError)
31
+ # # ...
32
+ # end
10
33
  class ServiceError < StandardError
11
34
  attr_reader :message
12
35
 
13
36
  DEFAULT_MESSAGE = 'An error occurred'
14
37
 
15
- # Initializes a new error instance
16
- # @param message [String] The error message
17
- # @return [ServiceError] The error instance
38
+ # Creates a new service error instance.
39
+ #
40
+ # @param message [String, nil] custom error message (uses DEFAULT_MESSAGE if nil)
41
+ # @return [ServiceError] the error instance
42
+ #
43
+ # @example With custom message
44
+ # error = ServiceError.new("Something went wrong")
45
+ # error.message # => "Something went wrong"
46
+ #
47
+ # @example With default message
48
+ # error = ServiceError.new
49
+ # error.message # => "An error occurred"
18
50
  def initialize(message = nil)
19
51
  @message = message || self.class::DEFAULT_MESSAGE
20
52
  super("#{self.class}: #{message}")
21
53
  end
22
54
 
23
- # 404 error response
24
- # @return [Hash] The error response
55
+ # Returns an API-friendly error response.
56
+ #
57
+ # This method formats the error for API responses, providing both a
58
+ # symbolic code and the error message. Override in subclasses to customize
59
+ # the error code for specific HTTP status codes.
60
+ #
61
+ # @return [Hash] hash with :code and :message keys
62
+ #
63
+ # @example
64
+ # error = ServiceError.new("Failed to process")
65
+ # error.api_error # => { code: :bad_request, message: "Failed to process" }
25
66
  def api_error
26
67
  { code: :bad_request, message: message }
27
68
  end
28
69
  end
29
70
 
30
- # Error class for bad request errors
31
- # @param message [String] The error message
32
- # @return [BadRequestError] The error instance
71
+ # Represents a 400 Bad Request error.
72
+ #
73
+ # Use this error when the client sends malformed or invalid request data.
74
+ #
75
+ # @example
76
+ # def call
77
+ # return failure("Invalid JSON format", type: BadRequestError)
78
+ # end
33
79
  class BadRequestError < ServiceError
34
80
  DEFAULT_MESSAGE = 'Bad request'
35
81
 
@@ -40,29 +86,45 @@ module Servus
40
86
  end
41
87
  end
42
88
 
43
- # Error class for authentication errors
44
- # @param message [String] The error message
45
- # @return [AuthenticationError] The error instance
89
+ # Represents a 401 Unauthorized error for authentication failures.
90
+ #
91
+ # Use this error when authentication credentials are missing, invalid, or expired.
92
+ #
93
+ # @example
94
+ # def call
95
+ # return failure("Invalid API key", type: AuthenticationError) unless valid_api_key?
96
+ # end
46
97
  class AuthenticationError < ServiceError
47
98
  DEFAULT_MESSAGE = 'Authentication failed'
48
99
 
49
- # 401 error response
50
- # @return [Hash] The error response
100
+ # @return [Hash] API error response with :unauthorized code
51
101
  def api_error
52
102
  { code: :unauthorized, message: message }
53
103
  end
54
104
  end
55
105
 
56
- # Error class for unauthorized errors
57
- # @param message [String] The error message
58
- # @return [UnauthorizedError] The error instance
106
+ # Represents a 401 Unauthorized error (alias for AuthenticationError).
107
+ #
108
+ # Use this error for authorization failures when credentials are valid but
109
+ # lack sufficient permissions.
110
+ #
111
+ # @example
112
+ # def call
113
+ # return failure("Access denied", type: UnauthorizedError) unless user.admin?
114
+ # end
59
115
  class UnauthorizedError < AuthenticationError
60
116
  DEFAULT_MESSAGE = 'Unauthorized'
61
117
  end
62
118
 
63
- # Error class for forbidden errors
64
- # @param message [String] The error message
65
- # @return [ForbiddenError] The error instance
119
+ # Represents a 403 Forbidden error.
120
+ #
121
+ # Use this error when the user is authenticated but not authorized to perform
122
+ # the requested action.
123
+ #
124
+ # @example
125
+ # def call
126
+ # return failure("Insufficient permissions", type: ForbiddenError) unless can_access?
127
+ # end
66
128
  class ForbiddenError < ServiceError
67
129
  DEFAULT_MESSAGE = 'Forbidden'
68
130
 
@@ -73,60 +135,88 @@ module Servus
73
135
  end
74
136
  end
75
137
 
76
- # Error class for not found errors
77
- # @param message [String] The error message
78
- # @return [NotFoundError] The error instance
138
+ # Represents a 404 Not Found error.
139
+ #
140
+ # Use this error when a requested resource cannot be found.
141
+ #
142
+ # @example
143
+ # def call
144
+ # user = User.find_by(id: @user_id)
145
+ # return failure("User not found", type: NotFoundError) unless user
146
+ # end
79
147
  class NotFoundError < ServiceError
80
148
  DEFAULT_MESSAGE = 'Not found'
81
149
 
82
- # 404 error response
83
- # @return [Hash] The error response
150
+ # @return [Hash] API error response with :not_found code
84
151
  def api_error
85
152
  { code: :not_found, message: message }
86
153
  end
87
154
  end
88
155
 
89
- # Error class for unprocessable entity errors
90
- # @param message [String] The error message
91
- # @return [UnprocessableEntityError] The error instance
156
+ # Represents a 422 Unprocessable Entity error.
157
+ #
158
+ # Use this error when the request is well-formed but contains semantic errors
159
+ # that prevent processing (e.g., business logic violations).
160
+ #
161
+ # @example
162
+ # def call
163
+ # return failure("Order already shipped", type: UnprocessableEntityError) if @order.shipped?
164
+ # end
92
165
  class UnprocessableEntityError < ServiceError
93
166
  DEFAULT_MESSAGE = 'Unprocessable entity'
94
167
 
95
- # 422 error response
96
- # @return [Hash] The error response
168
+ # @return [Hash] API error response with :unprocessable_entity code
97
169
  def api_error
98
170
  { code: :unprocessable_entity, message: message }
99
171
  end
100
172
  end
101
173
 
102
- # Error class for validation errors
103
- # @param message [String] The error message
104
- # @return [ValidationError] The error instance
174
+ # Represents validation failures (inherits 422 status).
175
+ #
176
+ # Automatically raised by the framework when schema validation fails.
177
+ # Can also be used for custom validation errors.
178
+ #
179
+ # @example
180
+ # def call
181
+ # return failure("Email format invalid", type: ValidationError) unless valid_email?
182
+ # end
105
183
  class ValidationError < UnprocessableEntityError
106
184
  DEFAULT_MESSAGE = 'Validation failed'
107
185
  end
108
186
 
109
- # Error class for internal server errors
110
- # @param message [String] The error message
111
- # @return [InternalServerError] The error instance
187
+ # Represents a 500 Internal Server Error.
188
+ #
189
+ # Use this error for unexpected server-side failures.
190
+ #
191
+ # @example
192
+ # def call
193
+ # return failure("Database connection lost", type: InternalServerError) if db_down?
194
+ # end
112
195
  class InternalServerError < ServiceError
113
196
  DEFAULT_MESSAGE = 'Internal server error'
114
197
 
115
- # 500 error response
116
- # @return [Hash] The error response
198
+ # @return [Hash] API error response with :internal_server_error code
117
199
  def api_error
118
200
  { code: :internal_server_error, message: message }
119
201
  end
120
202
  end
121
203
 
122
- # Error class for service unavailable errors
123
- # @param message [String] The error message
124
- # @return [ServiceUnavailableError] The error instance
204
+ # Represents a 503 Service Unavailable error.
205
+ #
206
+ # Use this error when a service dependency is temporarily unavailable.
207
+ #
208
+ # @example Using with rescue_from
209
+ # class MyService < Servus::Base
210
+ # rescue_from Net::HTTPError, use: ServiceUnavailableError
211
+ #
212
+ # def call
213
+ # make_external_api_call
214
+ # end
215
+ # end
125
216
  class ServiceUnavailableError < ServiceError
126
217
  DEFAULT_MESSAGE = 'Service unavailable'
127
218
 
128
- # 503 error response
129
- # @return [Hash] The error response
219
+ # @return [Hash] API error response with :service_unavailable code
130
220
  def api_error
131
221
  { code: :service_unavailable, message: message }
132
222
  end