README.md

<p align="center">
<img src="https://raw.githubusercontent.com/oarrabi/exrequester/master/res/logo.png" width="500" align="middle"/>
<br/>
<br/>
<br/>
</p>
[![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/).
<br/>

## Installation

Add `exrequester` to your `mix.exs` deps
```elixir
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

```elixir
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
```elixir
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:
```elixir
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:
```elixir
SampleAPI.client("http://base_url.com")
|> SampleAPI.get_picture(resource_id: 123)
```

### Setting HTTP method
Define a get request endpoint
```elixir
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:
```elixir
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:
```elixir
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
```elixir
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
```elixir
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:
```elixir
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:
```elixir
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
```elixir
defmodule SampleAPI do
  use EXRequester

  @headers [
    Authorization: :auth,
    Key1: :key1
  ]
  @get "/path/to/resource/{resource_id}"
  defreq get_resource
end
```

Now to use it:
```elixir
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

```elixir
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:

```elixir
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:

```elixir
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:
```elixir
@get "/path/to/resource/{resource_id}"
defreq get_resource
```
If you wrongly call the method as:
```elixir
AMod.client("http://localhost:9090/")
|> AMod.get_resource(key: 123)
```

The following error will be raised:
```elixir
** (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