bug_bunny 3.0.0 → 3.0.2

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: 0e154bfecb70c1fc85408e87876976dfc8c64c01c2816c4f811a05e39e8432f9
4
- data.tar.gz: 3b4e1508cd1a7f019fb638831c58117d00c46729b4f6bad4f8ecaf1b75914b47
3
+ metadata.gz: 42bf10dd1d3e749561b43b298e6dda00f9b6a76a40dce49103d1d094faa39948
4
+ data.tar.gz: 3088ecec60567a95972ed7abc7dff8f6dd9f5019ad6e0a19bb15dcae61e68bb3
5
5
  SHA512:
6
- metadata.gz: b43ec72552e6d17601665f7640aedd1e9c390463f6df5ca950b082e17316e387d0a10fa9e5ddfd3611d3f928a5d7d2af71367fa6c5409ba1f242402bd46843a1
7
- data.tar.gz: cdd64f5401a653a0fe839ea2a3cf926e1af7de6e8498892bbd20876ea2ed808f97d4db1daa1a31f70c54533a00f73e834a547943d34f8a95543b1befeb5a9591
6
+ metadata.gz: ccf976738d355512f3effec2d84a94087c62b41b329516df4cb15ca99034385da216592a7b52acec6aa224708b7c5c272f17dde349a8d5fe88f3903e33ff1122
7
+ data.tar.gz: 60252a5667d1178b4ac9de5118dd21cfd5538c12c9f69360988f493ffea50c81e4efec2cc08f50570fc9e76c9a57d9bb08c1ef6d75aef4a6b25f995fc765b512
data/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.0.2] - 2026-02-12
4
+
5
+ ### 🚀 Features
6
+ * **Automatic Parameter Wrapping:** `BugBunny::Resource` now automatically wraps the payload inside a root key derived from the model name (e.g., `Manager::Service` -> `{ service: { ... } }`). This mimics Rails form behavior and enables the use of `params.require(:service)` in controllers.
7
+ * Added `self.param_key = '...'` to `BugBunny::Resource` to allow custom root keys.
8
+ * **Declarative Error Handling:** Added Rails-like `rescue_from` DSL to `BugBunny::Controller`. You can now register exception handlers at the class level without overriding methods manually.
9
+ ```ruby
10
+ rescue_from ActiveRecord::RecordNotFound do |e|
11
+ render status: :not_found, json: { error: e.message }
12
+ end
13
+ ```
14
+
15
+ ### 🐛 Bug Fixes
16
+ * **RPC Timeouts on Crash:** Fixed a critical issue where the Client would hang until timeout (`BugBunny::RequestTimeout`) if the Consumer crashed or the route was not found.
17
+ * The Consumer now catches `NameError` (Route not found) and returns a **501 Not Implemented**.
18
+ * The Consumer catches unhandled `StandardError` (App crash) and returns a **500 Internal Server Error**.
19
+ * Ensures a reply is ALWAYS sent to the caller, preventing blocking processes.
20
+
21
+ ### 🛠 Improvements
22
+ * **Controller:** Refactored `BugBunny::Controller` to include a default safety net that catches unhandled errors and logs them properly before returning a 500 status.
23
+
24
+ ## [3.0.1] - 2026-02-10
25
+
26
+ ### 🚀 Features: RESTful Architecture
27
+ * **HTTP Verbs over AMQP:** Implemented support for semantic HTTP verbs (`GET`, `POST`, `PUT`, `DELETE`) within AMQP headers (`x-http-method`). This enables a true RESTful design over RabbitMQ.
28
+ * **Smart Router:** The `BugBunny::Consumer` now behaves like a Rails Router. It automatically infers the controller action based on the combination of the **Verb** and the **URL Path** (e.g., `GET users/1` dispatches to `show`, `POST users` to `create`).
29
+ * **Resource CRUD Mapping:** `BugBunny::Resource` now maps Ruby operations to their specific REST verbs:
30
+ * `create` -> `POST`
31
+ * `update` -> `PUT`
32
+ * `destroy` -> `DELETE`
33
+ * `find/where` -> `GET`.
34
+
35
+ ### 🛠 Improvements
36
+ * **Client API:** Updated `BugBunny::Client#request` and `#publish` to accept a `method:` argument (e.g., `client.request('users', method: :post)`), giving developers full control over the request semantics without changing the method signature.
37
+ * **Request Metadata:** `BugBunny::Request` now handles the `method` attribute and ensures it is properly injected into the AMQP headers for the consumer to read.
38
+
3
39
  ## [3.0.0] - 2026-02-05
