Packages

RestClient is a generic REST client library. It generates structs and functions for use with APIs.

Current section

Files

Jump to
rest_client lib rest_client.ex
Raw

lib/rest_client.ex

defmodule RestClient do
@moduledoc """
Provides functions to encapsulate a REST API into Elixir modules and
structs.
# Examples
defmodule Test do
api "http://test.com"
end
def Test.User do
resource Test, [:id, :username, :email]
end
This will give you the following functions:
* `Test.User.index/1`
* `Test.User.update/3`
* `Test.User.show/2`
* `Test.User.create/2`
* `Test.User.delete/2`
The first parameter is used to pass in authentication (currently we assume
http basic authentication), e.g.
Test.index {"myuser", "mypass"}
"""
@doc """
This marks the module as base module for the API.
# Examples
defmodule Test do
api "http://test.com"
end
This is needed for the resources you subsequently define to be able to
retrieve the API's base url.
"""
defmacro api(url) do
quote do
def url do
unquote url
end
end
end
defmacro resource(api, fields) do
quote do
defstruct unquote(fields)
@doc """
Updates an existing record of the resource via a HTTP PUT request to
http://apiurl.com/resources/id. The response is expected to be in JSON
and the default key is "data".
# Examples
defmodule Test do
api "http://test.com"
end
def Test.User do
resource Test, [:id, :username, :email]
end
`Test.User.update {"something", "pass123"}, "1", %Test.User{ username: "Peter Pan", email: "peter@pan123.com"}`
will issue a HTTP PUT request to `http://test.com/users/1` with the data
being form-urlencoded. Say that request returns
{
data: {
id: 1,
username: "Peter Pan",
email: "peter@pan123.com"
}
}
`Test.User.update/3` will return a `%Test.User` struct according to that
data.
"""
def update(auth, id, struct) do
HTTPotion.put("#{unquote(api).url}/#{path}/#{id}", basic_auth: auth,
body: struct_to_form_urlencoded(struct), headers: [
"Content-type": "application/x-www-form-urlencoded"
]).body
|> Poison.decode!(as: %{"data" => __MODULE__})
|> Map.get("data")
end
@doc """
Retrieves a record of the resource via a HTTP GET request to
http://apiurl.com/resources/id. The response is expected to be in JSON
and the default key is "data".
# Examples
defmodule Test do
api "http://test.com"
end
def Test.User do
resource Test, [:id, :username, :email]
end
`Test.User.show {"something", "pass123"}, "1"` will
issue a HTTP GET request to `http://test.com/users/1`. Say that request
returns
{
data: {
id: 1,
username: "Peter Pan",
email: "peter@pan123.com"
}
}
`Test.User.show/2` will return a `%Test.User` struct according to that
data.
"""
def show(auth, id) do
HTTPotion.get("#{unquote(api).url}/#{path}/#{id}",
basic_auth: auth).body
|> Poison.decode!(as: %{"data" => __MODULE__})
|> Map.get("data")
end
@doc """
Creates a new record of the resource via a HTTP POST request to
http://apiurl.com/resources. The response is expected to be in JSON
and the default key is "data".
# Examples
defmodule Test do
api "http://test.com"
end
def Test.User do
resource Test, [:id, :username, :email]
end
`Test.User.create {"something", "pass123"}, %Test.User{ username: "Peter Pan", email: "peter@pan123.com"}`
will issue a HTTP POST request to `http://test.com/users` with the data
being form-urlencoded. Say that request returns
{
data: {
id: 1,
username: "Peter Pan",
email: "peter@pan123.com"
}
}
`Test.User.create/2` will return a `%Test.User` struct according to that
data.
"""
def create(auth, struct) do
HTTPotion.post("#{unquote(api).url}/#{path}", basic_auth: auth,
body: struct_to_form_urlencoded(struct), headers: [
"Content-type": "application/x-www-form-urlencoded"
]).body
|> Poison.decode!(as: %{"data" => __MODULE__})
|> Map.get("data")
end
@doc """
Deletes a record of the resource via a HTTP DELETE request to
http://apiurl.com/resources/id. The response is expected to return a
status code if 200, otherwise the method returns false.
# Examples
defmodule Test do
api "http://test.com"
end
def Test.User do
resource Test, [:id, :username, :email]
end
`Test.User.delete {"something", "pass123"}, "1"` will
issue a HTTP DELETE request to `http://test.com/users/1`. Say that
request returns a status code of 200. Then `Test.User.delete/2` will
return true.
"""
def delete(auth, id) do
200 == HTTPotion.delete("#{unquote(api).url}/#{path}/#{id}",
basic_auth: auth).status_code
end
@doc """
Retrieves all records of the resource via a HTTP GET request to
http://apiurl.com/resources. The response is expected to be in JSON
and the default key is "data".
# Examples
defmodule Test do
api "http://test.com"
end
def Test.User do
resource Test, [:id, :username, :email]
end
`Test.User.index {"something", "pass123"}` will
issue a HTTP GET request to `http://test.com/users`. Say that request
returns
{
data: [
{
id: 1,
username: "Peter Pan",
email: "peter@pan123.com"
},
{
id: 2,
username: "Petra Paniol",
email: "petra@paniol.com"
}
]
}
`Test.User.index/1` will return an array of `%Test.User` structs
according to that data.
"""
def index(auth) do
HTTPotion.get("#{unquote(api).url}/#{path}", basic_auth: auth).body
|> Poison.decode!(as: %{"data" => [__MODULE__]})
|> Map.get("data")
end
defp struct_to_form_urlencoded(struct) do
struct
|> Map.to_list
|> Keyword.delete(:__struct__)
|> Enum.reject(fn({k, v}) -> v == nil end)
|> Enum.map(fn({k, v}) -> "#{k}=#{v}" end)
|> Enum.join("&")
end
@doc """
The path for this resource. It defaults to the pluralized,
downcased last part of the module, e.g. "Test.User" becomes "users".
# Example
defmodule Test do
api "http://test.com"
end
def Test.User do
resource Test, [:id, :username, :email]
end
Test.User.default_path == "users"
"""
def path do
Regex.run(~r/(?<=\.)[A-Za-z]*$/, Atom.to_string(__MODULE__))
|> List.first
|> String.downcase
|> Inflex.pluralize
end
end
end
end