bug_bunny 4.10.3 → 4.11.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 +8 -0
- data/lib/bug_bunny/consumer.rb +21 -4
- data/lib/bug_bunny/exception.rb +9 -0
- data/lib/bug_bunny/middleware/raise_error.rb +15 -1
- data/lib/bug_bunny/version.rb +1 -1
- data/spec/unit/raise_error_spec.rb +59 -0
- data/spec/unit/route_spec.rb +29 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: efe99ebd57d867b306ecb7bc2b67d92b72bb1b0b3ddaabaf1c72755ed337ded9
|
|
4
|
+
data.tar.gz: 997cb254c582a4524275c295ad12694762f1b7b010e690cbb70dfdd7c6684039
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7bb1d730e90c2d58c39087e65ea67415a258cd0f4dc4e0064610ee67d7167480726b3238155687f8d77f8390d2c41a707482df0d56da8022be9b2d89269a9f97
|
|
7
|
+
data.tar.gz: 4b46a0f617856554700b9c31e2917256ee5aa723f5699c0097cd8a1e82b28e8655e98ddf61ddb884e85794979ad9fcdf2b6c7bd2a8bcb7bd150bdeecdd71dfa8
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [4.11.0] - 2026-04-08
|
|
4
|
+
|
|
5
|
+
### Correcciones
|
|
6
|
+
- **Query string en route matching:** El consumer incluía el query string como parte del path al hacer route matching (ej. `secrets?q%5Bname%5D=postgres_password`), causando 404 en rutas válidas con query params. Ahora se separa el path limpio via `URI.parse` antes de invocar `RouteSet#recognize`. — @Gabriel
|
|
7
|
+
|
|
8
|
+
### ✨ New Features
|
|
9
|
+
- **`BugBunny::RoutingError`:** Nueva excepción `RoutingError < NotFound` análoga a `ActionController::RoutingError` en Rails. Permite al productor distinguir "la ruta no existe en el servicio remoto" de "el recurso no fue encontrado". El consumer ahora envía `error_type: 'routing_error'` en el body del 404 cuando la ruta o el controller no existen, y el middleware `RaiseError` levanta `RoutingError` con el detalle del error. `rescue BugBunny::NotFound` sigue capturando ambos casos. — @Gabriel
|
|
10
|
+
|
|
3
11
|
## [4.10.2] - 2026-04-08
|
|
4
12
|
|
|
5
13
|
### Correcciones
|
data/lib/bug_bunny/consumer.rb
CHANGED
|
@@ -184,17 +184,18 @@ module BugBunny
|
|
|
184
184
|
normalized_path = path.gsub(%r{^/|/$}, '')
|
|
185
185
|
|
|
186
186
|
uri = URI.parse("http://dummy/#{normalized_path}")
|
|
187
|
+
clean_path = uri.path.delete_prefix('/dummy/').delete_prefix('/')
|
|
187
188
|
|
|
188
189
|
# Extraemos query params (ej. /nodes?status=active)
|
|
189
190
|
query_params = uri.query ? Rack::Utils.parse_nested_query(uri.query) : {}
|
|
190
191
|
query_params = query_params.with_indifferent_access if defined?(ActiveSupport::HashWithIndifferentAccess)
|
|
191
192
|
|
|
192
193
|
# Le preguntamos al motor de rutas global quién debe manejar esto
|
|
193
|
-
route_info = BugBunny.routes.recognize(http_method,
|
|
194
|
+
route_info = BugBunny.routes.recognize(http_method, clean_path)
|
|
194
195
|
|
|
195
196
|
if route_info.nil?
|
|
196
|
-
safe_log(:warn, 'consumer.route_not_found', method: http_method, path:
|
|
197
|
-
|
|
197
|
+
safe_log(:warn, 'consumer.route_not_found', method: http_method, path: clean_path)
|
|
198
|
+
handle_routing_error(properties, "No route matches [#{http_method}] \"#{clean_path}\"")
|
|
198
199
|
session.channel.reject(delivery_info.delivery_tag, false)
|
|
199
200
|
return
|
|
200
201
|
end
|
|
@@ -213,7 +214,7 @@ module BugBunny
|
|
|
213
214
|
controller_class = controller_class_name.constantize
|
|
214
215
|
rescue NameError
|
|
215
216
|
safe_log(:warn, 'consumer.controller_not_found', controller: controller_class_name)
|
|
216
|
-
|
|
217
|
+
handle_routing_error(properties, "Controller #{controller_class_name} not found")
|
|
217
218
|
session.channel.reject(delivery_info.delivery_tag, false)
|
|
218
219
|
return
|
|
219
220
|
end
|
|
@@ -300,6 +301,22 @@ module BugBunny
|
|
|
300
301
|
# @param detail [String] Detalle del error.
|
|
301
302
|
# @param exception [StandardError, nil] Excepción original (para status 500).
|
|
302
303
|
# @api private
|
|
304
|
+
# Maneja errores de enrutamiento (ruta o controller no encontrado).
|
|
305
|
+
#
|
|
306
|
+
# Envía una respuesta 404 con `error_type: 'routing_error'` para que el
|
|
307
|
+
# middleware del productor pueda levantar {BugBunny::RoutingError} en vez
|
|
308
|
+
# de un {BugBunny::NotFound} genérico.
|
|
309
|
+
#
|
|
310
|
+
# @param properties [Bunny::MessageProperties] Headers y propiedades AMQP.
|
|
311
|
+
# @param detail [String] Descripción del error de routing.
|
|
312
|
+
# @api private
|
|
313
|
+
def handle_routing_error(properties, detail)
|
|
314
|
+
return unless properties.reply_to
|
|
315
|
+
|
|
316
|
+
body = { error: 'Not Found', detail: detail, error_type: 'routing_error' }
|
|
317
|
+
reply({ status: 404, body: body }, properties.reply_to, properties.correlation_id)
|
|
318
|
+
end
|
|
319
|
+
|
|
303
320
|
def handle_fatal_error(properties, status, error_title, detail, exception = nil)
|
|
304
321
|
return unless properties.reply_to
|
|
305
322
|
|
data/lib/bug_bunny/exception.rb
CHANGED
|
@@ -35,6 +35,15 @@ module BugBunny
|
|
|
35
35
|
# El recurso solicitado (o la ruta RPC) no existe en el servidor remoto.
|
|
36
36
|
class NotFound < ClientError; end
|
|
37
37
|
|
|
38
|
+
# Error 404 específico de enrutamiento.
|
|
39
|
+
# Se lanza cuando el servicio remoto no tiene una ruta registrada para el verbo y path solicitados.
|
|
40
|
+
# Análogo a `ActionController::RoutingError` en Rails.
|
|
41
|
+
#
|
|
42
|
+
# @example
|
|
43
|
+
# rescue BugBunny::RoutingError => e
|
|
44
|
+
# e.message # => 'No route matches [GET] "secrets"'
|
|
45
|
+
class RoutingError < NotFound; end
|
|
46
|
+
|
|
38
47
|
# Error 406: Not Acceptable.
|
|
39
48
|
# El servidor no puede generar una respuesta con las características de contenido aceptadas.
|
|
40
49
|
class NotAcceptable < ClientError; end
|
|
@@ -39,7 +39,7 @@ module BugBunny
|
|
|
39
39
|
when 400
|
|
40
40
|
raise BugBunny::BadRequest, format_error_message(body)
|
|
41
41
|
when 404
|
|
42
|
-
|
|
42
|
+
raise_not_found(body)
|
|
43
43
|
when 406
|
|
44
44
|
raise BugBunny::NotAcceptable
|
|
45
45
|
when 408
|
|
@@ -87,6 +87,20 @@ module BugBunny
|
|
|
87
87
|
end
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
+
# Distingue un error de routing (ruta/controller no existe en el servicio remoto)
|
|
91
|
+
# de un 404 genérico de recurso no encontrado.
|
|
92
|
+
#
|
|
93
|
+
# @param body [Hash, String, nil] El cuerpo de la respuesta 404.
|
|
94
|
+
# @raise [BugBunny::RoutingError] Si el consumer marcó `error_type: 'routing_error'`.
|
|
95
|
+
# @raise [BugBunny::NotFound] Para 404 genéricos.
|
|
96
|
+
def raise_not_found(body)
|
|
97
|
+
if body.is_a?(Hash) && body['error_type'] == 'routing_error'
|
|
98
|
+
raise BugBunny::RoutingError, format_error_message(body)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
raise BugBunny::NotFound, format_error_message(body)
|
|
102
|
+
end
|
|
103
|
+
|
|
90
104
|
# Maneja códigos de error genéricos no mapeados explícitamente en el `case`.
|
|
91
105
|
#
|
|
92
106
|
# @param status [Integer] El código de estado HTTP (ej. 418, 502).
|
data/lib/bug_bunny/version.rb
CHANGED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe BugBunny::Middleware::RaiseError do
|
|
6
|
+
subject(:middleware) { described_class.new(->(_env) { {} }) }
|
|
7
|
+
|
|
8
|
+
describe '#on_complete' do
|
|
9
|
+
context 'when status is 404 with error_type routing_error' do
|
|
10
|
+
let(:response) do
|
|
11
|
+
{
|
|
12
|
+
'status' => 404,
|
|
13
|
+
'body' => { 'error' => 'Not Found', 'detail' => 'No route matches [GET] "secrets"',
|
|
14
|
+
'error_type' => 'routing_error' }
|
|
15
|
+
}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'raises BugBunny::RoutingError' do
|
|
19
|
+
expect { middleware.on_complete(response) }.to raise_error(BugBunny::RoutingError)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'includes the detail in the error message' do
|
|
23
|
+
expect { middleware.on_complete(response) }.to raise_error(
|
|
24
|
+
BugBunny::RoutingError, /No route matches \[GET\] "secrets"/
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'is rescuable as BugBunny::NotFound' do
|
|
29
|
+
expect { middleware.on_complete(response) }.to raise_error(BugBunny::NotFound)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context 'when status is 404 without error_type (resource not found)' do
|
|
34
|
+
let(:response) do
|
|
35
|
+
{ 'status' => 404, 'body' => { 'error' => 'Not Found', 'detail' => 'User 999 not found' } }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'raises BugBunny::NotFound but not RoutingError' do
|
|
39
|
+
error = nil
|
|
40
|
+
begin
|
|
41
|
+
middleware.on_complete(response)
|
|
42
|
+
rescue BugBunny::NotFound => e
|
|
43
|
+
error = e
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
expect(error).to be_a(BugBunny::NotFound)
|
|
47
|
+
expect(error).not_to be_a(BugBunny::RoutingError)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
context 'when status is 404 with nil body' do
|
|
52
|
+
let(:response) { { 'status' => 404, 'body' => nil } }
|
|
53
|
+
|
|
54
|
+
it 'raises BugBunny::NotFound' do
|
|
55
|
+
expect { middleware.on_complete(response) }.to raise_error(BugBunny::NotFound)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
data/spec/unit/route_spec.rb
CHANGED
|
@@ -176,5 +176,34 @@ RSpec.describe BugBunny::Routing::Route do
|
|
|
176
176
|
expect(route_not_found_event[:kwargs][:path]).to eq('services/otaigccd59q0k7kxb1h193go2/restart')
|
|
177
177
|
end
|
|
178
178
|
end
|
|
179
|
+
|
|
180
|
+
context 'when properties.type includes query string' do
|
|
181
|
+
let(:properties) do
|
|
182
|
+
double('properties',
|
|
183
|
+
type: 'secrets?q%5Bname%5D=postgres_password',
|
|
184
|
+
headers: { 'x-http-method' => 'GET' },
|
|
185
|
+
correlation_id: 'corr-abc-123',
|
|
186
|
+
reply_to: nil,
|
|
187
|
+
content_type: 'application/json')
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
it 'strips query string before route recognition' do
|
|
191
|
+
allow(BugBunny.routes).to receive(:recognize).and_return(nil)
|
|
192
|
+
|
|
193
|
+
test_consumer.send(:process_message, delivery_info, properties, '')
|
|
194
|
+
|
|
195
|
+
expect(BugBunny.routes).to have_received(:recognize).with('GET', 'secrets')
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
it 'logs route_not_found with clean path (without query string)' do
|
|
199
|
+
allow(BugBunny.routes).to receive(:recognize).and_return(nil)
|
|
200
|
+
|
|
201
|
+
test_consumer.send(:process_message, delivery_info, properties, '')
|
|
202
|
+
|
|
203
|
+
route_not_found_event = logged_events.find { |e| e[:event] == 'consumer.route_not_found' }
|
|
204
|
+
expect(route_not_found_event).not_to be_nil
|
|
205
|
+
expect(route_not_found_event[:kwargs][:path]).to eq('secrets')
|
|
206
|
+
end
|
|
207
|
+
end
|
|
179
208
|
end
|
|
180
209
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bug_bunny
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.
|
|
4
|
+
version: 4.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- gabix
|
|
@@ -288,6 +288,7 @@ files:
|
|
|
288
288
|
- spec/unit/observability_spec.rb
|
|
289
289
|
- spec/unit/otel_spec.rb
|
|
290
290
|
- spec/unit/producer_spec.rb
|
|
291
|
+
- spec/unit/raise_error_spec.rb
|
|
291
292
|
- spec/unit/remote_error_spec.rb
|
|
292
293
|
- spec/unit/request_spec.rb
|
|
293
294
|
- spec/unit/resource_attributes_spec.rb
|
|
@@ -303,7 +304,7 @@ metadata:
|
|
|
303
304
|
homepage_uri: https://github.com/gedera/bug_bunny
|
|
304
305
|
source_code_uri: https://github.com/gedera/bug_bunny
|
|
305
306
|
changelog_uri: https://github.com/gedera/bug_bunny/blob/main/CHANGELOG.md
|
|
306
|
-
documentation_uri: https://github.com/gedera/bug_bunny/blob/v4.
|
|
307
|
+
documentation_uri: https://github.com/gedera/bug_bunny/blob/v4.11.0/skill
|
|
307
308
|
post_install_message:
|
|
308
309
|
rdoc_options: []
|
|
309
310
|
require_paths:
|