Build StatusHex.pmAPI DocsCoverage StatusInline docs

EXRequester

Quickly create API clients using module attributes, inspired by 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.

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 define a parse function/block to be used as a parser for the resceived response. The parser can be set in three ways.

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.

Second: By defining a body to the get_resource function, inside this body, you can use response object which will be injected by the macro

defmodule SampleAPI do
  ....
  defreq get_resource do
    "Value is " <> response.body
  end
end

response will be set by the macro to the value of the EXRequester.Response received.

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