respondo 1.0.0 → 2.1.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 +219 -0
- data/README.md +498 -194
- data/lib/generators/respondo/install/install_generator.rb +350 -0
- data/lib/respondo/controller_helpers.rb +232 -27
- data/lib/respondo/response_builder.rb +8 -12
- data/lib/respondo/version.rb +1 -1
- data/lib/respondo.rb +1 -1
- data/respondo.gemspec +13 -4
- metadata +25 -6
- data/lib/respondo/pagination.rb +0 -94
|
@@ -4,20 +4,39 @@ module Respondo
|
|
|
4
4
|
# Mixed into Rails controllers to provide render_success and render_error.
|
|
5
5
|
#
|
|
6
6
|
# Success helpers (2xx):
|
|
7
|
-
# render_success, render_created, render_accepted,
|
|
8
|
-
#
|
|
7
|
+
# render_success, render_ok, render_created, render_accepted,
|
|
8
|
+
# render_non_authoritative, render_no_content, render_reset_content,
|
|
9
|
+
# render_partial_content, render_multi_status, render_already_reported,
|
|
10
|
+
# render_im_used
|
|
11
|
+
#
|
|
12
|
+
# Informational helpers (1xx):
|
|
13
|
+
# render_continue, render_switching_protocols, render_processing,
|
|
14
|
+
# render_early_hints
|
|
15
|
+
#
|
|
16
|
+
# Redirect helpers (3xx):
|
|
17
|
+
# render_multiple_choices, render_moved_permanently, render_found,
|
|
18
|
+
# render_see_other, render_not_modified, render_temporary_redirect,
|
|
19
|
+
# render_permanent_redirect
|
|
9
20
|
#
|
|
10
21
|
# Client error helpers (4xx):
|
|
11
22
|
# render_bad_request, render_unauthorized, render_payment_required,
|
|
12
23
|
# render_forbidden, render_not_found, render_method_not_allowed,
|
|
13
|
-
# render_not_acceptable,
|
|
14
|
-
#
|
|
15
|
-
# render_precondition_failed,
|
|
16
|
-
#
|
|
24
|
+
# render_not_acceptable, render_proxy_auth_required, render_request_timeout,
|
|
25
|
+
# render_conflict, render_gone, render_length_required,
|
|
26
|
+
# render_precondition_failed, render_payload_too_large, render_uri_too_long,
|
|
27
|
+
# render_unsupported_media_type, render_range_not_satisfiable,
|
|
28
|
+
# render_expectation_failed, render_im_a_teapot, render_misdirected_request,
|
|
29
|
+
# render_unprocessable, render_locked, render_failed_dependency,
|
|
30
|
+
# render_too_early, render_upgrade_required, render_precondition_required,
|
|
31
|
+
# render_too_many_requests, render_request_header_fields_too_large,
|
|
32
|
+
# render_unavailable_for_legal_reasons
|
|
17
33
|
#
|
|
18
34
|
# Server error helpers (5xx):
|
|
19
35
|
# render_server_error, render_not_implemented, render_bad_gateway,
|
|
20
|
-
# render_service_unavailable, render_gateway_timeout
|
|
36
|
+
# render_service_unavailable, render_gateway_timeout,
|
|
37
|
+
# render_http_version_not_supported, render_variant_also_negotiates,
|
|
38
|
+
# render_insufficient_storage, render_loop_detected, render_not_extended,
|
|
39
|
+
# render_network_authentication_required
|
|
21
40
|
module ControllerHelpers
|
|
22
41
|
|
|
23
42
|
# =========================================================================
|
|
@@ -29,19 +48,19 @@ module Respondo
|
|
|
29
48
|
# @param data [Object] payload — AR model, collection, Hash, Array, nil
|
|
30
49
|
# @param message [String] human-readable description
|
|
31
50
|
# @param meta [Hash] extra meta fields merged into the meta block
|
|
32
|
-
# @param
|
|
33
|
-
#
|
|
34
|
-
#
|
|
51
|
+
# @param pagination [Hash, nil] pagination hash built by the caller, e.g.
|
|
52
|
+
# { current_page: 1, per_page: 25, total_pages: 4,
|
|
53
|
+
# total_count: 100, next_page: 2, prev_page: nil }
|
|
54
|
+
# Pass nil (default) to omit pagination from meta.
|
|
35
55
|
# @param status [Symbol, Integer] HTTP status (default: :ok / 200)
|
|
36
|
-
def render_success(data: nil, message: nil, meta: {}, code: nil,
|
|
37
|
-
merged_meta
|
|
56
|
+
def render_success(data: nil, message: nil, meta: {}, code: nil, pagination: nil, status: :ok)
|
|
57
|
+
merged_meta = code ? meta.merge(code: code, status: status) : meta
|
|
38
58
|
|
|
39
59
|
payload = ResponseBuilder.new(
|
|
40
60
|
success: true,
|
|
41
61
|
data: data,
|
|
42
62
|
message: message,
|
|
43
63
|
meta: merged_meta,
|
|
44
|
-
pagy: pagy,
|
|
45
64
|
pagination: pagination,
|
|
46
65
|
request: try(:request)
|
|
47
66
|
).build
|
|
@@ -67,46 +86,132 @@ module Respondo
|
|
|
67
86
|
message: message,
|
|
68
87
|
meta: merged_meta,
|
|
69
88
|
errors: extracted_errors,
|
|
70
|
-
pagination: false,
|
|
71
89
|
request: try(:request)
|
|
72
90
|
).build
|
|
73
91
|
|
|
74
92
|
render json: payload, status: status
|
|
75
93
|
end
|
|
76
94
|
|
|
95
|
+
# =========================================================================
|
|
96
|
+
# 1xx Informational helpers
|
|
97
|
+
# NOTE: 1xx responses are protocol-level and don't carry a body in HTTP/1.1.
|
|
98
|
+
# These helpers return a JSON body for API consistency / logging purposes,
|
|
99
|
+
# but most HTTP clients will not receive them as normal responses.
|
|
100
|
+
# =========================================================================
|
|
101
|
+
|
|
102
|
+
# 100 Continue
|
|
103
|
+
def render_continue(message: "Continue", meta: {})
|
|
104
|
+
render_success(data: nil, message: message, meta: meta, code: 100, status: :continue)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# 101 Switching Protocols
|
|
108
|
+
def render_switching_protocols(message: "Switching protocols", meta: {})
|
|
109
|
+
render_success(data: nil, message: message, meta: meta, code: 101, status: :switching_protocols)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# 102 Processing (WebDAV)
|
|
113
|
+
def render_processing(message: "Processing", meta: {})
|
|
114
|
+
render_success(data: nil, message: message, meta: meta, code: 102, status: :processing)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# 103 Early Hints
|
|
118
|
+
def render_early_hints(message: "Early hints", meta: {})
|
|
119
|
+
render_success(data: nil, message: message, meta: meta, code: 103, status: :early_hints)
|
|
120
|
+
end
|
|
121
|
+
|
|
77
122
|
# =========================================================================
|
|
78
123
|
# 2xx Success helpers
|
|
79
124
|
# =========================================================================
|
|
80
125
|
|
|
81
126
|
# 200 OK — alias for render_success with no pagination (single record)
|
|
82
|
-
def render_ok(data: nil, message: nil, meta: {}, pagination:
|
|
83
|
-
render_success(data: data, message: message, meta: meta, pagination: pagination, code:200, status: :ok)
|
|
127
|
+
def render_ok(data: nil, message: nil, meta: {}, pagination: nil)
|
|
128
|
+
render_success(data: data, message: message, meta: meta, pagination: pagination, code: 200, status: :ok)
|
|
84
129
|
end
|
|
85
130
|
|
|
86
131
|
# 201 Created
|
|
87
|
-
def render_created(data: nil, message: "Created successfully", pagination:
|
|
88
|
-
render_success(data: data, message: message, pagination: pagination, code:201, status: :created)
|
|
132
|
+
def render_created(data: nil, message: "Created successfully", meta: {}, pagination: nil)
|
|
133
|
+
render_success(data: data, message: message, meta: meta, pagination: pagination, code: 201, status: :created)
|
|
89
134
|
end
|
|
90
135
|
|
|
91
136
|
# 202 Accepted — async jobs, background processing
|
|
92
|
-
def render_accepted(data: nil, message: "Request accepted and is being processed")
|
|
93
|
-
render_success(data: data, message: message, pagination:
|
|
137
|
+
def render_accepted(data: nil, message: "Request accepted and is being processed", meta: {}, pagination: nil)
|
|
138
|
+
render_success(data: data, message: message, meta: meta, pagination: pagination, code: 202, status: :accepted)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# 203 Non-Authoritative Information — response from a third-party cache/proxy
|
|
142
|
+
def render_non_authoritative(data: nil, message: "Non-authoritative information", meta: {}, pagination: nil)
|
|
143
|
+
render_success(data: data, message: message, meta: meta, pagination: pagination, code: 203, status: :non_authoritative_information)
|
|
94
144
|
end
|
|
95
145
|
|
|
96
146
|
# 204 No Content — deletions, actions with no response body
|
|
97
147
|
# Note: we still return our standard JSON structure for consistency
|
|
98
|
-
def render_no_content(message: "Deleted successfully")
|
|
99
|
-
render_success(data: nil, message: message, pagination:
|
|
148
|
+
def render_no_content(message: "Deleted successfully", meta: {}, pagination: nil)
|
|
149
|
+
render_success(data: nil, message: message, meta: meta, pagination: pagination, code: 204, status: :ok)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# 205 Reset Content — tell the client to reset the document view
|
|
153
|
+
def render_reset_content(message: "Reset content", meta: {}, pagination: nil)
|
|
154
|
+
render_success(data: nil, message: message, meta: meta, pagination: pagination, code: 205, status: :reset_content)
|
|
100
155
|
end
|
|
101
156
|
|
|
102
157
|
# 206 Partial Content — chunked / range responses
|
|
103
|
-
def render_partial_content(data: nil, message: "Partial content returned", meta: {})
|
|
104
|
-
render_success(data: data, message: message, meta: meta, pagination:
|
|
158
|
+
def render_partial_content(data: nil, message: "Partial content returned", meta: {}, pagination: nil)
|
|
159
|
+
render_success(data: data, message: message, meta: meta, pagination: pagination, code: 206, status: :partial_content)
|
|
105
160
|
end
|
|
106
161
|
|
|
107
|
-
# 207 Multi-Status — batch operations with mixed results
|
|
108
|
-
def render_multi_status(data: nil, message: "Multi-status response", meta: {})
|
|
109
|
-
render_success(data: data, message: message, meta: meta, pagination:
|
|
162
|
+
# 207 Multi-Status (WebDAV) — batch operations with mixed results
|
|
163
|
+
def render_multi_status(data: nil, message: "Multi-status response", meta: {}, pagination: nil)
|
|
164
|
+
render_success(data: data, message: message, meta: meta, pagination: pagination, code: 207, status: :multi_status)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# 208 Already Reported (WebDAV) — members already enumerated
|
|
168
|
+
def render_already_reported(data: nil, message: "Already reported", meta: {}, pagination: nil)
|
|
169
|
+
render_success(data: data, message: message, meta: meta, pagination: pagination, code: 208, status: :already_reported)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# 226 IM Used — instance manipulations applied
|
|
173
|
+
def render_im_used(data: nil, message: "IM used", meta: {}, pagination: nil)
|
|
174
|
+
render_success(data: data, message: message, meta: meta, pagination: pagination, code: 226, status: :im_used)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# =========================================================================
|
|
178
|
+
# 3xx Redirect helpers
|
|
179
|
+
# NOTE: Pass the target URL via meta: render_moved_permanently(meta: { redirect_url: new_url })
|
|
180
|
+
# =========================================================================
|
|
181
|
+
|
|
182
|
+
# 300 Multiple Choices
|
|
183
|
+
def render_multiple_choices(data: nil, message: "Multiple choices available", meta: {}, pagination: nil)
|
|
184
|
+
render_success(data: data, message: message, meta: meta, pagination: pagination, code: 300, status: :multiple_choices)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# 301 Moved Permanently
|
|
188
|
+
def render_moved_permanently(message: "Resource has moved permanently", meta: {}, pagination: nil)
|
|
189
|
+
render_success(data: nil, message: message, meta: meta, pagination: pagination, code: 301, status: :moved_permanently)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# 302 Found (temporary redirect)
|
|
193
|
+
def render_found(message: "Resource temporarily located elsewhere", meta: {}, pagination: nil)
|
|
194
|
+
render_success(data: nil, message: message, meta: meta, pagination: pagination, code: 302, status: :found)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# 303 See Other — redirect to another URI with GET
|
|
198
|
+
def render_see_other(message: "See other resource", meta: {}, pagination: nil)
|
|
199
|
+
render_success(data: nil, message: message, meta: meta, pagination: pagination, code: 303, status: :see_other)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# 304 Not Modified — client cache is still valid
|
|
203
|
+
def render_not_modified(message: "Resource not modified", meta: {}, pagination: nil)
|
|
204
|
+
render_success(data: nil, message: message, meta: meta, pagination: pagination, code: 304, status: :not_modified)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# 307 Temporary Redirect — repeat request with same method to new URL
|
|
208
|
+
def render_temporary_redirect(message: "Temporary redirect", meta: {}, pagination: nil)
|
|
209
|
+
render_success(data: nil, message: message, meta: meta, pagination: pagination, code: 307, status: :temporary_redirect)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# 308 Permanent Redirect — like 301 but method must not change
|
|
213
|
+
def render_permanent_redirect(message: "Permanent redirect", meta: {}, pagination: nil)
|
|
214
|
+
render_success(data: nil, message: message, meta: meta, pagination: pagination, code: 308, status: :permanent_redirect)
|
|
110
215
|
end
|
|
111
216
|
|
|
112
217
|
# =========================================================================
|
|
@@ -148,6 +253,11 @@ module Respondo
|
|
|
148
253
|
render_error(message: message, errors: errors, meta: meta, code: 406, status: :not_acceptable)
|
|
149
254
|
end
|
|
150
255
|
|
|
256
|
+
# 407 Proxy Authentication Required
|
|
257
|
+
def render_proxy_auth_required(message: "Proxy authentication required", errors: nil, meta: {})
|
|
258
|
+
render_error(message: message, errors: errors, meta: meta, code: 407, status: :proxy_authentication_required)
|
|
259
|
+
end
|
|
260
|
+
|
|
151
261
|
# 408 Request Timeout
|
|
152
262
|
def render_request_timeout(message: "Request timed out", errors: nil, meta: {})
|
|
153
263
|
render_error(message: message, errors: errors, meta: meta, code: 408, status: :request_timeout)
|
|
@@ -163,16 +273,51 @@ module Respondo
|
|
|
163
273
|
render_error(message: message, errors: errors, meta: meta, code: 410, status: :gone)
|
|
164
274
|
end
|
|
165
275
|
|
|
276
|
+
# 411 Length Required — Content-Length header missing
|
|
277
|
+
def render_length_required(message: "Content-Length header required", errors: nil, meta: {})
|
|
278
|
+
render_error(message: message, errors: errors, meta: meta, code: 411, status: :length_required)
|
|
279
|
+
end
|
|
280
|
+
|
|
166
281
|
# 412 Precondition Failed — conditional request failed
|
|
167
282
|
def render_precondition_failed(message: "Precondition failed", errors: nil, meta: {})
|
|
168
283
|
render_error(message: message, errors: errors, meta: meta, code: 412, status: :precondition_failed)
|
|
169
284
|
end
|
|
170
285
|
|
|
286
|
+
# 413 Payload Too Large — request body exceeds server limit
|
|
287
|
+
def render_payload_too_large(message: "Payload too large", errors: nil, meta: {})
|
|
288
|
+
render_error(message: message, errors: errors, meta: meta, code: 413, status: :payload_too_large)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# 414 URI Too Long — request URI exceeds server limit
|
|
292
|
+
def render_uri_too_long(message: "URI too long", errors: nil, meta: {})
|
|
293
|
+
render_error(message: message, errors: errors, meta: meta, code: 414, status: :uri_too_long)
|
|
294
|
+
end
|
|
295
|
+
|
|
171
296
|
# 415 Unsupported Media Type — wrong Content-Type header
|
|
172
297
|
def render_unsupported_media_type(message: "Unsupported media type", errors: nil, meta: {})
|
|
173
298
|
render_error(message: message, errors: errors, meta: meta, code: 415, status: :unsupported_media_type)
|
|
174
299
|
end
|
|
175
300
|
|
|
301
|
+
# 416 Range Not Satisfiable — Range header cannot be fulfilled
|
|
302
|
+
def render_range_not_satisfiable(message: "Range not satisfiable", errors: nil, meta: {})
|
|
303
|
+
render_error(message: message, errors: errors, meta: meta, code: 416, status: :range_not_satisfiable)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# 417 Expectation Failed — Expect header cannot be met
|
|
307
|
+
def render_expectation_failed(message: "Expectation failed", errors: nil, meta: {})
|
|
308
|
+
render_error(message: message, errors: errors, meta: meta, code: 417, status: :expectation_failed)
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# 418 I'm a Teapot — RFC 2324
|
|
312
|
+
def render_im_a_teapot(message: "I'm a teapot", errors: nil, meta: {})
|
|
313
|
+
render_error(message: message, errors: errors, meta: meta, code: 418, status: :im_a_teapot)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# 421 Misdirected Request — request sent to wrong server
|
|
317
|
+
def render_misdirected_request(message: "Misdirected request", errors: nil, meta: {})
|
|
318
|
+
render_error(message: message, errors: errors, meta: meta, code: 421, status: :misdirected_request)
|
|
319
|
+
end
|
|
320
|
+
|
|
176
321
|
# 422 Unprocessable Entity — validation errors (most common for APIs)
|
|
177
322
|
def render_unprocessable(message: "Validation failed", errors: nil, meta: {})
|
|
178
323
|
render_error(message: message, errors: errors, meta: meta, code: 422, status: :unprocessable_content)
|
|
@@ -183,11 +328,41 @@ module Respondo
|
|
|
183
328
|
render_error(message: message, errors: errors, meta: meta, code: 423, status: :locked)
|
|
184
329
|
end
|
|
185
330
|
|
|
331
|
+
# 424 Failed Dependency (WebDAV) — previous request failed
|
|
332
|
+
def render_failed_dependency(message: "Failed dependency", errors: nil, meta: {})
|
|
333
|
+
render_error(message: message, errors: errors, meta: meta, code: 424, status: :failed_dependency)
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# 425 Too Early — server unwilling to risk processing replayed request
|
|
337
|
+
def render_too_early(message: "Too early", errors: nil, meta: {})
|
|
338
|
+
render_error(message: message, errors: errors, meta: meta, code: 425, status: :too_early)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# 426 Upgrade Required — client must switch protocols
|
|
342
|
+
def render_upgrade_required(message: "Upgrade required", errors: nil, meta: {})
|
|
343
|
+
render_error(message: message, errors: errors, meta: meta, code: 426, status: :upgrade_required)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# 428 Precondition Required — request must be conditional
|
|
347
|
+
def render_precondition_required(message: "Precondition required", errors: nil, meta: {})
|
|
348
|
+
render_error(message: message, errors: errors, meta: meta, code: 428, status: :precondition_required)
|
|
349
|
+
end
|
|
350
|
+
|
|
186
351
|
# 429 Too Many Requests — rate limiting
|
|
187
352
|
def render_too_many_requests(message: "Too many requests. Please slow down.", errors: nil, meta: {})
|
|
188
353
|
render_error(message: message, errors: errors, meta: meta, code: 429, status: :too_many_requests)
|
|
189
354
|
end
|
|
190
355
|
|
|
356
|
+
# 431 Request Header Fields Too Large
|
|
357
|
+
def render_request_header_fields_too_large(message: "Request header fields too large", errors: nil, meta: {})
|
|
358
|
+
render_error(message: message, errors: errors, meta: meta, code: 431, status: :request_header_fields_too_large)
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# 451 Unavailable for Legal Reasons — censored/DMCA etc.
|
|
362
|
+
def render_unavailable_for_legal_reasons(message: "Unavailable for legal reasons", errors: nil, meta: {})
|
|
363
|
+
render_error(message: message, errors: errors, meta: meta, code: 451, status: :unavailable_for_legal_reasons)
|
|
364
|
+
end
|
|
365
|
+
|
|
191
366
|
# =========================================================================
|
|
192
367
|
# 5xx Server error helpers
|
|
193
368
|
# =========================================================================
|
|
@@ -217,6 +392,36 @@ module Respondo
|
|
|
217
392
|
render_error(message: message, errors: errors, meta: meta, code: 504, status: :gateway_timeout)
|
|
218
393
|
end
|
|
219
394
|
|
|
395
|
+
# 505 HTTP Version Not Supported
|
|
396
|
+
def render_http_version_not_supported(message: "HTTP version not supported", errors: nil, meta: {})
|
|
397
|
+
render_error(message: message, errors: errors, meta: meta, code: 505, status: :http_version_not_supported)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# 506 Variant Also Negotiates — server configuration error
|
|
401
|
+
def render_variant_also_negotiates(message: "Variant also negotiates", errors: nil, meta: {})
|
|
402
|
+
render_error(message: message, errors: errors, meta: meta, code: 506, status: :variant_also_negotiates)
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
# 507 Insufficient Storage (WebDAV)
|
|
406
|
+
def render_insufficient_storage(message: "Insufficient storage", errors: nil, meta: {})
|
|
407
|
+
render_error(message: message, errors: errors, meta: meta, code: 507, status: :insufficient_storage)
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# 508 Loop Detected (WebDAV)
|
|
411
|
+
def render_loop_detected(message: "Loop detected", errors: nil, meta: {})
|
|
412
|
+
render_error(message: message, errors: errors, meta: meta, code: 508, status: :loop_detected)
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# 510 Not Extended — further extensions needed
|
|
416
|
+
def render_not_extended(message: "Not extended", errors: nil, meta: {})
|
|
417
|
+
render_error(message: message, errors: errors, meta: meta, code: 510, status: :not_extended)
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
# 511 Network Authentication Required — must authenticate to access network
|
|
421
|
+
def render_network_authentication_required(message: "Network authentication required", errors: nil, meta: {})
|
|
422
|
+
render_error(message: message, errors: errors, meta: meta, code: 511, status: :network_authentication_required)
|
|
423
|
+
end
|
|
424
|
+
|
|
220
425
|
private
|
|
221
426
|
|
|
222
427
|
# Normalize errors into a plain Hash regardless of source type.
|
|
@@ -17,24 +17,24 @@ module Respondo
|
|
|
17
17
|
# { timestamp: ISO8601 String }
|
|
18
18
|
# Plus pagination keys when pagination: true and the data is a paginated collection.
|
|
19
19
|
# Plus request_id when config.include_request_id is true.
|
|
20
|
+
# Plus pagination when a pagination hash is supplied by the caller.
|
|
20
21
|
class ResponseBuilder
|
|
21
22
|
# @param success [Boolean]
|
|
22
23
|
# @param data [Object] anything — serialized automatically
|
|
23
24
|
# @param message [String]
|
|
24
25
|
# @param meta [Hash] caller-supplied extra meta (merged in)
|
|
25
26
|
# @param errors [Hash] field-level errors (for 422 responses)
|
|
26
|
-
# @param
|
|
27
|
-
#
|
|
28
|
-
#
|
|
27
|
+
# @param pagination [Hash, nil] plain pagination hash supplied by the caller, e.g.
|
|
28
|
+
# { current_page: 1, per_page: 25, total_pages: 4,
|
|
29
|
+
# total_count: 100, next_page: 2, prev_page: nil }
|
|
29
30
|
# @param request [Object] ActionDispatch::Request (for request_id)
|
|
30
31
|
def initialize(success:, data: nil, message: nil, meta: {}, errors: nil,
|
|
31
|
-
|
|
32
|
+
pagination: nil, request: nil)
|
|
32
33
|
@success = success
|
|
33
34
|
@raw_data = data
|
|
34
35
|
@message = message
|
|
35
36
|
@extra_meta = meta || {}
|
|
36
37
|
@errors = errors
|
|
37
|
-
@pagy = pagy
|
|
38
38
|
@pagination = pagination
|
|
39
39
|
@request = request
|
|
40
40
|
end
|
|
@@ -85,14 +85,10 @@ module Respondo
|
|
|
85
85
|
# 4. Caller-supplied meta (overrides defaults)
|
|
86
86
|
meta.merge!(@extra_meta)
|
|
87
87
|
|
|
88
|
-
# 5. Pagination
|
|
89
|
-
if @pagination
|
|
90
|
-
pagination = @pagy ? Pagination.extract(@pagy) : Pagination.extract(@raw_data)
|
|
91
|
-
meta[:pagination] = pagination if pagination
|
|
92
|
-
end
|
|
88
|
+
# 5. Pagination — plain hash from the caller, placed last before code/status
|
|
89
|
+
meta[:pagination] = @pagination if @pagination.is_a?(Hash) && !@pagination.empty?
|
|
93
90
|
|
|
94
|
-
# 6.
|
|
95
|
-
# (these come from @extra_meta via render_error, so we re-pin them to the end)
|
|
91
|
+
# 6. Re-pin code and status to the very end
|
|
96
92
|
code = meta.delete(:code)
|
|
97
93
|
status = meta.delete(:status)
|
|
98
94
|
meta[:code] = code if code
|
data/lib/respondo/version.rb
CHANGED
data/lib/respondo.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require_relative "respondo/version"
|
|
4
4
|
require_relative "respondo/configuration"
|
|
5
5
|
require_relative "respondo/serializer"
|
|
6
|
-
require_relative "respondo/pagination"
|
|
6
|
+
# require_relative "respondo/pagination"
|
|
7
7
|
require_relative "respondo/response_builder"
|
|
8
8
|
require_relative "respondo/controller_helpers"
|
|
9
9
|
|
data/respondo.gemspec
CHANGED
|
@@ -19,11 +19,20 @@ Gem::Specification.new do |spec|
|
|
|
19
19
|
spec.license = "MIT"
|
|
20
20
|
spec.required_ruby_version = ">= 2.7.0"
|
|
21
21
|
|
|
22
|
+
spec.metadata = {
|
|
23
|
+
"homepage_uri" => spec.homepage,
|
|
24
|
+
"source_code_uri" => spec.homepage,
|
|
25
|
+
"changelog_uri" => "#{spec.homepage}/blob/main/CHANGELOG.md",
|
|
26
|
+
"bug_tracker_uri" => "#{spec.homepage}/auditron/issues",
|
|
27
|
+
"rubygems_mfa_required" => "true"
|
|
28
|
+
}
|
|
29
|
+
|
|
22
30
|
spec.files = Dir["lib/**/*.rb", "README.md", "LICENSE.txt", "CHANGELOG.md", "respondo.gemspec"]
|
|
23
31
|
spec.require_paths = ["lib"]
|
|
24
32
|
|
|
25
|
-
spec.add_development_dependency "
|
|
26
|
-
spec.add_development_dependency "
|
|
27
|
-
spec.add_development_dependency "
|
|
28
|
-
spec.add_development_dependency
|
|
33
|
+
spec.add_development_dependency "railties", "~> 8.1.3"
|
|
34
|
+
spec.add_development_dependency "rspec", "~> 3.12"
|
|
35
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
36
|
+
spec.add_development_dependency "activesupport", "~> 8.1.3"
|
|
37
|
+
spec.add_development_dependency 'simplecov', "~> 0.22"
|
|
29
38
|
end
|
metadata
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: respondo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 2.1.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-04-
|
|
11
|
+
date: 2026-04-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: railties
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 8.1.3
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 8.1.3
|
|
13
27
|
- !ruby/object:Gem::Dependency
|
|
14
28
|
name: rspec
|
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -44,14 +58,14 @@ dependencies:
|
|
|
44
58
|
requirements:
|
|
45
59
|
- - "~>"
|
|
46
60
|
- !ruby/object:Gem::Version
|
|
47
|
-
version:
|
|
61
|
+
version: 8.1.3
|
|
48
62
|
type: :development
|
|
49
63
|
prerelease: false
|
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
65
|
requirements:
|
|
52
66
|
- - "~>"
|
|
53
67
|
- !ruby/object:Gem::Version
|
|
54
|
-
version:
|
|
68
|
+
version: 8.1.3
|
|
55
69
|
- !ruby/object:Gem::Dependency
|
|
56
70
|
name: simplecov
|
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -80,10 +94,10 @@ files:
|
|
|
80
94
|
- CHANGELOG.md
|
|
81
95
|
- LICENSE.txt
|
|
82
96
|
- README.md
|
|
97
|
+
- lib/generators/respondo/install/install_generator.rb
|
|
83
98
|
- lib/respondo.rb
|
|
84
99
|
- lib/respondo/configuration.rb
|
|
85
100
|
- lib/respondo/controller_helpers.rb
|
|
86
|
-
- lib/respondo/pagination.rb
|
|
87
101
|
- lib/respondo/railtie.rb
|
|
88
102
|
- lib/respondo/response_builder.rb
|
|
89
103
|
- lib/respondo/serializer.rb
|
|
@@ -92,7 +106,12 @@ files:
|
|
|
92
106
|
homepage: https://github.com/spatelpatidar/respondo
|
|
93
107
|
licenses:
|
|
94
108
|
- MIT
|
|
95
|
-
metadata:
|
|
109
|
+
metadata:
|
|
110
|
+
homepage_uri: https://github.com/spatelpatidar/respondo
|
|
111
|
+
source_code_uri: https://github.com/spatelpatidar/respondo
|
|
112
|
+
changelog_uri: https://github.com/spatelpatidar/respondo/blob/main/CHANGELOG.md
|
|
113
|
+
bug_tracker_uri: https://github.com/spatelpatidar/respondo/auditron/issues
|
|
114
|
+
rubygems_mfa_required: 'true'
|
|
96
115
|
post_install_message:
|
|
97
116
|
rdoc_options: []
|
|
98
117
|
require_paths:
|
data/lib/respondo/pagination.rb
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Respondo
|
|
4
|
-
# Extracts pagination metadata from Kaminari or Pagy collection objects.
|
|
5
|
-
#
|
|
6
|
-
# Returned hash shape (always the same regardless of pagination library):
|
|
7
|
-
# {
|
|
8
|
-
# current_page: Integer,
|
|
9
|
-
# per_page: Integer,
|
|
10
|
-
# total_pages: Integer,
|
|
11
|
-
# total_count: Integer,
|
|
12
|
-
# next_page: Integer | nil,
|
|
13
|
-
# prev_page: Integer | nil
|
|
14
|
-
# }
|
|
15
|
-
module Pagination
|
|
16
|
-
module_function
|
|
17
|
-
|
|
18
|
-
# @param collection [Object] any object — returns nil if not a paginated collection
|
|
19
|
-
# @return [Hash, nil]
|
|
20
|
-
def extract(collection)
|
|
21
|
-
return nil if collection.nil?
|
|
22
|
-
|
|
23
|
-
if pagy?(collection)
|
|
24
|
-
from_pagy(collection)
|
|
25
|
-
elsif kaminari?(collection)
|
|
26
|
-
from_kaminari(collection)
|
|
27
|
-
elsif will_paginate?(collection)
|
|
28
|
-
from_will_paginate(collection)
|
|
29
|
-
else
|
|
30
|
-
nil
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
private
|
|
35
|
-
|
|
36
|
-
module_function
|
|
37
|
-
|
|
38
|
-
# ----- Pagy ---------------------------------------------------------------
|
|
39
|
-
# Pagy stores metadata on a separate Pagy object, not the collection itself.
|
|
40
|
-
# We support both: passing the Pagy object directly, or a collection that
|
|
41
|
-
# has been decorated with pagy metadata via pagy_metadata.
|
|
42
|
-
def pagy?(object)
|
|
43
|
-
defined?(Pagy) && object.is_a?(Pagy)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def from_pagy(pagy)
|
|
47
|
-
{
|
|
48
|
-
current_page: pagy.page,
|
|
49
|
-
per_page: pagy.items,
|
|
50
|
-
total_pages: pagy.pages,
|
|
51
|
-
total_count: pagy.count,
|
|
52
|
-
next_page: pagy.next,
|
|
53
|
-
prev_page: pagy.prev
|
|
54
|
-
}
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# ----- Kaminari -----------------------------------------------------------
|
|
58
|
-
def kaminari?(object)
|
|
59
|
-
object.respond_to?(:current_page) &&
|
|
60
|
-
object.respond_to?(:total_pages) &&
|
|
61
|
-
object.respond_to?(:limit_value)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def from_kaminari(collection)
|
|
65
|
-
{
|
|
66
|
-
current_page: collection.current_page,
|
|
67
|
-
per_page: collection.limit_value,
|
|
68
|
-
total_pages: collection.total_pages,
|
|
69
|
-
total_count: collection.total_count,
|
|
70
|
-
next_page: collection.next_page,
|
|
71
|
-
prev_page: collection.prev_page
|
|
72
|
-
}
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# ----- WillPaginate -------------------------------------------------------
|
|
76
|
-
def will_paginate?(object)
|
|
77
|
-
object.respond_to?(:current_page) &&
|
|
78
|
-
object.respond_to?(:total_pages) &&
|
|
79
|
-
object.respond_to?(:per_page) &&
|
|
80
|
-
!object.respond_to?(:limit_value) # distinguishes from Kaminari
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def from_will_paginate(collection)
|
|
84
|
-
{
|
|
85
|
-
current_page: collection.current_page,
|
|
86
|
-
per_page: collection.per_page,
|
|
87
|
-
total_pages: collection.total_pages,
|
|
88
|
-
total_count: collection.total_entries,
|
|
89
|
-
next_page: collection.next_page,
|
|
90
|
-
prev_page: collection.previous_page
|
|
91
|
-
}
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
end
|