mimi-messaging 0.1.12 → 1.0.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/.gitignore +1 -0
- data/.rubocop.yml +66 -0
- data/README.md +68 -3
- data/TODO.md +8 -0
- data/docs/Messaging_Layer_Properties.md +141 -0
- data/docs/Why_HTTP_is_a_bad_choice.md +20 -0
- data/docs/diagrams/Pattern -- Command.drawio +1 -0
- data/docs/diagrams/Pattern -- Event direct.drawio +1 -0
- data/docs/diagrams/Pattern -- Event with Queue.drawio +1 -0
- data/docs/diagrams/Pattern -- Event.drawio +1 -0
- data/docs/diagrams/Pattern -- Query.drawio +1 -0
- data/docs/img/pattern--command.png +0 -0
- data/docs/img/pattern--event-direct.png +0 -0
- data/docs/img/pattern--event-using-queue.png +0 -0
- data/docs/img/pattern--event.png +0 -0
- data/docs/img/pattern--query.png +0 -0
- data/examples/basic_event_listener.rb +35 -0
- data/examples/basic_request_processor.rb +38 -0
- data/examples/using_messaging_low.rb +59 -0
- data/examples/using_pure_adapter.rb +62 -0
- data/lib/mimi/messaging.rb +428 -92
- data/lib/mimi/messaging/adapters.rb +22 -0
- data/lib/mimi/messaging/adapters/base.rb +233 -0
- data/lib/mimi/messaging/adapters/memory.rb +119 -0
- data/lib/mimi/messaging/adapters/test.rb +50 -0
- data/lib/mimi/messaging/errors.rb +24 -12
- data/lib/mimi/messaging/json_serializer.rb +45 -0
- data/lib/mimi/messaging/version.rb +3 -1
- data/mimi-messaging.gemspec +25 -23
- metadata +34 -78
- data/lib/mimi/messaging/connection.rb +0 -182
- data/lib/mimi/messaging/listener.rb +0 -72
- data/lib/mimi/messaging/message.rb +0 -74
- data/lib/mimi/messaging/mock.rb +0 -13
- data/lib/mimi/messaging/mock/connection.rb +0 -153
- data/lib/mimi/messaging/mock/request.rb +0 -19
- data/lib/mimi/messaging/mock/request_processor.rb +0 -92
- data/lib/mimi/messaging/model.rb +0 -27
- data/lib/mimi/messaging/model_provider.rb +0 -100
- data/lib/mimi/messaging/msgpack/msgpack_ext.rb +0 -14
- data/lib/mimi/messaging/msgpack/type_packer.rb +0 -104
- data/lib/mimi/messaging/notification.rb +0 -35
- data/lib/mimi/messaging/provider.rb +0 -48
- data/lib/mimi/messaging/request.rb +0 -56
- data/lib/mimi/messaging/request_processor.rb +0 -216
- data/lib/mimi/messaging/request_processor/context.rb +0 -39
- data/lib/mimi/messaging/request_processor/dsl.rb +0 -121
- data/lib/tasks/console_ext.rake +0 -6
- data/lib/tasks/console_helpers.rb +0 -116
| @@ -1,100 +0,0 @@ | |
| 1 | 
            -
            module Mimi
         | 
| 2 | 
            -
              module Messaging
         | 
| 3 | 
            -
                class ModelProvider < Mimi::Messaging::Provider
         | 
| 4 | 
            -
                  abstract!
         | 
| 5 | 
            -
             | 
| 6 | 
            -
                  def self.model(model_class, options = {})
         | 
| 7 | 
            -
                    raise "#{self} already serves model #{@model_class}" if @model_class
         | 
| 8 | 
            -
                    @model_class = model_class
         | 
| 9 | 
            -
                    @model_options = options
         | 
| 10 | 
            -
                    @model_methods_only = options[:only] ? [*(options[:only])] : nil
         | 
| 11 | 
            -
                    @model_methods_except = options[:except] ? [*(options[:except])] : nil
         | 
| 12 | 
            -
                  end
         | 
| 13 | 
            -
             | 
| 14 | 
            -
                  def self.exposed_methods
         | 
| 15 | 
            -
                    model_methods = Mimi::Messaging::ModelProvider.public_instance_methods(false).dup
         | 
| 16 | 
            -
                    methods_hidden = []
         | 
| 17 | 
            -
                    methods_hidden = model_methods - model_methods.only(*@model_methods_only) if @model_methods_only
         | 
| 18 | 
            -
                    methods_hidden = model_methods.only(*@model_methods_except) if @model_methods_except
         | 
| 19 | 
            -
                    m = super
         | 
| 20 | 
            -
                    m - methods_hidden
         | 
| 21 | 
            -
                  end
         | 
| 22 | 
            -
             | 
| 23 | 
            -
                  def self.model_class
         | 
| 24 | 
            -
                    @model_class || raise("#{self} has no defined model")
         | 
| 25 | 
            -
                  end
         | 
| 26 | 
            -
             | 
| 27 | 
            -
                  def self.serialize(method_name = nil, &block)
         | 