4
40
 
5
41
  ### ⚠ Breaking Changes
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **BugBunny** es un framework RPC para Ruby on Rails sobre **RabbitMQ**.
4
4
 
5
- Su filosofía es **"Active Record over AMQP"**. Abstrae la complejidad de colas y exchanges transformando patrones de mensajería en una arquitectura **RESTful simulada**, donde los mensajes contienen "URLs" y "Query Params" que son enrutados automáticamente a controladores.
5
+ Su filosofía es **"Active Record over AMQP"**. Transforma la complejidad de la mensajería asíncrona en una arquitectura **RESTful simulada**. Los mensajes viajan con Verbos HTTP (`GET`, `POST`, `PUT`, `DELETE`) inyectados en los headers AMQP, permitiendo que un **Router Inteligente** despache las peticiones a controladores Rails estándar.
6
6
 
7
7
  ---
8
8
 
@@ -20,251 +20,262 @@ Ejecuta el bundle:
20
20
  bundle install
21
21
  ```
22
22
 
23
- Corre el instalador para generar la configuración:
23
+ Genera los archivos de configuración iniciales:
24
24
 
25
25
  ```bash
26
26
  rails g bug_bunny:install
27
27
  ```
28
28
 
29
+ Esto creará:
30
+ 1. `config/initializers/bug_bunny.rb`
31
+ 2. `app/rabbit/controllers/`
32
+
29
33
  ---
30
34
 
31
35
  ## ⚙️ Configuración
32
36
 
33
- Configura tus credenciales y el Pool de conexiones en el inicializador.
37
+ ### 1. Inicializador y Logging
38
+
39
+ BugBunny separa los logs de la aplicación (Requests) de los logs del driver (Heartbeats/Frames) para mantener la consola limpia.
34
40
 
35
41
  ```ruby
36
42
  # config/initializers/bug_bunny.rb
37
43
 
38
44
  BugBunny.configure do |config|
45
+ # --- Credenciales ---
39
46
  config.host = ENV.fetch('RABBITMQ_HOST', 'localhost')
40
47
  config.username = ENV.fetch('RABBITMQ_USER', 'guest')
41
48
  config.password = ENV.fetch('RABBITMQ_PASS', 'guest')
42
49
  config.vhost = ENV.fetch('RABBITMQ_VHOST', '/')
43
50
 
44
- # Timeouts y Recuperación
45
- config.rpc_timeout = 10 # Segundos a esperar respuesta síncrona
46
- config.network_recovery_interval = 5
51
+ # --- Timeouts ---
52
+ config.rpc_timeout = 10 # Timeout para esperar respuesta (Síncrono)
53
+ config.network_recovery_interval = 5 # Segundos para reintentar conexión
54
+
55
+ # --- Logging (Niveles recomendados) ---
56
+ # Logger de BugBunny: Muestra tus requests (INFO)
57
+ config.logger = Logger.new(STDOUT)
58
+ config.logger.level = Logger::INFO
59
+
60
+ # Logger de Bunny (Driver): Silencia el ruido de bajo nivel (WARN)
61
+ config.bunny_logger = Logger.new(STDOUT)
62
+ config.bunny_logger.level = Logger::WARN
47
63
  end
64
+ ```
65
+
66
+ ### 2. Connection Pool (Crítico) 🧵
48
67
 
49
- # Definimos el Pool Global (Vital para Puma/Sidekiq)
68
+ Para entornos concurrentes como **Puma** o **Sidekiq**, es **obligatorio** definir un Pool de conexiones global. BugBunny no gestiona hilos automáticamente sin esta configuración.
69
+
70
+ ```ruby
71
+ # config/initializers/bug_bunny.rb
72
+
73
+ # Define el pool global (ajusta el tamaño según tus hilos de Puma/Sidekiq)
50
74
  BUG_BUNNY_POOL = ConnectionPool.new(size: ENV.fetch('RAILS_MAX_THREADS', 5).to_i, timeout: 5) do
