Skip to content

Commit 14c1cc4

Browse files
author
蔡耀賢
committed
Merge branch 'add-message-resolver-config-hook' into 'master'
Add message resolver config hook See merge request kdanmobile/shared-code-base/gems/error_response!47
2 parents 32f2947 + e0ad32a commit 14c1cc4

8 files changed

Lines changed: 320 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## [1.3.0] - 2026-06-02
2+
- Add `config.error_message_resolver` extension hook to customize `ErrorResponse::Helper#error_response` without monkey patching.
3+
- Add compatibility support for both keyword and positional resolver signatures.
4+
- Add RSpec coverage for resolver behavior and fallback handling.
5+
16
## [1.2.1] - 2026-04-08
27
- Patch addressable to 2.9.0.
38
- Fix Regular Expression Denial of Service in Addressable templates.

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
error_response (1.2.1)
4+
error_response (1.3.0)
55
activesupport (~> 7.2.3.1)
66

77
GEM

README.md

Lines changed: 119 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,103 @@ ErrorResponse.configure do |config|
4444
end
4545
```
4646

47+
### Error Message Resolver
48+
49+
You can provide an extension hook to customize the message passed into `ErrorResponse::Helper#error_response` without monkey patching.
50+
51+
When `error_response` is called, the gem invokes `ErrorResponse.resolve_error_message` before building the API payload. The resolved value is then passed to `ErrorResponse.to_api` as the optional message suffix.
52+
53+
```ruby
54+
ErrorResponse.configure do |config|
55+
config.error_message_resolver = lambda do |key:, error_message:, error_data:, context:|
56+
# Return a string message, nil, or any value accepted by `to_api`.
57+
# context is the current controller instance when called from helper.
58+
"customized message for #{key}"
59+
end
60+
end
61+
```
62+
63+
#### Resolver arguments
64+
65+
| Argument | Description |
66+
| --- | --- |
67+
| `key` | The error key passed to `error_response` (e.g. `:bad_request_1`). |
68+
| `error_message` | The optional custom message passed to `error_response`. |
69+
| `error_data` | The optional hash or array passed to `error_response`. |
70+
| `context` | The current controller instance when called from `ErrorResponse::Helper`. `nil` when calling `ErrorResponse.resolve_error_message` directly. |
71+
72+
#### Return value
73+
74+
- Return a `String` to append after the YAML-defined message (e.g. `"bad request 1: customized message for bad_request_1"`).
75+
- Return `nil` to keep only the YAML-defined message (no suffix is appended).
76+
- If the resolver is not configured, does not respond to `call`, or raises an exception, the original `error_message` is used as a safe fallback.
77+
78+
#### Supported resolver signatures
79+
80+
Keyword arguments (recommended):
81+
82+
```ruby
83+
config.error_message_resolver = lambda do |key:, error_message:, error_data:, context:|
84+
error_message
85+
end
86+
```
87+
88+
Positional arguments (backward compatible):
89+
90+
```ruby
91+
config.error_message_resolver = lambda do |key, error_message, error_data, context|
92+
error_message
93+
end
94+
```
95+
96+
`**kwargs` style:
97+
98+
```ruby
99+
config.error_message_resolver = lambda do |**kwargs|
100+
kwargs[:error_message]
101+
end
102+
```
103+
104+
Method object:
105+
106+
```ruby
107+
class ErrorMessageResolver
108+
def call(key:, error_message:, error_data:, context:)
109+
error_message
110+
end
111+
end
112+
113+
ErrorResponse.configure do |config|
114+
config.error_message_resolver = ErrorMessageResolver.new
115+
end
116+
```
117+
118+
#### Example with `error_response`
119+
120+
Given this resolver:
121+
122+
```ruby
123+
ErrorResponse.configure do |config|
124+
config.error_message_resolver = lambda do |key:, error_message:, error_data:, context:|
125+
data = error_data.is_a?(Hash) ? error_data : {}
126+
I18n.t("errors.#{key}", default: error_message, **data)
127+
end
128+
end
129+
```
130+
131+
Calling `error_response(:bad_request_1, 'no required data', { a: 1 })` produces:
132+
133+
```json
134+
{
135+
"error_code": 400001,
136+
"error_message": "bad request 1: no required data",
137+
"error_key": "bad_request_1",
138+
"a": 1
139+
}
140+
```
141+
142+
The resolver can rewrite `error_message` before it is appended. `error_data` is merged into the JSON response after message resolution and is not modified by the resolver.
143+
47144

48145
## Usage
49146

