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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 94ebeacf9ef38e8d7d2c41182edca0cd2ae55b0520e93a5900dafea65e84584d
4
- data.tar.gz: 4e185739d3eb720712a9c0264558a7669d1f6bdd420d474ce9b68a85409ddf45
3
+ metadata.gz: cbb9a42fee14d341990b700fd36592d17757abccb2c1a7cac406177a0eee259f
4
+ data.tar.gz: 31885b2ddb68d296998f82900273e36a76ec896c4ca691291544db0242932508
5
5
  SHA512:
6
- metadata.gz: 560d4983326902e3c8c5859af0105142a8799aeeeda2a4c6036917db393f117fc3d885cc8389ec6ad6f24b6a0846d4ad5262809e5b37af4c2c63dcdceb7a6059
7
- data.tar.gz: 35a6a092f71d876b30c749ce92dfeff9414689898aa12adb2c1c74466b2e9df233763f446745d93fdcb63c9a4a9f7b1332aa1c2a743fe6683afb804a0e713f35
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
+ [![Gem Version](https://badge.fury.io/rb/respondo.svg?icon=si%3Arubygems&icon_color=%23ce0303)](https://badge.fury.io/rb/respondo)
4
+ ![GitHub Repo Views](https://gitviews.com/repo/spatelpatidar/respondo.svg)
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, code: "BAD_REQUEST")
118
- render_error(message: message, errors: errors, code: code, status: :bad_request)
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, code: "UNAUTHORIZED")
123
- render_error(message: message, errors: errors, code: code, status: :unauthorized)
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, code: "PAYMENT_REQUIRED")
128
- render_error(message: message, errors: errors, code: code, status: :payment_required)
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, code: "FORBIDDEN")
133
- render_error(message: message, errors: errors, code: code, status: :forbidden)
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, code: "NOT_FOUND")
138
- render_error(message: message, errors: errors, code: code, status: :not_found)
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, code: "METHOD_NOT_ALLOWED")
143
- render_error(message: message, errors: errors, code: code, status: :method_not_allowed)
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, code: "NOT_ACCEPTABLE")
148
- render_error(message: message, errors: errors, code: code, status: :not_acceptable)
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, code: "REQUEST_TIMEOUT")
153
- render_error(message: message, errors: errors, code: code, status: :request_timeout)
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, code: "CONFLICT")
158
- render_error(message: message, errors: errors, code: code, status: :conflict)
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, code: "GONE")
163
- render_error(message: message, errors: errors, code: code, status: :gone)
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, code: "PRECONDITION_FAILED")
168
- render_error(message: message, errors: errors, code: code, status: :precondition_failed)
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, code: "UNSUPPORTED_MEDIA_TYPE")
173
- render_error(message: message, errors: errors, code: code, status: :unsupported_media_type)
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, code: "UNPROCESSABLE_ENTITY")
178
- render_error(message: message, errors: errors, code: code, status: :unprocessable_entity)
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, code: "LOCKED")
183
- render_error(message: message, errors: errors, code: code, status: :locked)
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, code: "RATE_LIMITED")
188
- render_error(message: message, errors: errors, code: code, status: :too_many_requests)
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, code: "SERVER_ERROR")
197
- render_error(message: message, errors: errors, code: code, status: :internal_server_error)
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, code: "NOT_IMPLEMENTED")
202
- render_error(message: message, errors: errors, code: code, status: :not_implemented)
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, code: "BAD_GATEWAY")
207
- render_error(message: message, errors: errors, code: code, status: :bad_gateway)
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, code: "SERVICE_UNAVAILABLE")
212
- render_error(message: message, errors: errors, code: code, status: :service_unavailable)
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, code: "GATEWAY_TIMEOUT")
217
- render_error(message: message, errors: errors, code: code, status: :gateway_timeout)
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 = { timestamp: current_timestamp }
72
+ meta = {}
73
73
 
74
- # Only extract pagination when caller has not explicitly disabled it
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 = if @pagy
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
- # Request ID (Rails only, opt-in via config)
85
- if Respondo.config.include_request_id && @request&.respond_to?(:request_id)
86
- meta[:request_id] = @request.request_id
87
- end
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
- # Merge any caller-supplied meta last (allows overriding)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Respondo
4
- VERSION = "0.1.0"
4
+ VERSION = "1.0.0"
5
5
  end
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: 0.1.0
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-31 00:00:00.000000000 Z
11
+ date: 2026-04-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec