Skip to content

Instantly share code, notes, and snippets.

@zenhob
Created April 4, 2014 03:57

Revisions

  1. zenhob created this gist Apr 4, 2014.
    98 changes: 98 additions & 0 deletions generic_api.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,98 @@
    #
    # This module is a starter Ruby client for your REST API, based on Faraday:
    # https://github.com/lostisland/faraday/
    #
    # It supports HTTP basic authentication, sending and recieving JSON, and simple error reporting.
    # I use this as a basis for building API clients in most of my Ruby projects.
    #
    # Use it like this:
    #
    # http = GenericApi.connect('http://localhost:3000/', 'myname', 'secret')
    # http.get('boxes.json').body.each do |box|
    # puts "found a box named #{box[:name]}! Let's put something inside."
    # http.post('boxes/#{box[:id]}.json', contents: 'something!')
    # end
    #
    # A few hints for getting started:
    #
    # This uses Patron as the HTTP client but you can change those constants to use
    # another adapter or set to nil to use the built-in default.
    #
    # The response body is a Hash with Symbol keys (not String!).
    # If you're using ActiveSupport, you should use a hash with indifferent access instead.
    #
    # The default MIME type is appropriate for most purposes, but some APIs use it for
    # versioning so bear that in mind and change MIME_TYPE if necessary.
    #
    # Warmest regards,
    #
    # Zack Hobson <[email protected]>
    #
    require 'faraday'
    module GenericApi
    FARADAY_ADAPTER = :patron
    REQUIRE_DEPENDENCIES = ['patron']
    MIME_TYPE = 'application/json'

    # Connect and return a `Faraday::Connection` instance:
    # http://rdoc.info/github/lostisland/faraday/Faraday/Connection
    #
    # If login and password are provided, the connection will use basic auth.
    def self.connect url, login=nil, password=nil
    Faraday.new(url) do |f|
    f.use :generic_api, login, password
    f.adapter(FARADAY_ADAPTER || Faraday.default_adapter)
    end
    end

    class Middleware < Faraday::Request::BasicAuthentication
    Faraday::Middleware.register_middleware generic_api: ->{ self }

    dependency do
    require 'yajl'
    (REQUIRE_DEPENDENCIES||[]).each {|dep| require dep }
    end

    def call(env)
    # encode with and accept json
    env[:request_headers]['Accept'] = MIME_TYPE
    env[:request_headers]['Content-Type'] = MIME_TYPE
    env[:body] = Yajl::Encoder.encode(env[:body])

    # response processing
    super(env).on_complete do |env|
    begin
    env[:body] = Yajl::Parser.parse(env[:body], symbolize_keys:true)
    # XXX or, if you're using ActiveSupport:
    # env[:body] = Yajl::Parser.parse(env[:body]).with_indifferent_access
    rescue Yajl::ParseError
    raise ApiError.new("Unable to parse the response:\n#{env[:body]}", env)
    end
    case env[:status]
    when 300..399
    raise RedirectError.new(env[:body][:message], env)
    when 400..499
    raise AuthError.new(env[:body][:message], env)
    when 500..599
    raise ApiError.new(env[:body][:message], env)
    end
    end
    end
    end
    class ApiError < Faraday::ClientError
    def initialize(message, response)
    super("Upstream API failure: #{message||'Internal error.'}", response)
    end
    end
    class AuthError < Faraday::ClientError
    def initialize(message, response)
    super("Authentication failure: #{message||'No reason given.'}", response)
    end
    end
    class RedirectError < Faraday::ClientError
    def initialize(message, response)
    super("Redirected: #{message||'No reason given.'}", response)
    end
    end
    end