@@ -68,7 +165,7 @@ return success_response(data) if success?
68165
```
69166

70167
> response status: 200
71-
>
168+
>
72169
> response body:
73170
74171
```json
@@ -90,7 +187,7 @@ return error_response(:bad_request_1) if failed?
90187
```
91188

92189
> response status: 400
93-
>
190+
>
94191
> response body:
95192
96193
```json
@@ -103,13 +200,15 @@ return error_response(:bad_request_1) if failed?
103200

104201
You can also provide your custom error message and error data. If error data is a hash, it will be merged into the json response; If it is an array, it will be merged into the json response with an `error_data` key.
105202

203+
When `config.error_message_resolver` is configured, the custom message is passed through the resolver before being appended to the YAML-defined message. See [Error Message Resolver](#error-message-resolver) for details.
204+
106205
```ruby
107206
# in controller actions
108207
return error_response(:bad_request_1, 'no required data', { a: 1, b: 2 }) if failed?
109208
```
110209

111210
> response status: 400
112-
>
211+
>
113212
> response body:
114213
115214
```json
@@ -148,10 +247,6 @@ ErrorResponse.to_hash(:bad_request_1)
148247

149248
gives you
150249

151-
> response status: 400
152-
>
153-
> response body:
154-
155250
```json
156251
{
157252
"error_code": 400001,
@@ -160,6 +255,23 @@ gives you
160255
}
161256
```
162257

258+
Resolve error message with optional resolver hook
259+
260+
```ruby
261+
ErrorResponse.resolve_error_message(
262+
key: :bad_request_1,
263+
error_message: "no required data",
264+
error_data: { field: "email" },
265+
context: controller # optional
266+
)
267+
```
268+
269+
gives you the resolved message string (or the original `error_message` when no resolver is configured), for example:
270+
271+
```
272+
"no required data"
273+
```
274+
163275
## Security
164276

165277
If you would like to report a security issue, please review the [Security Policy](SECURITY.md).

VERSION.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.2.1
1+
1.3.0

lib/error_response.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
require "error_response/request_error"
1010

1111
module ErrorResponse
12+
RESOLVER_KEYWORD_PARAMETER_TYPES = %i[key keyreq keyrest].freeze
13+
1214
class << self
1315
attr_writer :configuration
1416

@@ -41,8 +43,25 @@ def to_api(key, message = nil)
4143
}
4244
end
4345

46+
def resolve_error_message(key:, error_message: nil, error_data: {}, context: nil)
47+
resolver = configuration.error_message_resolver
48+
return error_message unless resolver.respond_to?(:call)
49+
50+
call_resolver(resolver, key, error_message, error_data, context)
51+
rescue StandardError
52+
error_message
53+
end
54+
4455
private
4556

57+
def call_resolver(resolver, key, error_message, error_data, context)
58+
if resolver_parameters(resolver).any? { |parameter| RESOLVER_KEYWORD_PARAMETER_TYPES.include?(parameter[0]) }
59+
resolver.call(key: key, error_message: error_message, error_data: error_data, context: context)
60+
else
61+
resolver.call(key, error_message, error_data, context)
62+
end
63+
end
64+
4665
def yaml_hash
4766
return @hash unless @hash.nil?
4867

@@ -101,5 +120,11 @@ def permitted_classes
101120
NilClass
102121
]
103122
end
123+
124+
def resolver_parameters(resolver)
125+
resolver.parameters
126+
rescue StandardError
127+
resolver.method(:call).parameters
128+
end
104129
end
105130
end

lib/error_response/configuration.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
module ErrorResponse
44
class Configuration
5-
attr_accessor :yaml_config_path
5+
attr_accessor :yaml_config_path, :error_message_resolver
66

77
def initialize
88
@yaml_config_path = ENV["YAML_CONFIG_PATH"] || "config/error_response.yml"
9+
@error_message_resolver = nil
910
end
1011
end
1112
end

lib/error_response/helper.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@ def success_response(data = {})
1717
end
1818

1919
def error_response(key, error_message = nil, error_data = {})
20-
render_content = ErrorResponse.to_api(key, error_message).deep_dup
20+
resolved_message = ErrorResponse.resolve_error_message(
21+
key: key,
22+
error_message: error_message,
23+
error_data: error_data,
24+
context: self
25+
)
26+
render_content = ErrorResponse.to_api(key, resolved_message).deep_dup
2127
if error_data.is_a?(Hash) && !error_data.empty?
2228
render_content[:json] = render_content[:json].merge(error_data)
2329
elsif error_data.is_a?(Array) && !error_data.empty?

0 commit comments

Comments
 (0)