istox_gruf 2.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +246 -0
- data/CODE_OF_CONDUCT.md +46 -0
- data/README.md +544 -0
- data/bin/gruf +29 -0
- data/lib/gruf.rb +50 -0
- data/lib/gruf/cli/executor.rb +99 -0
- data/lib/gruf/client.rb +217 -0
- data/lib/gruf/client/error.rb +66 -0
- data/lib/gruf/client/error_factory.rb +105 -0
- data/lib/gruf/configuration.rb +137 -0
- data/lib/gruf/controllers/base.rb +102 -0
- data/lib/gruf/controllers/request.rb +121 -0
- data/lib/gruf/controllers/service_binder.rb +117 -0
- data/lib/gruf/error.rb +230 -0
- data/lib/gruf/errors/debug_info.rb +56 -0
- data/lib/gruf/errors/field.rb +56 -0
- data/lib/gruf/errors/helpers.rb +44 -0
- data/lib/gruf/hooks/base.rb +34 -0
- data/lib/gruf/hooks/executor.rb +47 -0
- data/lib/gruf/hooks/registry.rb +159 -0
- data/lib/gruf/instrumentable_grpc_server.rb +64 -0
- data/lib/gruf/integrations/rails/railtie.rb +10 -0
- data/lib/gruf/interceptors/active_record/connection_reset.rb +48 -0
- data/lib/gruf/interceptors/authentication/basic.rb +87 -0
- data/lib/gruf/interceptors/base.rb +53 -0
- data/lib/gruf/interceptors/client_interceptor.rb +125 -0
- data/lib/gruf/interceptors/context.rb +56 -0
- data/lib/gruf/interceptors/instrumentation/output_metadata_timer.rb +61 -0
- data/lib/gruf/interceptors/instrumentation/request_logging/formatters/base.rb +41 -0
- data/lib/gruf/interceptors/instrumentation/request_logging/formatters/logstash.rb +43 -0
- data/lib/gruf/interceptors/instrumentation/request_logging/formatters/plain.rb +48 -0
- data/lib/gruf/interceptors/instrumentation/request_logging/interceptor.rb +225 -0
- data/lib/gruf/interceptors/instrumentation/statsd.rb +82 -0
- data/lib/gruf/interceptors/registry.rb +161 -0
- data/lib/gruf/interceptors/server_interceptor.rb +34 -0
- data/lib/gruf/interceptors/timer.rb +85 -0
- data/lib/gruf/loggable.rb +30 -0
- data/lib/gruf/logging.rb +53 -0
- data/lib/gruf/outbound/request_context.rb +71 -0
- data/lib/gruf/response.rb +71 -0
- data/lib/gruf/serializers/errors/base.rb +57 -0
- data/lib/gruf/serializers/errors/json.rb +43 -0
- data/lib/gruf/server.rb +294 -0
- data/lib/gruf/synchronized_client.rb +97 -0
- data/lib/gruf/timer.rb +78 -0
- data/lib/gruf/version.rb +20 -0
- metadata +203 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
6
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
7
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
8
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
11
|
+
# Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
14
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
15
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
16
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
17
|
+
#
|
18
|
+
module Gruf
|
19
|
+
module Controllers
|
20
|
+
##
|
21
|
+
# Binds gRPC services to a gruf controller
|
22
|
+
#
|
23
|
+
class ServiceBinder
|
24
|
+
##
|
25
|
+
# Represents a bound RPC descriptor for future-proofing internal helpers
|
26
|
+
#
|
27
|
+
class BoundDesc < SimpleDelegator; end
|
28
|
+
|
29
|
+
##
|
30
|
+
# Initialize a service binder instance with the given service
|
31
|
+
#
|
32
|
+
# @param [GRPC::GenericService] service The gRPC service stub to bind
|
33
|
+
#
|
34
|
+
def initialize(service)
|
35
|
+
@service = service
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Bind all methods on the service to the passed controller
|
40
|
+
#
|
41
|
+
# @param [Gruf::Controllers::Base] controller
|
42
|
+
#
|
43
|
+
def bind!(controller)
|
44
|
+
rpc_methods.each { |name, desc| bind_method(controller, name, desc) }
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
##
|
50
|
+
# Bind the grpc methods to the service, allowing for server interception and execution control
|
51
|
+
#
|
52
|
+
# @param [Gruf::Controllers::Base] controller
|
53
|
+
# @param [Symbol] method_name
|
54
|
+
# @param [BoundDesc] desc
|
55
|
+
#
|
56
|
+
def bind_method(controller, method_name, desc)
|
57
|
+
method_key = method_name.to_s.underscore.to_sym
|
58
|
+
service_ref = @service
|
59
|
+
|
60
|
+
@service.class_eval do
|
61
|
+
if desc.request_response?
|
62
|
+
define_method(method_key) do |message, active_call|
|
63
|
+
c = controller.new(
|
64
|
+
method_key: method_key,
|
65
|
+
service: service_ref,
|
66
|
+
message: message,
|
67
|
+
active_call: active_call,
|
68
|
+
rpc_desc: desc
|
69
|
+
)
|
70
|
+
c.call(method_key)
|
71
|
+
end
|
72
|
+
elsif desc.client_streamer?
|
73
|
+
define_method(method_key) do |active_call|
|
74
|
+
c = controller.new(
|
75
|
+
method_key: method_key,
|
76
|
+
service: service_ref,
|
77
|
+
message: proc { |&block| active_call.each_remote_read(&block) },
|
78
|
+
active_call: active_call,
|
79
|
+
rpc_desc: desc
|
80
|
+
)
|
81
|
+
c.call(method_key)
|
82
|
+
end
|
83
|
+
elsif desc.server_streamer?
|
84
|
+
define_method(method_key) do |message, active_call, &block|
|
85
|
+
c = controller.new(
|
86
|
+
method_key: method_key,
|
87
|
+
service: service_ref,
|
88
|
+
message: message,
|
89
|
+
active_call: active_call,
|
90
|
+
rpc_desc: desc
|
91
|
+
)
|
92
|
+
c.call(method_key, &block)
|
93
|
+
end
|
94
|
+
else # bidi
|
95
|
+
define_method(method_key) do |messages, active_call, &block|
|
96
|
+
c = controller.new(
|
97
|
+
method_key: method_key,
|
98
|
+
service: service_ref,
|
99
|
+
message: messages,
|
100
|
+
active_call: active_call,
|
101
|
+
rpc_desc: desc
|
102
|
+
)
|
103
|
+
c.call(method_key, &block)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
##
|
110
|
+
# @return Array<Gruf::Controllers::ServiceBinder::BoundDesc>
|
111
|
+
#
|
112
|
+
def rpc_methods
|
113
|
+
@service.rpc_descs.map { |rd| BoundDesc.new(rd) }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/lib/gruf/error.rb
ADDED
@@ -0,0 +1,230 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
6
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
7
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
8
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
11
|
+
# Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
14
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
15
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
16
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
17
|
+
#
|
18
|
+
require_relative 'errors/field'
|
19
|
+
require_relative 'errors/debug_info'
|
20
|
+
require_relative 'serializers/errors/base'
|
21
|
+
require_relative 'serializers/errors/json'
|
22
|
+
|
23
|
+
module Gruf
|
24
|
+
##
|
25
|
+
# Represents a error that can be transformed into a gRPC error and have metadata attached to the trailing headers.
|
26
|
+
# This layer acts as an middle layer that can have metadata injection, tracing support, and other functionality
|
27
|
+
# not present in the gRPC core.
|
28
|
+
#
|
29
|
+
class Error
|
30
|
+
include Gruf::Loggable
|
31
|
+
|
32
|
+
# @return [Hash<GRPC::BadStatus>] A hash mapping of gRPC BadStatus codes to error symbols
|
33
|
+
TYPES = {
|
34
|
+
ok: GRPC::Ok,
|
35
|
+
cancelled: GRPC::Cancelled,
|
36
|
+
unknown: GRPC::Unknown,
|
37
|
+
invalid_argument: GRPC::InvalidArgument,
|
38
|
+
bad_request: GRPC::InvalidArgument,
|
39
|
+
deadline_exceeded: GRPC::DeadlineExceeded,
|
40
|
+
not_found: GRPC::NotFound,
|
41
|
+
already_exists: GRPC::AlreadyExists,
|
42
|
+
unauthorized: GRPC::PermissionDenied,
|
43
|
+
permission_denied: GRPC::PermissionDenied,
|
44
|
+
unauthenticated: GRPC::Unauthenticated,
|
45
|
+
resource_exhausted: GRPC::ResourceExhausted,
|
46
|
+
failed_precondition: GRPC::FailedPrecondition,
|
47
|
+
aborted: GRPC::Aborted,
|
48
|
+
out_of_range: GRPC::OutOfRange,
|
49
|
+
unimplemented: GRPC::Unimplemented,
|
50
|
+
internal: GRPC::Internal,
|
51
|
+
unavailable: GRPC::Unavailable,
|
52
|
+
data_loss: GRPC::DataLoss
|
53
|
+
}.freeze
|
54
|
+
|
55
|
+
# Default limit on trailing metadata is 8KB. We need to be careful
|
56
|
+
# not to overflow this limit, or the response message will never
|
57
|
+
# be sent. Instead, resource_exhausted will be thrown.
|
58
|
+
MAX_METADATA_SIZE = 7.5 * 1_024
|
59
|
+
METADATA_SIZE_EXCEEDED_CODE = 'metadata_size_exceeded'
|
60
|
+
METADATA_SIZE_EXCEEDED_MSG = 'Metadata too long, risks exceeding http2 trailing metadata limit.'
|
61
|
+
|
62
|
+
# @return [Symbol] The given internal gRPC code for the error
|
63
|
+
attr_accessor :code
|
64
|
+
# @return [Symbol] An arbitrary application code that can be used for logical processing of the error by the client
|
65
|
+
attr_accessor :app_code
|
66
|
+
# @return [String] The error message returned by the server
|
67
|
+
attr_accessor :message
|
68
|
+
# @return [Array] An array of field errors that can be returned by the server
|
69
|
+
attr_accessor :field_errors
|
70
|
+
# @return [Errors::DebugInfo] A object containing debugging information, such as a stack trace and exception name,
|
71
|
+
# that can be used to debug an given error response. This is sent by the server over the trailing metadata.
|
72
|
+
attr_accessor :debug_info
|
73
|
+
# @return [GRPC::BadStatus] The gRPC BadStatus error object that was generated
|
74
|
+
attr_writer :grpc_error
|
75
|
+
# @return [Hash] The trailing metadata that was attached to the error
|
76
|
+
attr_reader :metadata
|
77
|
+
|
78
|
+
##
|
79
|
+
# Initialize the error, setting default values
|
80
|
+
#
|
81
|
+
# @param [Hash] args (Optional) An optional hash of arguments that will set fields on the error object
|
82
|
+
#
|
83
|
+
def initialize(args = {})
|
84
|
+
@field_errors = []
|
85
|
+
@metadata = {}
|
86
|
+
args.each do |k, v|
|
87
|
+
send("#{k}=", v) if respond_to?(k)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
##
|
92
|
+
# Add a field error to this error package
|
93
|
+
#
|
94
|
+
# @param [Symbol] field_name The field name for the error
|
95
|
+
# @param [Symbol] error_code The application error code for the error; e.g. :job_not_found
|
96
|
+
# @param [String] message The application error message for the error; e.g. "Job not found with ID 123"
|
97
|
+
#
|
98
|
+
def add_field_error(field_name, error_code, message = '')
|
99
|
+
@field_errors << Errors::Field.new(field_name, error_code, message)
|
100
|
+
end
|
101
|
+
|
102
|
+
##
|
103
|
+
# Return true if there are any present field errors
|
104
|
+
#
|
105
|
+
# @return [Boolean] True if the service has any field errors
|
106
|
+
#
|
107
|
+
def has_field_errors?
|
108
|
+
@field_errors.any?
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
# Set the debugging information for the error message
|
113
|
+
#
|
114
|
+
# @param [String] detail The detailed message generated by the exception
|
115
|
+
# @param [Array<String>] stack_trace An array of strings that represents the exception backtrace generated by the
|
116
|
+
# service
|
117
|
+
#
|
118
|
+
def set_debug_info(detail, stack_trace = [])
|
119
|
+
@debug_info = Errors::DebugInfo.new(detail, stack_trace)
|
120
|
+
end
|
121
|
+
|
122
|
+
##
|
123
|
+
# Ensure all metadata values are strings as HTTP/2 requires string values for transport
|
124
|
+
#
|
125
|
+
# @param [Hash] metadata The existing metadata hash
|
126
|
+
# @return [Hash] The newly set metadata
|
127
|
+
#
|
128
|
+
def metadata=(metadata)
|
129
|
+
@metadata = metadata.map { |k, str| [k, str.to_s] }.to_h
|
130
|
+
end
|
131
|
+
|
132
|
+
##
|
133
|
+
# Serialize the error for transport
|
134
|
+
#
|
135
|
+
# @return [String] The serialized error message
|
136
|
+
#
|
137
|
+
def serialize
|
138
|
+
serializer = serializer_class.new(self)
|
139
|
+
serializer.serialize.to_s
|
140
|
+
end
|
141
|
+
|
142
|
+
##
|
143
|
+
# Update the trailing metadata on the given gRPC call, including the error payload if configured
|
144
|
+
# to do so.
|
145
|
+
#
|
146
|
+
# @param [GRPC::ActiveCall] active_call The marshalled gRPC call
|
147
|
+
# @return [Error] Return the error itself after updating metadata on the given gRPC call.
|
148
|
+
# In the case of a metadata overflow error, we replace the current error with
|
149
|
+
# a new one that won't cause a low-level http2 error.
|
150
|
+
#
|
151
|
+
def attach_to_call(active_call)
|
152
|
+
metadata[Gruf.error_metadata_key.to_sym] = serialize if Gruf.append_server_errors_to_trailing_metadata
|
153
|
+
return self if metadata.empty? || !active_call || !active_call.respond_to?(:output_metadata)
|
154
|
+
|
155
|
+
# Check if we've overflown the maximum size of output metadata. If so,
|
156
|
+
# log a warning and replace the metadata with something smaller to avoid
|
157
|
+
# resource exhausted errors.
|
158
|
+
if metadata.inspect.size > MAX_METADATA_SIZE
|
159
|
+
code = METADATA_SIZE_EXCEEDED_CODE
|
160
|
+
msg = METADATA_SIZE_EXCEEDED_MSG
|
161
|
+
logger.warn "#{code}: #{msg} Original error: #{to_h.inspect}"
|
162
|
+
err = Gruf::Error.new(code: :internal, app_code: code, message: msg)
|
163
|
+
return err.attach_to_call(active_call)
|
164
|
+
end
|
165
|
+
|
166
|
+
active_call.output_metadata.update(metadata)
|
167
|
+
self
|
168
|
+
end
|
169
|
+
|
170
|
+
##
|
171
|
+
# Fail the current gRPC call with the given error, properly attaching it to the call and raising the appropriate
|
172
|
+
# gRPC BadStatus code.
|
173
|
+
#
|
174
|
+
# @param [GRPC::ActiveCall] active_call The marshalled gRPC call
|
175
|
+
# @return [GRPC::BadStatus] The gRPC BadStatus code this error is mapped to
|
176
|
+
#
|
177
|
+
def fail!(active_call)
|
178
|
+
raise attach_to_call(active_call).grpc_error
|
179
|
+
end
|
180
|
+
|
181
|
+
##
|
182
|
+
# Return the error represented in Hash form
|
183
|
+
#
|
184
|
+
# @return [Hash] The error as a hash
|
185
|
+
#
|
186
|
+
def to_h
|
187
|
+
{
|
188
|
+
code: code,
|
189
|
+
app_code: app_code,
|
190
|
+
message: message,
|
191
|
+
field_errors: field_errors.map(&:to_h),
|
192
|
+
debug_info: debug_info.to_h
|
193
|
+
}
|
194
|
+
end
|
195
|
+
|
196
|
+
##
|
197
|
+
# Return the appropriately mapped GRPC::BadStatus error object for this error
|
198
|
+
#
|
199
|
+
# @return [GRPC::BadStatus]
|
200
|
+
#
|
201
|
+
def grpc_error
|
202
|
+
md = @metadata || {}
|
203
|
+
@grpc_error = grpc_class.new(message, **md)
|
204
|
+
end
|
205
|
+
|
206
|
+
private
|
207
|
+
|
208
|
+
##
|
209
|
+
# Return the error serializer being used for gruf
|
210
|
+
#
|
211
|
+
# @return [Gruf::Serializers::Errors::Base]
|
212
|
+
#
|
213
|
+
def serializer_class
|
214
|
+
if Gruf.error_serializer
|
215
|
+
Gruf.error_serializer.is_a?(Class) ? Gruf.error_serializer : Gruf.error_serializer.to_s.constantize
|
216
|
+
else
|
217
|
+
Gruf::Serializers::Errors::Json
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
##
|
222
|
+
# Return the appropriate gRPC class for the given error code
|
223
|
+
#
|
224
|
+
# @return [Class] The gRPC error class
|
225
|
+
#
|
226
|
+
def grpc_class
|
227
|
+
TYPES[code]
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
6
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
7
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
8
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
11
|
+
# Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
14
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
15
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
16
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
17
|
+
#
|
18
|
+
module Gruf
|
19
|
+
module Errors
|
20
|
+
##
|
21
|
+
# Represents debugging information for an exception that occurred in a gRPC service
|
22
|
+
#
|
23
|
+
class DebugInfo
|
24
|
+
# @return [String] The detail message of the exception
|
25
|
+
attr_reader :detail
|
26
|
+
# @return [Array<String>] The stack trace generated by the exception as an array of strings
|
27
|
+
attr_reader :stack_trace
|
28
|
+
|
29
|
+
##
|
30
|
+
# @param [String] detail The detail message of the exception
|
31
|
+
# @param [Array<String>] stack_trace The stack trace generated by the exception as an array of strings
|
32
|
+
#
|
33
|
+
def initialize(detail, stack_trace = [])
|
34
|
+
@detail = detail
|
35
|
+
@stack_trace = (stack_trace.is_a?(String) ? stack_trace.split("\n") : stack_trace)
|
36
|
+
|
37
|
+
# Limit the size of the stack trace to reduce risk of overflowing metadata
|
38
|
+
stack_trace_limit = Gruf.backtrace_limit.to_i
|
39
|
+
stack_trace_limit = 10 if stack_trace_limit.negative?
|
40
|
+
@stack_trace = @stack_trace[0..stack_trace_limit] if stack_trace_limit.positive?
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Return this object marshalled into a hash
|
45
|
+
#
|
46
|
+
# @return [Hash] The debug info represented as a hash
|
47
|
+
#
|
48
|
+
def to_h
|
49
|
+
{
|
50
|
+
detail: detail,
|
51
|
+
stack_trace: stack_trace
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
6
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
7
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
8
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
11
|
+
# Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
14
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
15
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
16
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
17
|
+
#
|
18
|
+
module Gruf
|
19
|
+
module Errors
|
20
|
+
##
|
21
|
+
# Represents a field-specific error
|
22
|
+
#
|
23
|
+
class Field
|
24
|
+
# @return [Symbol] The name of the field as a Symbol
|
25
|
+
attr_reader :field_name
|
26
|
+
# @return [Symbol] The application error code for the field, e.g. :job_not_found
|
27
|
+
attr_reader :error_code
|
28
|
+
# @return [String] The error message for the field, e.g. "Job with ID 123 not found"
|
29
|
+
attr_reader :message
|
30
|
+
|
31
|
+
##
|
32
|
+
# @param [Symbol] field_name The name of the field as a Symbol
|
33
|
+
# @param [Symbol] error_code The application error code for the field, e.g. :job_not_found
|
34
|
+
# @param [String] message (Optional) The error message for the field, e.g. "Job with ID 123 not found"
|
35
|
+
#
|
36
|
+
def initialize(field_name, error_code, message = '')
|
37
|
+
@field_name = field_name
|
38
|
+
@error_code = error_code
|
39
|
+
@message = message
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Return the field error represented as a hash
|
44
|
+
#
|
45
|
+
# @return [Hash] The error represented as a hash
|
46
|
+
#
|
47
|
+
def to_h
|
48
|
+
{
|
49
|
+
field_name: field_name,
|
50
|
+
error_code: error_code,
|
51
|
+
message: message
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|