lib/airbrake-ruby/response.rb



module Airbrake
  # Parses responses coming from the Airbrake API. Handles HTTP errors by
  # logging them.
  #
  # @api private
  # @since v1.0.0
  module Response
    # @return [Integer] the limit of the response body
    TRUNCATE_LIMIT = 100

    # @return [Integer] HTTP code returned when an IP sends over 10k/min notices
    TOO_MANY_REQUESTS = 429

    class << self
      include Loggable
    end

    # Parses HTTP responses from the Airbrake API.
    #
    # @param [Net::HTTPResponse] response
    # @return [Hash{String=>String}] parsed response
    # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
    def self.parse(response)
      code = response.code.to_i
      body = response.body

      begin
        case code
        when 200, 204
          logger.debug("#{LOG_LABEL} #{name} (#{code}): #{body}")
          { response.msg => response.body }
        when 201
          parsed_body = JSON.parse(body)
          logger.debug("#{LOG_LABEL} #{name} (#{code}): #{parsed_body}")
          parsed_body
        when 400, 401, 403, 420
          parsed_body = JSON.parse(body)
          logger.error("#{LOG_LABEL} #{parsed_body['message']}")
          parsed_body
        when TOO_MANY_REQUESTS
          parsed_body = JSON.parse(body)
          msg = "#{LOG_LABEL} #{parsed_body['message']}"
          logger.error(msg)
          { 'error' => msg, 'rate_limit_reset' => rate_limit_reset(response) }
        else
          body_msg = truncated_body(body)
          logger.error("#{LOG_LABEL} unexpected code (#{code}). Body: #{body_msg}")
          { 'error' => body_msg }
        end
      rescue StandardError => ex
        body_msg = truncated_body(body)
        logger.error("#{LOG_LABEL} error while parsing body (#{ex}). Body: #{body_msg}")
        { 'error' => ex.inspect }
      end
    end
    # rubocop:enable Metrics/MethodLength, Metrics/AbcSize

    def self.truncated_body(body)
      if body.nil?
        '[EMPTY_BODY]'.freeze
      elsif body.length > TRUNCATE_LIMIT
        body[0..TRUNCATE_LIMIT] << '...'
      else
        body
      end
    end
    private_class_method :truncated_body

    def self.rate_limit_reset(response)
      Time.now + response['X-RateLimit-Delay'].to_i
    end
    private_class_method :rate_limit_reset
  end
end