| 28 | 
            -
                    if method_name && block_given?
         | 
| 29 | 
            -
                      raise "Only one of method_name or block is accepted by #{self}.serialize"
         | 
| 30 | 
            -
                    end
         | 
| 31 | 
            -
                    @serialize = method_name.to_sym if method_name
         | 
| 32 | 
            -
                    @serialize = block if block_given?
         | 
| 33 | 
            -
                    @serialize || parent_property(:serialize) || :as_json
         | 
| 34 | 
            -
                  end
         | 
| 35 | 
            -
             | 
| 36 | 
            -
                  def self.scope(&block)
         | 
| 37 | 
            -
                    @scope = block if block_given?
         | 
| 38 | 
            -
                    @scope || -> (r) { r }
         | 
| 39 | 
            -
                  end
         | 
| 40 | 
            -
             | 
| 41 | 
            -
                  def self.permitted_params(*names)
         | 
| 42 | 
            -
                    @permitted_params = names.map(&:to_sym) if names.size > 0
         | 
| 43 | 
            -
                    @permitted_params || model_class.attribute_names.map(&:to_sym)
         | 
| 44 | 
            -
                  end
         | 
| 45 | 
            -
             | 
| 46 | 
            -
                  def create
         | 
| 47 | 
            -
                    serialize model_class_scoped.create!(permitted_params)
         | 
| 48 | 
            -
                  end
         | 
| 49 | 
            -
             | 
| 50 | 
            -
                  def update(id:)
         | 
| 51 | 
            -
                    model = model_class_scoped.find(id)
         | 
| 52 | 
            -
                    model.update!(permitted_params)
         | 
| 53 | 
            -
                    serialize model
         | 
| 54 | 
            -
                  end
         | 
| 55 | 
            -
             | 
| 56 | 
            -
                  def show(id:)
         | 
| 57 | 
            -
                    serialize model_class_scoped.find(id)
         | 
| 58 | 
            -
                  end
         | 
| 59 | 
            -
             | 
| 60 | 
            -
                  def find
         | 
| 61 | 
            -
                    serialize model_class_scoped.find_by!(params)
         | 
| 62 | 
            -
                  end
         | 
| 63 | 
            -
             | 
| 64 | 
            -
                  def destroy(id:)
         | 
| 65 | 
            -
                    model_class_scoped.find(id)
         | 
| 66 | 
            -
                    raise "#{self.class}#destroy is not implemented"
         | 
| 67 | 
            -
                  end
         | 
| 68 | 
            -
             | 
| 69 | 
            -
                  def list
         | 
| 70 | 
            -
                    reply list: model_class_scoped.all.map { |v| serialize v }
         | 
| 71 | 
            -
                  end
         | 
| 72 | 
            -
             | 
| 73 | 
            -
                  private
         | 
| 74 | 
            -
             | 
| 75 | 
            -
                  def permitted_params
         | 
| 76 | 
            -
                    params.except('id').only(*self.class.permitted_params.map(&:to_s)).to_hash
         | 
| 77 | 
            -
                  end
         | 
| 78 | 
            -
             | 
| 79 | 
            -
                  def model_class
         | 
| 80 | 
            -
                    self.class.model_class
         | 
| 81 | 
            -
                  end
         | 
| 82 | 
            -
             | 
| 83 | 
            -
                  def model_class_scoped
         | 
| 84 | 
            -
                    scope_block = self.class.scope
         | 
| 85 | 
            -
                    __execute(model_class, &scope_block)
         | 
| 86 | 
            -
                  end
         | 
| 87 | 
            -
             | 
| 88 | 
            -
                  def serialize(model_instance, opts = nil)
         | 
| 89 | 
            -
                    opts ||= params
         | 
| 90 | 
            -
                    if self.class.serialize.is_a?(Symbol)
         | 
| 91 | 
            -
                      model_instance.send(self.class.serialize, opts)
         | 
| 92 | 
            -
                    elsif self.class.serialize.is_a?(Proc)
         | 
| 93 | 
            -
                      self.class.serialize.call(model_instance, opts)
         | 
| 94 | 
            -
                    else
         | 
| 95 | 
            -
                      raise "#{self.class}#serialize is neither a Symbol or Proc"
         | 
| 96 | 
            -
                    end
         | 
| 97 | 
            -
                  end
         | 
| 98 | 
            -
                end # class ModelProvider
         | 
| 99 | 
            -
              end # module Messaging
         | 
| 100 | 
            -
            end # module Mimi
         | 
| @@ -1,14 +0,0 @@ | |
| 1 | 
            -
            #
         | 
| 2 | 
            -
            # MessagePack extensions for common types
         | 
| 3 | 
            -
            #
         | 
| 4 | 
            -
            Mimi::Messaging::TypePacker.register(
         | 
| 5 | 
            -
              Time,
         | 
| 6 | 
            -
              from_bytes: -> (b) { Time.at(b.unpack('D').first).utc },
         | 
| 7 | 
            -
              to_bytes: -> (v) { [v.utc.to_f].pack('D') }
         | 
| 8 | 
            -
            )
         | 
