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.
@getis 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/1is used to set hte base url that will be used in get pictureget_picture/2will execute the url, it takes the client and the parameters specified in the call todefreq 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 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
- Ability to set the URL in the function definition instead