51
75
  BugBunny.create_connection
52
76
  end
53
77
 
54
- # Inyectamos el pool por defecto a los recursos
78
+ # Inyecta el pool a los recursos para que lo usen automáticamente
55
79
  BugBunny::Resource.connection_pool = BUG_BUNNY_POOL
56
80
  ```
57
81
 
58
82
  ---
59
83
 
60
- ## 🚀 Modo Resource (ORM / Active Record)
84
+ ## 🚀 Modo Resource (ORM / Cliente)
61
85
 
62
- Define modelos que actúan como proxis de recursos remotos. BugBunny separa la **Lógica de Transporte** (RabbitMQ) de la **Lógica de Aplicación** (Controladores).
86
+ Define modelos que actúan como proxies de recursos remotos. BugBunny se encarga de serializar, "wrappear" parámetros y enviar el verbo correcto.
63
87
 
64
- ### Escenario A: Routing Dinámico (Topic / Estándar)
65
- Ideal cuando quieres enrutar por acción. La Routing Key se genera automáticamente usando `resource_name.action`.
88
+ ### Definición del Modelo
66
89
 
67
90
  ```ruby
68
- class RemoteUser < BugBunny::Resource
69
- # --- Configuración ---
70
- self.exchange = 'app.topic'
71
- self.exchange_type = 'topic'
72
-
73
- # Define el nombre lógico del recurso.
74
- # 1. Routing Key: 'users.create', 'users.show.12'
75
- # 2. Header Type: 'users/create', 'users/show/12'
76
- self.resource_name = 'users'
77
-
78
- # No necesitas definir atributos, BugBunny soporta atributos dinámicos (Schema-less)
79
- end
80
- ```
91
+ # app/models/manager/service.rb
92
+ class Manager::Service < BugBunny::Resource
93
+ # 1. Configuración de Transporte
94
+ self.exchange = 'box_cluster_manager'
95
+ self.exchange_type = 'direct'
81
96
 
82
- ### Escenario B: Routing Estático (Direct / Cola Dedicada)
83
- Ideal cuando quieres enviar todo a una cola específica (ej: un Manager), independientemente de la acción.
97
+ # 2. Configuración Lógica (Routing)
98
+ # Define la URL base y la routing key por defecto.
99
+ self.resource_name = 'services'
84
100
 
85
- ```ruby
86
- class BoxManager < BugBunny::Resource
87
- # --- Configuración ---
88
- self.exchange = 'warehouse.direct'
89
- self.exchange_type = 'direct'
90
-
91
- # FORZAMOS LA ROUTING KEY.
92
- # Todo viaja con esta key, sin importar la acción.
93
- self.routing_key = 'manager_queue'
94
-
95
- # Define el nombre lógico para el Controlador.
96
- # Header Type: 'box_manager/create', 'box_manager/show/12'
97
- self.resource_name = 'box_manager'
101
+ # 3. Wrapping de Parámetros (Opcional)
102
+ # Por defecto usa el nombre del modelo sin módulo (Manager::Service -> 'service').
103
+ # Puedes forzarlo con:
104
+ # self.param_key = 'docker_service'
98
105
  end
99
106
  ```
100
107
 
101
- ### Consumiendo el Servicio (CRUD)
108
+ ### CRUD RESTful
102
109
 
103
- La API simula ActiveRecord. Por debajo, construye una "URL" en el header `type` para que el consumidor sepa qué hacer.
110
+ Las operaciones de Ruby se traducen a verbos HTTP sobre AMQP.
104
111
 
105
112
  ```ruby