| 9 | 
            -
             | 
| 10 | 
            -
            Mimi::Messaging::TypePacker.register(
         | 
| 11 | 
            -
              BigDecimal,
         | 
| 12 | 
            -
              from_bytes: -> (b) { BigDecimal.new(b) },
         | 
| 13 | 
            -
              to_bytes: -> (v) { v.to_s }
         | 
| 14 | 
            -
            )
         | 
| @@ -1,104 +0,0 @@ | |
| 1 | 
            -
            require 'msgpack'
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            module Mimi
         | 
| 4 | 
            -
              module Messaging
         | 
| 5 | 
            -
                class TypePacker
         | 
| 6 | 
            -
                  APPLICATION_TYPE_EXT = 0x00
         | 
| 7 | 
            -
             | 
| 8 | 
            -
                  # Registers a new type packer for msgpack.
         | 
| 9 | 
            -
                  #
         | 
| 10 | 
            -
                  # @example
         | 
| 11 | 
            -
                  #   TypePacker.register(
         | 
| 12 | 
            -
                  #     Time,
         | 
| 13 | 
            -
                  #     from_bytes: -> (b) { Time.at(b.unpack('D').first) },
         | 
| 14 | 
            -
                  #     to_bytes: -> (v) { [v.to_f].pack('D') }
         | 
| 15 | 
            -
                  #   )
         | 
| 16 | 
            -
                  #
         | 
| 17 | 
            -
                  def self.register(type, opts = {})
         | 
| 18 | 
            -
                    raise ArgumentError, 'Invalid :from_bytes, proc expected' unless opts[:from_bytes].is_a?(Proc)
         | 
| 19 | 
            -
                    raise ArgumentError, 'Invalid :to_bytes, proc expected' unless opts[:to_bytes].is_a?(Proc)
         | 
| 20 | 
            -
                    type_name = type.to_s
         | 
| 21 | 
            -
                    params = opts.dup.merge(type: type, type_name: type_name)
         | 
| 22 | 
            -
                    type_packers[type] = params
         | 
| 23 | 
            -
                    type.send(:define_method, :to_msgpack_ext) { Mimi::Messaging::TypePacker.pack(self) }
         | 
| 24 | 
            -
                    MessagePack::DefaultFactory.register_type(
         | 
| 25 | 
            -
                      APPLICATION_TYPE_EXT,
         | 
| 26 | 
            -
                      type,
         | 
| 27 | 
            -
                      packer: :to_msgpack_ext
         | 
| 28 | 
            -
                    )
         | 
| 29 | 
            -
             | 
| 30 | 
            -
                    MessagePack::DefaultFactory.register_type(
         | 
| 31 | 
            -
                      APPLICATION_TYPE_EXT,
         | 
| 32 | 
            -
                      self,
         | 
| 33 | 
            -
                      unpacker: :unpack
         | 
| 34 | 
            -
                    )
         | 
| 35 | 
            -
                  end
         | 
| 36 | 
            -
             | 
| 37 | 
            -
                  # Returns a set of registered type packers
         | 
| 38 | 
            -
                  #
         | 
| 39 | 
            -
                  def self.type_packers
         | 
| 40 | 
            -
                    @type_packers ||= {}
         | 
| 41 | 
            -
                  end
         | 
| 42 | 
            -
             | 
| 43 | 
            -
                  # Pack a value using a type packer, registered for the value class
         | 
| 44 | 
            -
                  #
         | 
| 45 | 
            -
                  def self.pack(value)
         | 
| 46 | 
            -
                    type_packer = type_packers.values.find { |p| value.is_a?(p[:type]) }
         | 
| 47 | 
            -
                    raise "No packer registered for type #{value.class}" unless type_packer
         | 
| 48 | 
            -
                    bytes = type_packer[:to_bytes].call(value)
         | 
| 49 | 
            -
                    "#{type_packer[:type_name]}=#{bytes}"
         | 
| 50 | 
            -
                  end
         | 
| 51 | 
            -
             | 
| 52 | 
            -
                  # Unpack a value, using a registered type packer
         | 
| 53 | 
            -
                  #
         | 
| 54 | 
            -
                  def self.unpack(value)
         | 
| 55 | 
            -
                    type_name, _, bytes = value.partition('=') # splits "Type=bytes" into ['Type', '=', 'bytes']
         | 
| 56 | 
            -
                    type_packer = type_packers.values.find { |p| p[:type_name] == type_name }
         | 
| 57 | 
            -
                    raise "No unpacker registered for type #{type_name}" unless type_packer
         | 
| 58 | 
            -
                    type_packer[:from_bytes].call(bytes)
         | 
| 59 | 
            -
                  end
         | 
| 60 | 
            -
             | 
| 61 | 
            -
                  # def self.register_type_packer!
         | 
| 62 | 
            -
                  #   return if @type_packer_registered
         | 
| 63 | 
            -
                  #   # pk = MessagePack::Packer.new
         | 
