respondo 0.1.0 → 2.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.
@@ -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
@@ -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, render_no_content,
8
- # render_partial_content, render_multi_status
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, render_conflict, render_gone,
14
- # render_unprocessable, render_too_many_requests, render_locked,
15
- # render_precondition_failed, render_unsupported_media_type,
16
- # render_request_timeout
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 pagy [Pagy] optional Pagy object (pass when using Pagy backend)
33
- # @param pagination [Boolean] true = include pagination meta when available (default)
34
- # false = always suppress pagination meta
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, pagy: nil, pagination: true, status: :ok)
37
- merged_meta = code ? meta.merge(code: code, status: status) : 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: true)
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: false)
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: false, code:202, status: :accepted)
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: false, code:204, status: :ok)
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: false, code:206, status: :partial_content)
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: false, code:207, status: :multi_status)
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
  # =========================================================================
@@ -114,78 +219,148 @@ module Respondo
114
219
  # =========================================================================
115
220
 
116
221
  # 400 Bad Request — malformed request, invalid params
117
- def render_bad_request(message: "Bad request", errors: nil, code: "BAD_REQUEST")
118
- render_error(message: message, errors: errors, code: code, status: :bad_request)
222
+ def render_bad_request(message: "Bad request", errors: nil, meta: {})
223
+ render_error(message: message, errors: errors, meta: meta, code: 400, status: :bad_request)
119
224
  end
120
225
 
121
226
  # 401 Unauthorized — not authenticated
122
- def render_unauthorized(message: "Unauthorized", errors: nil, code: "UNAUTHORIZED")
123
- render_error(message: message, errors: errors, code: code, status: :unauthorized)
227
+ def render_unauthorized(message: "Unauthorized", errors: nil, meta: {})
228
+ render_error(message: message, errors: errors, meta: meta, code: 401, status: :unauthorized)
124
229
  end
125
230
 
126
231
  # 402 Payment Required — paywalled features
127
- def render_payment_required(message: "Payment required to access this resource", errors: nil, code: "PAYMENT_REQUIRED")
128
- render_error(message: message, errors: errors, code: code, status: :payment_required)
232
+ def render_payment_required(message: "Payment required to access this resource", errors: nil, meta: {})
233
+ render_error(message: message, errors: errors, meta: meta, code: 402, status: :payment_required)
129
234
  end
130
235
 
131
236
  # 403 Forbidden — authenticated but not authorized
132
- def render_forbidden(message: "You do not have permission to perform this action", errors: nil, code: "FORBIDDEN")
133
- render_error(message: message, errors: errors, code: code, status: :forbidden)
237
+ def render_forbidden(message: "You do not have permission to perform this action", errors: nil, meta: {})
238
+ render_error(message: message, errors: errors, meta: meta, code: 403, status: :forbidden)
134
239
  end
135
240
 
136
241
  # 404 Not Found
137
- def render_not_found(message: "Resource not found", errors: nil, code: "NOT_FOUND")
138
- render_error(message: message, errors: errors, code: code, status: :not_found)
242
+ def render_not_found(message: "Resource not found", errors: nil, meta: {})
243
+ render_error(message: message, errors: errors, meta: meta, code: 404, status: :not_found)
139
244
  end
140
245
 
141
246
  # 405 Method Not Allowed
142
- def render_method_not_allowed(message: "HTTP method not allowed", errors: nil, code: "METHOD_NOT_ALLOWED")
143
- render_error(message: message, errors: errors, code: code, status: :method_not_allowed)
247
+ def render_method_not_allowed(message: "HTTP method not allowed", errors: nil, meta: {})
248
+ render_error(message: message, errors: errors, meta: meta, code: 405, status: :method_not_allowed)
144
249
  end
145
250
 
146
251
  # 406 Not Acceptable — client Accept header can't be satisfied
147
- def render_not_acceptable(message: "Requested format not acceptable", errors: nil, code: "NOT_ACCEPTABLE")
148
- render_error(message: message, errors: errors, code: code, status: :not_acceptable)
252
+ def render_not_acceptable(message: "Requested format not acceptable", errors: nil, meta: {})
253
+ render_error(message: message, errors: errors, meta: meta, code: 406, status: :not_acceptable)
254
+ end
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)
149
259
  end
150
260
 
151
261
  # 408 Request Timeout
152
- def render_request_timeout(message: "Request timed out", errors: nil, code: "REQUEST_TIMEOUT")
153
- render_error(message: message, errors: errors, code: code, status: :request_timeout)
262
+ def render_request_timeout(message: "Request timed out", errors: nil, meta: {})
263
+ render_error(message: message, errors: errors, meta: meta, code: 408, status: :request_timeout)
154
264
  end
155
265
 
156
266
  # 409 Conflict — duplicate record, state conflict
157
- def render_conflict(message: "Resource conflict", errors: nil, code: "CONFLICT")
158
- render_error(message: message, errors: errors, code: code, status: :conflict)
267
+ def render_conflict(message: "Resource conflict", errors: nil, meta: {})
268
+ render_error(message: message, errors: errors, meta: meta, code: 409, status: :conflict)
159
269
  end
160
270
 
161
271
  # 410 Gone — resource permanently deleted
162
- def render_gone(message: "Resource no longer available", errors: nil, code: "GONE")
163
- render_error(message: message, errors: errors, code: code, status: :gone)
272
+ def render_gone(message: "Resource no longer available", errors: nil, meta: {})
273
+ render_error(message: message, errors: errors, meta: meta, code: 410, status: :gone)
274
+ end
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)
164
279
  end
