[![Build Status](https://travis-ci.org/oarrabi/exrequester.svg?branch=master)](https://travis-ci.org/oarrabi/exrequester) [![Hex.pm](https://img.shields.io/hexpm/v/exrequester.svg)](https://hex.pm/packages/exrequester) [![API Docs](https://img.shields.io/badge/api-docs-yellow.svg?style=flat)](http://hexdocs.pm/exrequester/) [![Coverage Status](https://coveralls.io/repos/github/oarrabi/exrequester/badge.svg?branch=master)](https://coveralls.io/github/oarrabi/exrequester?branch=master) [![Inline docs](http://inch-ci.org/github/oarrabi/exrequester.svg?branch=master)](http://inch-ci.org/github/oarrabi/exrequester) # EXRequester Quickly create API clients using module attributes, inspired by [retrofit](http://square.github.io/retrofit/).

Installation

Add exrequester to your mix.exs deps

def deps do
[{:exrequester, "~> 0.1.0"}]
end

Then fetch your project's dependencies:

$ mix deps.get

Usage

Start by adding use EXRequester in your module

defmodule SampleAPI do
use EXRequester
end

This will make defreq/1 macro available. This macro takes a function name and a set of parametrs as its argument

defmodule SampleAPI do
use EXRequester
@get "/path/to/resource/{resource_id}"
defreq get_picture
end

The above is the simplest form to define an api function.

Compiling the above will make the following functions available:

defmodule SampleAPI do
def client(base_url)
def get_picture(client, resource_id: resource_id)
end

For example, to call get_picture you would do this:

SampleAPI.client("http://base_url.com")
|> SampleAPI.get_picture(resource_id: 123)

Setting HTTP method

Define a get request endpoint

defmodule SampleAPI do
use EXRequester
@get "/path/to/resource/{resource_id}"
defreq get_resource
@delete "/path/to/resource/{resource_id}"
defreq delete_picture
end

Now to use it:

SampleAPI.client("http://base_url.com")
|> SampleAPI.get_resource(resource_id: 123)
SampleAPI.client("http://base_url.com")
|> SampleAPI.post_picture(resource_id: 123, body: %{key: value})

This will hit http://base_url.com/path/to/resource/123

Available http methods are:

defmodule SampleAPI do
use EXRequester
@get "/path/to/resource/{resource_id}"
defreq get_resource
@put "/path/to/resource/{resource_id}"
defreq put_resource
@delete "/path/to/resource/{resource_id}"
defreq delete_resource
end

Handling request body

Body is handled as a normal parameter

defmodule SampleAPI do
use EXRequester
@post "/path/to/resource/{resource_id}"
defreq post_picture
end

Body is handled in a special way based on its type.

SampleAPI.post_picture(resource_id: 123, body: ["1", "2"])
SampleAPI.post_picture(resource_id: 123, body: %{key: value})

Will send json: [\"1\", \"2\"] and {\"key\":\"value\"}

Handling query

To add query to your api endpoint you would use the following:

defmodule SampleAPI do
use EXRequester
@query [:sort, :filter]
@get "/path/to/resource/{resource_id}"
defreq get_resource
end

You now can call the function defined with all, some or none of the query values:

SampleAPI.client("http://base_url.com")
|> SampleAPI.get_resource(resource_id: 123)
SampleAPI.client("http://base_url.com")
|> SampleAPI.get_resource(resource_id: 123, sort: "ascending")
SampleAPI.client("http://base_url.com")
|> SampleAPI.get_resource(resource_id: 123, sort: "ascending", filter: "all")

These will hit the following endpoint in order:

http://base_url.com/path/to/resource/123
http://base_url.com/path/to/resource/123?sort=ascending
http://base_url.com/path/to/resource/123?sort=ascending&filter=all

Setting headers

Dynamic Headers

defmodule SampleAPI do
use EXRequester
@headers [
Authorization: :auth,
Key1: :key1
]
@get "/path/to/resource/{resource_id}"
defreq get_resource
end

Now to use it:

SampleAPI.client("http://base_url.com")
|> SampleAPI.get_resource(resource_id: 123, auth1: "1", key1: "2")

This will hit http://base_url.com/path/to/resource/123 The Authorization and Key1 headers will also be set.

Static Headers

Static headers are defined by using strings, instead of atom, in the @headers definition

defmodule SampleAPI do
use EXRequester
@headers [
Authorization: :auth,
Accept: "application/json",
"Accept-Language": "en-US"
]
@get "/path/to/resource/{resource_id}"
defreq get_resource
end

Calling SampleAPI.get_resource will perform a request that always sends these headers:

Accept: application/json
Accept-Language: en-US

Notice the use of quotes in the "Accept-Language". This is needed since Accept-Language is not a valid atom name. In order to solve that, add quotation around atoms.

Decoding HTTP Response

EXRequester allows you to pass an anonymous function to be used as response parser. This anonymouse function can be definied in two places.

First, You can pass the anonymouse function at the function definition, For example:

defmodule SampleAPI do
....
defreq get_resource(fn response ->
"Value is " <> response.body
end)
end

When calling get_resource the HTTP response of type EXRequester.Response will be sent to the passed anonymous function. Using this way, you can create a response decoder in place.

Alternatively, you can pass a response decoder when calling the method pass a decoder as a parameter when calling get_resource For example:

SampleAPI.client("http://base_url.com")
|> SampleAPI.get_resource(resource_id: 123, auth: "1", decoder: fn response ->
# Parse the response and return a new one
"Response is " <> response.body
end)

The anonymous function passed to decoder will receive an EXRequester.Response structure. The anonymous function can parse the response and return a new response. The returned new parsed response will finally returned from get_resource.

In the above example, the return value will be "Response is The body content"

Handle response

Hitting any request will return a EXRequester.Response strucutre. This structure contains headers, status_code and body

The body will not be parsed and will be returned as is.

Runtime safty

When calling the wrong method at runtime, exrequester will fail with a descriptive message.

For example:

@get "/path/to/resource/{resource_id}"
defreq get_resource

If you wrongly call the method as:

AMod.client("http://localhost:9090/")
|> AMod.get_resource(key: 123)

The following error will be raised:

** (RuntimeError) You are trying to call the wrong function
get_resource(client, key: key)
please instead call:
get_resource(client, resource_id: resource_id)

The error will inform you about the correct method invocation

Future improvments