| 64 | 
            -
                  #   # pk.register_type(APPLICATION_TYPE_EXT) { |v| TypePacker.pack(v) }
         | 
| 65 | 
            -
                  #   uk = MessagePack::Unpacker.new
         | 
| 66 | 
            -
                  #   uk.register_type(APPLICATION_TYPE_EXT) { |b| TypePacker.unpack(b) }
         | 
| 67 | 
            -
                  #   @type_packer_registered = true
         | 
| 68 | 
            -
                  # end
         | 
| 69 | 
            -
                end # class TypePacker
         | 
| 70 | 
            -
              end # module Messaging
         | 
| 71 | 
            -
             | 
| 72 | 
            -
            end # module Mimi
         | 
| 73 | 
            -
             | 
| 74 | 
            -
             | 
| 75 | 
            -
             | 
| 76 | 
            -
            # class A
         | 
| 77 | 
            -
            #   class B
         | 
| 78 | 
            -
            #     attr_reader :value
         | 
| 79 | 
            -
            #     def initialize(value)
         | 
| 80 | 
            -
            #       @value = value
         | 
| 81 | 
            -
            #     end
         | 
| 82 | 
            -
            #   end # class B
         | 
| 83 | 
            -
            # end # class A
         | 
| 84 | 
            -
             | 
| 85 | 
            -
            # TypePacker.register(
         | 
| 86 | 
            -
            #   Time,
         | 
| 87 | 
            -
            #   from_bytes: -> (b) { Time.at(b.unpack('D').first) },
         | 
| 88 | 
            -
            #   to_bytes: -> (v) { [v.to_f].pack('D') }
         | 
| 89 | 
            -
            # )
         | 
| 90 | 
            -
             | 
| 91 | 
            -
            # TypePacker.register(
         | 
| 92 | 
            -
            #   A::B,
         | 
| 93 | 
            -
            #   from_bytes: -> (b) { A::B.new(b.unpack('D').first) },
         | 
| 94 | 
            -
            #   to_bytes: -> (v) { [v.value.to_f].pack('D') }
         | 
| 95 | 
            -
            # )
         | 
| 96 | 
            -
             | 
| 97 | 
            -
            # TypePacker.register(
         | 
| 98 | 
            -
            #   DateTime,
         | 
| 99 | 
            -
            #   from_bytes: -> (b) { Time.at(b.unpack('D').first).to_datetime },
         | 
| 100 | 
            -
            #   to_bytes: -> (v) { [v.to_time.to_f].pack('D') }
         | 
| 101 | 
            -
            # )
         | 
| 102 | 
            -
             | 
| 103 | 
            -
            # m = "\x83\xA1a\xC7\r\x00Time=\x1D@\x95\xC7\x80\xCA\xD5A\xA1b\x02\xA1c\xC7\x11\x00DateTime=X@\x95\xC7\x80\xCA\xD5A"
         | 
| 104 | 
            -
             | 
| @@ -1,35 +0,0 @@ | |
| 1 | 
            -
            module Mimi
         | 
| 2 | 
            -
              module Messaging
         | 
| 3 | 
            -
                class Notification < Hashie::Mash
         | 
| 4 | 
            -
                  def self.notification(name, _opts = {})
         | 
| 5 | 
            -
                    @notification_name = name
         | 
| 6 | 
            -
                  end
         | 
| 7 | 
            -
             | 
| 8 | 
            -
                  def self.notification_name
         | 
| 9 | 
            -
                    @notification_name || default_notification_name
         | 
| 10 | 
            -
                  end
         | 
| 11 | 
            -
             | 
| 12 | 
            -
                  def self.default_notification_name
         | 
| 13 | 
            -
                    Mimi::Messaging::RequestProcessor.class_name_to_resource_name(self, 'notification')
         | 
| 14 | 
            -
                  end
         | 
| 15 | 
            -
             | 
| 16 | 
            -
                  def self.broadcast(name, data = {}, opts = {})
         | 
| 17 | 
            -
                    headers = {
         | 
| 18 | 
            -
                      method_name: name.to_s,
         | 
| 19 | 
            -
                      Mimi::Messaging::CONTEXT_ID_KEY => Mimi::Messaging.logger.context_id
         | 
| 20 | 
            -
                    }
         | 
| 21 | 
            -
                    Mimi::Messaging.broadcast(
         | 
| 22 | 
            -
                      notification_name, Message.encode(data), opts.merge(headers: headers)
         | 
| 23 | 
            -
                    )
         | 
| 24 | 
            -
                  end
         | 
| 25 | 
            -
             | 
| 26 | 
            -
                  def broadcast(name, opts = {})
         | 
| 27 | 
            -
                    self.class.broadcast(name, self, opts)
         | 
| 28 | 
            -
                  end
         | 
| 29 | 
            -
             | 
| 30 | 
            -
                  def to_s
         | 
| 31 | 
            -
                    to_hash.to_s
         | 
| 32 | 
            -
                  end
         | 
| 33 | 
            -
                end # class Notification
         | 
| 34 | 
            -
              end # module Messaging
         | 
| 35 | 
            -
            end # module Mimi
         | 