106
- # --- READ (Colección con Filtros) ---
107
- # Header Type: "users/index?active=true" (Query Params)
108
- # Routing Key: "users.index" (Dinámico) o "manager_queue" (Estático)
109
- users = RemoteUser.where(active: true)
110
-
111
- # --- READ (Singular) ---
112
- # Header Type: "users/show/123" (ID en Path)
113
- # Routing Key: "users.show.123" (Dinámico) o "manager_queue" (Estático)
114
- user = RemoteUser.find(123)
115
- puts user.name # Acceso dinámico a atributos
116
-
117
- # --- CREATE ---
118
- # Header Type: "users/create"
119
- user = RemoteUser.create(email: "test@test.com")
120
-
121
- # --- UPDATE ---
122
- # Header Type: "users/update/123"
123
- user.update(email: "edit@test.com")
124
- # Dirty Tracking: Solo se envían los atributos modificados.
125
-
126
- # --- DESTROY ---
127
- # Header Type: "users/destroy/123"
128
- user.destroy
113
+ # --- LEER (GET) ---
114
+ # Envia: GET services
115
+ # Routing Key: "services"
116
+ services = Manager::Service.all
117
+
118
+ # Envia: GET services/123
119
+ service = Manager::Service.find('123')
120
+
121
+ # --- CREAR (POST) ---
122
+ # Envia: POST services
123
+ # Body: { "service": { "name": "nginx", "replicas": 3 } }
124
+ # Nota: Envuelve los params automáticamente en la clave 'service'.
125
+ svc = Manager::Service.create(name: 'nginx', replicas: 3)
126
+
127
+ # --- ACTUALIZAR (PUT) ---
128
+ # Envia: PUT services/123
129
+ # Body: { "service": { "replicas": 5 } }
130
+ svc.update(replicas: 5)
131
+
132
+ # --- ELIMINAR (DELETE) ---
133
+ # Envia: DELETE services/123
134
+ svc.destroy
129
135
  ```
130
136
 
131
- ---
132
-
133
- ## 🔌 Modo Publisher (Cliente Manual)
134
-
135
- Si no necesitas mapear un recurso o quieres enviar mensajes crudos ("Fire-and-Forget"), puedes usar `BugBunny::Client` directamente.
136
-
137
- ### 1. Instanciar el Cliente
138
-
139
- ```ruby
140
- # Puedes inyectar middlewares personalizados aquí si lo deseas
141
- client = BugBunny::Client.new(pool: BUG_BUNNY_POOL) do |conn|
142
- conn.use BugBunny::Middleware::JsonResponse
143
- end
144
- ```
137
+ ### Contexto Dinámico (`.with`)
145
138
 
146
- ### 2. Publicar Asíncronamente (Fire-and-Forget)
147
- Envía el mensaje y retorna inmediatamente. Ideal para eventos o logs.
139
+ Puedes cambiar la configuración (Routing Key, Exchange) para una operación específica sin afectar al modelo global. El contexto se mantiene durante el ciclo de vida del objeto.
148
140
 
149
141
  ```ruby
150
- # publish(url_logica, opciones)
151
- client.publish('notifications/alert',
152
- exchange: 'events.topic',
153
- exchange_type: 'topic',
154
- routing_key: 'alerts.critical',
155
- body: { message: 'CPU High', server: 'web-1' }
156
- )
157
- ```
142
+ # La instancia nace sabiendo que pertenece a la routing_key 'urgent'
143
+ svc = Manager::Service.with(routing_key: 'urgent').new(name: 'redis')
158
144
 
159
- ### 3. Petición Síncrona (RPC)
160
- Envía el mensaje y bloquea el hilo esperando la respuesta del consumidor.
145
+ # ... lógica de negocio ...
161
146
 
162
- ```ruby
163
- begin
164
- # request(url_logica, opciones)
165
- response = client.request('math/calculate',
166
- exchange: 'rpc.direct',
167
- routing_key: 'calculator',
168
- body: { a: 10, b: 20 },
169
- timeout: 5 # Segundos de espera máxima
170
- )
171
-
172
- puts response['body'] # => { "result": 30 }
173
-
174
- rescue BugBunny::RequestTimeout
175
- puts "El servidor tardó demasiado."
176
- end
147
+ # Al guardar, BugBunny recuerda el contexto y envía a 'urgent'
148
+ svc.save
149
+ # Log: [BugBunny] [POST] '/services' | Routing Key: 'urgent'
177
150
  ```
178
151
 
179
152
  ---
180
153
 
181
- ## 📡 Modo Servidor (El Worker)
154
+ ## 📡 Modo Servidor (Worker & Router)
182
155
 
183
- BugBunny incluye un **Router Inteligente** que parsea el header `type` (la URL simulada), extrae parámetros y despacha al controlador.
156
+ BugBunny incluye un **Router Inteligente** que despacha mensajes a controladores basándose en el Verbo y el Path, imitando a Rails.
184
157
 
185
- ### 1. Definir Controladores
158
+ ### 1. El Controlador (`app/rabbit/controllers/`)
186
159
 
187
- Crea tus controladores en `app/rabbit/controllers/`. Heredan de `BugBunny::Controller`.
160
+ Hereda de `BugBunny::Controller`. Tienes acceso a `params`, `before_action` y `rescue_from`.
188
161
 
189
162
  ```ruby
