respondo 2.1.0 → 2.1.1
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 +58 -0
- data/README.md +189 -24
- data/lib/respondo/controller_helpers.rb +1 -1
- data/lib/respondo/response_builder.rb +0 -22
- data/lib/respondo/version.rb +1 -1
- data/respondo.gemspec +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fbe7751852587a50eb66e9d18128db0f0b98295f6e309ea1db812d2c6802827e
|
|
4
|
+
data.tar.gz: d59797929ccbf67f507eba232a36c04f5642318b1f3992aa0251f3949b4ed284
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d3d210ee9413164b2d0b80b4c84e8f238ffac624145704dbabfb4a09242d655460e18ecd0591cd010a43a5a9c37d1b0a1ad6086ac0ab8f0a7e36536f0d6b16c3
|
|
7
|
+
data.tar.gz: 8683372f8e107d696b62d6f5e69e9f693638ce3dc50f21775d54820781c987fae14e924bce00c126b83747ce8ef14857652582a7a1e0d1c881c1536c7c4bbfe5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.1.1] — Bug Fix
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
#### `render_no_content` — status mismatch corrected
|
|
8
|
+
Previously `render_no_content` was sending `status: :ok` (200) while setting
|
|
9
|
+
`code: 204` in the meta block. This meant the HTTP response status was 200
|
|
10
|
+
but the meta claimed 204 — inconsistent and misleading to clients.
|
|
11
|
+
|
|
12
|
+
**Before (buggy):**
|
|
13
|
+
```ruby
|
|
14
|
+
def render_no_content(message: "Deleted successfully", meta: {}, pagination: nil)
|
|
15
|
+
render_success(data: nil, message: message, meta: meta, pagination: pagination, code: 204, status: :ok)
|
|
16
|
+
# ^^^^^^^^^^
|
|
17
|
+
# wrong — sends 200
|
|
18
|
+
end
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**After (fixed):**
|
|
22
|
+
```ruby
|
|
23
|
+
def render_no_content(message: "Deleted successfully", meta: {}, pagination: nil)
|
|
24
|
+
render_success(data: nil, message: message, meta: meta, pagination: pagination, code: 204, status: :no_content)
|
|
25
|
+
# ^^^^^^^^^^^^^^^^^^
|
|
26
|
+
# correct — sends 204
|
|
27
|
+
end
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Why body is kept:** HTTP spec discourages a body on 204 but does not
|
|
31
|
+
strictly forbid it. Respondo keeps the JSON body so FE clients can display
|
|
32
|
+
the `message` field on delete confirmations. Rails and all major HTTP clients
|
|
33
|
+
(Axios, Fetch, OkHttp) handle this correctly.
|
|
34
|
+
|
|
35
|
+
**Migration:** No changes needed — method signature is identical. If you were
|
|
36
|
+
relying on the incorrect 200 status in tests, update your assertions to
|
|
37
|
+
expect 204.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
#### `respondo.gemspec` — corrected `bug_tracker_uri`
|
|
42
|
+
A stray `auditron/` path segment was present in the `bug_tracker_uri` metadata
|
|
43
|
+
field, producing a broken link on RubyGems.
|
|
44
|
+
|
|
45
|
+
**Before:**
|
|
46
|
+
```ruby
|
|
47
|
+
"bug_tracker_uri" => "#{spec.homepage}/auditron/issues"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**After:**
|
|
51
|
+
```ruby
|
|
52
|
+
"bug_tracker_uri" => "#{spec.homepage}/issues"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
#### `response_builder.rb` — removed stale commented-out code
|
|
56
|
+
A dead `build_meta` implementation was left commented out from a previous
|
|
57
|
+
refactor. Removed to keep the file clean and readable.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
3
61
|
## [2.1.0] — Interactive Install Generator
|
|
4
62
|
|
|
5
63
|
### Added
|
data/README.md
CHANGED
|
@@ -143,8 +143,9 @@ Error responses additionally include:
|
|
|
143
143
|
|
|
144
144
|
| Key | Type | Description |
|
|
145
145
|
|----------|------|--------------------------------------|
|
|
146
|
-
| `errors` | Hash | Field-level errors `{field: [
|
|
147
|
-
|
|
146
|
+
| `errors` | Hash | Field-level errors `{ field: ["message", ...] }` |
|
|
147
|
+
|
|
148
|
+
> **`errors` is optional.** Pass it when you want to surface field-level detail to the client (e.g. form validation, token issues). Omit it for simple human-readable-only responses — `message` alone is perfectly valid.
|
|
148
149
|
|
|
149
150
|
---
|
|
150
151
|
|
|
@@ -304,222 +305,386 @@ render_permanent_redirect(message: "Permanently moved — update your bookmarks"
|
|
|
304
305
|
|
|
305
306
|
### 4xx — Client Error Helpers
|
|
306
307
|
|
|
308
|
+
> **Two usage patterns for every error helper:**
|
|
309
|
+
> - **Message only** — a human-readable string shown to the end user.
|
|
310
|
+
> - **Message + errors** — add `errors:` when you also need field-level detail for the client to act on (e.g. highlight a form field, log a specific token issue). `errors` is a Hash of `{ field: ["message", ...] }`.
|
|
311
|
+
|
|
307
312
|
#### `render_bad_request` — 400 Bad Request
|
|
308
313
|
```ruby
|
|
314
|
+
# Message only
|
|
309
315
|
render_bad_request(message: "The 'date' parameter is required")
|
|
310
|
-
|
|
316
|
+
|
|
317
|
+
# Message + errors
|
|
318
|
+
render_bad_request(message: "Invalid input", errors: { date: "must be a valid date"})
|
|
311
319
|
```
|
|
312
320
|
|
|
313
321
|
#### `render_unauthorized` — 401 Unauthorized
|
|
314
322
|
```ruby
|
|
323
|
+
# Message only
|
|
315
324
|
render_unauthorized(message: "Please log in to continue")
|
|
316
|
-
|
|
325
|
+
|
|
326
|
+
# Message + errors
|
|
327
|
+
render_unauthorized(message: "Token has expired", errors: { token: "has expired, please log in again"})
|
|
317
328
|
```
|
|
318
329
|
|
|
319
330
|
#### `render_payment_required` — 402 Payment Required
|
|
320
331
|
```ruby
|
|
332
|
+
# Message only
|
|
321
333
|
render_payment_required(message: "Upgrade to Pro to access this feature")
|
|
334
|
+
|
|
335
|
+
# Message + errors
|
|
336
|
+
render_payment_required( message: "Upgrade to Pro to access this feature", errors: { plan: "must be Pro or higher to access this feature"})
|
|
322
337
|
```
|
|
323
338
|
|
|
324
339
|
#### `render_forbidden` — 403 Forbidden
|
|
325
340
|
```ruby
|
|
341
|
+
# Message only
|
|
326
342
|
render_forbidden(message: "You can only edit your own posts")
|
|
343
|
+
|
|
344
|
+
# Message + errors
|
|
345
|
+
render_forbidden(message: "You can only edit your own posts",errors: { post: "does not belong to you" })
|
|
327
346
|
```
|
|
328
347
|
|
|
329
348
|
#### `render_not_found` — 404 Not Found
|
|
330
349
|
```ruby
|
|
350
|
+
# Message only
|
|
331
351
|
render_not_found(message: "User not found")
|
|
332
|
-
|
|
352
|
+
|
|
353
|
+
# Message + errors
|
|
354
|
+
render_not_found(message: "User not found", errors: { id: "no user exists with this ID" })
|
|
333
355
|
```
|
|
334
356
|
|
|
335
357
|
#### `render_method_not_allowed` — 405
|
|
336
358
|
```ruby
|
|
359
|
+
# Message only
|
|
337
360
|
render_method_not_allowed(message: "This endpoint only accepts POST requests")
|
|
361
|
+
|
|
362
|
+
# Message + errors
|
|
363
|
+
render_method_not_allowed(message: "This endpoint only accepts POST requests",errors: { method: "GET is not allowed, use POST" })
|
|
338
364
|
```
|
|
339
365
|
|
|
340
366
|
#### `render_not_acceptable` — 406
|
|
341
367
|
```ruby
|
|
368
|
+
# Message only
|
|
342
369
|
render_not_acceptable(message: "Only application/json is supported")
|
|
370
|
+
|
|
371
|
+
# Message + errors
|
|
372
|
+
render_not_acceptable(message: "Only application/json is supported", errors: { content_type: "must be application/json" })
|
|
343
373
|
```
|
|
344
374
|
|
|
345
375
|
#### `render_proxy_auth_required` — 407
|
|
346
376
|
```ruby
|
|
377
|
+
# Message only
|
|
347
378
|
render_proxy_auth_required(message: "Authenticate with the proxy first")
|
|
379
|
+
|
|
380
|
+
# Message + errors
|
|
381
|
+
render_proxy_auth_required(message: "Authenticate with the proxy first", errors: { proxy_token: "is missing or invalid"})
|
|
348
382
|
```
|
|
349
383
|
|
|
350
384
|
#### `render_request_timeout` — 408
|
|
351
385
|
```ruby
|
|
386
|
+
# Message only
|
|
352
387
|
render_request_timeout(message: "The query took too long. Try a smaller date range.")
|
|
388
|
+
|
|
389
|
+
# Message + errors
|
|
390
|
+
render_request_timeout( message: "The query took too long. Try a smaller date range.", errors: { date_range: "must span 90 days or fewer" })
|
|
353
391
|
```
|
|
354
392
|
|
|
355
393
|
#### `render_conflict` — 409 Conflict
|
|
356
394
|
```ruby
|
|
395
|
+
# Message only
|
|
357
396
|
render_conflict(message: "Email address is already registered")
|
|
358
|
-
|
|
397
|
+
|
|
398
|
+
# Message + errors
|
|
399
|
+
render_conflict(message: "Email address is already registered",errors: { email: "has already been taken" })
|
|
359
400
|
```
|
|
360
401
|
|
|
361
402
|
#### `render_gone` — 410 Gone
|
|
362
403
|
```ruby
|
|
404
|
+
# Message only
|
|
363
405
|
render_gone(message: "This account has been permanently deleted")
|
|
406
|
+
|
|
407
|
+
# Message + errors
|
|
408
|
+
render_gone(message: "This account has been permanently deleted",errors: { account: ["no longer exists and cannot be recovered"] })
|
|
364
409
|
```
|
|
365
410
|
|
|
366
411
|
#### `render_length_required` — 411
|
|
367
412
|
```ruby
|
|
413
|
+
# Message only
|
|
368
414
|
render_length_required(message: "Content-Length header is required")
|
|
415
|
+
|
|
416
|
+
# Message + errors
|
|
417
|
+
render_length_required(message: "Content-Length header is required",errors: { content_length: ["header is missing from the request"] })
|
|
369
418
|
```
|
|
370
419
|
|
|
371
420
|
#### `render_precondition_failed` — 412
|
|
372
421
|
```ruby
|
|
422
|
+
# Message only
|
|
373
423
|
render_precondition_failed(message: "Resource has been modified since your last request")
|
|
424
|
+
|
|
425
|
+
# Message + errors
|
|
426
|
+
render_precondition_failed(message: "Resource has been modified since your last request",errors: { etag: ["does not match the current resource version"] })
|
|
374
427
|
```
|
|
375
428
|
|
|
376
429
|
#### `render_payload_too_large` — 413
|
|
377
430
|
```ruby
|
|
431
|
+
# Message only
|
|
378
432
|
render_payload_too_large(message: "File exceeds the 10 MB upload limit")
|
|
433
|
+
|
|
434
|
+
# Message + errors
|
|
435
|
+
render_payload_too_large(message: "File exceeds the 10 MB upload limit",errors: { file: ["must be smaller than 10 MB"] })
|
|
379
436
|
```
|
|
380
437
|
|
|
381
438
|
#### `render_uri_too_long` — 414
|
|
382
439
|
```ruby
|
|
440
|
+
# Message only
|
|
383
441
|
render_uri_too_long(message: "That URL is too long to process")
|
|
442
|
+
|
|
443
|
+
# Message + errors
|
|
444
|
+
render_uri_too_long(message: "That URL is too long to process",errors: { url: ["must not exceed 2048 characters"] })
|
|
384
445
|
```
|
|
385
446
|
|
|
386
447
|
#### `render_unsupported_media_type` — 415
|
|
387
448
|
```ruby
|
|
449
|
+
# Message only
|
|
388
450
|
render_unsupported_media_type(message: "Please send requests as application/json")
|
|
451
|
+
|
|
452
|
+
# Message + errors
|
|
453
|
+
render_unsupported_media_type(message: "Please send requests as application/json",errors: { content_type: ["must be application/json, got text/xml"] })
|
|
389
454
|
```
|
|
390
455
|
|
|
391
456
|
#### `render_range_not_satisfiable` — 416
|
|
392
457
|
```ruby
|
|
458
|
+
# Message only
|
|
393
459
|
render_range_not_satisfiable(message: "Requested byte range is out of bounds")
|
|
460
|
+
|
|
461
|
+
# Message + errors
|
|
462
|
+
render_range_not_satisfiable(message: "Requested byte range is out of bounds", errors: { range: ["exceeds the total file size"] })
|
|
394
463
|
```
|
|
395
464
|
|
|
396
465
|
#### `render_expectation_failed` — 417
|
|
397
466
|
```ruby
|
|
467
|
+
# Message only
|
|
398
468
|
render_expectation_failed(message: "Expect header value cannot be met")
|
|
469
|
+
|
|
470
|
+
# Message + errors
|
|
471
|
+
render_expectation_failed(message: "Expect header value cannot be met",errors: { expect: ["100-continue is not supported on this endpoint"] })
|
|
399
472
|
```
|
|
400
473
|
|
|
401
474
|
#### `render_im_a_teapot` — 418
|
|
402
475
|
```ruby
|
|
476
|
+
# Message only
|
|
403
477
|
render_im_a_teapot(message: "I'm a teapot — I cannot brew coffee")
|
|
478
|
+
|
|
479
|
+
# Message + errors
|
|
480
|
+
render_im_a_teapot(message: "I'm a teapot — I cannot brew coffee",errors: { beverage: ["coffee is not supported, try tea"] })
|
|
404
481
|
```
|
|
405
482
|
|
|
406
483
|
#### `render_misdirected_request` — 421
|
|
407
484
|
```ruby
|
|
485
|
+
# Message only
|
|
408
486
|
render_misdirected_request(message: "Request sent to the wrong server")
|
|
487
|
+
|
|
488
|
+
# Message + errors
|
|
489
|
+
render_misdirected_request(message: "Request sent to the wrong server",errors: { host: ["this server does not handle requests for this host"] })
|
|
409
490
|
```
|
|
410
491
|
|
|
411
492
|
#### `render_unprocessable` — 422 Unprocessable Entity
|
|
412
493
|
Validation errors. The most commonly used error helper in Rails APIs.
|
|
413
494
|
|
|
414
495
|
```ruby
|
|
415
|
-
|
|
416
|
-
render_unprocessable(message: "
|
|
496
|
+
# Message only
|
|
497
|
+
render_unprocessable(message: "Validation failed")
|
|
498
|
+
|
|
499
|
+
# Message + errors — pass an ActiveModel::Errors object directly
|
|
500
|
+
render_unprocessable(message: "Validation failed",errors: user.errors)
|
|
501
|
+
|
|
502
|
+
# Message + errors — pass a plain hash
|
|
503
|
+
render_unprocessable(message: "Invalid data",errors: { name: ["can't be blank"], email: ["is invalid"] })
|
|
417
504
|
```
|
|
418
505
|
|
|
419
506
|
#### `render_locked` — 423
|
|
420
507
|
```ruby
|
|
508
|
+
# Message only
|
|
421
509
|
render_locked(message: "This record is locked by another user")
|
|
510
|
+
|
|
511
|
+
# Message + errors
|
|
512
|
+
render_locked(message: "This record is locked by another user",errors: { record: ["is currently locked, try again later"] })
|
|
422
513
|
```
|
|
423
514
|
|
|
424
515
|
#### `render_failed_dependency` — 424
|
|
425
516
|
```ruby
|
|
517
|
+
# Message only
|
|
426
518
|
render_failed_dependency(message: "Prerequisite resource creation failed")
|
|
519
|
+
|
|
520
|
+
# Message + errors
|
|
521
|
+
render_failed_dependency(message: "Prerequisite resource creation failed",errors: { dependency: ["parent record must exist before creating this resource"] })
|
|
427
522
|
```
|
|
428
523
|
|
|
429
524
|
#### `render_too_early` — 425
|
|
430
525
|
```ruby
|
|
526
|
+
# Message only
|
|
431
527
|
render_too_early(message: "Request may be a replay — rejected for safety")
|
|
528
|
+
|
|
529
|
+
# Message + errors
|
|
530
|
+
render_too_early(message: "Request may be a replay — rejected for safety",errors: { request: ["early data replay detected, resend after handshake"] })
|
|
432
531
|
```
|
|
433
532
|
|
|
434
533
|
#### `render_upgrade_required` — 426
|
|
435
534
|
```ruby
|
|
535
|
+
# Message only
|
|
436
536
|
render_upgrade_required(message: "Please upgrade to TLS 1.3")
|
|
537
|
+
|
|
538
|
+
# Message + errors
|
|
539
|
+
render_upgrade_required(message: "Please upgrade to TLS 1.3",errors: { protocol: ["TLS 1.2 is no longer supported, upgrade to TLS 1.3"] })
|
|
437
540
|
```
|
|
438
541
|
|
|
439
542
|
#### `render_precondition_required` — 428
|
|
440
543
|
```ruby
|
|
544
|
+
# Message only
|
|
441
545
|
render_precondition_required(message: "Include an If-Match header with your request")
|
|
546
|
+
|
|
547
|
+
# Message + errors
|
|
548
|
+
render_precondition_required(message: "Include an If-Match header with your request",errors: { if_match: ["header is required to prevent lost updates"] })
|
|
442
549
|
```
|
|
443
550
|
|
|
444
551
|
#### `render_too_many_requests` — 429
|
|
445
552
|
```ruby
|
|
553
|
+
# Message only
|
|
446
554
|
render_too_many_requests(message: "You have exceeded 100 requests per minute.")
|
|
447
|
-
|
|
555
|
+
|
|
556
|
+
# Message + errors
|
|
557
|
+
render_too_many_requests(message: "Rate limit exceeded",errors: { rate_limit: ["100 requests per minute allowed, retry after 60 seconds"] },meta: { retry_after: 60 })
|
|
448
558
|
```
|
|
449
559
|
|
|
450
560
|
#### `render_request_header_fields_too_large` — 431
|
|
451
561
|
```ruby
|
|
562
|
+
# Message only
|
|
452
563
|
render_request_header_fields_too_large(message: "Cookie header is too large")
|
|
564
|
+
|
|
565
|
+
# Message + errors
|
|
566
|
+
render_request_header_fields_too_large(message: "Cookie header is too large",errors: { cookie: ["must not exceed 4096 bytes"] })
|
|
453
567
|
```
|
|
454
568
|
|
|
455
569
|
#### `render_unavailable_for_legal_reasons` — 451
|
|
456
570
|
```ruby
|
|
571
|
+
# Message only
|
|
457
572
|
render_unavailable_for_legal_reasons(message: "This content is blocked in your region")
|
|
573
|
+
|
|
574
|
+
# Message + errors
|
|
575
|
+
render_unavailable_for_legal_reasons(message: "This content is blocked in your region",errors: { region: ["content is not licensed for distribution in your country"] })
|
|
458
576
|
```
|
|
459
577
|
|
|
460
578
|
---
|
|
461
579
|
|
|
462
580
|
### 5xx — Server Error Helpers
|
|
463
581
|
|
|
582
|
+
> **Two usage patterns for every error helper:**
|
|
583
|
+
> - **Message only** — a human-readable string shown to the end user.
|
|
584
|
+
> - **Message + errors** — add `errors:` when you need to surface internal detail for debugging or logging (e.g. which downstream service failed). `errors` is a Hash of `{ field: ["message", ...] }`.
|
|
585
|
+
|
|
464
586
|
#### `render_server_error` — 500 Internal Server Error
|
|
465
587
|
```ruby
|
|
588
|
+
# Message only
|
|
466
589
|
render_server_error(message: "Something went wrong. Our team has been notified.")
|
|
467
590
|
|
|
591
|
+
# Message + errors
|
|
592
|
+
render_server_error(message: "Something went wrong. Our team has been notified.",errors: { server: ["unexpected exception in OrdersController#create"] })
|
|
593
|
+
|
|
468
594
|
# Common pattern — rescue unexpected exceptions
|
|
469
595
|
rescue StandardError => e
|
|
470
596
|
Rails.logger.error(e)
|
|
471
|
-
render_server_error("An unexpected error occurred")
|
|
597
|
+
render_server_error(message: "An unexpected error occurred",errors: { server: [e.message] })
|
|
472
598
|
```
|
|
473
599
|
|
|
474
600
|
#### `render_not_implemented` — 501
|
|
475
601
|
```ruby
|
|
602
|
+
# Message only
|
|
476
603
|
render_not_implemented(message: "CSV export is coming soon")
|
|
604
|
+
|
|
605
|
+
# Message + errors
|
|
606
|
+
render_not_implemented(message: "CSV export is coming soon",errors: { format: ["csv export is not yet implemented, use json"] })
|
|
477
607
|
```
|
|
478
608
|
|
|
479
609
|
#### `render_bad_gateway` — 502
|
|
480
610
|
```ruby
|
|
611
|
+
# Message only
|
|
481
612
|
render_bad_gateway(message: "Payment gateway is currently unavailable")
|
|
613
|
+
|
|
614
|
+
# Message + errors
|
|
615
|
+
render_bad_gateway(message: "Payment gateway is currently unavailable",errors: { gateway: ["Stripe returned a 502, please try again"] })
|
|
482
616
|
```
|
|
483
617
|
|
|
484
618
|
#### `render_service_unavailable` — 503
|
|
485
619
|
```ruby
|
|
620
|
+
# Message only
|
|
486
621
|
render_service_unavailable(message: "Down for maintenance. Back in 30 minutes.")
|
|
487
|
-
|
|
622
|
+
|
|
623
|
+
# Message + errors
|
|
624
|
+
render_service_unavailable(message: "Down for maintenance. Back in 30 minutes.",errors: { service: ["scheduled maintenance window until 03:00 UTC"] },meta: { retry_after: 1800 })
|
|
488
625
|
```
|
|
489
626
|
|
|
490
627
|
#### `render_gateway_timeout` — 504
|
|
491
628
|
```ruby
|
|
629
|
+
# Message only
|
|
492
630
|
render_gateway_timeout(message: "The payment processor did not respond in time.")
|
|
631
|
+
|
|
632
|
+
# Message + errors
|
|
633
|
+
render_gateway_timeout(message: "The payment processor did not respond in time.",errors: { gateway: ["upstream timeout after 30 seconds, you have not been charged"] })
|
|
493
634
|
```
|
|
494
635
|
|
|
495
636
|
#### `render_http_version_not_supported` — 505
|
|
496
637
|
```ruby
|
|
638
|
+
# Message only
|
|
497
639
|
render_http_version_not_supported(message: "Only HTTP/1.1 and HTTP/2 are supported")
|
|
640
|
+
|
|
641
|
+
# Message + errors
|
|
642
|
+
render_http_version_not_supported(message: "Only HTTP/1.1 and HTTP/2 are supported",errors: { http_version: ["HTTP/1.0 is not supported"] })
|
|
498
643
|
```
|
|
499
644
|
|
|
500
645
|
#### `render_variant_also_negotiates` — 506
|
|
501
646
|
```ruby
|
|
647
|
+
# Message only
|
|
502
648
|
render_variant_also_negotiates(message: "Server content-negotiation loop detected")
|
|
649
|
+
|
|
650
|
+
# Message + errors
|
|
651
|
+
render_variant_also_negotiates(message: "Server content-negotiation loop detected",errors: { variant: ["misconfigured content negotiation caused an infinite loop"] })
|
|
503
652
|
```
|
|
504
653
|
|
|
505
654
|
#### `render_insufficient_storage` — 507
|
|
506
655
|
```ruby
|
|
656
|
+
# Message only
|
|
507
657
|
render_insufficient_storage(message: "Disk quota exceeded on this node")
|
|
658
|
+
|
|
659
|
+
# Message + errors
|
|
660
|
+
render_insufficient_storage(message: "Disk quota exceeded on this node",errors: { storage: ["upload failed, node has 0 bytes remaining"] })
|
|
508
661
|
```
|
|
509
662
|
|
|
510
663
|
#### `render_loop_detected` — 508
|
|
511
664
|
```ruby
|
|
665
|
+
# Message only
|
|
512
666
|
render_loop_detected(message: "Infinite redirect loop detected")
|
|
667
|
+
|
|
668
|
+
# Message + errors
|
|
669
|
+
render_loop_detected(message: "Infinite redirect loop detected",errors: { redirect: ["request visited the same URL more than 10 times"] })
|
|
513
670
|
```
|
|
514
671
|
|
|
515
672
|
#### `render_not_extended` — 510
|
|
516
673
|
```ruby
|
|
674
|
+
# Message only
|
|
517
675
|
render_not_extended(message: "Further extensions required to fulfil this request")
|
|
676
|
+
|
|
677
|
+
# Message + errors
|
|
678
|
+
render_not_extended(message: "Further extensions required to fulfil this request",errors: { extension: ["mandatory extension 'auth' is missing from the request"] })
|
|
518
679
|
```
|
|
519
680
|
|
|
520
681
|
#### `render_network_authentication_required` — 511
|
|
521
682
|
```ruby
|
|
683
|
+
# Message only
|
|
522
684
|
render_network_authentication_required(message: "Sign in to the network portal first")
|
|
685
|
+
|
|
686
|
+
# Message + errors
|
|
687
|
+
render_network_authentication_required(message: "Sign in to the network portal first",errors: { network: ["captive portal authentication required before accessing the API"] })
|
|
523
688
|
```
|
|
524
689
|
|
|
525
690
|
---
|
|
@@ -539,12 +704,12 @@ class UsersController < ApplicationController
|
|
|
539
704
|
data: @users,
|
|
540
705
|
message: "Users fetched",
|
|
541
706
|
pagination: {
|
|
542
|
-
per_page:
|
|
543
|
-
current_page:
|
|
544
|
-
next_page:
|
|
545
|
-
prev_page:
|
|
546
|
-
total_pages:
|
|
547
|
-
total_count:
|
|
707
|
+
per_page: per_page.to_i,
|
|
708
|
+
current_page: @users.current_page,
|
|
709
|
+
next_page: @users.next_page,
|
|
710
|
+
prev_page: @users.prev_page,
|
|
711
|
+
total_pages: @users.total_pages,
|
|
712
|
+
total_count: @users.total_count
|
|
548
713
|
}
|
|
549
714
|
)
|
|
550
715
|
end
|
|
@@ -553,7 +718,7 @@ class UsersController < ApplicationController
|
|
|
553
718
|
user = User.find(params[:id])
|
|
554
719
|
render_ok(data: user, message: "User found")
|
|
555
720
|
rescue ActiveRecord::RecordNotFound
|
|
556
|
-
render_not_found(message: "User ##{params[:id]} not found",
|
|
721
|
+
render_not_found(message: "User ##{params[:id]} not found",errors: { id: ["no user exists with ID #{params[:id]}"] })
|
|
557
722
|
end
|
|
558
723
|
|
|
559
724
|
def create
|
|
@@ -569,7 +734,7 @@ class UsersController < ApplicationController
|
|
|
569
734
|
user = User.find(params[:id])
|
|
570
735
|
|
|
571
736
|
unless user == current_user || current_user.admin?
|
|
572
|
-
render_forbidden(message: "You can only update your own profile",
|
|
737
|
+
render_forbidden( message: "You can only update your own profile", errors: { profile: ["you do not have permission to update this profile"] } )
|
|
573
738
|
return
|
|
574
739
|
end
|
|
575
740
|
|
|
@@ -595,11 +760,11 @@ class PaymentsController < ApplicationController
|
|
|
595
760
|
result = PaymentGateway.charge(amount: params[:amount], token: params[:token])
|
|
596
761
|
render_created(data: result, message: "Payment successful")
|
|
597
762
|
rescue PaymentGateway::CardDeclined => e
|
|
598
|
-
render_unprocessable(message: e.message)
|
|
763
|
+
render_unprocessable(message: e.message, errors: { card: [e.message] })
|
|
599
764
|
rescue PaymentGateway::Timeout
|
|
600
|
-
render_gateway_timeout(message: "Payment processor timed out. You have not been charged.")
|
|
765
|
+
render_gateway_timeout( message: "Payment processor timed out. You have not been charged.", errors: { gateway: ["upstream timeout, transaction was not processed"] } )
|
|
601
766
|
rescue PaymentGateway::Error => e
|
|
602
|
-
render_bad_gateway(message: "Payment gateway error: #{e.message}")
|
|
767
|
+
render_bad_gateway( message: "Payment gateway error: #{e.message}", errors: { gateway: [e.message] })
|
|
603
768
|
end
|
|
604
769
|
|
|
605
770
|
end
|
|
@@ -700,7 +865,7 @@ render_ok(data: @user, message: "User found")
|
|
|
700
865
|
|
|
701
866
|
```ruby
|
|
702
867
|
# Core
|
|
703
|
-
render_success(data:, message:, meta:,
|
|
868
|
+
render_success(data:, message:, meta:, pagination:, code:, status:)
|
|
704
869
|
render_error(message:, errors:, code:, meta:, status:)
|
|
705
870
|
|
|
706
871
|
# 1xx — Informational
|
|
@@ -710,7 +875,7 @@ render_processing(message:, meta:)
|
|
|
710
875
|
render_early_hints(message:, meta:)
|
|
711
876
|
|
|
712
877
|
# 2xx — Success
|
|
713
|
-
render_success(data:, message:, meta:, pagination:, code
|
|
878
|
+
render_success(data:, message:, meta:, pagination:, code:, status:)
|
|
714
879
|
render_ok(data:, message:, meta:, pagination:)
|
|
715
880
|
render_created(data:, message:, meta:, pagination:)
|
|
716
881
|
render_accepted(data:, message:, meta:, pagination:)
|
|
@@ -146,7 +146,7 @@ module Respondo
|
|
|
146
146
|
# 204 No Content — deletions, actions with no response body
|
|
147
147
|
# Note: we still return our standard JSON structure for consistency
|
|
148
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: :
|
|
149
|
+
render_success(data: nil, message: message, meta: meta, pagination: pagination, code: 204, status: :no_content)
|
|
150
150
|
end
|
|
151
151
|
|
|
152
152
|
# 205 Reset Content — tell the client to reset the document view
|
|
@@ -97,28 +97,6 @@ module Respondo
|
|
|
97
97
|
meta
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
-
# def build_meta
|
|
101
|
-
# meta = { timestamp: current_timestamp }
|
|
102
|
-
|
|
103
|
-
# # Only extract pagination when caller has not explicitly disabled it
|
|
104
|
-
# if @pagination
|
|
105
|
-
# pagination = if @pagy
|
|
106
|
-
# Pagination.extract(@pagy)
|
|
107
|
-
# else
|
|
108
|
-
# Pagination.extract(@raw_data)
|
|
109
|
-
# end
|
|
110
|
-
# meta[:pagination] = pagination if pagination
|
|
111
|
-
# end
|
|
112
|
-
|
|
113
|
-
# # Request ID (Rails only, opt-in via config)
|
|
114
|
-
# if Respondo.config.include_request_id && @request&.respond_to?(:request_id)
|
|
115
|
-
# meta[:request_id] = @request.request_id
|
|
116
|
-
# end
|
|
117
|
-
|
|
118
|
-
# # Merge any caller-supplied meta last (allows overriding)
|
|
119
|
-
# meta.merge(@extra_meta)
|
|
120
|
-
# end
|
|
121
|
-
|
|
122
100
|
def current_timestamp
|
|
123
101
|
if defined?(Time.current)
|
|
124
102
|
Time.current.iso8601
|
data/lib/respondo/version.rb
CHANGED
data/respondo.gemspec
CHANGED
|
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
|
|
|
23
23
|
"homepage_uri" => spec.homepage,
|
|
24
24
|
"source_code_uri" => spec.homepage,
|
|
25
25
|
"changelog_uri" => "#{spec.homepage}/blob/main/CHANGELOG.md",
|
|
26
|
-
"bug_tracker_uri" => "#{spec.homepage}/
|
|
26
|
+
"bug_tracker_uri" => "#{spec.homepage}/issues",
|
|
27
27
|
"rubygems_mfa_required" => "true"
|
|
28
28
|
}
|
|
29
29
|
|
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: 2.1.
|
|
4
|
+
version: 2.1.1
|
|
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-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: railties
|
|
@@ -110,7 +110,7 @@ metadata:
|
|
|
110
110
|
homepage_uri: https://github.com/spatelpatidar/respondo
|
|
111
111
|
source_code_uri: https://github.com/spatelpatidar/respondo
|
|
112
112
|
changelog_uri: https://github.com/spatelpatidar/respondo/blob/main/CHANGELOG.md
|
|
113
|
-
bug_tracker_uri: https://github.com/spatelpatidar/respondo/
|
|
113
|
+
bug_tracker_uri: https://github.com/spatelpatidar/respondo/issues
|
|
114
114
|
rubygems_mfa_required: 'true'
|
|
115
115
|
post_install_message:
|
|
116
116
|
rdoc_options: []
|