| @@ -1,48 +0,0 @@ | |
| 1 | 
            -
            module Mimi
         | 
| 2 | 
            -
              module Messaging
         | 
| 3 | 
            -
                class Provider < RequestProcessor
         | 
| 4 | 
            -
                  abstract!
         | 
| 5 | 
            -
                  queue_options exclusive: false, auto_delete: true
         | 
| 6 | 
            -
             | 
| 7 | 
            -
                  def self.default_queue_name
         | 
| 8 | 
            -
                    class_name_to_resource_name(name, 'provider')
         | 
| 9 | 
            -
                  end
         | 
| 10 | 
            -
             | 
| 11 | 
            -
                  def initialize(d, m, p)
         | 
| 12 | 
            -
                    super
         | 
| 13 | 
            -
                    begin
         | 
| 14 | 
            -
                      catch(:halt) do
         | 
| 15 | 
            -
                        reply(@result) unless request.replied?
         | 
| 16 | 
            -
                      end
         | 
| 17 | 
            -
                    rescue StandardError => e
         | 
| 18 | 
            -
                      __execute_error_handlers(e)
         | 
| 19 | 
            -
                    end
         | 
| 20 | 
            -
                    if request.get? && !request.replied?
         | 
| 21 | 
            -
                      logger.error "No response sent to #{request.canonical_name}"
         | 
| 22 | 
            -
                    end
         | 
| 23 | 
            -
                  end
         | 
| 24 | 
            -
             | 
| 25 | 
            -
                  # Default error handler for RequestError and its descendants
         | 
| 26 | 
            -
                  #
         | 
| 27 | 
            -
                  error RequestError do |e|
         | 
| 28 | 
            -
                    logger.warn "#{request.canonical_name}: #{e} (#{e.params})"
         | 
| 29 | 
            -
                    reply error: e.message, params: e.params
         | 
| 30 | 
            -
                  end
         | 
| 31 | 
            -
             | 
| 32 | 
            -
                  # Default error handler for StandardError and its descendants
         | 
| 33 | 
            -
                  #
         | 
| 34 | 
            -
                  error StandardError do |e|
         | 
| 35 | 
            -
                    logger.error "#{request.canonical_name}: #{e} (#{e.class})"
         | 
| 36 | 
            -
                    logger.debug((e.backtrace || ['<no backtrace>']).join("\n"))
         | 
| 37 | 
            -
                    reply error: e.message
         | 
| 38 | 
            -
                  end
         | 
| 39 | 
            -
             | 
| 40 | 
            -
                  private
         | 
| 41 | 
            -
             | 
| 42 | 
            -
                  def reply(data = {})
         | 
| 43 | 
            -
                    request.send_response(data)
         | 
| 44 | 
            -
                    halt
         | 
| 45 | 
            -
                  end
         | 
| 46 | 
            -
                end # class Provider
         | 
| 47 | 
            -
              end # module Messaging
         | 
| 48 | 
            -
            end # module Mimi
         | 
| @@ -1,56 +0,0 @@ | |
| 1 | 
            -
            module Mimi
         | 
| 2 | 
            -
              module Messaging
         | 
| 3 | 
            -
                class Request
         | 
| 4 | 
            -
                  attr_reader :request_processor, :delivery_info, :metadata, :raw_message, :params
         | 
| 5 | 
            -
             | 
| 6 | 
            -
                  def initialize(request_processor, d, m, p)
         | 
| 7 | 
            -
                    @request_processor = request_processor
         | 
| 8 | 
            -
                    @delivery_info = d
         | 
| 9 | 
            -
                    @metadata = m
         | 
| 10 | 
            -
                    @raw_message = p
         | 
| 11 | 
            -
                    @params = Params.new(Mimi::Messaging::Message.decode(@raw_message))
         | 
| 12 | 
            -
                  end
         | 
| 13 | 
            -
             | 
| 14 | 
            -
                  def method_name
         | 
| 15 | 
            -
                    metadata.headers && metadata.headers['method_name']
         | 
| 16 | 
            -
                  end
         | 
| 17 | 
            -
             | 
| 18 | 
            -
                  def type
         | 
| 19 | 
            -
                    request_processor.request_type(@delivery_info, @metadata, @raw_message)
         | 
| 20 | 
            -
                  end
         | 
| 21 | 
            -
             | 
| 22 | 
            -
                  def canonical_name
         | 
| 23 | 
            -
                    "#{type.to_s.upcase} #{request_processor.resource_name}/#{method_name}"
         | 
| 24 | 
            -
                  end
         | 
| 25 | 
            -
             | 
| 26 | 
            -
                  def params_symbolized
         | 
| 27 | 
            -
                    Hashie.symbolize_keys(params.to_hash)
         | 
| 28 | 
            -
                  end
         | 
| 29 | 
            -
             | 
| 30 | 
            -
                  def get?
         | 
| 31 | 
            -
                    type == :get
         | 
| 32 | 
            -
                  end
         | 
| 33 | 
            -
             | 
