blazeverify/blazeverify-ruby

View on GitHub
lib/emailable/client.rb

Summary

Maintainability
A
0 mins
Test Coverage
module Emailable
  class Client
    ERRORS = {
      400 => BadRequestError,
      401 => UnauthorizedError,
      402 => PaymentRequiredError,
      403 => ForbiddenError,
      404 => NotFoundError,
      429 => TooManyRequestsError,
      500 => InternalServerError,
      503 => ServiceUnavailableError
    }.freeze

    def initialize
      @base_url = 'https://api.emailable.com/v1'
      @connection = create_connection(URI(@base_url))
    end

    def request(method, endpoint, params = {})
      begin
        tries ||= 3

        uri = URI("#{@base_url}/#{endpoint}")
        params[:api_key] = Emailable.api_key

        http_response =
          if method == :get
            uri.query = URI.encode_www_form(params)
            @connection.get(uri)
          elsif method == :post
            request = Net::HTTP::Post.new(uri, 'Content-Type': 'application/json')
            request.body = params.to_json
            @connection.request(request)
          end

        response = Response.new(http_response)
      rescue => e
        retry if (tries -= 1) > 0 && should_retry?(e, tries)

        raise e
      end

      status = response.status
      return response if status.between?(200, 299)

      raise ERRORS[status].new(response.body['message'])
    end

    private

    def create_connection(uri)
      connection = Net::HTTP.new(uri.host, uri.port)

      # Time in seconds within which Net::HTTP will try to reuse an already
      # open connection when issuing a new operation. Outside this window, Ruby
      # will transparently close and re-open the connection without trying to
      # reuse it.
      #
      # Ruby's default of 2 seconds is almost certainly too short. Here I've
      # reused Go's default for `DefaultTransport`.
      connection.keep_alive_timeout = 30

      connection.open_timeout = Emailable.open_timeout
      connection.read_timeout = Emailable.read_timeout
      if connection.respond_to?(:write_timeout=)
        connection.write_timeout = Emailable.write_timeout
      end
      connection.use_ssl = uri.scheme == "https"

      connection
    end

    def should_retry?(error, num_retries)
      return false if num_retries >= Emailable.max_network_retries

      case error
      when Net::OpenTimeout, Net::ReadTimeout
        # Retry on timeout-related problems (either on open or read).
        true
      when EOFError, Errno::ECONNREFUSED, Errno::ECONNRESET,
        Errno::EHOSTUNREACH, Errno::ETIMEDOUT, SocketError
        # Destination refused the connection, the connection was reset, or a
        # variety of other connection failures. This could occur from a single
        # saturated server, so retry in case it's intermittent.
        true
      when Net::HTTPError
        # 409 Conflict
        return true if error.http_status == 409

        # 429 Too Many Requests
        return true if error.http_status == 429

        # 500 Internal Server Error
        return true if error.http_status == 500

        # 503 Service Unavailable
        error.http_status == 503
      else
        false
      end
    end

  end
end