190
- # app/rabbit/controllers/users_controller.rb
191
- class UsersController < BugBunny::Controller
163
+ # app/rabbit/controllers/services_controller.rb
164
+ class ServicesController < BugBunny::Controller
165
+ # Callbacks
166
+ before_action :set_service, only: %i[show update destroy]
192
167
 
193
- # Acción para type: "users/index?active=true"
168
+ # GET services
194
169
  def index
195
- # params fusiona Query Params y Body
196
- users = User.where(active: params[:active])
197
- render status: 200, json: users
170
+ render status: 200, json: DockerService.all
198
171
  end
199
172
 
200
- # Acción para type: "users/show/12"
201
- def show
202
- # params[:id] se extrae automáticamente del Path de la URL
203
- user = User.find_by(id: params[:id])
204
-
205
- if user
206
- render status: 200, json: user
207
- else
208
- render status: 404, json: { error: 'Not Found' }
209
- end
173
+ # POST services
174
+ def create
175
+ # BugBunny wrappea los params automáticamente en el Resource.
176
+ # Aquí los consumimos con seguridad usando Strong Parameters simulados o hash access.
177
+ # params[:service] estará disponible gracias al param_key del Resource.
178
+
179
+ result = DockerService.create(params[:service])
180
+ render status: 201, json: result
210
181
  end
211
182
 
212
- # Acción para type: "users/create"
213
- def create
214
- user = User.new(params)
215
- if user.save
216
- render status: 201, json: user
217
- else
218
- # Estos errores se propagan como BugBunny::UnprocessableEntity
219
- render status: 422, json: { errors: user.errors }
183
+ private
184
+
185
+ def set_service
186
+ # params[:id] se extrae automágicamente de la URL (Route Param)
187
+ @service = DockerService.find(params[:id])
188
+
189
+ unless @service
190
+ render status: 404, json: { error: "Service not found" }
220
191
  end
221
192
  end
222
193
  end
223
194
  ```
224
195
 
225
- ### 2. Ejecutar el Worker
196
+ ### 2. Manejo de Errores (`rescue_from`)
226
197
 
227
- ```bash
228
- bundle exec rake bug_bunny:work
229
- ```
198
+ Puedes definir un `ApplicationController` base para manejar errores de forma centralizada y declarativa.
230
199
 
231
- ---
200
+ ```ruby
201
+ # app/rabbit/controllers/application.rb
202
+ class ApplicationController < BugBunny::Controller
203
+ # Manejo específico
204
+ rescue_from ActiveRecord::RecordNotFound do
205
+ render status: :not_found, json: { error: "Resource missing" }
206
+ end
232
207
 
233
- ## 🏗 Arquitectura REST-over-AMQP
208
+ rescue_from ActiveModel::ValidationError do |e|
209
+ render status: :unprocessable_entity, json: e.model.errors
210
+ end
234
211
 