| 34 | 
            -
                  def send_response(data = {})
         | 
| 35 | 
            -
                    return if !get? || replied?
         | 
| 36 | 
            -
                    raise ArgumentError, 'Invalid response format, Hash is expected' unless data.is_a?(Hash)
         | 
| 37 | 
            -
                    reply_to_queue_name = metadata[:reply_to]
         | 
| 38 | 
            -
                    raw_message = Mimi::Messaging::Message.encode(data)
         | 
| 39 | 
            -
                    request_processor.connection.post(
         | 
| 40 | 
            -
                      reply_to_queue_name, raw_message, correlation_id: metadata[:correlation_id]
         | 
| 41 | 
            -
                    )
         | 
| 42 | 
            -
                    @replied = true
         | 
| 43 | 
            -
                  end
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                  def replied?
         | 
| 46 | 
            -
                    @replied
         | 
| 47 | 
            -
                  end
         | 
| 48 | 
            -
             | 
| 49 | 
            -
                  class Params < Hashie::Mash
         | 
| 50 | 
            -
                    def to_s
         | 
| 51 | 
            -
                      to_hash.to_s
         | 
| 52 | 
            -
                    end
         | 
| 53 | 
            -
                  end # class Params
         | 
| 54 | 
            -
                end # class Request
         | 
| 55 | 
            -
              end # module Messaging
         | 
| 56 | 
            -
            end # module Mimi
         | 
| @@ -1,216 +0,0 @@ | |
| 1 | 
            -
            require_relative 'request_processor/dsl'
         | 
| 2 | 
            -
            require_relative 'request_processor/context'
         | 
| 3 | 
            -
             | 
| 4 | 
            -
            module Mimi
         | 
| 5 | 
            -
              module Messaging
         | 
| 6 | 
            -
                class RequestProcessor
         | 
| 7 | 
            -
                  extend DSL
         | 
| 8 | 
            -
                  include Context
         | 
| 9 | 
            -
             | 
| 10 | 
            -
                  abstract!
         | 
| 11 | 
            -
                  queue_options exclusive: false, auto_delete: true
         | 
| 12 | 
            -
             | 
| 13 | 
            -
                  RequestError = Mimi::Messaging::RequestError
         | 
| 14 | 
            -
             | 
| 15 | 
            -
                  def self.inherited(request_processor_class)
         | 
| 16 | 
            -
                    request_processor_class.parent = self
         | 
| 17 | 
            -
                    Mimi::Messaging.register_request_processor_class(request_processor_class)
         | 
| 18 | 
            -
                  end
         | 
| 19 | 
            -
             | 
| 20 | 
            -
                  def self.resource_name
         | 
| 21 | 
            -
                    queue_name
         | 
| 22 | 
            -
                  end
         | 
| 23 | 
            -
             | 
| 24 | 
            -
                  def self.request_type(_d, metadata, _p)
         | 
| 25 | 
            -
                    metadata.reply_to ? :get : :post
         | 
| 26 | 
            -
                  end
         | 
| 27 | 
            -
             | 
| 28 | 
            -
                  def self.started?
         | 
| 29 | 
            -
                    !@consumer.nil?
         | 
| 30 | 
            -
                  end
         | 
| 31 | 
            -
             | 
| 32 | 
            -
                  def self.connection
         | 
| 33 | 
            -
                    @connection ||= Mimi::Messaging.connection_for(resource_name)
         | 
| 34 | 
            -
                  end
         | 
| 35 | 
            -
             | 
| 36 | 
            -
                  def self.channel
         | 
| 37 | 
            -
                    @channel ||= connection.create_channel(options)
         | 
| 38 | 
            -
                  end
         | 
| 39 | 
            -
             | 
| 40 | 
            -
                  def self.construct_queue
         | 
| 41 | 
            -
                    channel.create_queue(queue_name, queue_options)
         | 
| 42 | 
            -
                  end
         | 
| 43 | 
            -
             | 
| 44 | 
            -
                  def self.start
         | 
| 45 | 
            -
                    return if abstract?
         | 
| 46 | 
            -
                    raise "#{name} already started" if started?
         | 
| 47 | 
            -
                    logger.debug "#{self} starting to serve '#{resource_name}' (#{exposed_methods})"
         | 
| 48 | 
            -
                    @queue = construct_queue
         | 
| 49 | 
            -
                    @consumer_mutex = Mutex.new
         | 
| 50 | 
            -
                    @consumer_mutex.synchronize do
         | 
| 51 | 
            -
                      @consumer = @queue.subscribe(manual_ack: true) do |d, m, p|
         | 
| 52 | 
            -
                        begin
         | 
| 53 | 
            -
                          new(d, m, p)
         | 
| 54 | 
            -
                        rescue StandardError => e
         | 
| 55 | 
            -
                          logger.error e.to_s
         | 
| 56 | 
            -
                          logger.debug e.backtrace.join("\n")
         | 
| 57 | 
            -
                        ensure
         | 
| 58 | 
            -
                          @consumer_mutex.synchronize do
         | 
