Project

johac

0.0
No commit activity in last 3 years
No release in over 3 years
Opintionated library for implementing HTTP+JSON API clients
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

>= 4.7.5, ~> 4.7
~> 1.4
>= 10.4.2, ~> 10.4
~> 3.3.0
~> 0.9.11

Runtime

>= 0.9.2, ~> 0.15.2
>= 2.9.4, ~> 2.9
 Project Readme

Johac - JSON Over HTTP API Client

build status

Opinionated HTTP client which provides good defaults for your API Client. Defaults:

  • Requests/Responses are JSON
  • Retry up to 2 times on connection errors, and HTTP errors (429, 503, 504)
  • Simple backoff on retries
  • Configurable exception handling (raise or return)

Configuring Defaults

Johac.defaults do |config|
  config.env = 'development'

  # Determine if connection errors, and HTTP errors should raise an exception
  # or return a Johac::Response wrapping the exception
  config.raise_exceptions = false
  config.base_uri = 'https://api.myservice.com'
end

Building an API Client

class MyAPIClient < Johac::Client

  def initialize(uri)
    super({
      base_uri: uri,
      raise_exceptions: false
    })
  end

  # Returns a Johac::Response which wraps the response, or exception
  def my_api_call(myparam)
    get("/path", {
      query: {
        param: myparam
      }
    })
  end

end

client = MyAPIClient.new('https://api.service.com')

Working with Responses

The Response class wraps a faraday response or exception, and provides methods for checking status, handling the exception, response, etc.

response = client.my_api_call('param')

# was there a networking issue, or HTTP status code >=400 ?
response.error?

# Access the underlying exception
response.exception

# Access the body of the response, or error response (json)
response.body

# http status or 0 if no response
response.status
response.code

Helpers

# helper to dig the response content, only if hash and success
response.dig('item', 'attribute')

# Map the body object to a domain model, or nil if error?
response.map { |hash| MyModel.new(hash) }

response.map_error { |exception| false }

# Get an ostruct for the body (empty struct if missing)
response.object

Capturing Requests

Johac has some helpers to capture requests and emit faraday Env structs or curl commands

client.capture { |client|
  client.my_api_call({ foo: 'bar' })
}.each { |env|
  puts env.inspect # Faraday::Env
}

client.capture_curl { |client|
  client.my_api_call({ foo: 'bar' })
}.each { |curl|
  puts curl # curl -s -XMETHOD -H'..' 'uri...' [-d '{ ... }']
}

The Endpoints Pattern

I sometimes like to organize calls into logical namespaces, then include them in the main client API. Here is a simple example.

module ModelOneEndpoints

  def get_ones
    get("/api/ones")
  end

end

module ModelTwoEndpoints

  def get_twos
    get("/api/twos")
  end

end

class MyClient < Johac::Client

  include ModelOneEndpoints
  include ModelTwoEndpoints

  def initialize(options)
    super(options)
  end

end

client = MyClient.new({})
client.get_twos.error?