235
- BugBunny desacopla el transporte de la lógica usando headers.
212
+ # Catch-all (Red de seguridad)
213
+ rescue_from StandardError do |e|
214
+ BugBunny.configuration.logger.error(e)
215
+ render status: :internal_server_error, json: { error: "Internal Error" }
216
+ end
217
+ end
218
+ ```
236
219
 
237
- | Concepto | REST (HTTP) | BugBunny (AMQP) | Configuración |
220
+ ### 3. Tabla de Ruteo (Convención)
221
+
222
+ El Router infiere la acción automáticamente:
223
+
224
+ | Verbo | URL Pattern | Controlador | Acción |
238
225
  | :--- | :--- | :--- | :--- |
239
- | **Endpoint** | URL Path (`/users/1`) | Header `type` (`users/show/1`) | `resource_name` |
240
- | **Filtros** | Query String (`?active=true`) | Header `type` (`users/index?active=true`) | Automático (`where`) |
241
- | **Destino Físico** | IP/Dominio | Routing Key (`users.create` o `manager`) | `routing_key` (Estático) o `resource_name` (Dinámico) |
242
- | **Payload** | Body (JSON) | Body (JSON) | N/A |
243
- | **Status** | HTTP Code (200, 404) | JSON Response `status` | N/A |
226
+ | `GET` | `services` | `ServicesController` | `index` |
227
+ | `GET` | `services/12` | `ServicesController` | `show` |
228
+ | `POST` | `services` | `ServicesController` | `create` |
229
+ | `PUT` | `services/12` | `ServicesController` | `update` |
230
+ | `DELETE` | `services/12` | `ServicesController` | `destroy` |
231
+ | `POST` | `services/12/restart` | `ServicesController` | `restart` (Custom) |
244
232
 
245
233
  ---
246
234
 
247
- ## 🛠 Middlewares
235
+ ## 🔌 Modo Publisher (Cliente Manual)
248
236
 
249
- BugBunny usa una pila de middlewares para procesar respuestas.
237
+ Si necesitas enviar mensajes crudos fuera de la lógica Resource, usa `BugBunny::Client`.
250
238
 
251
239
  ```ruby
252
- # Configuración global en el Resource
253
- BugBunny::Resource.client_middleware do |conn|
254
- # 1. Lanza excepciones Ruby para errores 4xx/5xx
255
- conn.use BugBunny::Middleware::RaiseError
240
+ client = BugBunny::Client.new(pool: BUG_BUNNY_POOL)
241
+
242
+ # --- REQUEST (Síncrono / RPC) ---
243
+ # Espera la respuesta. Lanza BugBunny::RequestTimeout si falla.
244
+ response = client.request('services/123/logs',
245
+ method: :get,
246
+ exchange: 'logs_exchange',
247
+ timeout: 5
248
+ )
249
+ puts response['body']
256
250
 