| 59 | 
            -
                            @consumer.channel.ack(d.delivery_tag) if @consumer.channel && @consumer.channel.active
         | 
| 60 | 
            -
                          end
         | 
| 61 | 
            -
                        end
         | 
| 62 | 
            -
                      end
         | 
| 63 | 
            -
                    end
         | 
| 64 | 
            -
                    # consumer created, mutex released
         | 
| 65 | 
            -
                  end
         | 
| 66 | 
            -
             | 
| 67 | 
            -
                  def self.stop
         | 
| 68 | 
            -
                    return if abstract?
         | 
| 69 | 
            -
                    raise "#{name} already stopped" unless started?
         | 
| 70 | 
            -
                    @consumer_mutex.synchronize do
         | 
| 71 | 
            -
                      @consumer.cancel if @consumer
         | 
| 72 | 
            -
                    end
         | 
| 73 | 
            -
                    @consumer = nil
         | 
| 74 | 
            -
                    @queue = nil
         | 
| 75 | 
            -
                    @channel = nil
         | 
| 76 | 
            -
                    @connection = nil
         | 
| 77 | 
            -
                  end
         | 
| 78 | 
            -
             | 
| 79 | 
            -
                  def initialize(d, m, p)
         | 
| 80 | 
            -
                    initialize_logging_context!(m.headers)
         | 
| 81 | 
            -
                    @request = Mimi::Messaging::Request.new(self.class, d, m, p)
         | 
| 82 | 
            -
                    @result = nil
         | 
| 83 | 
            -
                    method_name = request.method_name
         | 
| 84 | 
            -
                    begin
         | 
| 85 | 
            -
                      catch(:halt) do
         | 
| 86 | 
            -
                        @result = __execute_method(method_name)
         | 
| 87 | 
            -
                      end
         | 
| 88 | 
            -
                    rescue StandardError => e
         | 
| 89 | 
            -
                      __execute_error_handlers(e)
         | 
| 90 | 
            -
                    end
         | 
| 91 | 
            -
                  end
         | 
| 92 | 
            -
             | 
| 93 | 
            -
                  # Initializes logging context.
         | 
| 94 | 
            -
                  #
         | 
| 95 | 
            -
                  # Starts a new logging contenxt or inherits a context id from the message headers.
         | 
| 96 | 
            -
                  #
         | 
| 97 | 
            -
                  # @param headers [Hash,nil] message headers
         | 
| 98 | 
            -
                  #
         | 
| 99 | 
            -
                  def initialize_logging_context!(headers)
         | 
| 100 | 
            -
                    context_id = (headers || {})[Mimi::Messaging::CONTEXT_ID_KEY]
         | 
| 101 | 
            -
                    return logger.new_context! unless context_id
         | 
| 102 | 
            -
                    logger.context_id = context_id
         | 
| 103 | 
            -
                  end
         | 
| 104 | 
            -
             | 
| 105 | 
            -
                  # Request logger
         | 
| 106 | 
            -
                  #
         | 
| 107 | 
            -
                  # Usage:
         | 
| 108 | 
            -
                  #   options log_requests: true
         | 
| 109 | 
            -
                  # Or:
         | 
| 110 | 
            -
                  #   options log_requests: { log_level: :info }
         | 
| 111 | 
            -
                  #
         | 
| 112 | 
            -
                  before do
         | 
| 113 | 
            -
                    opts = self.class.options[:log_requests]
         | 
| 114 | 
            -
                    next unless opts
         | 
| 115 | 
            -
                    message = "#{request.canonical_name}: #{params}"
         | 
| 116 | 
            -
                    level = opts.is_a?(Hash) ? (opts[:log_level] || 'debug') : 'debug'
         | 
| 117 | 
            -
                    logger.send level.to_sym, message
         | 
| 118 | 
            -
                  end
         | 
| 119 | 
            -
             | 
| 120 | 
            -
                  # Request benchmark logger
         | 
| 121 | 
            -
                  #
         | 
| 122 | 
            -
                  # Usage:
         | 
| 123 | 
            -
                  #   options log_benchmarks: true
         | 
| 124 | 
            -
                  # Or:
         | 
| 125 | 
            -
                  #   options log_benchmarks: { log_level: :info }
         | 
| 126 | 
            -
                  #
         | 
| 127 | 
            -
                  around do |b|
         | 
| 128 | 
            -
                    opts = self.class.options[:log_benchmarks]
         | 
| 129 | 
            -
                    t_start = Time.now
         | 
| 130 | 
            -
                    b.call
         | 
| 131 | 
            -
                    next unless opts
         | 
| 132 | 
            -
                    message = "#{request.canonical_name}: completed in %.1fms" % [(Time.now - t_start) * 1000.0]
         | 
| 133 | 
            -
                    level = opts.is_a?(Hash) ? (opts[:log_level] || 'debug') : 'debug'
         | 
| 134 | 
            -
                    logger.send level.to_sym, message
         | 
| 135 | 
            -
                  end
         | 
| 136 | 
            -
             | 
| 137 | 
            -
                  # Default error handler for StandardError and its descendants
         | 
