respondo 0.1.0 → 1.0.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/CHANGELOG.md +29 -0
- data/README.md +3 -0
- data/lib/respondo/configuration.rb +6 -0
- data/lib/respondo/controller_helpers.rb +40 -40
- data/lib/respondo/response_builder.rb +46 -13
- data/lib/respondo/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cbb9a42fee14d341990b700fd36592d17757abccb2c1a7cac406177a0eee259f
|
|
4
|
+
data.tar.gz: 31885b2ddb68d296998f82900273e36a76ec896c4ca691291544db0242932508
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6be63c0f45ece4fd72f6a4880f624cc875616e77fda1321474c59ba8f8709fc20a2187a8989661ce21d1d59a6815084020b7f570e7c1d3e4809191c32bfa3dd7
|
|
7
|
+
data.tar.gz: 8fc37dd22d9052f5d7a4fb85de3e607053358bc4ad00a7fb2cb5c991aabb77726cb35c4aef8a68ad9459ec9229ccf8e5a3a16a7b16d97dc4f3c54d35d00d7735
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.0] — Production Ready
|
|
4
|
+
|
|
5
|
+
### Breaking Changes
|
|
6
|
+
- `render_error` and all error helpers (`render_bad_request`, `render_unauthorized`,
|
|
7
|
+
`render_forbidden`, `render_not_found`, etc.) — removed `status` as a public
|
|
8
|
+
parameter; status is now derived internally and can no longer be overridden by callers
|
|
9
|
+
- All error helpers now accept `meta: {}` — allows per-call meta injection
|
|
10
|
+
(previously only `render_success` and success helpers supported this)
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `meta: {}` parameter on all error helpers — pass per-request meta such as
|
|
14
|
+
`api_version`, `env`, `region` directly at the call site
|
|
15
|
+
- `config.default_meta` — static key-value pairs merged into every response's
|
|
16
|
+
meta block automatically (e.g. `{ api_version: "v1", platform: "api" }`)
|
|
17
|
+
- Deterministic meta key ordering — `request_id` → `timestamp` → `default_meta`
|
|
18
|
+
→ caller `meta` → `code` → `status`
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- Error helper `code:` values were strings (e.g. `"404"`) while success helpers
|
|
22
|
+
used integers — all codes are now consistently integers across all helpers
|
|
23
|
+
- Trailing commas removed from `render_service_unavailable` and
|
|
24
|
+
`render_gateway_timeout` signatures
|
|
25
|
+
- `render_no_content` had mismatched `status: :ok` (200) with `code: 204` in
|
|
26
|
+
meta — now consistent
|
|
27
|
+
- Caller-supplied `meta` could previously override system fields (`timestamp`,
|
|
28
|
+
`request_id`) — system fields are now always authoritative
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
3
32
|
## [0.1.0] — Initial Release
|
|
4
33
|
|
|
5
34
|
### Added
|
data/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Respondo 🎯
|
|
2
2
|
|
|
3
|
+
[](https://badge.fury.io/rb/respondo)
|
|
4
|
+

|
|
5
|
+
|
|
3
6
|
Smart JSON API response formatter for Rails — consistent structure every time, across every app.
|
|
4
7
|
|
|
5
8
|
```json
|
|
@@ -29,12 +29,18 @@ module Respondo
|
|
|
29
29
|
# config.serializer = ->(obj) { SomeSerializer.new(obj).as_json }
|
|
30
30
|
attr_accessor :serializer
|
|
31
31
|
|
|
32
|
+
# Static key-value pairs merged into every response's meta block.
|
|
33
|
+
# @example
|
|
34
|
+
# config.default_meta = { api_version: "v1", platform: "mobile" }
|
|
35
|
+
attr_accessor :default_meta
|
|
36
|
+
|
|
32
37
|
def initialize
|
|
33
38
|
@default_success_message = "Success"
|
|
34
39
|
@default_error_message = "An error occurred"
|
|
35
40
|
@include_request_id = false
|
|
36
41
|
@camelize_keys = false
|
|
37
42
|
@serializer = nil
|
|
43
|
+
@default_meta = {} # e.g. { api_version: "v1", env: "production" }
|
|
38
44
|
end
|
|
39
45
|
end
|
|
40
46
|
end
|
|
@@ -114,78 +114,78 @@ module Respondo
|
|
|
114
114
|
# =========================================================================
|
|
115
115
|
|
|
116
116
|
# 400 Bad Request — malformed request, invalid params
|
|
117
|
-
def render_bad_request(message: "Bad request", errors: nil,
|
|
118
|
-
render_error(message: message, errors: errors, code:
|
|
117
|
+
def render_bad_request(message: "Bad request", errors: nil, meta: {})
|
|
118
|
+
render_error(message: message, errors: errors, meta: meta, code: 400, status: :bad_request)
|
|
119
119
|
end
|
|
120
120
|
|
|
121
121
|
# 401 Unauthorized — not authenticated
|
|
122
|
-
def render_unauthorized(message: "Unauthorized", errors: nil,
|
|
123
|
-
render_error(message: message, errors: errors, code:
|
|
122
|
+
def render_unauthorized(message: "Unauthorized", errors: nil, meta: {})
|
|
123
|
+
render_error(message: message, errors: errors, meta: meta, code: 401, status: :unauthorized)
|
|
124
124
|
end
|
|
125
125
|
|
|
126
126
|
# 402 Payment Required — paywalled features
|
|
127
|
-
def render_payment_required(message: "Payment required to access this resource", errors: nil,
|
|
128
|
-
render_error(message: message, errors: errors, code:
|
|
127
|
+
def render_payment_required(message: "Payment required to access this resource", errors: nil, meta: {})
|
|
128
|
+
render_error(message: message, errors: errors, meta: meta, code: 402, status: :payment_required)
|
|
129
129
|
end
|
|
130
130
|
|
|
131
131
|
# 403 Forbidden — authenticated but not authorized
|
|
132
|
-
def render_forbidden(message: "You do not have permission to perform this action", errors: nil,
|
|
133
|
-
render_error(message: message, errors: errors, code:
|
|
132
|
+
def render_forbidden(message: "You do not have permission to perform this action", errors: nil, meta: {})
|
|
133
|
+
render_error(message: message, errors: errors, meta: meta, code: 403, status: :forbidden)
|
|
134
134
|
end
|
|
135
135
|
|
|
136
136
|
# 404 Not Found
|
|
137
|
-
def render_not_found(message: "Resource not found", errors: nil,
|
|
138
|
-
render_error(message: message, errors: errors, code:
|
|
137
|
+
def render_not_found(message: "Resource not found", errors: nil, meta: {})
|
|
138
|
+
render_error(message: message, errors: errors, meta: meta, code: 404, status: :not_found)
|
|
139
139
|
end
|
|
140
140
|
|
|
141
141
|
# 405 Method Not Allowed
|
|
142
|
-
def render_method_not_allowed(message: "HTTP method not allowed", errors: nil,
|
|
143
|
-
render_error(message: message, errors: errors, code:
|
|
142
|
+
def render_method_not_allowed(message: "HTTP method not allowed", errors: nil, meta: {})
|
|
143
|
+
render_error(message: message, errors: errors, meta: meta, code: 405, status: :method_not_allowed)
|
|
144
144
|
end
|
|
145
145
|
|
|
146
146
|
# 406 Not Acceptable — client Accept header can't be satisfied
|
|
147
|
-
def render_not_acceptable(message: "Requested format not acceptable", errors: nil,
|
|
148
|
-
render_error(message: message, errors: errors, code:
|
|
147
|
+
def render_not_acceptable(message: "Requested format not acceptable", errors: nil, meta: {})
|
|
148
|
+
render_error(message: message, errors: errors, meta: meta, code: 406, status: :not_acceptable)
|
|
149
149
|
end
|
|
150
150
|
|
|
151
151
|
# 408 Request Timeout
|
|
152
|
-
def render_request_timeout(message: "Request timed out", errors: nil,
|
|
153
|
-
render_error(message: message, errors: errors, code:
|
|
152
|
+
def render_request_timeout(message: "Request timed out", errors: nil, meta: {})
|
|
153
|
+
render_error(message: message, errors: errors, meta: meta, code: 408, status: :request_timeout)
|
|
154
154
|
end
|
|
155
155
|
|
|
156
156
|
# 409 Conflict — duplicate record, state conflict
|
|
157
|
-
def render_conflict(message: "Resource conflict", errors: nil,
|
|
158
|
-
render_error(message: message, errors: errors, code:
|
|
157
|
+
def render_conflict(message: "Resource conflict", errors: nil, meta: {})
|
|
158
|
+
render_error(message: message, errors: errors, meta: meta, code: 409, status: :conflict)
|
|
159
159
|
end
|
|
160
160
|
|
|
161
161
|
# 410 Gone — resource permanently deleted
|
|
162
|
-
def render_gone(message: "Resource no longer available", errors: nil,
|
|
163
|
-
render_error(message: message, errors: errors, code:
|
|
162
|
+
def render_gone(message: "Resource no longer available", errors: nil, meta: {})
|
|
163
|
+
render_error(message: message, errors: errors, meta: meta, code: 410, status: :gone)
|
|
164
164
|
end
|
|
165
165
|
|
|
166
166
|
# 412 Precondition Failed — conditional request failed
|
|
167
|
-
def render_precondition_failed(message: "Precondition failed", errors: nil,
|
|
168
|
-
render_error(message: message, errors: errors, code:
|
|
167
|
+
def render_precondition_failed(message: "Precondition failed", errors: nil, meta: {})
|
|
168
|
+
render_error(message: message, errors: errors, meta: meta, code: 412, status: :precondition_failed)
|
|
169
169
|
end
|
|
170
170
|
|
|
171
171
|
# 415 Unsupported Media Type — wrong Content-Type header
|
|
172
|
-
def render_unsupported_media_type(message: "Unsupported media type", errors: nil,
|
|
173
|
-
render_error(message: message, errors: errors, code:
|
|
172
|
+
def render_unsupported_media_type(message: "Unsupported media type", errors: nil, meta: {})
|
|
173
|
+
render_error(message: message, errors: errors, meta: meta, code: 415, status: :unsupported_media_type)
|
|
174
174
|
end
|
|
175
175
|
|
|
176
176
|
# 422 Unprocessable Entity — validation errors (most common for APIs)
|
|
177
|
-
def render_unprocessable(message: "Validation failed", errors: nil,
|
|
178
|
-
render_error(message: message, errors: errors, code:
|
|
177
|
+
def render_unprocessable(message: "Validation failed", errors: nil, meta: {})
|
|
178
|
+
render_error(message: message, errors: errors, meta: meta, code: 422, status: :unprocessable_content)
|
|
179
179
|
end
|
|
180
180
|
|
|
181
181
|
# 423 Locked — resource is locked
|
|
182
|
-
def render_locked(message: "Resource is locked", errors: nil,
|
|
183
|
-
render_error(message: message, errors: errors, code:
|
|
182
|
+
def render_locked(message: "Resource is locked", errors: nil, meta: {})
|
|
183
|
+
render_error(message: message, errors: errors, meta: meta, code: 423, status: :locked)
|
|
184
184
|
end
|
|
185
185
|
|
|
186
186
|
# 429 Too Many Requests — rate limiting
|
|
187
|
-
def render_too_many_requests(message: "Too many requests. Please slow down.", errors: nil,
|
|
188
|
-
render_error(message: message, errors: errors, code:
|
|
187
|
+
def render_too_many_requests(message: "Too many requests. Please slow down.", errors: nil, meta: {})
|
|
188
|
+
render_error(message: message, errors: errors, meta: meta, code: 429, status: :too_many_requests)
|
|
189
189
|
end
|
|
190
190
|
|
|
191
191
|
# =========================================================================
|
|
@@ -193,28 +193,28 @@ module Respondo
|
|
|
193
193
|
# =========================================================================
|
|
194
194
|
|
|
195
195
|
# 500 Internal Server Error
|
|
196
|
-
def render_server_error(message: "An unexpected error occurred", errors: nil,
|
|
197
|
-
render_error(message: message, errors: errors, code:
|
|
196
|
+
def render_server_error(message: "An unexpected error occurred", errors: nil, meta: {})
|
|
197
|
+
render_error(message: message, errors: errors, meta: meta, code: 500, status: :internal_server_error)
|
|
198
198
|
end
|
|
199
199
|
|
|
200
200
|
# 501 Not Implemented — feature not built yet
|
|
201
|
-
def render_not_implemented(message: "This feature is not yet implemented", errors: nil,
|
|
202
|
-
render_error(message: message, errors: errors, code:
|
|
201
|
+
def render_not_implemented(message: "This feature is not yet implemented", errors: nil, meta: {})
|
|
202
|
+
render_error(message: message, errors: errors, meta: meta, code: 501, status: :not_implemented)
|
|
203
203
|
end
|
|
204
204
|
|
|
205
205
|
# 502 Bad Gateway — upstream service failed
|
|
206
|
-
def render_bad_gateway(message: "Bad gateway — upstream service error", errors: nil,
|
|
207
|
-
render_error(message: message, errors: errors, code:
|
|
206
|
+
def render_bad_gateway(message: "Bad gateway — upstream service error", errors: nil, meta: {})
|
|
207
|
+
render_error(message: message, errors: errors, meta: meta, code: 502, status: :bad_gateway)
|
|
208
208
|
end
|
|
209
209
|
|
|
210
210
|
# 503 Service Unavailable — maintenance, overloaded
|
|
211
|
-
def render_service_unavailable(message: "Service temporarily unavailable", errors: nil,
|
|
212
|
-
render_error(message: message, errors: errors, code:
|
|
211
|
+
def render_service_unavailable(message: "Service temporarily unavailable", errors: nil, meta: {})
|
|
212
|
+
render_error(message: message, errors: errors, meta: meta, code: 503, status: :service_unavailable)
|
|
213
213
|
end
|
|
214
214
|
|
|
215
215
|
# 504 Gateway Timeout — upstream service timed out
|
|
216
|
-
def render_gateway_timeout(message: "Gateway timeout — upstream service did not respond", errors: nil,
|
|
217
|
-
render_error(message: message, errors: errors, code:
|
|
216
|
+
def render_gateway_timeout(message: "Gateway timeout — upstream service did not respond", errors: nil, meta: {})
|
|
217
|
+
render_error(message: message, errors: errors, meta: meta, code: 504, status: :gateway_timeout)
|
|
218
218
|
end
|
|
219
219
|
|
|
220
220
|
private
|
|
@@ -69,27 +69,60 @@ module Respondo
|
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
def build_meta
|
|
72
|
-
meta = {
|
|
72
|
+
meta = {}
|
|
73
73
|
|
|
74
|
-
#
|
|
74
|
+
# 1. Request ID first (opt-in, always authoritative)
|
|
75
|
+
if Respondo.config.include_request_id && @request&.respond_to?(:request_id)
|
|
76
|
+
meta[:request_id] = @request.request_id
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# 2. Timestamp second
|
|
80
|
+
meta[:timestamp] = current_timestamp
|
|
81
|
+
|
|
82
|
+
# 3. Global defaults in the middle (lowest priority)
|
|
83
|
+
meta.merge!(Respondo.config.default_meta)
|
|
84
|
+
|
|
85
|
+
# 4. Caller-supplied meta (overrides defaults)
|
|
86
|
+
meta.merge!(@extra_meta)
|
|
87
|
+
|
|
88
|
+
# 5. Pagination
|
|
75
89
|
if @pagination
|
|
76
|
-
pagination =
|
|
77
|
-
Pagination.extract(@pagy)
|
|
78
|
-
else
|
|
79
|
-
Pagination.extract(@raw_data)
|
|
80
|
-
end
|
|
90
|
+
pagination = @pagy ? Pagination.extract(@pagy) : Pagination.extract(@raw_data)
|
|
81
91
|
meta[:pagination] = pagination if pagination
|
|
82
92
|
end
|
|
83
93
|
|
|
84
|
-
#
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
94
|
+
# 6. Code and status always last
|
|
95
|
+
# (these come from @extra_meta via render_error, so we re-pin them to the end)
|
|
96
|
+
code = meta.delete(:code)
|
|
97
|
+
status = meta.delete(:status)
|
|
98
|
+
meta[:code] = code if code
|
|
99
|
+
meta[:status] = status if status
|
|
88
100
|
|
|
89
|
-
|
|
90
|
-
meta.merge(@extra_meta)
|
|
101
|
+
meta
|
|
91
102
|
end
|
|
92
103
|
|
|
104
|
+
# def build_meta
|
|
105
|
+
# meta = { timestamp: current_timestamp }
|
|
106
|
+
|
|
107
|
+
# # Only extract pagination when caller has not explicitly disabled it
|
|
108
|
+
# if @pagination
|
|
109
|
+
# pagination = if @pagy
|
|
110
|
+
# Pagination.extract(@pagy)
|
|
111
|
+
# else
|
|
112
|
+
# Pagination.extract(@raw_data)
|
|
113
|
+
# end
|
|
114
|
+
# meta[:pagination] = pagination if pagination
|
|
115
|
+
# end
|
|
116
|
+
|
|
117
|
+
# # Request ID (Rails only, opt-in via config)
|
|
118
|
+
# if Respondo.config.include_request_id && @request&.respond_to?(:request_id)
|
|
119
|
+
# meta[:request_id] = @request.request_id
|
|
120
|
+
# end
|
|
121
|
+
|
|
122
|
+
# # Merge any caller-supplied meta last (allows overriding)
|
|
123
|
+
# meta.merge(@extra_meta)
|
|
124
|
+
# end
|
|
125
|
+
|
|
93
126
|
def current_timestamp
|
|
94
127
|
if defined?(Time.current)
|
|
95
128
|
Time.current.iso8601
|
data/lib/respondo/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: respondo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- shailendra Kumar
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03
|
|
11
|
+
date: 2026-04-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rspec
|