257
- # 2. Parsea JSON a HashWithIndifferentAccess
258
- conn.use BugBunny::Middleware::JsonResponse
259
- end
251
+ # --- PUBLISH (Asíncrono / Fire-and-Forget) ---
252
+ # No espera respuesta.
253
+ client.publish('audit/events',
254
+ method: :post,
255
+ body: { event: 'login', user_id: 1 }
256
+ )
260
257
  ```
261
258
 
262
- ### Excepciones
259
+ ---
260
+
261
+ ## 🏗 Arquitectura REST-over-AMQP
262
+
263
+ BugBunny desacopla el transporte de la lógica usando headers estándar.
264
+
265
+ 1. **Semántica:** El mensaje lleva headers `type` (URL) y `x-http-method` (Verbo).
266
+ 2. **Ruteo:** El consumidor lee estos headers y ejecuta el controlador correspondiente.
267
+ 3. **Parametros:** `params` unifica:
268
+ * **Route Params:** `services/123` -> `params[:id] = 123`
269
+ * **Query Params:** `services?force=true` -> `params[:force] = true`
270
+ * **Body:** Payload JSON fusionado en el hash.
263
271
 
264
- * `BugBunny::UnprocessableEntity` (422): Error de validación.
265
- * `BugBunny::NotFound` (404): Recurso no encontrado.
266
- * `BugBunny::RequestTimeout`: Timeout RPC.
267
- * `BugBunny::CommunicationError`: Fallo de red RabbitMQ.
272
+ ### Logs Estructurados
273
+
274
+ Facilita el debugging mostrando claramente qué recurso se está tocando y por dónde viaja.
275
+
276
+ ```text
277
+ [BugBunny] [POST] '/services' | Exchange: 'cluster' (Type: direct) | Routing Key: 'node-1'
278
+ ```
268
279
 
269
280
  ---
270
281
 
@@ -4,31 +4,25 @@ require_relative 'middleware/stack'
4
4
  module BugBunny
5
5
  # Cliente principal para realizar peticiones a RabbitMQ.
6
6
  #
7
- # Implementa el patrón "Onion Middleware" (Arquitectura de Cebolla) similar a Faraday,
8
- # permitiendo interceptar, transformar y procesar tanto las peticiones salientes
9
- # como las respuestas entrantes mediante una pila de middlewares.
7
+ # Implementa el patrón "Onion Middleware" (Arquitectura de Cebolla) similar a Faraday.
8
+ # Mantiene una interfaz flexible donde el verbo HTTP se pasa como opción.
10
9
  #
11
- # @example Inicialización básica
12
- # client = BugBunny::Client.new(pool: MY_POOL)
10
+ # @example Petición RPC (GET)
11
+ # client.request('users/123', method: :get)
13
12
  #
14
- # @example Con Middlewares personalizados
15
- # client = BugBunny::Client.new(pool: MY_POOL) do |conn|
16
- # conn.use BugBunny::Middleware::RaiseError
17
- # conn.use BugBunny::Middleware::JsonResponse
18
- # conn.use BugBunny::Middleware::Logger, Rails.logger
19
- # end
13
+ # @example Publicación Fire-and-Forget (POST)
14
+ # client.publish('logs', method: :post, body: { msg: 'Error' })
20
15
  class Client
21
16
  # @return [ConnectionPool] El pool de conexiones subyacente a RabbitMQ.
22
17
  attr_reader :pool
23
18
 
24
- # @return [BugBunny::Middleware::Stack] La pila de middlewares configurada para este cliente.
19
+ # @return [BugBunny::Middleware::Stack] La pila de middlewares configurada.
25
20
  attr_reader :stack
26
21
 
27
22
  # Inicializa un nuevo cliente.
28
23
  #
29
24
  # @param pool [ConnectionPool] Pool de conexiones a RabbitMQ configurado previamente.
30
25
  # @yield [stack] Bloque opcional para configurar la pila de middlewares.
31
- # @yieldparam stack [BugBunny::Middleware::Stack] El objeto stack para registrar middlewares con {#use}.
32
26
  # @raise [ArgumentError] Si no se proporciona un `pool`.
33
27
  def initialize(pool:)
34
28
  raise ArgumentError, "BugBunny::Client requiere un 'pool:'" if pool.nil?
@@ -39,21 +33,16 @@ module BugBunny
39
33
 
40
34
  # Realiza una petición Síncrona (RPC / Request-Response).
41
35
  #
42
- # Envía un mensaje y bloquea la ejecución del hilo actual hasta recibir una respuesta
43
- # correlacionada del servidor o hasta que se supere el tiempo de espera (timeout).
36
+ # Envía un mensaje y bloquea la ejecución del hilo actual hasta recibir respuesta.
44
37
  #
45
- # @param url [String] La ruta o acción del mensaje (ej: 'users/create').
46
- # @param args [Hash] Opciones de configuración de la petición.
47
- # @option args [Object] :body El cuerpo del mensaje (Hash o String).
48
- # @option args [String] :exchange Nombre del exchange destino.
49
- # @option args [String] :exchange_type Tipo de exchange ('direct', 'topic', 'fanout').
50
- # @option args [String] :routing_key Routing key manual (opcional).
51
- # @option args [Integer] :timeout Tiempo máximo de espera en segundos antes de lanzar timeout.
52
- # @option args [Hash] :headers Headers AMQP adicionales para metadatos.
53
- # @yield [req] Bloque opcional para configurar el objeto Request directamente.
54
- # @yieldparam req [BugBunny::Request] Objeto request configurable.
55
- # @return [Hash] La respuesta del servidor, conteniendo habitualmente `status` y `body`.
56
- # @raise [BugBunny::RequestTimeout] Si no se recibe respuesta en el tiempo límite.
38
+ # @param url [String] La ruta del recurso (ej: 'users/1').
39
+ # @param args [Hash] Opciones de configuración.
40
+ # @option args [Symbol] :method El verbo HTTP (:get, :post, :put, :delete). Default: :get.
41
+ # @option args [Object] :body El cuerpo del mensaje.
42
+ # @option args [Hash] :headers Headers AMQP adicionales.
43
+ # @option args [Integer] :timeout Tiempo máximo de espera.
44
+ # @yield [req] Bloque para configurar el objeto Request directamente.
45
+ # @return [Hash] La respuesta del servidor.
57
46
  def request(url, **args)
58
47
  run_in_pool(:rpc, url, args) do |req|
59
48
  yield req if block_given?
@@ -62,13 +51,9 @@ module BugBunny
62
51
 
63
52
  # Realiza una publicación Asíncrona (Fire-and-Forget).
64
53
  #
65
- # Envía el mensaje al exchange y retorna el control inmediatamente sin esperar
66
- # ninguna confirmación o respuesta del consumidor.
67
- #
68
- # @param url [String] La ruta o acción del mensaje.
69
- # @param args [Hash] Mismas opciones que {#request}, excepto `:timeout` (no aplica).
70
- # @yield [req] Bloque opcional para configurar el objeto Request.
71
- # @yieldparam req [BugBunny::Request] Objeto request configurable.
54
+ # @param url [String] La ruta del evento/recurso.
55
+ # @param args [Hash] Mismas opciones que {#request}, excepto `:timeout`.
56
+ # @yield [req] Bloque para configurar el objeto Request.
72
57
  # @return [void]
73
58
  def publish(url, **args)
74
59
  run_in_pool(:fire, url, args) do |req|
@@ -78,16 +63,14 @@ module BugBunny
78
63
 
79
64
  private
80
65
 
81
- # Ejecuta la lógica de envío dentro del contexto del Pool y aplica la cadena de middlewares.
82
- #
83
- # @param method_name [Symbol] El método a invocar en el Producer (:rpc o :fire).
84
- # @param url [String] La URL/Acción del request.
85
- # @param args [Hash] Argumentos pasados al método público.
66
+ # Ejecuta la lógica de envío dentro del contexto del Pool.
67
+ # Mapea los argumentos al objeto Request y ejecuta la cadena de middlewares.
86
68
  def run_in_pool(method_name, url, args)
87
69
  # 1. Builder del Request
88
70
  req = BugBunny::Request.new(url)
89
71
 
90
72
  # 2. Syntactic Sugar: Mapeo de argumentos a atributos del Request
73
+ req.method = args[:method] if args[:method]
91
74
  req.body = args[:body] if args[:body]
92
75
  req.exchange = args[:exchange] if args[:exchange]
93
76
  req.exchange_type = args[:exchange_type] if args[:exchange_type]
@@ -26,6 +26,10 @@ module BugBunny
26
26
  # @return [Logger] Instancia del logger para depuración (default: Logger a STDOUT).
27
27
  attr_accessor :logger
28
28
 
29
+ # @return [Logger] Logger específico para el driver Bunny (Conexión, Heartbeats, Frames).
30
+ # Se recomienda nivel WARN para evitar ruido.
31
+ attr_accessor :bunny_logger
32
+
29
33
  # @return [Boolean] Si `true`, Bunny intentará reconectar automáticamente ante fallos de red (default: true).
30
34
  attr_accessor :automatically_recover
31
35
 
@@ -58,7 +62,13 @@ module BugBunny
58
62
 
59
63
  # Inicializa la configuración con valores por defecto seguros.
60
64
  def initialize
65
+ # Logger de la Aplicación (BugBunny) -> INFO (Ves tus requests)
61
66
  @logger = Logger.new(STDOUT)
67
+ @logger.level = Logger::INFO
68
+
69
+ # Logger del Driver (Bunny) -> WARN (Oculta el ruido de "handle_frame")
70
+ @bunny_logger = Logger.new(STDOUT)
71
+ @bunny_logger.level = Logger::WARN
62
72
  @automatically_recover = true
63
73
  @network_recovery_interval = 5
64
74
  @connection_timeout = 10