GarageClient Build Status

GarageClient is a simple Ruby library to provide a primitive client interface to the Garage application API.

Install

Install from rake command:

$ bundle install
$ rake install

or modify Gemfile in your application and invoke bundle install.

# Gemfile
gem "garage_client"

Usage

Here are quick examples.

Client initialization

require "garage_client"

# First, you have to create a GarageClient::Client with an access token.
# You can get `YOUR_ACCESS_TOKEN` with OAuth 2.0 flow.
client = GarageClient::Client.new(access_token: YOUR_ACCESS_TOKEN)

Read from Garage application

# GET https://garage.example.com/v1/recipes
client.get("/recipes")

# GET https://garage.example.com/v1/recipes?fields=id,name,ingredients
client.get("/recipes", fields: "id,name,ingredients")

# GET https://garage.example.com/v1/me
user = client.get("/me")

# GET https://garage.example.com/v1/users/:user_id/recipes?fields=__default__,user[id,name]
client.get("/users/#{user.id}/recipes", fields: "__default__,user[id,name]")

Write to Garage application

# POST https://garage.example.com/v1/suggestions
client.post("/suggestions", message: "suggestion message")

# POST https://garage.example.com/v1/users/:user_id/bookmark_tags with JSON {"name":"tag name"}
bookmark_tag = client.post("/users/#{user.id}/bookmark_tags", name: "tag name")

# PUT https://garage.example.com/v1/bookmark_tags/:id with JSON {"name":"new tag name"}
client.put("/bookmark_tags/#{bookmark_tag.id}", name: "new tag name")

# DELETE https://garage.example.com/v1/bookmark_tags/:id
client.delete("/bookmark_tags/#{bookmark_tag.id}")

Response

# `.get` method returns a GarageClient::Response of a resource.
user = client.get("/me")
user.id
user.url
user.name

# `.get` method also returns a GarageClient::Response of an array of resources.
# In this case, the response object can respond to `.total_count` method.
recipes = client.get("/recipes")
recipes.total_count
recipes[0].id
recipes[0].name

# While Garage application API returns all default properties, some additional properties are not included in them.
# You can specify the returned properties by `?fields=...` URI query parameters.
recipes = client.get("/recipes", fields: "__default__,user")
recipes[0].id
recipes[0].name
recipes[0].user.id
recipes[0].user.url
recipes[0].user.name

# `.post` method also returns a GarageClient::Response of the newly created resource.
suggestion = client.post("/suggestions", message: "suggestion message")
suggestion.message

Configuration

There are the following options:

  • adapter - faraday adapter for http client (default: :net_http)
  • cacher - take a cacher class in which caching logic is defined (default: nil)
  • name - Client's application name, which is embedded in User-Agent by default (default: nil. For Rails, Rails.application.class.parent_name.underscore is set by default.)
    • name must be configured globally.
  • headers - default http headers (default: { "Accept" => "application/json", "User-Agent" => "garage_client #{VERSION} #{name}" })
  • endpoint - Garage application API endpoint (default: nil)
  • path_prefix - API path prefix (default: '/v1')
  • verbose - Enable verbose http log (default: false)
  • tracing - (client instance only) Enable distributed tracing and set a logical name of the down stream service. See detail in "Tracing" section below.

You can configure the global settings:

GarageClient.configure do |c|
  c.endpoint = "http://localhost:3000"
  c.name = 'my-awesome-client'
  c.verbose = true
end

or each GarageClient::Client settings:

client = GarageClient::Client.new(
  adapter: :test,
  headers: { "Host" => "garage.example.com" },
  endpoint: "http://localhost:3000",
  path_prefix: "/v2",
  verbose: true,
)

Exceptions

GarageClient raises one of the following exceptions upon an error. Make sure to always look out for these in your code.

GarageClient::BadRequest
GarageClient::Unauthorized
GarageClient::Forbidden
GarageClient::NotFound
GarageClient::NotAcceptable
GarageClient::Conflict
GarageClient::UnsupportedMediaType
GarageClient::UnprocessableEntity
GarageClient::InternalServerError
GarageClient::ServiceUnavailable
GarageClient::GatewayTimeout

Utility

.properties returns a list of properties of the resource.

user = client.get("/me")
user.properties #=> [:id, :url, :name, :_links]

.links returns a list of link names related to the resource.

user = client.get("/me")
user.properties #=> [:self, :bookmarks, :recipes, ...]
user.links.recipes #=> "https://garage.example.com/v1/users/:user_id/recipes"

Caching

Define a cacher class with your custom caching logic to let it cache response, inheriting GarageClient::Cachers::Base. It must override read_from_cache?, written_to_cache?, key, and store to compose your caching logic.

class MyCacher < GarageClient::Cachers::Base
  private

  def read_from_cache?
    has_get_method? && has_cached_path?
  end

  def written_to_cache?
    read_from_cache?
  end

  def key
    @env[:url].to_s
  end

  def store
    Rails.cache
  end

  def options
    { expires_in: 5.minutes }
  end

  def has_get_method?
    @env[:method] == :get
  end

  def has_cached_path?
    case @env[:url].path
    when %r<^/v1/searches>
      true
    when %r<^/v1/recipes/\d+>
      true
    end
  end
end

GarageClient::Client.new(cacher: MyCacher)

Tracing

GarageClient supports distributed tracing. To enable tracing, specify tracing.tracer option for GarageClient::Client instance. Choose one of supported tracers from below. If you want to add new tracer, please give us a PR.

aws-xray

Bundle aws-xray gem in your Gemfile, then configure GarageClient::Client instance with tracing.service option:

require 'aws/xray/faraday'
GarageClient::Client.new(..., tracing: { tracer: 'aws-xray', service: 'user' })

service will be name of the sub segments.