bug_bunny 1.0.1 → 2.0.1

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: 04b430be6237b82713e183846665873b602b15ebece1dfc307031f92a665eb0f
4
- data.tar.gz: 922b431bf9bf427cd7dab5ece6abc657b08dd759b72c52e7468f4c5d6e58c634
3
+ metadata.gz: 074005f6739a5a8486230e47edbce60b9719f5e15f35d9ab3ed95d91eaf28a47
4
+ data.tar.gz: 39f1d8afb65be6ff2b86e0461c16c93e0ff6ae6cbe629668ba8e6601fe898e80
5
5
  SHA512:
6
- metadata.gz: fef3ed50441448e66f7e23d02189f5b8b51294c14bdfc61f27d3a68fb3b2179b9ada92bdea906ae0d4605a3761a051700a1e3f4957bbb8c4ef6afa0518e2a238
7
- data.tar.gz: 3192b179b3e35c4b2850b5933410afdc71a9a904d1df1673a88efb4bb0e66ef2d5952d04def37b7aff82a580b26943d362169ff896924f4ff91a9947223caeb5
6
+ metadata.gz: 88e99f4d71b1bb78599cf7a1750218cf5fde2e9ccb010c1aa671d1ba5e1ffe011a9bc70c39fe284b150d6fb497827e94766a82085a3758308990e57eb0f0b5da
7
+ data.tar.gz: 66acaa871a7f5adabf4135c3b84216f71072be0f9bc7f76c4e42247dde064709596c78b88fed32c9bd24bc47a6ba737d54bc7c3396509f4dae61f06c69d026fd
data/README.md CHANGED
@@ -1,63 +1,183 @@
1
1
  # BugBunny
2
2
 
3
- This gem simplify use of bunny gem. You can use 2 types of comunitaction, sync and async, Only is necessary define one `Adapter` to publish messages and `Consumer` to consume messages.
3
+ ## Configuration
4
+
5
+ ```ruby
6
+ config/initializers/bug_bunny.rb
7
+ BugBunny.configure do |config|
8
+ config.host = 'Host'
9
+ config.username = 'Username'
10
+ config.password = 'Password'
11
+ config.vhost = '/'
12
+ config.logger = Rails.logger
13
+ config.automatically_recover = false
14
+ config.network_recovery_interval = 5
15
+ config.connection_timeout = 10
16
+ config.read_timeout = 30
17
+ config.write_timeout = 30
18
+ config.heartbeat = 15
19
+ config.continuation_timeout = 15_000
20
+ end
21
+ ```
4
22
 
5
- # Example
23
+ ## Publish
24
+
25
+ ### Rutas
6
26
 
7
27
  ```
8
- # Adapter Code
9
- class TestAdapter < ::BugBunny::Adapter
10
- def self.publish_and_consume
11
- service_adapter = TestAdapter.new
12
- sync_queue = service_adapter.build_queue(:queue_test, durable: true, exclusive: false, auto_delete: false)
28
+ # config/rabbit_rest.yml
29
+ default: &default
30
+ healt_check:
31
+ up: 'healt_check/up'
32
+ manager:
33
+ services:
34
+ index: 'services/index'
35
+ create: 'services/create'
36
+ show: 'services/%<id>s/show'
37
+ update: 'services/%<id>s/update'
38
+ destroy: 'services/%<id>s/destroy'
39
+ swarm:
40
+ info: 'swarm/info'
41
+ version: 'swarm/version'
42
+ swarm: 'swarm/swarm'
43
+ tasks:
44
+ index: 'tasks/index'
45
+
46
+ development:
47
+ <<: *default
48
+
49
+ test:
50
+ <<: *default
51
+
52
+ production:
53
+ <<: *default
13
54
 
14
- message = ::BugBunny::Message.new(service_action: :test_action, body: { msg: 'test message' })
55
+ ```
15
56
 
