Class: Postio::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/postio/client.rb

Overview

Postio::Client is the synchronous Postio API client.

Example:

client = Postio::Client.new(api_key: "pk_live_...")
r = client.address.search("downing street")
r.results.each { |hit| puts "#{hit.udprn}: #{hit.suggestion}" }

The API key may also come from the POSTIO_API_KEY environment variable.

Constant Summary collapse

DEFAULT_BASE_URL =
"https://api.postio.co.uk/v1"
DEFAULT_TIMEOUT =
10
RETRYABLE_STATUSES =
[408, 409, 429, 500, 502, 503, 504].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(api_key: nil, base_url: DEFAULT_BASE_URL, timeout: DEFAULT_TIMEOUT, retries: 2, headers: {}) ⇒ Client

Returns a new instance of Client.

Raises:

  • (ArgumentError)


29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/postio/client.rb', line 29

def initialize(api_key: nil, base_url: DEFAULT_BASE_URL, timeout: DEFAULT_TIMEOUT,
               retries: 2, headers: {})
  @api_key = api_key || ENV["POSTIO_API_KEY"]
  raise ArgumentError, "Postio: api_key is required (pass api_key: ... or set POSTIO_API_KEY)" if @api_key.nil? || @api_key.empty?

  @base_url       = base_url.chomp("/")
  @timeout        = timeout
  @retries        = retries
  @extra_headers  = headers

  @address = AddressResource.new(self)
  @email   = EmailResource.new(self)
  @phone   = PhoneResource.new(self)
end

Instance Attribute Details

#addressObject (readonly)

Returns the value of attribute address.



27
28
29
# File 'lib/postio/client.rb', line 27

def address
  @address
end

#emailObject (readonly)

Returns the value of attribute email.



27
28
29
# File 'lib/postio/client.rb', line 27

def email
  @email
end

#phoneObject (readonly)

Returns the value of attribute phone.



27
28
29
# File 'lib/postio/client.rb', line 27

def phone
  @phone
end

Instance Method Details

#connectObject

Health probe — confirms the API is reachable and the key is valid.



45
46
47
# File 'lib/postio/client.rb', line 45

def connect
  Models::ConnectSuccess.from_hash(request("/connect"))
end

#request(path, query: {}) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/postio/client.rb', line 50

def request(path, query: {})
  uri = URI(@base_url + path)
  params = query.compact.transform_values(&:to_s)
  uri.query = URI.encode_www_form(params) unless params.empty?

  max_attempts = @retries + 1
  last_error   = nil

  max_attempts.times do |attempt|
    begin
      response = perform_http(uri)
    rescue Net::OpenTimeout, Net::ReadTimeout => e
      last_error = TimeoutError.new("Request timed out.", error_code: "request_timeout", cause: e)
      raise last_error if attempt == max_attempts - 1

      sleep(backoff(attempt))
      next
    rescue StandardError => e
      last_error = ConnectionError.new("Network error: #{e.message}", error_code: "network_error", cause: e)
      raise last_error if attempt == max_attempts - 1

      sleep(backoff(attempt))
      next
    end

    body = parse_body(response)

    if response.is_a?(Net::HTTPSuccess)
      return body
    end

    # Non-2xx — retryable status?
    if RETRYABLE_STATUSES.include?(response.code.to_i) && attempt < max_attempts - 1
      last_error = build_error(response, body)
      sleep(backoff(attempt))
      next
    end

    raise build_error(response, body)
  end

  # Unreachable — loop above either returns or raises.
  raise(last_error || Error.new("Postio: retry loop exhausted unexpectedly."))
end