bug_bunny 4.10.0 → 4.10.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 +4 -4
- data/CHANGELOG.md +10 -0
- data/lib/bug_bunny/consumer.rb +6 -4
- data/lib/bug_bunny/remote_error.rb +1 -1
- data/lib/bug_bunny/version.rb +1 -1
- data/skill/SKILL.md +4 -0
- data/skill/references/consumer.md +8 -7
- data/skill/references/errores.md +17 -1
- data/spec/unit/remote_error_spec.rb +68 -0
- data/spec/unit/route_spec.rb +180 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '08e0d33ac09d046d3de52d3d7f75b7b87df8d6e96f495ab37c7433fe9f4add33'
|
|
4
|
+
data.tar.gz: '09373953a7dc292c97363e3514f9e3db1b9b041527a9e26e09e52089abfda0b9'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 34799b71420bb76ca71439e80d5b27a5d2ed38f424b16ba914b85566c5277279d3f4e1743caa4f2e8d932d125ceb019479add38df49573ab42880ef103cf1c34
|
|
7
|
+
data.tar.gz: 7b8cceae4b29ff12e9e42fc51ee3b68ec5a1ae92122a91f35c6bb132f09edf1b5171785ef6494d3580e86da715218f1602f2ef8071a2e9b77b0e0123c28795ff
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [4.10.2] - 2026-04-08
|
|
4
|
+
|
|
5
|
+
### Correcciones
|
|
6
|
+
- **RemoteError#to_s recursión infinita:** Corregir `SystemStackError` al invocar `to_s` en `BugBunny::RemoteError` en IRB. Antes, `to_s` llamaba a `message`, que en Ruby delega a `to_s`, generando recursión infinita. Ahora usa `super` para invocar `Exception#to_s` directamente. — @Gabriel
|
|
7
|
+
|
|
8
|
+
## [4.10.1] - 2026-04-08
|
|
9
|
+
|
|
10
|
+
### Correcciones
|
|
11
|
+
- Corregir route matching 404: el path del cliente ahora se normaliza antes de pasarlo a `RouteSet#recognize`. Antes, `URI.parse("http://dummy/#{path}")` prependead un `/` extra al path, causando que rutas existentes no hicieran match. Ahora se usa `path.gsub(%r{^/|/$}, '')` antes del recognize. — @Gabriel
|
|
12
|
+
|
|
3
13
|
## [4.9.1] - 2026-04-06
|
|
4
14
|
|
|
5
15
|
### Correcciones
|
data/lib/bug_bunny/consumer.rb
CHANGED
|
@@ -181,18 +181,20 @@ module BugBunny
|
|
|
181
181
|
# ===================================================================
|
|
182
182
|
# 3. Ruteo Declarativo
|
|
183
183
|
# ===================================================================
|
|
184
|
-
|
|
184
|
+
normalized_path = path.gsub(%r{^/|/$}, '')
|
|
185
|
+
|
|
186
|
+
uri = URI.parse("http://dummy/#{normalized_path}")
|
|
185
187
|
|
|
186
188
|
# Extraemos query params (ej. /nodes?status=active)
|
|
187
189
|
query_params = uri.query ? Rack::Utils.parse_nested_query(uri.query) : {}
|
|
188
190
|
query_params = query_params.with_indifferent_access if defined?(ActiveSupport::HashWithIndifferentAccess)
|
|
189
191
|
|
|
190
192
|
# Le preguntamos al motor de rutas global quién debe manejar esto
|
|
191
|
-
route_info = BugBunny.routes.recognize(http_method,
|
|
193
|
+
route_info = BugBunny.routes.recognize(http_method, normalized_path)
|
|
192
194
|
|
|
193
195
|
if route_info.nil?
|
|
194
|
-
safe_log(:warn, 'consumer.route_not_found', method: http_method, path:
|
|
195
|
-
handle_fatal_error(properties, 404, 'Not Found', "No route matches [#{http_method}] \"
|
|
196
|
+
safe_log(:warn, 'consumer.route_not_found', method: http_method, path: normalized_path)
|
|
197
|
+
handle_fatal_error(properties, 404, 'Not Found', "No route matches [#{http_method}] \"#{normalized_path}\"")
|
|
196
198
|
session.channel.reject(delivery_info.delivery_tag, false)
|
|
197
199
|
return
|
|
198
200
|
end
|
data/lib/bug_bunny/version.rb
CHANGED
data/skill/SKILL.md
CHANGED
|
@@ -230,6 +230,10 @@ No registrar consumer middlewares durante la ejecución de `call()`. El stack to
|
|
|
230
230
|
**Causa:** El mensaje intenta ejecutar un controlador que no hereda de `BugBunny::Controller`.
|
|
231
231
|
**Resolución:** Verificar la jerarquía de controladores y que `config.controller_namespace` coincida.
|
|
232
232
|
|
|
233
|
+
### BugBunny::RouteNotFoundError (404)
|
|
234
|
+
**Causa:** El path del mensaje no coincide con ninguna ruta registrada. El path debe estar normalizado (sin slashes iniciales/trailing).
|
|
235
|
+
**Resolución:** Verificar que el cliente envíe el path sin leading/trailing slashes (ej: `users/42`, no `/users/42/`).
|
|
236
|
+
|
|
233
237
|
### BugBunny::UnprocessableEntity (422)
|
|
234
238
|
**Causa:** Fallo de validación en el servicio remoto.
|
|
235
239
|
**Resolución:** `resource.save` devuelve `false`. Acceder a `resource.errors` o `rescue` con `e.error_messages`.
|
|
@@ -21,13 +21,14 @@ consumer = BugBunny::Consumer.subscribe(
|
|
|
21
21
|
2. Extrae campos **OTel messaging** del mensaje para logs estructurados (sin mutar headers).
|
|
22
22
|
3. Valida que el mensaje tenga header `type` (path).
|
|
23
23
|
4. Parsea el método HTTP de headers (`x-http-method` o `method`).
|
|
24
|
-
5.
|
|
25
|
-
6.
|
|
26
|
-
7.
|
|
27
|
-
8.
|
|
28
|
-
9.
|
|
29
|
-
10.
|
|
30
|
-
11.
|
|
24
|
+
5. **Normaliza el path**: remueve slashes iniciales/trailing (`path.gsub(%r{^/|/$}, '')`).
|
|
25
|
+
6. Emite log `consumer.message_received` con campos OTel (`messaging_operation: 'process'`).
|
|
26
|
+
7. Reconoce la ruta con `BugBunny.routes.recognize(method, normalized_path)`.
|
|
27
|
+
8. Resuelve el controlador validando herencia de `BugBunny::Controller`.
|
|
28
|
+
9. Ejecuta consumer middlewares → controller callbacks → acción.
|
|
29
|
+
10. Responde via `reply_to` si está presente (RPC), inyectando campos OTel (`messaging_operation: 'publish'`).
|
|
30
|
+
11. Emite log `consumer.message_processed` con campos OTel y duraciones.
|
|
31
|
+
12. Hace `ack` del mensaje. En caso de error, `reject`.
|
|
31
32
|
|
|
32
33
|
## Observability: OTel Fields
|
|
33
34
|
|
data/skill/references/errores.md
CHANGED
|
@@ -16,7 +16,8 @@ StandardError
|
|
|
16
16
|
│ ├── BugBunny::Conflict (409)
|
|
17
17
|
│ └── BugBunny::UnprocessableEntity (422)
|
|
18
18
|
└── BugBunny::ServerError (5xx)
|
|
19
|
-
|
|
19
|
+
├── BugBunny::InternalServerError (500+)
|
|
20
|
+
└── BugBunny::RemoteError (500)
|
|
20
21
|
```
|
|
21
22
|
|
|
22
23
|
## Errores de Infraestructura
|
|
@@ -83,6 +84,21 @@ end
|
|
|
83
84
|
**Causa:** Cualquier error de servidor no mapeado a InternalServerError.
|
|
84
85
|
**Resolución:** Similar a InternalServerError.
|
|
85
86
|
|
|
87
|
+
### BugBunny::RemoteError (500)
|
|
88
|
+
**Causa:** Excepción no manejada en el controller remoto. El error se serializa y propaga al cliente RPC.
|
|
89
|
+
**Acceso a detalles:**
|
|
90
|
+
```ruby
|
|
91
|
+
begin
|
|
92
|
+
client.request('users/42')
|
|
93
|
+
rescue BugBunny::RemoteError => e
|
|
94
|
+
e.original_class # String: clase original (ej: "TypeError")
|
|
95
|
+
e.original_message # String: mensaje original
|
|
96
|
+
e.original_backtrace # Array<String>: backtrace original
|
|
97
|
+
end
|
|
98
|
+
```
|
|
99
|
+
**Serialización:** El controller captura excepciones con `rescue_from` → `handle_exception` → serializa con clase, mensaje y primeras 25 líneas del backtrace.
|
|
100
|
+
**Propagación:** El middleware `RaiseError` del cliente reconstituye `RemoteError` y la lanza localmente.
|
|
101
|
+
|
|
86
102
|
## Formato de Mensajes de Error
|
|
87
103
|
|
|
88
104
|
El middleware `RaiseError` construye el mensaje así:
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe BugBunny::RemoteError do
|
|
6
|
+
subject(:error) do
|
|
7
|
+
described_class.new('TypeError', 'nil can\'t be coerced into Integer', [
|
|
8
|
+
"/app/controllers/services_controller.rb:5:in 'Integer#*'",
|
|
9
|
+
"/app/controllers/services_controller.rb:5:in 'ServicesController#index'"
|
|
10
|
+
])
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe '#to_s' do
|
|
14
|
+
it 'does not cause infinite recursion (message -> to_s -> message)' do
|
|
15
|
+
expect(error.to_s).to eq("BugBunny::RemoteError(TypeError): nil can't be coerced into Integer")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe '#message' do
|
|
20
|
+
it 'returns the formatted string without stack overflow' do
|
|
21
|
+
expect(error.message).to eq("BugBunny::RemoteError(TypeError): nil can't be coerced into Integer")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe '#inspect' do
|
|
26
|
+
it 'is renderable by IRB without raising' do
|
|
27
|
+
expect { error.inspect }.not_to raise_error
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe '.serialize' do
|
|
32
|
+
it 'serializes an exception with class, message and backtrace' do
|
|
33
|
+
exception = TypeError.new('test error')
|
|
34
|
+
exception.set_backtrace(%w[line1 line2])
|
|
35
|
+
|
|
36
|
+
result = described_class.serialize(exception)
|
|
37
|
+
|
|
38
|
+
expect(result).to eq(class: 'TypeError', message: 'test error', backtrace: %w[line1 line2])
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'truncates backtrace to max_lines' do
|
|
42
|
+
exception = TypeError.new('test')
|
|
43
|
+
exception.set_backtrace(Array.new(30) { |i| "line#{i}" })
|
|
44
|
+
|
|
45
|
+
result = described_class.serialize(exception, max_lines: 5)
|
|
46
|
+
|
|
47
|
+
expect(result[:backtrace].size).to eq(5)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
describe 'attributes' do
|
|
52
|
+
it 'exposes the original exception class' do
|
|
53
|
+
expect(error.original_class).to eq('TypeError')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'exposes the original message' do
|
|
57
|
+
expect(error.original_message).to eq("nil can't be coerced into Integer")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'exposes the original backtrace' do
|
|
61
|
+
expect(error.original_backtrace.size).to eq(2)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it 'sets the Ruby backtrace to the remote backtrace' do
|
|
65
|
+
expect(error.backtrace).to eq(error.original_backtrace)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe BugBunny::Routing::Route do
|
|
6
|
+
describe '#match? and #extract_params — path normalization' do
|
|
7
|
+
let(:route) { described_class.new('GET', 'services/otaigccd59q0k7kxb1h193go2/restart', to: 'services#restart') }
|
|
8
|
+
|
|
9
|
+
it 'matches path without leading slash' do
|
|
10
|
+
expect(route.match?('GET', 'services/otaigccd59q0k7kxb1h193go2/restart')).to be true
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'matches path with leading slash' do
|
|
14
|
+
expect(route.match?('GET', '/services/otaigccd59q0k7kxb1h193go2/restart')).to be true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'matches path with trailing slash' do
|
|
18
|
+
expect(route.match?('GET', 'services/otaigccd59q0k7kxb1h193go2/restart/')).to be true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'matches path with leading and trailing slash' do
|
|
22
|
+
expect(route.match?('GET', '/services/otaigccd59q0k7kxb1h193go2/restart/')).to be true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'extracts params from path without slash' do
|
|
26
|
+
params = route.extract_params('services/otaigccd59q0k7kxb1h193go2/restart')
|
|
27
|
+
expect(params).to eq({})
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'extracts params from path with slash' do
|
|
31
|
+
params = route.extract_params('/services/otaigccd59q0k7kxb1h193go2/restart')
|
|
32
|
+
expect(params).to eq({})
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
context 'route with dynamic segment' do
|
|
36
|
+
let(:route_with_id) { described_class.new('GET', 'nodes/:id', to: 'nodes#show') }
|
|
37
|
+
|
|
38
|
+
it 'extracts params from path without slash' do
|
|
39
|
+
params = route_with_id.extract_params('nodes/123')
|
|
40
|
+
expect(params).to eq({ 'id' => '123' })
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'extracts params from path with slash' do
|
|
44
|
+
params = route_with_id.extract_params('/nodes/123')
|
|
45
|
+
expect(params).to eq({ 'id' => '123' })
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
context 'route defined with leading slash' do
|
|
50
|
+
let(:route_with_leading_slash) do
|
|
51
|
+
described_class.new('GET', '/services/otaigccd59q0k7kxb1h193go2/restart', to: 'services#restart')
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'matches path without slash' do
|
|
55
|
+
expect(route_with_leading_slash.match?('GET', 'services/otaigccd59q0k7kxb1h193go2/restart')).to be true
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'matches path with slash' do
|
|
59
|
+
expect(route_with_leading_slash.match?('GET', '/services/otaigccd59q0k7kxb1h193go2/restart')).to be true
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe 'RouteSet#recognize path normalization' do
|
|
65
|
+
let(:route_set) { BugBunny::Routing::RouteSet.new }
|
|
66
|
+
|
|
67
|
+
before do
|
|
68
|
+
route_set.draw do
|
|
69
|
+
get 'services/otaigccd59q0k7kxb1h193go2/restart', to: 'services#restart'
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'recognizes path without slash' do
|
|
74
|
+
result = route_set.recognize('GET', 'services/otaigccd59q0k7kxb1h193go2/restart')
|
|
75
|
+
expect(result).not_to be_nil
|
|
76
|
+
expect(result[:controller]).to eq('services')
|
|
77
|
+
expect(result[:action]).to eq('restart')
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it 'recognizes path with leading slash' do
|
|
81
|
+
result = route_set.recognize('GET', '/services/otaigccd59q0k7kxb1h193go2/restart')
|
|
82
|
+
expect(result).not_to be_nil
|
|
83
|
+
expect(result[:controller]).to eq('services')
|
|
84
|
+
expect(result[:action]).to eq('restart')
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it 'returns nil for non-matching path' do
|
|
88
|
+
result = route_set.recognize('GET', 'services/nonexistent')
|
|
89
|
+
expect(result).to be_nil
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe 'Consumer path handling — URI parsing edge case' do
|
|
94
|
+
let(:channel) { BunnyMocks::FakeChannel.new(true) }
|
|
95
|
+
let(:connection) { BunnyMocks::FakeConnection.new(true, channel) }
|
|
96
|
+
|
|
97
|
+
let(:mock_channel) do
|
|
98
|
+
ch = double('channel')
|
|
99
|
+
allow(ch).to receive(:reject)
|
|
100
|
+
allow(ch).to receive(:ack)
|
|
101
|
+
allow(ch).to receive(:open?).and_return(true)
|
|
102
|
+
default_ex = double('default_exchange')
|
|
103
|
+
allow(default_ex).to receive(:publish)
|
|
104
|
+
allow(ch).to receive(:default_exchange).and_return(default_ex)
|
|
105
|
+
ch
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
let(:mock_session) do
|
|
109
|
+
s = instance_double(BugBunny::Session)
|
|
110
|
+
allow(s).to receive(:exchange).and_return(double('exchange'))
|
|
111
|
+
allow(s).to receive(:queue).and_return(double('queue'))
|
|
112
|
+
allow(s).to receive(:close)
|
|
113
|
+
allow(s).to receive(:channel).and_return(mock_channel)
|
|
114
|
+
s
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
let(:test_consumer) do
|
|
118
|
+
c = BugBunny::Consumer.new(connection)
|
|
119
|
+
c.instance_variable_set(:@session, mock_session)
|
|
120
|
+
c
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
let(:delivery_info) do
|
|
124
|
+
double('delivery_info',
|
|
125
|
+
exchange: 'events_x',
|
|
126
|
+
routing_key: 'users.created',
|
|
127
|
+
delivery_tag: 'delivery-tag-1',
|
|
128
|
+
redelivered?: false)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
let(:properties) do
|
|
132
|
+
double('properties',
|
|
133
|
+
type: 'services/otaigccd59q0k7kxb1h193go2/restart',
|
|
134
|
+
headers: { 'x-http-method' => 'GET' },
|
|
135
|
+
correlation_id: 'corr-abc-123',
|
|
136
|
+
reply_to: nil,
|
|
137
|
+
content_type: 'application/json')
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
let(:logged_events) { [] }
|
|
141
|
+
|
|
142
|
+
before do
|
|
143
|
+
allow(test_consumer).to receive(:safe_log) do |level, event, **kwargs|
|
|
144
|
+
logged_events << { level: level, event: event, kwargs: kwargs }
|
|
145
|
+
end
|
|
146
|
+
allow(test_consumer).to receive(:handle_fatal_error)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it 'logs route_not_found with normalized path (without extra slash)' do
|
|
150
|
+
allow(BugBunny.routes).to receive(:recognize).and_return(nil)
|
|
151
|
+
|
|
152
|
+
test_consumer.send(:process_message, delivery_info, properties, '{}')
|
|
153
|
+
|
|
154
|
+
route_not_found_event = logged_events.find { |e| e[:event] == 'consumer.route_not_found' }
|
|
155
|
+
expect(route_not_found_event).not_to be_nil
|
|
156
|
+
expect(route_not_found_event[:kwargs][:path]).to eq('services/otaigccd59q0k7kxb1h193go2/restart')
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
context 'when properties.type has leading slash' do
|
|
160
|
+
let(:properties) do
|
|
161
|
+
double('properties',
|
|
162
|
+
type: '/services/otaigccd59q0k7kxb1h193go2/restart',
|
|
163
|
+
headers: { 'x-http-method' => 'GET' },
|
|
164
|
+
correlation_id: 'corr-abc-123',
|
|
165
|
+
reply_to: nil,
|
|
166
|
+
content_type: 'application/json')
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
it 'logs route_not_found with normalized path (stripped leading slash)' do
|
|
170
|
+
allow(BugBunny.routes).to receive(:recognize).and_return(nil)
|
|
171
|
+
|
|
172
|
+
test_consumer.send(:process_message, delivery_info, properties, '{}')
|
|
173
|
+
|
|
174
|
+
route_not_found_event = logged_events.find { |e| e[:event] == 'consumer.route_not_found' }
|
|
175
|
+
expect(route_not_found_event).not_to be_nil
|
|
176
|
+
expect(route_not_found_event[:kwargs][:path]).to eq('services/otaigccd59q0k7kxb1h193go2/restart')
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
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.10.
|
|
4
|
+
version: 4.10.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- gabix
|
|
@@ -288,8 +288,10 @@ 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/remote_error_spec.rb
|
|
291
292
|
- spec/unit/request_spec.rb
|
|
292
293
|
- spec/unit/resource_attributes_spec.rb
|
|
294
|
+
- spec/unit/route_spec.rb
|
|
293
295
|
- spec/unit/session_spec.rb
|
|
294
296
|
- test/integration/infrastructure_test.rb
|
|
295
297
|
- test/integration/manual_client_test.rb
|
|
@@ -301,7 +303,7 @@ metadata:
|
|
|
301
303
|
homepage_uri: https://github.com/gedera/bug_bunny
|
|
302
304
|
source_code_uri: https://github.com/gedera/bug_bunny
|
|
303
305
|
changelog_uri: https://github.com/gedera/bug_bunny/blob/main/CHANGELOG.md
|
|
304
|
-
documentation_uri: https://github.com/gedera/bug_bunny/blob/v4.10.
|
|
306
|
+
documentation_uri: https://github.com/gedera/bug_bunny/blob/v4.10.2/skill
|
|
305
307
|
post_install_message:
|
|
306
308
|
rdoc_options: []
|
|
307
309
|
require_paths:
|