16
- service_adapter.publish_and_consume!(message, sync_queue, check_consumers_count: false)
57
+ ### Configuration
17
58
 
18
- service_adapter.close_connection! # ensure the adapter is close
59
+ ```ruby
60
+ # config/initializers/bug_bunny.rb
61
+ BUG_BUNNY_ENDPOINTS = Rails.application.config_for(:rabbit_rest)
19
62
 
20
- service_adapter.make_response
21
- end
63
+ BUNNY_POOL = ConnectionPool.new(size: RABBIT_MAX_THREADS) do
64
+ BugBunny::Rabbit.create_connection(host: RABBIT_HOST, username: RABBIT_USER, password: RABBIT_PASS, vhost: RABBIT_VIRTUAL_HOST)
22
65
  end
66
+ ```
67
+
68
+ ### Publisher
69
+
70
+ Creamos cualquier clase que herede de `BugBunny::Publisher`, luego definimos metodos de clase y dentro de cada una de ella su implementacion
23
71
 
24
- # Controller Code
25
- class TestController < ::BugBunny::Controller
26
- ##############################################################################
27
- # SYNC SERVICE ACTIONS
28
- ##############################################################################
29
- def self.test_action(message)
30
- puts 'sleeping 5 seconds...'
31
- sleep 5
32
- { status: :success, body: message.body }
72
+ 1. Mensajes sincronicos
73
+
74
+ ```
75
+ class Rabbit::Publisher::Manager < BugBunny::Publisher
76
+ ROUTING_KEY = :manager
77
+ ROUTES = BUG_BUNNY_ENDPOINTS[:manager][:swarm]
78
+
79
+ def self.info(exchange:, message: nil)
80
+ obj = new(pool: NEW_BUNNY_POOL, exchange_name: exchange, action: self::ROUTES[:info], message: message)
81
+ obj.publish_and_consume!
82
+ end
83
+
84
+ def self.version(exchange:, message: nil)
85
+ obj = new(pool: NEW_BUNNY_POOL, exchange_name: exchange, action: self::ROUTES[:version], message: message)
86
+ obj.publish_and_consume!
33
87
  end
34
88
  end
35
89
  ```
36
90
 
91
+ 2. Mensajes Asincronicos
37
92
 
93
+ ```
94
+ class Rabbit::Publisher::Manager < BugBunny::Publisher
95
+ ROUTING_KEY = :manager
96
+ ROUTES = BUG_BUNNY_ENDPOINTS[:manager][:swarm]
38
97
 
39
- ## Installation
98
+ def self.info(exchange:, message: nil)
99
+ obj = new(pool: NEW_BUNNY_POOL, exchange_name: exchange, action: self::ROUTES[:info], message: message)
100
+ obj.publish!
101
+ end
40
102
 
41
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
103
+ def self.version(exchange:, message: nil)
104
+ obj = new(pool: NEW_BUNNY_POOL, exchange_name: exchange, action: self::ROUTES[:version], message: message)
105
+ obj.publish!
106
+ end
107
+ end
108
+ ```
42
109
 
43
- Install the gem and add to the application's Gemfile by executing:
110
+ 3. Attributes del objeto BugBunny::Publisher
111
+
112
+ - content_type
113
+ - content_encoding
114
+ - correlation_id
115
+ - reply_to
116
+ - message_id
117
+ - timestamp
118
+ - priority
119
+ - expiration
120
+ - user_id
121
+ - app_id
122
+ - action
123
+ - aguments
124
+ - cluster_id
125
+ - persistent
126
+ - expiration
127
+
128
+ ## Consumer
44
129
 