| 138 | 
            -
                  #
         | 
| 139 | 
            -
                  error StandardError do |e|
         | 
| 140 | 
            -
                    logger.error "#{request.canonical_name}: #{e} (#{e.class})"
         | 
| 141 | 
            -
                    logger.debug((e.backtrace || ['<no backtrace>']).join("\n"))
         | 
| 142 | 
            -
                    halt
         | 
| 143 | 
            -
                  end
         | 
| 144 | 
            -
             | 
| 145 | 
            -
                  private
         | 
| 146 | 
            -
             | 
| 147 | 
            -
                  attr_reader :request
         | 
| 148 | 
            -
             | 
| 149 | 
            -
                  def __execute_method(method_name)
         | 
| 150 | 
            -
                    unless method_name && method_name.is_a?(String)
         | 
| 151 | 
            -
                      raise 'RequestProcessor method name is not specified in the request'
         | 
| 152 | 
            -
                    end
         | 
| 153 | 
            -
                    method_name = method_name.to_sym
         | 
| 154 | 
            -
                    unless self.class.exposed_methods.include?(method_name)
         | 
| 155 | 
            -
                      raise "RequestProcessor method (\##{method_name}) is not exposed"
         | 
| 156 | 
            -
                    end
         | 
| 157 | 
            -
             | 
| 158 | 
            -
                    method = self.class.instance_method(method_name.to_sym)
         | 
| 159 | 
            -
                    accepted_params = request.params_symbolized.only(*method.parameters.map(&:last))
         | 
| 160 | 
            -
                    result = nil
         | 
| 161 | 
            -
                    method_block = proc do
         | 
| 162 | 
            -
                      args = [method_name]
         | 
| 163 | 
            -
                      args << accepted_params unless accepted_params.empty?
         | 
| 164 | 
            -
                      catch(:halt) do
         | 
| 165 | 
            -
                        result = send(*args)
         | 
| 166 | 
            -
                      end
         | 
| 167 | 
            -
                    end
         | 
| 168 | 
            -
                    self.class.filters(:before).each { |f| __execute(&f[:block]) }
         | 
| 169 | 
            -
                    wrapped_block = self.class.filters(:around).reduce(method_block) do |a, e|
         | 
| 170 | 
            -
                      __bind(a, &e[:block])
         | 
| 171 | 
            -
                    end
         | 
| 172 | 
            -
                    wrapped_block.call
         | 
| 173 | 
            -
                    self.class.filters(:after, false).each { |f| __execute(&f[:block]) }
         | 
| 174 | 
            -
                    result
         | 
| 175 | 
            -
                  end
         | 
| 176 | 
            -
             | 
| 177 | 
            -
                  def __execute_error_handlers(error)
         | 
| 178 | 
            -
                    catch(:halt) do
         | 
| 179 | 
            -
                      result = self.class.filters(:error, false).reduce(error) do |a, e|
         | 
| 180 | 
            -
                        if e[:args].any? { |error_klass| a.is_a?(error_klass) }
         | 
| 181 | 
            -
                          __execute(a, &e[:block])
         | 
| 182 | 
            -
                        else
         | 
| 183 | 
            -
                          a
         | 
| 184 | 
            -
                        end
         | 
| 185 | 
            -
                      end
         | 
| 186 | 
            -
                      logger.error "Error '#{error}' (#{error.class}) unprocessed by error handlers " \
         | 
| 187 | 
            -
                        "(result=#{result.class})"
         | 
| 188 | 
            -
                    end
         | 
| 189 | 
            -
                  rescue StandardError => e
         | 
| 190 | 
            -
                    logger.error "Error raised by error handler '#{request.canonical_name}': #{e}"
         | 
| 191 | 
            -
                    logger.debug e.backtrace.join("\n")
         | 
| 192 | 
            -
                  end
         | 
| 193 | 
            -
             | 
| 194 | 
            -
                  def reply(_data = {})
         | 
| 195 | 
            -
                    logger.warn "#{self.class}#reply not implemented"
         | 
| 196 | 
            -
                    halt
         | 
| 197 | 
            -
                  end
         | 
| 198 | 
            -
             | 
| 199 | 
            -
                  def halt
         | 
| 200 | 
            -
                    throw :halt
         | 
| 201 | 
            -
                  end
         | 
| 202 | 
            -
             | 
| 203 | 
            -
                  def params
         | 
| 204 | 
            -
                    request.params
         | 
| 205 | 
            -
                  end
         | 
| 206 | 
            -
             | 
| 207 | 
            -
                  def logger
         | 
| 208 | 
            -
                    Mimi::Messaging.logger
         | 
| 209 | 
            -
                  end
         | 
| 210 | 
            -
             | 
| 211 | 
            -
                  def self.logger
         | 
| 212 | 
            -
                    Mimi::Messaging.logger
         | 
| 213 | 
            -
                  end
         | 
| 214 | 
            -
                end # class RequestProcessor
         | 
| 215 | 
            -
              end # module Messaging
         | 
| 216 | 
            -
            end # module Mimi
         |