165
280
 
166
281
  # 412 Precondition Failed — conditional request failed
167
- def render_precondition_failed(message: "Precondition failed", errors: nil, code: "PRECONDITION_FAILED")
168
- render_error(message: message, errors: errors, code: code, status: :precondition_failed)
282
+ def render_precondition_failed(message: "Precondition failed", errors: nil, meta: {})
283
+ render_error(message: message, errors: errors, meta: meta, code: 412, status: :precondition_failed)
284
+ end
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)
169
294
  end
170
295
 
171
296
  # 415 Unsupported Media Type — wrong Content-Type header
172
- def render_unsupported_media_type(message: "Unsupported media type", errors: nil, code: "UNSUPPORTED_MEDIA_TYPE")
173
- render_error(message: message, errors: errors, code: code, status: :unsupported_media_type)
297
+ def render_unsupported_media_type(message: "Unsupported media type", errors: nil, meta: {})
298
+ render_error(message: message, errors: errors, meta: meta, code: 415, status: :unsupported_media_type)
299
+ end
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)
174
319
  end
175
320
 
176
321
  # 422 Unprocessable Entity — validation errors (most common for APIs)
177
- def render_unprocessable(message: "Validation failed", errors: nil, code: "UNPROCESSABLE_ENTITY")
178
- render_error(message: message, errors: errors, code: code, status: :unprocessable_entity)
322
+ def render_unprocessable(message: "Validation failed", errors: nil, meta: {})
323
+ render_error(message: message, errors: errors, meta: meta, code: 422, status: :unprocessable_content)
179
324
  end
180
325
 
181
326
  # 423 Locked — resource is locked
182
- def render_locked(message: "Resource is locked", errors: nil, code: "LOCKED")
183
- render_error(message: message, errors: errors, code: code, status: :locked)
327
+ def render_locked(message: "Resource is locked", errors: nil, meta: {})
328
+ render_error(message: message, errors: errors, meta: meta, code: 423, status: :locked)
329
+ end
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)
184
349
  end
185
350
 
186
351
  # 429 Too Many Requests — rate limiting
187
- def render_too_many_requests(message: "Too many requests. Please slow down.", errors: nil, code: "RATE_LIMITED")
188
- render_error(message: message, errors: errors, code: code, status: :too_many_requests)
352
+ def render_too_many_requests(message: "Too many requests. Please slow down.", errors: nil, meta: {})
353
+ render_error(message: message, errors: errors, meta: meta, code: 429, status: :too_many_requests)
354
+ end
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)
189
364
  end
190
365
 
191
366
  # =========================================================================
@@ -193,28 +368,58 @@ module Respondo
193
368
  # =========================================================================
194
369
 
195
370
  # 500 Internal Server Error
196
- def render_server_error(message: "An unexpected error occurred", errors: nil, code: "SERVER_ERROR")
197
- render_error(message: message, errors: errors, code: code, status: :internal_server_error)
371
+ def render_server_error(message: "An unexpected error occurred", errors: nil, meta: {})
372
+ render_error(message: message, errors: errors, meta: meta, code: 500, status: :internal_server_error)
198
373
  end
199
374
 
200
375
  # 501 Not Implemented — feature not built yet
201
- def render_not_implemented(message: "This feature is not yet implemented", errors: nil, code: "NOT_IMPLEMENTED")
202
- render_error(message: message, errors: errors, code: code, status: :not_implemented)
376
+ def render_not_implemented(message: "This feature is not yet implemented", errors: nil, meta: {})
377
+ render_error(message: message, errors: errors, meta: meta, code: 501, status: :not_implemented)
203
378
  end
204
379
 
205
380
  # 502 Bad Gateway — upstream service failed
206
- def render_bad_gateway(message: "Bad gateway — upstream service error", errors: nil, code: "BAD_GATEWAY")
207
- render_error(message: message, errors: errors, code: code, status: :bad_gateway)
381
+ def render_bad_gateway(message: "Bad gateway — upstream service error", errors: nil, meta: {})
382
+ render_error(message: message, errors: errors, meta: meta, code: 502, status: :bad_gateway)
208
383
  end
209
384
 
210
385
  # 503 Service Unavailable — maintenance, overloaded
211
- def render_service_unavailable(message: "Service temporarily unavailable", errors: nil, code: "SERVICE_UNAVAILABLE")
212
- render_error(message: message, errors: errors, code: code, status: :service_unavailable)
386
+ def render_service_unavailable(message: "Service temporarily unavailable", errors: nil, meta: {})
387
+ render_error(message: message, errors: errors, meta: meta, code: 503, status: :service_unavailable)
213
388
  end
214
389
 
215
390
  # 504 Gateway Timeout — upstream service timed out
216
- def render_gateway_timeout(message: "Gateway timeout — upstream service did not respond", errors: nil, code: "GATEWAY_TIMEOUT")
217
- render_error(message: message, errors: errors, code: code, status: :gateway_timeout)
391
+ def render_gateway_timeout(message: "Gateway timeout — upstream service did not respond", errors: nil, meta: {})
392
+ render_error(message: message, errors: errors, meta: meta, code: 504, status: :gateway_timeout)
393
+ end
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)
218
423
  end
219
424
 
220
425
  private