45
- $ bundle add UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
130
+ ```
131
+ class Rabbit::Controllers::Application < BugBunny::Controller
132
+ end
46
133
 
47
- If bundler is not being used to manage dependencies, install the gem by executing:
134
+ class Rabbit::Controllers::Swarm < Rabbit::Controllers::Application
135
+ def info
136
+ render status: :ok, json: Api::Docker.info
137
+ end
48
138
 
49
- $ gem install UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
139
+ def version
140
+ render status: :ok, json: Api::Docker.version
141
+ end
50
142
 
51
- ## Usage
143
+ def swarm
144
+ render status: :ok, json: Api::Docker.swarm
145
+ end
146
+ end
52
147
 
53
- TODO: Write usage instructions here
148
+ ```
54
149
 
55
- ## Development
150
+ ## Resource
151
+ Solo para recursos que se adaptan al crud de rails estoy utilizando automaticamente la logica de los publicadores. Los atributos solo se ponen si son necesarios, si no la dejas vacia y actua igual que active resource.
56
152
 
57
- After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
153
+ ```
154
+ class Manager::Application < BugBunny::Resource
155
+ self.resource_path = 'rabbit/publisher/manager'
156
+
157
+ attribute :id # 'ID'
158
+ attribute :version # 'Version'
159
+ attribute :created_at # 'CreatedAt'
160
+ attribute :update_at # 'UpdatedAt'
161
+ attribute :spec # 'Spec'
162
+ end
58
163
 
59
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
164
+ class Manager::Service < Manager::Application
165
+ attribute :endpoint # 'Endpoint'
166
+ end
60
167
 
61
- ## Contributing
168
+ ```
62
169
 
63
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/bug_bunny.
170
+ ## Exceptions
171
+ - Error General:
172
+ - `BugBunny::Error` hereda de `::StandardError` (Captura cualquier error de la gema.)
173
+ - Error de Publicadores:
174
+ - `BugBunny::PublishError` hereda de `BugBunny::Error` (Para fallos de envío o conexión.)
175
+ - Error de Respuestas:
176
+ - `BugBunny::ResponseError::Base` hereda de `BugBunny::Error` (Captura todos los errores de respuesta).
177
+ - Errores Específicos de Respuesta:
178
+ - `BugBunny::ResponseError::BadRequest`
179
+ - `BugBunny::ResponseError::NotFound`
180
+ - `BugBunny::ResponseError::NotAcceptable`
181
+ - `BugBunny::ResponseError::RequestTimeout`
182
+ - `BugBunny::ResponseError::UnprocessableEntity`: En este el error viene el error details a lo rails.
183
+ - `BugBunny::ResponseError::InternalServerError`
data/Rakefile CHANGED
@@ -1,4 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "bundler/gem_tasks"
4
- task default: %i[]
4
+ require "rubocop/rake_task"
5
+
6
+ RuboCop::RakeTask.new
7
+
8
+ task default: :rubocop
@@ -1,11 +1,11 @@
1
1
  module BugBunny
2
2
  class Config
3
3
  # getter y setter para cada propiedad.
4
- attr_accessor :user, :pass, :host, :virtual_host, :logger, :log_level
4
+ attr_accessor :host, :username, :password, :vhost, :logger, :automatically_recover, :network_recovery_interval, :connection_timeout, :read_timeout, :write_timeout, :heartbeat, :continuation_timeout
5
5
 
6
6
  # Método para generar la URL de conexión
7
7
  def url
8
- "amqp://#{user}:#{pass}@#{host}/#{virtual_host}"
8
+ "amqp://#{username}:#{password}@#{host}/#{vhost}"
9
9
  end
10
10
  end
11
11
  end
@@ -1,16 +1,89 @@
1
1
  module BugBunny
2
2
  class Controller
