servus 0.1.6 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.claude/settings.json +9 -0
- data/CHANGELOG.md +39 -1
- data/READme.md +120 -15
- data/docs/features/6_guards.md +356 -0
- data/docs/features/guards_naming_convention.md +540 -0
- data/docs/integration/1_configuration.md +52 -2
- data/lib/generators/servus/guard/guard_generator.rb +75 -0
- data/lib/generators/servus/guard/templates/guard.rb.erb +69 -0
- data/lib/generators/servus/guard/templates/guard_spec.rb.erb +65 -0
- data/lib/servus/base.rb +9 -1
- data/lib/servus/config.rb +17 -2
- data/lib/servus/guard.rb +289 -0
- data/lib/servus/guards/falsey_guard.rb +59 -0
- data/lib/servus/guards/presence_guard.rb +80 -0
- data/lib/servus/guards/state_guard.rb +62 -0
- data/lib/servus/guards/truthy_guard.rb +61 -0
- data/lib/servus/guards.rb +48 -0
- data/lib/servus/helpers/controller_helpers.rb +20 -48
- data/lib/servus/railtie.rb +11 -3
- data/lib/servus/support/errors.rb +69 -140
- data/lib/servus/support/message_resolver.rb +166 -0
- data/lib/servus/version.rb +1 -1
- data/lib/servus.rb +5 -0
- metadata +13 -8
- data/builds/servus-0.0.1.gem +0 -0
- data/builds/servus-0.1.1.gem +0 -0
- data/builds/servus-0.1.2.gem +0 -0
- data/builds/servus-0.1.3.gem +0 -0
- data/builds/servus-0.1.4.gem +0 -0
- data/builds/servus-0.1.5.gem +0 -0
- data/docs/current_focus.md +0 -569
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servus
|
|
4
|
+
module Guards
|
|
5
|
+
# Guard that ensures all specified attributes on an object are truthy.
|
|
6
|
+
#
|
|
7
|
+
# @example Single attribute
|
|
8
|
+
# enforce_truthy!(on: user, check: :active)
|
|
9
|
+
#
|
|
10
|
+
# @example Multiple attributes (all must be truthy)
|
|
11
|
+
# enforce_truthy!(on: user, check: [:active, :verified, :confirmed])
|
|
12
|
+
#
|
|
13
|
+
# @example Conditional check
|
|
14
|
+
# if check_truthy?(on: subscription, check: :valid?)
|
|
15
|
+
# # subscription is valid
|
|
16
|
+
# end
|
|
17
|
+
class TruthyGuard < Servus::Guard
|
|
18
|
+
http_status 422
|
|
19
|
+
error_code 'must_be_truthy'
|
|
20
|
+
|
|
21
|
+
message '%<class_name>s.%<failed_attr>s must be truthy (got %<value>s)' do
|
|
22
|
+
message_data
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Tests whether all specified attributes are truthy.
|
|
26
|
+
#
|
|
27
|
+
# @param on [Object] the object to check
|
|
28
|
+
# @param check [Symbol, Array<Symbol>] attribute(s) to verify
|
|
29
|
+
# @return [Boolean] true if all attributes are truthy
|
|
30
|
+
def test(on:, check:)
|
|
31
|
+
Array(check).all? { |attr| !!on.public_send(attr) }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
# Builds the interpolation data for the error message.
|
|
37
|
+
#
|
|
38
|
+
# @return [Hash] message interpolation data
|
|
39
|
+
def message_data
|
|
40
|
+
object = kwargs[:on]
|
|
41
|
+
check = kwargs[:check]
|
|
42
|
+
failed = find_failing_attribute(object, Array(check))
|
|
43
|
+
|
|
44
|
+
{
|
|
45
|
+
failed_attr: failed,
|
|
46
|
+
class_name: object.class.name,
|
|
47
|
+
value: object.public_send(failed).inspect
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Finds the first attribute that fails the truthy check.
|
|
52
|
+
#
|
|
53
|
+
# @param object [Object] the object to check
|
|
54
|
+
# @param checks [Array<Symbol>] attributes to check
|
|
55
|
+
# @return [Symbol] the first failing attribute
|
|
56
|
+
def find_failing_attribute(object, checks)
|
|
57
|
+
checks.find { |attr| !object.public_send(attr) }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servus
|
|
4
|
+
# Module providing guard functionality to Servus services.
|
|
5
|
+
#
|
|
6
|
+
# Guard methods are defined directly on this module when guard classes
|
|
7
|
+
# inherit from Servus::Guard. The inherited hook triggers method definition,
|
|
8
|
+
# so no registry or method_missing is needed.
|
|
9
|
+
#
|
|
10
|
+
# @example Using guards in a service
|
|
11
|
+
# class TransferService < Servus::Base
|
|
12
|
+
# def call
|
|
13
|
+
# enforce_presence!(user: user, account: account)
|
|
14
|
+
# enforce_state!(on: account, check: :status, is: :active)
|
|
15
|
+
# # ... perform transfer ...
|
|
16
|
+
# success(result)
|
|
17
|
+
# end
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# @see Servus::Guard
|
|
21
|
+
module Guards
|
|
22
|
+
# Guard methods are defined dynamically via Servus::Guard.inherited
|
|
23
|
+
# when guard classes are loaded. Each guard class defines:
|
|
24
|
+
# - enforce_<name>! (throws :guard_failure on failure)
|
|
25
|
+
# - check_<name>? (returns boolean)
|
|
26
|
+
|
|
27
|
+
class << self
|
|
28
|
+
# Loads default guards if configured.
|
|
29
|
+
#
|
|
30
|
+
# Called after Guards module is defined to load built-in guards
|
|
31
|
+
# when Servus.config.include_default_guards is true.
|
|
32
|
+
#
|
|
33
|
+
# @return [void]
|
|
34
|
+
# @api private
|
|
35
|
+
def load_defaults
|
|
36
|
+
return unless Servus.config.include_default_guards
|
|
37
|
+
|
|
38
|
+
require_relative 'guards/presence_guard'
|
|
39
|
+
require_relative 'guards/truthy_guard'
|
|
40
|
+
require_relative 'guards/falsey_guard'
|
|
41
|
+
require_relative 'guards/state_guard'
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Load default guards based on configuration
|
|
48
|
+
Servus::Guards.load_defaults
|
|
@@ -14,16 +14,12 @@ module Servus
|
|
|
14
14
|
# end
|
|
15
15
|
#
|
|
16
16
|
# @see #run_service
|
|
17
|
-
# @see #
|
|
17
|
+
# @see #render_service_error
|
|
18
18
|
module ControllerHelpers
|
|
19
19
|
# Executes a service and handles success/failure automatically.
|
|
20
20
|
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
# automatically calls {#render_service_object_error} with the error details.
|
|
24
|
-
#
|
|
25
|
-
# The result is always stored in the @result instance variable, making it
|
|
26
|
-
# available in views for rendering successful responses.
|
|
21
|
+
# On success, stores the result in @result for use in views.
|
|
22
|
+
# On failure, renders the error as JSON with the appropriate HTTP status.
|
|
27
23
|
#
|
|
28
24
|
# @param klass [Class] service class to execute (must inherit from {Servus::Base})
|
|
29
25
|
# @param params [Hash] keyword arguments to pass to the service
|
|
@@ -33,69 +29,45 @@ module Servus
|
|
|
33
29
|
# class UsersController < ApplicationController
|
|
34
30
|
# def create
|
|
35
31
|
# run_service Services::CreateUser::Service, user_params
|
|
36
|
-
# # If successful, @result is available for rendering
|
|
37
|
-
# # If failed, error response is automatically rendered
|
|
38
32
|
# end
|
|
39
33
|
# end
|
|
40
34
|
#
|
|
41
|
-
# @
|
|
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
|
|
47
|
-
#
|
|
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?
|
|
53
|
-
#
|
|
54
|
-
# # Custom success handling
|
|
55
|
-
# redirect_to user_path(@result.data[:user_id])
|
|
56
|
-
# end
|
|
57
|
-
# end
|
|
58
|
-
#
|
|
59
|
-
# @see #render_service_object_error
|
|
35
|
+
# @see #render_service_error
|
|
60
36
|
# @see Servus::Base.call
|
|
61
37
|
def run_service(klass, params)
|
|
62
38
|
@result = klass.call(**params)
|
|
63
|
-
|
|
39
|
+
render_service_error(@result.error) unless @result.success?
|
|
64
40
|
end
|
|
65
41
|
|
|
66
42
|
# Renders a service error as a JSON response.
|
|
67
43
|
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
# error's api_error hash with the appropriate HTTP status code.
|
|
44
|
+
# Uses error.http_status for the response status code and
|
|
45
|
+
# error.api_error for the response body.
|
|
71
46
|
#
|
|
72
47
|
# Override this method in your controller to customize error response format.
|
|
73
48
|
#
|
|
74
|
-
# @param
|
|
49
|
+
# @param error [Servus::Support::Errors::ServiceError] the error to render
|
|
75
50
|
# @return [void]
|
|
76
51
|
#
|
|
77
52
|
# @example Default behavior
|
|
78
|
-
# # Renders: { code: :not_found, message: "User not found" }
|
|
53
|
+
# # Renders: { error: { code: :not_found, message: "User not found" } }
|
|
79
54
|
# # With status: 404
|
|
80
|
-
# render_service_object_error(result.error.api_error)
|
|
81
55
|
#
|
|
82
56
|
# @example Custom error rendering
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
# error:
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
#
|
|
91
|
-
# }, status: api_error[:code]
|
|
92
|
-
# end
|
|
57
|
+
# def render_service_error(error)
|
|
58
|
+
# render json: {
|
|
59
|
+
# error: {
|
|
60
|
+
# type: error.api_error[:code],
|
|
61
|
+
# details: error.message,
|
|
62
|
+
# timestamp: Time.current
|
|
63
|
+
# }
|
|
64
|
+
# }, status: error.http_status
|
|
93
65
|
# end
|
|
94
66
|
#
|
|
95
67
|
# @see Servus::Support::Errors::ServiceError#api_error
|
|
96
|
-
# @see #
|
|
97
|
-
def
|
|
98
|
-
render json: api_error, status:
|
|
68
|
+
# @see Servus::Support::Errors::ServiceError#http_status
|
|
69
|
+
def render_service_error(error)
|
|
70
|
+
render json: { error: error.api_error }, status: error.http_status
|
|
99
71
|
end
|
|
100
72
|
end
|
|
101
73
|
end
|
data/lib/servus/railtie.rb
CHANGED
|
@@ -19,14 +19,22 @@ module Servus
|
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
# Load event handlers
|
|
22
|
+
# Load guards and event handlers, clear caches on reload
|
|
23
23
|
config.to_prepare do
|
|
24
|
+
# Load custom guards from guards_dir
|
|
25
|
+
guards_path = Rails.root.join(Servus.config.guards_dir)
|
|
26
|
+
if Dir.exist?(guards_path)
|
|
27
|
+
Dir[File.join(guards_path, '**/*_guard.rb')].each do |file|
|
|
28
|
+
require_dependency file
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
24
32
|
Servus::Events::Bus.clear if Rails.env.development?
|
|
25
33
|
|
|
26
34
|
# Eager load all event handlers
|
|
27
35
|
events_path = Rails.root.join(Servus.config.events_dir)
|
|
28
|
-
Dir[File.join(events_path, '**/*_handler.rb')].each do |
|
|
29
|
-
require_dependency
|
|
36
|
+
Dir[File.join(events_path, '**/*_handler.rb')].each do |file|
|
|
37
|
+
require_dependency file
|
|
30
38
|
end
|
|
31
39
|
end
|
|
32
40
|
|
|
@@ -4,31 +4,31 @@ module Servus
|
|
|
4
4
|
module Support
|
|
5
5
|
# Contains all error classes used by Servus services.
|
|
6
6
|
#
|
|
7
|
-
# All error classes inherit from {ServiceError} and provide
|
|
8
|
-
#
|
|
7
|
+
# All error classes inherit from {ServiceError} and provide:
|
|
8
|
+
# - {ServiceError#http_status} for the HTTP response status
|
|
9
|
+
# - {ServiceError#api_error} for the JSON response body
|
|
9
10
|
#
|
|
10
11
|
# @see ServiceError
|
|
11
12
|
module Errors
|
|
12
13
|
# Base error class for all Servus service errors.
|
|
13
14
|
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# - API-friendly error responses via {#api_error}
|
|
17
|
-
# - Automatic message fallback to default if none provided
|
|
15
|
+
# Subclasses define their HTTP status via {#http_status} and their
|
|
16
|
+
# API response format via {#api_error}.
|
|
18
17
|
#
|
|
19
18
|
# @example Creating a custom error type
|
|
20
|
-
# class
|
|
21
|
-
# DEFAULT_MESSAGE = '
|
|
19
|
+
# class InsufficientFundsError < Servus::Support::Errors::ServiceError
|
|
20
|
+
# DEFAULT_MESSAGE = 'Insufficient funds'
|
|
21
|
+
#
|
|
22
|
+
# def http_status = :unprocessable_entity
|
|
22
23
|
#
|
|
23
24
|
# def api_error
|
|
24
|
-
# { code:
|
|
25
|
+
# { code: 'insufficient_funds', message: message }
|
|
25
26
|
# end
|
|
26
27
|
# end
|
|
27
28
|
#
|
|
28
29
|
# @example Using with failure method
|
|
29
30
|
# def call
|
|
30
|
-
# return failure("User not found", type:
|
|
31
|
-
# # ...
|
|
31
|
+
# return failure("User not found", type: NotFoundError)
|
|
32
32
|
# end
|
|
33
33
|
class ServiceError < StandardError
|
|
34
34
|
attr_reader :message
|
|
@@ -38,188 +38,117 @@ module Servus
|
|
|
38
38
|
# Creates a new service error instance.
|
|
39
39
|
#
|
|
40
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"
|
|
50
41
|
def initialize(message = nil)
|
|
51
42
|
@message = message || self.class::DEFAULT_MESSAGE
|
|
52
|
-
super("#{self.class}: #{message}")
|
|
43
|
+
super("#{self.class}: #{@message}")
|
|
53
44
|
end
|
|
54
45
|
|
|
55
|
-
# Returns
|
|
46
|
+
# Returns the HTTP status code for this error.
|
|
56
47
|
#
|
|
57
|
-
#
|
|
58
|
-
|
|
59
|
-
|
|
48
|
+
# @return [Symbol] Rails-compatible status symbol
|
|
49
|
+
def http_status = :bad_request
|
|
50
|
+
|
|
51
|
+
# Returns an API-friendly error response.
|
|
60
52
|
#
|
|
61
53
|
# @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" }
|
|
66
|
-
def api_error
|
|
67
|
-
{ code: :bad_request, message: message }
|
|
68
|
-
end
|
|
54
|
+
def api_error = { code: http_status, message: message }
|
|
69
55
|
end
|
|
70
56
|
|
|
71
|
-
#
|
|
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
|
|
57
|
+
# 400 Bad Request - malformed or invalid request data.
|
|
79
58
|
class BadRequestError < ServiceError
|
|
80
59
|
DEFAULT_MESSAGE = 'Bad request'
|
|
81
60
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def api_error
|
|
85
|
-
{ code: :bad_request, message: message }
|
|
86
|
-
end
|
|
61
|
+
def http_status = :bad_request
|
|
62
|
+
def api_error = { code: http_status, message: message }
|
|
87
63
|
end
|
|
88
64
|
|
|
89
|
-
#
|
|
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
|
|
65
|
+
# 401 Unauthorized - authentication credentials missing or invalid.
|
|
97
66
|
class AuthenticationError < ServiceError
|
|
98
67
|
DEFAULT_MESSAGE = 'Authentication failed'
|
|
99
68
|
|
|
100
|
-
|
|
101
|
-
def api_error
|
|
102
|
-
{ code: :unauthorized, message: message }
|
|
103
|
-
end
|
|
69
|
+
def http_status = :unauthorized
|
|
70
|
+
def api_error = { code: http_status, message: message }
|
|
104
71
|
end
|
|
105
72
|
|
|
106
|
-
#
|
|
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
|
|
73
|
+
# 401 Unauthorized (alias for AuthenticationError).
|
|
115
74
|
class UnauthorizedError < AuthenticationError
|
|
116
75
|
DEFAULT_MESSAGE = 'Unauthorized'
|
|
117
76
|
end
|
|
118
77
|
|
|
119
|
-
#
|
|
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
|
|
78
|
+
# 403 Forbidden - authenticated but not authorized.
|
|
128
79
|
class ForbiddenError < ServiceError
|
|
129
80
|
DEFAULT_MESSAGE = 'Forbidden'
|
|
130
81
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def api_error
|
|
134
|
-
{ code: :forbidden, message: message }
|
|
135
|
-
end
|
|
82
|
+
def http_status = :forbidden
|
|
83
|
+
def api_error = { code: http_status, message: message }
|
|
136
84
|
end
|
|
137
85
|
|
|
138
|
-
#
|
|
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
|
|
86
|
+
# 404 Not Found - requested resource does not exist.
|
|
147
87
|
class NotFoundError < ServiceError
|
|
148
88
|
DEFAULT_MESSAGE = 'Not found'
|
|
149
89
|
|
|
150
|
-
|
|
151
|
-
def api_error
|
|
152
|
-
{ code: :not_found, message: message }
|
|
153
|
-
end
|
|
90
|
+
def http_status = :not_found
|
|
91
|
+
def api_error = { code: http_status, message: message }
|
|
154
92
|
end
|
|
155
93
|
|
|
156
|
-
#
|
|
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
|
|
94
|
+
# 422 Unprocessable Entity - semantic errors in request.
|
|
165
95
|
class UnprocessableEntityError < ServiceError
|
|
166
96
|
DEFAULT_MESSAGE = 'Unprocessable entity'
|
|
167
97
|
|
|
168
|
-
|
|
169
|
-
def api_error
|
|
170
|
-
{ code: :unprocessable_entity, message: message }
|
|
171
|
-
end
|
|
98
|
+
def http_status = :unprocessable_entity
|
|
99
|
+
def api_error = { code: http_status, message: message }
|
|
172
100
|
end
|
|
173
101
|
|
|
174
|
-
#
|
|
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
|
|
102
|
+
# 422 Validation Error - schema or business validation failed.
|
|
183
103
|
class ValidationError < UnprocessableEntityError
|
|
184
104
|
DEFAULT_MESSAGE = 'Validation failed'
|
|
105
|
+
|
|
106
|
+
def api_error = { code: http_status, message: message }
|
|
185
107
|
end
|
|
186
108
|
|
|
187
|
-
#
|
|
109
|
+
# Guard validation failure with custom code.
|
|
188
110
|
#
|
|
189
|
-
#
|
|
111
|
+
# Guards define their own error code and HTTP status via the DSL.
|
|
190
112
|
#
|
|
191
113
|
# @example
|
|
192
|
-
#
|
|
193
|
-
|
|
194
|
-
|
|
114
|
+
# GuardError.new("Amount must be positive", code: 'invalid_amount', http_status: 422)
|
|
115
|
+
class GuardError < ServiceError
|
|
116
|
+
DEFAULT_MESSAGE = 'Guard validation failed'
|
|
117
|
+
|
|
118
|
+
# @return [String] application-specific error code
|
|
119
|
+
attr_reader :code
|
|
120
|
+
|
|
121
|
+
# @return [Symbol, Integer] HTTP status code
|
|
122
|
+
attr_reader :http_status
|
|
123
|
+
|
|
124
|
+
# Creates a new guard error with metadata.
|
|
125
|
+
#
|
|
126
|
+
# @param message [String, nil] error message
|
|
127
|
+
# @param code [String] error code for API responses (default: 'guard_failed')
|
|
128
|
+
# @param http_status [Symbol, Integer] HTTP status (default: :unprocessable_entity)
|
|
129
|
+
def initialize(message = nil, code: 'guard_failed', http_status: :unprocessable_entity)
|
|
130
|
+
super(message)
|
|
131
|
+
@code = code
|
|
132
|
+
@http_status = http_status
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def api_error = { code: code, message: message }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# 500 Internal Server Error - unexpected server-side failure.
|
|
195
139
|
class InternalServerError < ServiceError
|
|
196
140
|
DEFAULT_MESSAGE = 'Internal server error'
|
|
197
141
|
|
|
198
|
-
|
|
199
|
-
def api_error
|
|
200
|
-
{ code: :internal_server_error, message: message }
|
|
201
|
-
end
|
|
142
|
+
def http_status = :internal_server_error
|
|
143
|
+
def api_error = { code: http_status, message: message }
|
|
202
144
|
end
|
|
203
145
|
|
|
204
|
-
#
|
|
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
|
|
146
|
+
# 503 Service Unavailable - dependency temporarily unavailable.
|
|
216
147
|
class ServiceUnavailableError < ServiceError
|
|
217
148
|
DEFAULT_MESSAGE = 'Service unavailable'
|
|
218
149
|
|
|
219
|
-
|
|
220
|
-
def api_error
|
|
221
|
-
{ code: :service_unavailable, message: message }
|
|
222
|
-
end
|
|
150
|
+
def http_status = :service_unavailable
|
|
151
|
+
def api_error = { code: http_status, message: message }
|
|
223
152
|
end
|
|
224
153
|
end
|
|
225
154
|
end
|