All Projects → nsomar → exrequester

nsomar / exrequester

Licence: other
Quickly create API clients using module attributes.

Programming Languages

elixir
2628 projects




[![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.

  • @get is used to define the relative path that will be fetched
  • {resource_id} the resource id to use, this has to be passed when calling the 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
  • client/1 is used to set hte base url that will be used in get picture
  • get_picture/2 will execute the url, it takes the client and the parameters specified in the call to defreq get_picture...

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.

  • String bodyis sent as is
  • List and map bodies is Json encode
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\"}

  • Keyword list are currently ignored and will send an empty body

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

  • Ability to set the URL in the function definition instead
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].