3
- def self.health_check(_message)
4
- { status: :success, body: {} }
3
+ include ActiveModel::Model
4
+ include ActiveModel::Attributes
5
+
6
+ attribute :headers
7
+ attribute :params
8
+ attribute :raw_string
9
+
10
+ attr_reader :rendered_response
11
+
12
+ def self.before_actions
13
+ # Nota el uso de '@' en lugar de '@@'
14
+ @before_actions ||= Hash.new { |hash, key| hash[key] = [] }
15
+ end
16
+
17
+ def self.before_action(method_name, **options)
18
+ actions = options.delete(:only) || []
19
+
20
+ if actions.empty?
21
+ before_actions[:_all_actions] << method_name
22
+ else
23
+ Array(actions).each do |action|
24
+ before_actions[action.to_sym] << method_name
25
+ end
26
+ end
27
+ end
28
+
29
+ def _run_before_actions
30
+ current_action = headers[:action].to_sym
31
+
32
+ callbacks = self.class.before_actions[:_all_actions] + self.class.before_actions[current_action]
33
+
34
+ callbacks.each do |method_name|
35
+ send(method_name) if respond_to?(method_name, true)
36
+ return false if @rendered_response
37
+ end
38
+
39
+ true
40
+ end
41
+
42
+ def render(status:, json: nil)
43
+ @rendered_response = self.class.render(status: status, json: json)
44
+ end
45
+
46
+ def safe_parse_body(body)
47
+ self.params ||= {}
48
+
49
+ return if body.blank?
50
+
51
+ case headers[:content_type]
52
+ when 'application/json', 'application/x-www-form-urlencoded'
53
+ if body.instance_of?(Hash)
54
+ params.merge!(body.deep_symbolize_keys)
55
+ else # es string
56
+ params.merge!(ActiveSupport::JSON.decode(body).deep_symbolize_keys)
57
+ end
58
+ when 'text/plain'
59
+ self.raw_string = body
60
+ end
5
61
  end
6
62
 
7
- def self.exec_action(message)
8
- send(message.service_action, message)
63
+ def self.status_code_number(code)
64
+ codes = Rack::Utils::SYMBOL_TO_STATUS_CODE
65
+ codes[:unprocessable_entity] = 422
66
+ code = codes[code.to_sym] if codes.key?(code.to_sym)
67
+ code
9
68
  end
10
69
 
11
- def self.method_missing(name, message, *args, &block)
12
- Session.message = message
13
- message.build_message(reply_to: message.reply_to).server_no_action!
70
+ def self.render(status:, json: nil)
71
+ status_number = status_code_number(status)
72
+ { status: status_number, body: json }
73
+ end
74
+
75
+ def self.call(headers:, body: {})
76
+ controller = new(headers: headers)
77
+ controller.safe_parse_body(body)
78
+ controller.params[:id] = headers[:id] if headers.key?(:id)
79
+ controller.params.with_indifferent_access
80
+ return controller.rendered_response unless controller._run_before_actions
81
+
82
+ controller.send(controller.headers[:action])
83
+ rescue NoMethodError => e # action controller no exist
84
+ raise e
85
+ rescue StandardError => e
86
+ render status: :internal_server_error, json: e.message
14
87
  end
15
88
  end
16
89
  end
@@ -1,69 +1,17 @@
1
1
  module BugBunny
