lib/posthog/message_batch.rb



# frozen_string_literal: true

require 'forwardable'
require 'posthog/logging'

module PostHog
  # A batch of `Message`s to be sent to the API
  class MessageBatch
    class JSONGenerationError < StandardError
    end

    extend Forwardable
    include PostHog::Logging
    include PostHog::Defaults::MessageBatch

    def initialize(max_message_count)
      @messages = []
      @max_message_count = max_message_count
      @json_size = 0
    end

    def <<(message)
      begin
        message_json = message.to_json
      rescue StandardError => e
        raise JSONGenerationError, "Serialization error: #{e}"
      end

      message_json_size = message_json.bytesize
      if message_too_big?(message_json_size)
        logger.error('a message exceeded the maximum allowed size')
      else
        @messages << message
        @json_size += message_json_size + 1 # One byte for the comma
      end
    end

    def full?
      item_count_exhausted? || size_exhausted?
    end

    def clear
      @messages.clear
      @json_size = 0
    end

    def_delegators :@messages, :to_json
    def_delegators :@messages, :empty?
    def_delegators :@messages, :length

    private

    def item_count_exhausted?
      @messages.length >= @max_message_count
    end

    def message_too_big?(message_json_size)
      message_json_size > Defaults::Message::MAX_BYTES
    end

    # We consider the max size here as just enough to leave room for one more
    # message of the largest size possible. This is a shortcut that allows us
    # to use a native Ruby `Queue` that doesn't allow peeking. The tradeoff
    # here is that we might fit in less messages than possible into a batch.
    #
    # The alternative is to use our own `Queue` implementation that allows
    # peeking, and to consider the next message size when calculating whether
    # the message can be accomodated in this batch.
    def size_exhausted?
      @json_size >= (MAX_BYTES - Defaults::Message::MAX_BYTES)
    end
  end
end