bug_bunny 4.6.0 → 4.7.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/.claude/commands/pr.md +42 -0
- data/.claude/commands/release.md +41 -0
- data/.claude/commands/rubocop.md +22 -0
- data/.claude/commands/test.md +28 -0
- data/.claude/commands/yard.md +46 -0
- data/CHANGELOG.md +42 -15
- data/CLAUDE.md +228 -0
- data/README.md +154 -221
- data/Rakefile +19 -3
- data/docs/concepts.md +140 -0
- data/docs/howto/controller.md +194 -0
- data/docs/howto/middleware_client.md +119 -0
- data/docs/howto/middleware_consumer.md +127 -0
- data/docs/howto/rails.md +214 -0
- data/docs/howto/resource.md +200 -0
- data/docs/howto/routing.md +133 -0
- data/docs/howto/testing.md +259 -0
- data/docs/howto/tracing.md +119 -0
- data/lib/bug_bunny/client.rb +41 -18
- data/lib/bug_bunny/configuration.rb +63 -0
- data/lib/bug_bunny/consumer.rb +51 -37
- data/lib/bug_bunny/consumer_middleware.rb +14 -5
- data/lib/bug_bunny/controller.rb +29 -4
- data/lib/bug_bunny/exception.rb +4 -0
- data/lib/bug_bunny/observability.rb +24 -3
- data/lib/bug_bunny/resource.rb +31 -21
- data/lib/bug_bunny/routing/route.rb +6 -1
- data/lib/bug_bunny/routing/route_set.rb +30 -3
- data/lib/bug_bunny/session.rb +18 -11
- data/lib/bug_bunny/version.rb +1 -1
- data/lib/bug_bunny.rb +1 -0
- data/mejoras.md +33 -0
- data/plan_test.txt +63 -0
- data/spec/integration/client_spec.rb +117 -0
- data/spec/integration/consumer_middleware_spec.rb +86 -0
- data/spec/integration/controller_spec.rb +140 -0
- data/spec/integration/error_handling_spec.rb +57 -0
- data/spec/integration/infrastructure_spec.rb +52 -0
- data/spec/integration/resource_spec.rb +113 -0
- data/spec/spec_helper.rb +70 -0
- data/spec/support/bunny_mocks.rb +18 -0
- data/spec/support/integration_helper.rb +87 -0
- data/spec/unit/client_session_pool_spec.rb +159 -0
- data/spec/unit/configuration_spec.rb +164 -0
- data/spec/unit/consumer_middleware_spec.rb +129 -0
- data/spec/unit/consumer_spec.rb +90 -0
- data/spec/unit/controller_after_action_spec.rb +155 -0
- data/spec/unit/observability_spec.rb +167 -0
- data/spec/unit/resource_attributes_spec.rb +69 -0
- data/spec/unit/session_spec.rb +98 -0
- metadata +36 -3
- data/sig/bug_bunny.rbs +0 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b3b2260d4ec9ac9684adce0a4ea1c8ab7d86508a601eba51e630438ba5c36059
|
|
4
|
+
data.tar.gz: c8a41ed643ba5293babfe946476fce1c022137916dfe534224159574928e8b08
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6e8ef4dc6d1e9fc6aa4ac067da0aa48ccf5a291c91cb2c8474ed5811e34a57cbbeb44b9b6a813de8d5158a3351a96c15513c1e0715adf5a47e12ef70681935d4
|
|
7
|
+
data.tar.gz: a036a4e0a8c8b8fc29286f5490be2c506561951167c765461ef668b79c77dfcf0bb8e7f2020df28847820875697008129f24e4a97f34079cb54d5202a6e36d65
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Create a Pull Request for BugBunny via GitHub CLI. Usage: /pr
|
|
2
|
+
|
|
3
|
+
Creá un PR desde la rama actual hacia `main` usando `gh`.
|
|
4
|
+
|
|
5
|
+
## Pasos
|
|
6
|
+
|
|
7
|
+
1. Verificá que hay commits en la rama que no están en main: `git log main..HEAD --oneline`
|
|
8
|
+
2. Revisá todos los cambios del PR: `git diff main...HEAD`
|
|
9
|
+
3. Determiná el tipo de cambio (feature, bugfix, refactor, docs, chore)
|
|
10
|
+
4. Creá el PR con `gh pr create`:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
gh pr create --title "tipo: descripción breve" --body "$(cat <<'EOF'
|
|
14
|
+
## Summary
|
|
15
|
+
- Bullet points de los cambios principales
|
|
16
|
+
|
|
17
|
+
## Test plan
|
|
18
|
+
- [ ] Tests existentes pasan (`/test`)
|
|
19
|
+
- [ ] RuboCop sin offenses (`/rubocop`)
|
|
20
|
+
- [ ] YARD documentado en métodos nuevos/modificados
|
|
21
|
+
|
|
22
|
+
## Notes
|
|
23
|
+
Contexto adicional si es necesario.
|
|
24
|
+
|
|
25
|
+
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
|
26
|
+
EOF
|
|
27
|
+
)"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Convenciones de título
|
|
31
|
+
|
|
32
|
+
- `feat: descripción` — feature nueva
|
|
33
|
+
- `fix: descripción` — bugfix
|
|
34
|
+
- `chore: descripción` — mantenimiento, deps, config
|
|
35
|
+
- `docs: descripción` — solo documentación
|
|
36
|
+
- `refactor: descripción` — refactor sin cambio de comportamiento
|
|
37
|
+
|
|
38
|
+
## Importante
|
|
39
|
+
|
|
40
|
+
- El remote SSH está roto — si el PR requiere push previo, usar HTTPS temporalmente
|
|
41
|
+
- Mostrar la URL del PR creado al usuario
|
|
42
|
+
- No crear el PR sin confirmación del usuario
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Release BugBunny gem. Usage: /release [patch|minor|major]
|
|
2
|
+
|
|
3
|
+
Ejecutá el flujo completo de release para BugBunny. El argumento determina el tipo de bump:
|
|
4
|
+
- `patch` → bugfix (4.6.1 → 4.6.2)
|
|
5
|
+
- `minor` → feature nueva (4.6.1 → 4.7.0)
|
|
6
|
+
- `major` → breaking change (4.6.1 → 5.0.0)
|
|
7
|
+
|
|
8
|
+
## Pasos
|
|
9
|
+
|
|
10
|
+
1. **Leer versión actual** de `lib/bug_bunny/version.rb`
|
|
11
|
+
2. **Calcular nueva versión** según el tipo de bump
|
|
12
|
+
3. **Actualizar `lib/bug_bunny/version.rb`** con la nueva versión
|
|
13
|
+
4. **Agregar entrada al tope de `CHANGELOG.md`** con formato:
|
|
14
|
+
```
|
|
15
|
+
## [X.Y.Z] - YYYY-MM-DD
|
|
16
|
+
### ✨ New Features / 🐛 Bug Fixes / 💥 Breaking Changes
|
|
17
|
+
* Descripción de los cambios
|
|
18
|
+
```
|
|
19
|
+
5. **Mostrar el diff completo** al usuario y pedir confirmación antes de continuar
|
|
20
|
+
6. **Commit** con mensaje: `feat|fix|chore: descripción breve vX.Y.Z`
|
|
21
|
+
7. **Merge a main** desde `/Users/gabriel/src/gems/bug_bunny`: `git merge --ff-only <branch>`
|
|
22
|
+
8. **Push via HTTPS**:
|
|
23
|
+
```bash
|
|
24
|
+
git remote set-url origin https://github.com/gedera/bug_bunny.git
|
|
25
|
+
git push origin main
|
|
26
|
+
git remote set-url origin git@github.com:gedera/bug_bunny.git
|
|
27
|
+
```
|
|
28
|
+
9. **Tag y push**:
|
|
29
|
+
```bash
|
|
30
|
+
git tag vX.Y.Z
|
|
31
|
+
git remote set-url origin https://github.com/gedera/bug_bunny.git
|
|
32
|
+
git push origin vX.Y.Z
|
|
33
|
+
git remote set-url origin git@github.com:gedera/bug_bunny.git
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Importante
|
|
37
|
+
|
|
38
|
+
- Nunca commitear ni pushear sin confirmación explícita del usuario
|
|
39
|
+
- El worktree de main está en `/Users/gabriel/src/gems/bug_bunny`
|
|
40
|
+
- SSH está roto — siempre usar HTTPS para push y restaurar SSH después
|
|
41
|
+
- Sourcear chruby antes de cualquier comando Ruby: `source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8`
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Run RuboCop for BugBunny. Usage: /rubocop [--fix]
|
|
2
|
+
|
|
3
|
+
Ejecutá RuboCop con rubocop-rails-omakase sobre el código modificado.
|
|
4
|
+
|
|
5
|
+
## Comandos
|
|
6
|
+
|
|
7
|
+
Sin argumentos — solo reporte:
|
|
8
|
+
```bash
|
|
9
|
+
source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8 && bundle exec rubocop
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Con `--fix` — autocorrect seguro:
|
|
13
|
+
```bash
|
|
14
|
+
source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8 && bundle exec rubocop -a
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Reglas importantes
|
|
18
|
+
|
|
19
|
+
- **Solo corregir código nuevo o modificado en el PR actual** — nunca tocar código existente no relacionado
|
|
20
|
+
- Si hay offenses que requieren intervención manual (no autocorregibles), reportalos con línea y descripción
|
|
21
|
+
- rubocop-rails-omakase tiene opiniones fuertes sobre estilo — seguirlas sin discutir
|
|
22
|
+
- Si hay un offense legítimo que debe ignorarse, usar `# rubocop:disable Cop/Name` en la línea específica con un comentario explicativo
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Run RSpec tests for BugBunny. Usage: /test [path]
|
|
2
|
+
|
|
3
|
+
Ejecutá la suite de tests de BugBunny con Ruby 3.3.8.
|
|
4
|
+
|
|
5
|
+
## Comando base
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8 && bundle exec rspec
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Si se pasa un path como argumento, corré solo ese archivo o directorio:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8 && bundle exec rspec $ARGUMENTS
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Después de correr los tests
|
|
18
|
+
|
|
19
|
+
- Si hay failures: analizá el error, identificá la causa raíz, proponé el fix al usuario antes de tocar código
|
|
20
|
+
- Si hay warnings de deprecación: reportalos al usuario
|
|
21
|
+
- Si todos pasan: confirmá con el conteo de ejemplos y tiempo de ejecución
|
|
22
|
+
|
|
23
|
+
## Convenciones de RSpec en este proyecto
|
|
24
|
+
|
|
25
|
+
- Tests en `spec/`
|
|
26
|
+
- Sin mocks de RabbitMQ real — usar doubles de Bunny
|
|
27
|
+
- Describir comportamiento, no implementación
|
|
28
|
+
- Un `context` por escenario, `it` con descripción en español o inglés consistente con el archivo
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Validate and generate YARD documentation for BugBunny. Usage: /yard
|
|
2
|
+
|
|
3
|
+
Verificá y generá la documentación YARD de la gema.
|
|
4
|
+
|
|
5
|
+
## Comandos
|
|
6
|
+
|
|
7
|
+
Generar docs:
|
|
8
|
+
```bash
|
|
9
|
+
source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8 && bundle exec yard doc
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Ver métodos sin documentar:
|
|
13
|
+
```bash
|
|
14
|
+
source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8 && bundle exec yard stats --list-undoc
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Estándar YARD de este proyecto
|
|
18
|
+
|
|
19
|
+
Todo método público nuevo o modificado debe tener:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
# Descripción breve en una línea.
|
|
23
|
+
#
|
|
24
|
+
# Descripción extendida opcional si la firma no es autoexplicativa.
|
|
25
|
+
#
|
|
26
|
+
# @param nombre [Tipo] Descripción del parámetro
|
|
27
|
+
# @return [Tipo] Descripción del valor de retorno
|
|
28
|
+
# @raise [ClaseError] Condición bajo la cual se lanza
|
|
29
|
+
# @example
|
|
30
|
+
# resultado = mi_metodo(arg)
|
|
31
|
+
def mi_metodo(nombre)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Tipos comunes en este proyecto
|
|
35
|
+
|
|
36
|
+
- `[String]`, `[Integer]`, `[Boolean]`, `[Hash]`, `[Array]`, `[Symbol]`
|
|
37
|
+
- `[Bunny::Session]`, `[Bunny::Channel]`, `[Bunny::MessageProperties]`
|
|
38
|
+
- `[BugBunny::Session]`, `[BugBunny::Request]`, `[BugBunny::Configuration]`
|
|
39
|
+
- `[Proc, nil]` para callbacks opcionales
|
|
40
|
+
- `[void]` para métodos sin return value relevante
|
|
41
|
+
|
|
42
|
+
## Después de correr
|
|
43
|
+
|
|
44
|
+
- Reportá los métodos públicos sin documentar
|
|
45
|
+
- No documentar métodos privados (YARD los ignora por defecto con `private`)
|
|
46
|
+
- No documentar métodos triviales (`attr_reader`, `attr_accessor`) salvo que necesiten contexto
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [4.7.0] - 2026-04-01
|
|
4
|
+
|
|
5
|
+
### ✨ New Features
|
|
6
|
+
* **Routing — Namespace blocks:** Nuevo método `namespace` en el DSL de rutas para organizar controladores en módulos Ruby. Los namespaces son acumulativos y anidables: `namespace :api { namespace :v1 { resources :metrics } }` resuelve a `Api::V1::MetricsController`. El namespace de la ruta tiene precedencia sobre `config.controller_namespace`.
|
|
7
|
+
* **Controller — `after_action`:** Nuevo callback que se ejecuta después de la acción exitosa. No se invoca si un `before_action` haltó la cadena ni si la acción lanzó una excepción, siguiendo el comportamiento de Rails. Soporta `only:` y `except:`, y se hereda entre controladores.
|
|
8
|
+
* **Controller — `render` con `headers:`:** El método `render` acepta un keyword `headers:` para adjuntar headers por-respuesta sin mutar `response_headers`. `response_headers` se inicializa con `with_indifferent_access`.
|
|
9
|
+
* **Consumer — `shutdown`:** Nuevo método público que detiene el health check timer y cierra el canal AMQP de forma ordenada. Se invoca automáticamente vía `ensure` cuando `subscribe` termina por cualquier motivo (señal, error, fin de loop), garantizando limpieza completa de recursos.
|
|
10
|
+
* **Configuration — `validate!`:** `BugBunny.configure` invoca `validate!` al final del bloque. Verifica presencia de campos requeridos (`host`, `port`, `username`, `password`, `vhost`) y rangos válidos para timeouts y `channel_prefetch`. Lanza `BugBunny::ConfigurationError` con mensaje descriptivo en lugar de fallar silenciosamente al conectar.
|
|
11
|
+
|
|
12
|
+
### ⚡ Performance & Robustness
|
|
13
|
+
* **Client — Session & Producer pooling:** `BugBunny::Client` ya no crea ni destruye un `Session` (canal AMQP) y un `Producer` por request. Ambos se cachean como ivars sobre el objeto conexión del pool (`@_bug_bunny_session`, `@_bug_bunny_producer`) y se reutilizan en todos los requests del mismo slot. El cacheo del `Producer` es crítico: el Producer registra un `basic_consume` en el canal para escuchar replies RPC; recrearlo sobre un canal reutilizado intentaría un segundo `basic_consume` causando un error AMQP. Thread-safe sin mutex adicional: `ConnectionPool` garantiza que cada slot es usado por un único thread a la vez.
|
|
14
|
+
* **Session — Double-checked locking:** El método `channel` usa un patrón de double-checked locking con `@channel_mutex` para evitar que múltiples threads creen canales simultáneamente cuando el canal cae. `close` también está protegido por el mismo mutex.
|
|
15
|
+
* **ConsumerMiddleware::Stack — Thread safety:** `use`, `empty?` y `call` están protegidos por un `Mutex`. `call` toma un snapshot del array bajo mutex y ejecuta la cadena fuera del lock, evitando serializar el procesamiento de mensajes durante registros concurrentes.
|
|
16
|
+
|
|
17
|
+
### 🔍 Observability
|
|
18
|
+
* **`Observability::SENSITIVE_KEYS` expandido:** La lista de claves filtradas en logs crece de 5 a 11 entradas: se agregan `authorization`, `credential`, `private_key`, `csrf`, `session_id` y `passwd`. El matching pasa de comparación exacta por symbol a substring matching en lowercase con normalización de hyphens a underscores, cubriendo variantes como `X-Api-Key`, `user_password` o `accessToken`.
|
|
19
|
+
* **`Observability.sensitive_key?` público:** El método de detección de claves sensibles se expone como método de módulo reutilizable por componentes externos (middlewares, integraciones).
|
|
20
|
+
|
|
21
|
+
### 🐛 Bug Fixes
|
|
22
|
+
* **Resource — dirty tracking híbrido:** Se corrigen los overrides `changed?` y `changed` para combinar correctamente el tracking nativo de `ActiveModel::Dirty` (atributos tipados) con el tracking manual de atributos dinámicos (`@dynamic_changes`). Anteriormente, `changed?` solo reflejaba atributos tipados. `id=` ahora registra el cambio en `@dynamic_changes` cuando `id` no está declarado como `attribute`.
|
|
23
|
+
* **Resource — inicialización:** `initialize` pasa `attributes` directamente a `super` en lugar del patrón `super() + assign_attributes`, delegando correctamente a `ActiveModel::Model`.
|
|
24
|
+
|
|
25
|
+
## [4.6.1] - 2026-03-31
|
|
26
|
+
|
|
27
|
+
### 🐛 Bug Fixes
|
|
28
|
+
* **Observability:** `safe_log` ahora serializa valores `Hash` siempre como JSON compacto (`val.to_json`), sin pasar por `.inspect`. Anteriormente, si el JSON del Hash contenía espacios, se llamaba `.inspect` produciendo strings escapados como `"{\"exclusive\":false}"` en lugar del objeto JSON esperado `{"exclusive":false}`. Afectaba a los eventos `consumer.start` (campo `queue_opts`) y `consumer.bound` (campo `exchange_opts`).
|
|
29
|
+
|
|
3
30
|
## [4.6.0] - 2026-03-31
|
|
4
31
|
|
|
5
32
|
### ✨ New Features
|
|
@@ -73,33 +100,33 @@
|
|
|
73
100
|
|
|
74
101
|
## [4.2.0] - 2026-03-22
|
|
75
102
|
|
|
76
|
-
###
|
|
77
|
-
* **Structured Logs (Key-Value):** Se migraron todos los logs del framework a un formato
|
|
78
|
-
* **Lazy Evaluation (Debug Blocks):** Las llamadas a
|
|
103
|
+
### Observability & Structured Logging
|
|
104
|
+
* **Structured Logs (Key-Value):** Se migraron todos los logs del framework a un formato `key=value` estructurado, ideal para herramientas de monitoreo como Datadog o CloudWatch. Se eliminaron emojis y texto libre para mejorar el parseo automático.
|
|
105
|
+
* **Lazy Evaluation (Debug Blocks):** Las llamadas a `logger.debug` ahora utilizan bloques para evitar la interpolación de strings innecesaria en producción, optimizando el uso de CPU y memoria.
|
|
79
106
|
|
|
80
|
-
###
|
|
81
|
-
* **Exponential Backoff:** El
|
|
82
|
-
* **Max Reconnect Attempts:** Nueva
|
|
83
|
-
* **Performance Tuning:** Se desactivaron los
|
|
107
|
+
### Resilience & Connectivity
|
|
108
|
+
* **Exponential Backoff:** El `Consumer` ahora implementa un algoritmo de reintento exponencial para reconectarse a RabbitMQ, evitando picos de carga durante caídas del broker.
|
|
109
|
+
* **Max Reconnect Attempts:** Nueva configuración `max_reconnect_attempts` que permite que el worker falle definitivamente tras N intentos, facilitando el reinicio del Pod por parte de orquestadores como Kubernetes.
|
|
110
|
+
* **Performance Tuning:** Se desactivaron los `publisher_confirms` en el canal del `Consumer` al responder RPCs para reducir la latencia de respuesta (round-trips innecesarios).
|
|
84
111
|
|
|
85
112
|
## [4.1.2] - 2026-03-22
|
|
86
113
|
|
|
87
|
-
###
|
|
88
|
-
* **Controller:** Ahora lanza una
|
|
89
|
-
* **Resource:** Se
|
|
114
|
+
### Improvements
|
|
115
|
+
* **Controller:** Ahora lanza una excepción `BugBunny::BadRequest` (400) si el cuerpo de la petición contiene un JSON inválido, mejorando la depuración en el cliente.
|
|
116
|
+
* **Resource:** Se añadió una protección a `.with` (`ScopeProxy`) para asegurar que el contexto sea de un solo uso, evitando efectos secundarios en llamadas encadenadas.
|
|
90
117
|
|
|
91
118
|
## [4.1.1] - 2026-03-22
|
|
92
119
|
|
|
93
120
|
### 🐛 Bug Fixes
|
|
94
|
-
* **Consumer:** Previene memory leak al detener el `TimerTask` de health check previo antes de realizar una
|
|
95
|
-
* **Controller:** Corrige la
|
|
121
|
+
* **Consumer:** Previene memory leak al detener el `TimerTask` de health check previo antes de realizar una reconexión.
|
|
122
|
+
* **Controller:** Corrige la mutación accidental de `log_tags` globales al usar una lógica de herencia no destructiva en `compute_tags`.
|
|
96
123
|
|
|
97
124
|
## [4.1.0] - 2026-03-22
|
|
98
125
|
|
|
99
126
|
### 🚀 New Features & Improvements
|
|
100
|
-
* **Faraday-style Client API:** Se introdujo el
|
|
101
|
-
* **Flexible Delivery Modes:**
|
|
102
|
-
* **Smart Request Defaults:** Los
|
|
127
|
+
* **Faraday-style Client API:** Se introdujo el método `Client#send` como punto de entrada genérico, permitiendo una sintaxis más familiar y flexible.
|
|
128
|
+
* **Flexible Delivery Modes:** Introducción del atributo `delivery_mode` (:rpc o :publish). Ahora es posible configurar la estrategia de envío a nivel de cliente o por cada petición individual.
|
|
129
|
+
* **Smart Request Defaults:** Los métodos `request` y `publish` ahora delegan internamente en `send`, manteniendo la compatibilidad pero beneficiándose de la nueva arquitectura de peticiones.
|
|
103
130
|
|
|
104
131
|
## [4.0.1] - 2026-03-13
|
|
105
132
|
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# BugBunny — Project Intelligence
|
|
2
|
+
|
|
3
|
+
## ¿Qué es BugBunny?
|
|
4
|
+
|
|
5
|
+
BugBunny es una gema Ruby que implementa una capa de enrutamiento RESTful sobre AMQP (RabbitMQ). Permite que microservicios se comuniquen via RabbitMQ usando patrones familiares de HTTP: verbos (GET, POST, PUT, DELETE), controladores, rutas declarativas, RPC síncrono y fire-and-forget.
|
|
6
|
+
|
|
7
|
+
**Problema que resuelve:** Eliminar el acoplamiento directo entre microservicios via HTTP, usando RabbitMQ como bus de mensajes con la misma ergonomía de un framework web.
|
|
8
|
+
|
|
9
|
+
## Arquitectura
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Publisher (Client/Resource)
|
|
13
|
+
└─ Producer → Session → Bunny → RabbitMQ Exchange
|
|
14
|
+
│
|
|
15
|
+
RabbitMQ Queue
|
|
16
|
+
│
|
|
17
|
+
Consumer (subscribe loop)
|
|
18
|
+
└─ ConsumerMiddleware::Stack
|
|
19
|
+
└─ process_message
|
|
20
|
+
└─ Router → Controller → Action
|
|
21
|
+
└─ reply (RPC)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Componentes clave
|
|
25
|
+
|
|
26
|
+
| Clase | Responsabilidad |
|
|
27
|
+
|---|---|
|
|
28
|
+
| `BugBunny::Session` | Wrapper de canal Bunny. Declara exchanges y queues. |
|
|
29
|
+
| `BugBunny::Consumer` | Subscribe loop. Rutea mensajes a controladores via `BugBunny.routes`. |
|
|
30
|
+
| `BugBunny::ConsumerMiddleware::Stack` | Pipeline de middlewares antes de `process_message`. |
|
|
31
|
+
| `BugBunny::Producer` | Publica mensajes. Implementa RPC con `Concurrent::IVar`. |
|
|
32
|
+
| `BugBunny::Client` | API de alto nivel para el publicador. Pool de conexiones. |
|
|
33
|
+
| `BugBunny::Controller` | Base class tipo Rails. `around_action`, `before_action`, `render`. |
|
|
34
|
+
| `BugBunny::Resource` | ActiveRecord-like sobre AMQP. `find`, `where`, `create`, etc. |
|
|
35
|
+
| `BugBunny::Request` | Value object del mensaje saliente (path, method, params, headers). |
|
|
36
|
+
| `BugBunny::Observability` | Mixin de logging estructurado. `safe_log`, `exception_metadata`. |
|
|
37
|
+
| `BugBunny::Configuration` | Configuración global. Logger, timeouts, middleware hooks. |
|
|
38
|
+
|
|
39
|
+
### Flujo RPC completo
|
|
40
|
+
|
|
41
|
+
1. `Resource.find(id)` → `Client#request` → `Producer#rpc`
|
|
42
|
+
2. Producer publica en exchange con `reply_to: 'amq.rabbitmq.reply-to'`
|
|
43
|
+
3. `Concurrent::IVar` bloquea el thread principal (`future.value(timeout)`)
|
|
44
|
+
4. Consumer recibe → middleware stack → controller → `reply(response)`
|
|
45
|
+
5. Reply listener thread setea `future.set({ body:, headers: })`
|
|
46
|
+
6. Thread principal: `on_rpc_reply&.call(headers)` → `parse_response(body)`
|
|
47
|
+
|
|
48
|
+
## Hooks de extensión
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
# Middleware antes de process_message (ej: tracing, auth)
|
|
52
|
+
BugBunny.consumer_middlewares.use MyMiddleware
|
|
53
|
+
|
|
54
|
+
# Headers a inyectar en el reply RPC (ej: trace context actualizado)
|
|
55
|
+
config.rpc_reply_headers = -> { { 'X-Amzn-Trace-Id' => Tracer.header } }
|
|
56
|
+
|
|
57
|
+
# Callback en el thread principal al recibir el reply (ej: hidratar tracer)
|
|
58
|
+
config.on_rpc_reply = ->(headers) { Tracer.hydrate(headers['X-Amzn-Trace-Id']) }
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Dominio y Expertise
|
|
64
|
+
|
|
65
|
+
Al trabajar en esta gema aplicá expertise en:
|
|
66
|
+
|
|
67
|
+
- **Ruby idiomático**: módulos, mixins, metaprogramación, `class_attribute`, `Concurrent::*`
|
|
68
|
+
- **RabbitMQ / AMQP**: exchanges (direct/topic/fanout), queues, bindings, `reply_to`, `correlation_id`, `properties.headers`, publisher confirms, manual ack
|
|
69
|
+
- **Bunny**: la gema Ruby que wrappea AMQP. `channel`, `basic_consume`, `basic_publish`, `IVar`
|
|
70
|
+
- **Rails patterns**: `ActiveModel`, `ActiveSupport`, `class_attribute`, `concerns`, `constantize`
|
|
71
|
+
- **Rack**: `Rack::Utils.parse_nested_query`, `build_nested_query`
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Observability — Estándar de Logging
|
|
76
|
+
|
|
77
|
+
Esta gema implementa su propio patrón de observability via `BugBunny::Observability`.
|
|
78
|
+
|
|
79
|
+
### Reglas fundamentales
|
|
80
|
+
|
|
81
|
+
- **Formato**: `component=x event=clase.evento [key=value ...]` — todo en una línea
|
|
82
|
+
- **Nunca** llamar al logger directamente. Siempre usar `safe_log`
|
|
83
|
+
- **Nunca** `Kernel#warn`, `$stderr`, `puts`
|
|
84
|
+
- **Niveles**: `ERROR`=excepción, `WARN`=inesperado+continuó, `INFO`=normal, `DEBUG`=detalle
|
|
85
|
+
- `DEBUG` siempre en bloque: `logger.debug { "k=#{v}" }` — `safe_log` lo maneja internamente
|
|
86
|
+
- Duraciones: `Process.clock_gettime(Process::CLOCK_MONOTONIC)`, nunca `Time.now`
|
|
87
|
+
- Logger failures **nunca** interrumpen el flujo — `safe_log` tiene `rescue StandardError`
|
|
88
|
+
|
|
89
|
+
### Uso en clases nuevas
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
class BugBunny::MiClase
|
|
93
|
+
include BugBunny::Observability
|
|
94
|
+
|
|
95
|
+
def initialize
|
|
96
|
+
@logger = BugBunny.configuration.logger
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def mi_metodo
|
|
100
|
+
start = monotonic_now
|
|
101
|
+
# ...
|
|
102
|
+
safe_log(:info, "mi_clase.mi_evento", campo: valor, duration_s: duration_s(start))
|
|
103
|
+
rescue StandardError => e
|
|
104
|
+
safe_log(:error, "mi_clase.error", **exception_metadata(e))
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Naming de eventos
|
|
110
|
+
|
|
111
|
+
Formato estricto: `"clase.evento"` (string, nunca symbol)
|
|
112
|
+
|
|
113
|
+
| Evento | Nivel | Cuándo |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| `consumer.start` | INFO | Consumer inicia subscribe |
|
|
116
|
+
| `consumer.bound` | INFO | Queue bindeada al exchange |
|
|
117
|
+
| `consumer.message_received` | INFO | Mensaje recibido, antes del routing |
|
|
118
|
+
| `consumer.route_matched` | DEBUG | Ruta encontrada |
|
|
119
|
+
| `consumer.message_processed` | INFO | Procesamiento exitoso con duración |
|
|
120
|
+
| `consumer.execution_error` | ERROR | Excepción en el procesamiento |
|
|
121
|
+
| `producer.publish` | INFO | Mensaje publicado |
|
|
122
|
+
| `producer.rpc_waiting` | DEBUG | Bloqueando esperando respuesta |
|
|
123
|
+
| `producer.rpc_response_received` | DEBUG | Reply recibido (thread principal) |
|
|
124
|
+
|
|
125
|
+
### Campos estándar
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
safe_log(:error, "clase.error", **exception_metadata(e))
|
|
129
|
+
# => error_class: "RuntimeError", error_message: "..."
|
|
130
|
+
|
|
131
|
+
safe_log(:info, "clase.evento", duration_s: duration_s(start_time))
|
|
132
|
+
# => duration_s: 0.001234
|
|
133
|
+
|
|
134
|
+
# Valores sensibles se filtran automáticamente:
|
|
135
|
+
# password, token, secret, api_key, auth → [FILTERED]
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Standards de Código
|
|
141
|
+
|
|
142
|
+
### RuboCop
|
|
143
|
+
|
|
144
|
+
Esta gema usa **rubocop-rails-omakase**. Todo código nuevo o modificado debe cumplir.
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8
|
|
148
|
+
bundle exec rubocop
|
|
149
|
+
bundle exec rubocop -a # autocorrect
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**No corregir código existente no tocado.** Solo el código nuevo o modificado en el PR.
|
|
153
|
+
|
|
154
|
+
### YARD
|
|
155
|
+
|
|
156
|
+
Todo método público nuevo o modificado lleva documentación YARD:
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
# Descripción breve.
|
|
160
|
+
#
|
|
161
|
+
# Descripción extendida si es necesario.
|
|
162
|
+
#
|
|
163
|
+
# @param name [Type] Descripción
|
|
164
|
+
# @return [Type] Descripción
|
|
165
|
+
# @raise [ErrorClass] Cuándo se lanza
|
|
166
|
+
# @example
|
|
167
|
+
# resultado = mi_metodo(arg)
|
|
168
|
+
def mi_metodo(name)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
bundle exec yard doc
|
|
173
|
+
bundle exec yard stats --list-undoc
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### RSpec
|
|
177
|
+
|
|
178
|
+
Tests en `spec/`. Sin mocks de dependencias externas reales (RabbitMQ se mockea con doubles de Bunny).
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8
|
|
182
|
+
bundle exec rspec
|
|
183
|
+
bundle exec rspec spec/bug_bunny/consumer_spec.rb # archivo específico
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Entorno de Desarrollo
|
|
189
|
+
|
|
190
|
+
### Ruby
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Nunca usar `bundle exec ruby` con el Ruby del sistema (2.6). Siempre sourcear chruby primero.
|
|
197
|
+
|
|
198
|
+
### Worktrees
|
|
199
|
+
|
|
200
|
+
- **Main**: `/Users/gabriel/src/gems/bug_bunny` (rama `main`)
|
|
201
|
+
- **Work**: `/Users/gabriel/src/gems/worktrees/current-5n3` (ramas de feature)
|
|
202
|
+
- `main` está checkeado en otro worktree — no se puede hacer `git checkout main` desde el worktree de trabajo
|
|
203
|
+
|
|
204
|
+
### Push a remoto
|
|
205
|
+
|
|
206
|
+
SSH está roto en este entorno. Para push siempre:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
git remote set-url origin https://github.com/gedera/bug_bunny.git
|
|
210
|
+
git push origin main
|
|
211
|
+
git remote set-url origin git@github.com:gedera/bug_bunny.git # restaurar
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Release Workflow
|
|
217
|
+
|
|
218
|
+
Usá el comando `/release` para el flujo completo. Manualmente:
|
|
219
|
+
|
|
220
|
+
1. Determinar tipo: `patch`=bugfix, `minor`=feature nueva, `major`=breaking change
|
|
221
|
+
2. Actualizar `lib/bug_bunny/version.rb`
|
|
222
|
+
3. Agregar entrada al tope de `CHANGELOG.md`
|
|
223
|
+
4. Commit con mensaje convencional
|
|
224
|
+
5. Desde `/Users/gabriel/src/gems/bug_bunny`: `git merge --ff-only <branch>`
|
|
225
|
+
6. Push via HTTPS + restaurar SSH
|
|
226
|
+
7. `git tag vX.Y.Z && git push origin vX.Y.Z`
|
|
227
|
+
|
|
228
|
+
**Nunca commitear ni pushear sin permiso explícito del usuario.**
|