2
- class Exception
3
- class ServiceError < StandardError
4
- def to_s
5
- :service_error
6
- end
7
- end
8
-
9
- class NeedSignature < StandardError
10
- def to_s
11
- :need_signature
12
- end
13
- end
14
-
15
- class InvalidSignature < StandardError
16
- def to_s
17
- :invalid_signature
18
- end
19
- end
20
-
21
- class GatewayError < StandardError
22
- def to_s
23
- :gateway_error
24
- end
25
- end
26
-
27
- class UltraCriticError < StandardError
28
- end
29
-
30
- class ComunicationRabbitError < StandardError
31
- attr_accessor :backtrace
32
-
33
- def initialize(msg, backtrace)
34
- @backtrace = backtrace
35
- super(msg)
36
- end
37
- end
38
-
39
- class WithOutConsumer < StandardError
40
- attr_accessor :backtrace
41
-
42
- def initialize(msg, backtrace)
43
- @backtrace = backtrace
44
- super(msg)
45
- end
46
- end
47
-
48
- class RetryWithoutError < StandardError
49
- def to_s
50
- "retry_sidekiq_without_error"
51
- end
52
-
53
- def backtrace
54
- []
55
- end
56
- end
57
-
58
- ServiceClasses = [
59
- Exception::NeedSignature,
60
- Exception::InvalidSignature,
61
- Exception::ServiceError,
62
- Exception::GatewayError,
63
- Exception::RetryWithoutError
64
- ]
65
-
66
- # Exceptions from ActiveRecord::StatementInvalid
67
- PG_EXCEPTIONS_TO_EXIT = %w[PG::ConnectionBad PG::UnableToSend].freeze
2
+ # Clase base para TODOS los errores de la gema BugBunny.
3
+ # Ayuda a atrapar cualquier error de la gema con un solo 'rescue BugBunny::Error'.
4
+ class Error < ::StandardError; end
5
+ class PublishError < Error; end
6
+
7
+ module ResponseError
8
+ class Base < Error; end
9
+
10
+ class BadRequest < Base; end # HTTP 400
11
+ class NotFound < Base; end # HTTP 404
12
+ class NotAcceptable < Base; end # HTTP 406
13
+ class RequestTimeout < Base; end # HTTP 408
14
+ class UnprocessableEntity < Base; end # HTTP 422
15
+ class InternalServerError < Base; end # HTTP 500
68
16
  end
69
17
  end
@@ -0,0 +1,115 @@
1
+ # content_type:
2
+ # Propósito: Indica el formato de codificación del cuerpo del mensaje (ej. application/json, text/plain, application/xml).
3
+ # Uso Recomendado: dice a tu código qué lógica de deserialización aplicar. Si es application/json, usas JSON.parse.
4
+
5
+ # content_encoding:
6
+ # Propósito: Indica cómo se comprimió o codificó el cuerpo del mensaje (ej. gzip, utf-8).
7
+ # Uso Recomendado: Si envías cuerpos grandes, puedes comprimirlos (ej. con Gzip) para ahorrar ancho de banda y usar este campo para que el consumidor sepa cómo descomprimirlos antes de usar el content_type.
8
+
9
+ # correlation_id:
10
+ # Propósito: Un identificador único que se usa para correlacionar una respuesta con una petición previa.
11
+ # Uso Recomendado: Es indispensable en el patrón Remote Procedure Call (RPC). Si un productor envía una petición, copia este ID a la respuesta. Cuando el productor recibe la respuesta, usa este ID para saber a qué petición original corresponde.
12
+
13
+ # reply_to:
14
+ # Propósito: Especifica el nombre de la cola a la que el consumidor debe enviar la respuesta.
15
+ # Uso Recomendado: También clave en RPC. El productor especifica aquí su cola de respuesta temporal o exclusiva. El consumidor toma el mensaje, procesa, y publica el resultado en la cola indicada en reply_to.
16
+
17
+ # message_id:
18
+ # Propósito: Un identificador único para el mensaje en sí.
19
+ # Uso Recomendado: Ayuda a prevenir el procesamiento duplicado si un sistema de consumo cae y se recupera. El consumidor puede almacenar los message_id ya procesados.
20
+
21
+ # timestamp:
22
+ # Propósito: Indica la hora y fecha en que el mensaje fue publicado por el productor.
23
+ # Uso Recomendado: Útil para auditoría, diagnóstico y seguimiento de la latencia del sistema.
24
+
25
+ # priority:
26
+ # Propósito: Un valor entero que indica la prioridad relativa del mensaje (de 0 a 9, siendo 9 la más alta).
27
+ # Uso Recomendado: Solo funciona si la cola receptora está configurada como una Cola de Prioridades. Si lo está, RabbitMQ dará preferencia a los mensajes con mayor prioridad.
28
+
29
+ # expiration:
30
+ # Propósito: Especifica el tiempo de vida (TTL - Time To Live) del mensaje en la cola, en milisegundos.
31
+ # Uso Recomendado: Si el mensaje caduca antes de ser consumido, RabbitMQ lo descartará o lo moverá a la Dead Letter Queue (DLQ). Es vital para mensajes sensibles al tiempo (ej. tokens o alertas).
32
+
33
+ # user_id y app_id:
34
+ # Propósito: Identificadores que especifican qué usuario y qué aplicación generaron el mensaje.
35
+ # Uso Recomendado: Auditoría y seguridad. El broker (RabbitMQ) puede verificar que el user_id coincida con el usuario de la conexión AMQP utilizada para publicar.
36
+
37
+ # type:
38
+ # Propósito: Un identificador de aplicación para describir el "tipo" o "clase" de la carga útil del mensaje.
39
+ # Uso Recomendado: Usado a menudo para el enrutamiento interno dentro de una aplicación consumidora, similar al header Action que usas. Por ejemplo, en lugar de usar headers[:action], podrías usar properties[:type].
40
+
41
+ # cluster_id:
42
+ # Propósito: Obsoleto en AMQP 0-9-1 y no debe ser utilizado.
43
+
44
+ # persistent:
45
+ # Un valor booleano (true o false). Cuando es true, le dice a RabbitMQ que el mensaje debe persistir en el disco. Si el servidor de RabbitMQ se reinicia, el mensaje no se perderá.
46
+
47
+ # expiration:
48
+ # El tiempo de vida del mensaje en milisegundos. Después de este tiempo, RabbitMQ lo descartará automáticamente si no ha sido consumido.
49
+ module BugBunny
50
+ class Publisher
51
+ include ActiveModel::Model
52
+ include ActiveModel::Attributes
53
+
54
+ attribute :message
55
+ attribute :pool
56
+ attribute :routing_key, :string
57
+ attribute :persistent, :boolean, default: false
58
+ attribute :content_type, :string, default: "application/json"
59
+ attribute :content_encoding, :string, default: "utf-8"
60
+ attribute :correlation_id, :string
61
+ attribute :reply_to, :string
62
+ attribute :app_id, :string
63
+ attribute :headers, default: {}
64
+ attribute :message_id, :string, default: -> { SecureRandom.uuid }
65
+ attribute :timestamp, :datetime, default: -> { Time.zone.now.utc.to_i }
66
+ attribute :expiration, :integer, default: -> { 1.day.in_milliseconds } #ms
67
+ attribute :exchange_name, :string
68
+ attribute :exchange_type, :string, default: 'direct'
69
+ attr_accessor :type
70
+
71
+ attribute :action, :string
72
+ attribute :arguments, default: {}
73
+
74
+ def publish!
75
+ pool.with do |conn|
76
+ app = Rabbit.new(connection: conn)
77
+ app.build_exchange(name: exchange_name, type: exchange_type)
78
+ app.publish!(message, publish_opts)
79
+ end
80
+ end
81
+
82
+ def publish_and_consume!
83
+ pool.with do |conn|
84
+ app = Rabbit.new(connection: conn)
85
+ app.build_exchange(name: exchange_name, type: exchange_type)
86
+ app.publish_and_consume!(message, publish_opts)
87
+ end
88
+ end
89
+
90
+ def publish_opts
91
+ { routing_key: routing_key,
92
+ type: type,
93
+ persistent: persistent,
94
+ content_type: content_type,
95
+ content_encoding: content_encoding,
96
+ correlation_id: correlation_id,
97
+ reply_to: reply_to,
98
+ app_id: app_id,
99
+ headers: headers,
100
+ timestamp: timestamp,
101
+ expiration: expiration }
102
+ end
103
+
104
+ def type
105
+ return if action.blank?
106
+
107
+ self.type = format(action, arguments)
108
+ end
109
+
110
+ def initialize(attrs = {})
111
+ super(attrs)
112
+ self.routing_key ||= self.class::ROUTING_KEY
113
+ end
114
+ end
115
+ end