Current section
Files
Jump to
Current section
Files
lib/exhal.ex
defmodule ExHal do
@moduledoc """
Use HAL APIs with ease.
## Example
```elixir
iex> doc = ExHal.parse(~s|
...> { "name": "Hello!",
...> "_links": {
...> "self" : { "href": "http://example.com" },
...> "profile": [{ "href": "http://example.com/special" },
...> { "href": "http://example.com/normal" }]
...> }
...> }
...> |)
%ExHal.Document{headers: [], links: %{"profile" => [%ExHal.Link{name: nil, rel: "profile", target: nil,
href: "http://example.com/normal", templated: false},
%ExHal.Link{name: nil, rel: "profile", target: nil, href: "http://example.com/special",
templated: false}],
"self" => [%ExHal.Link{name: nil, rel: "self", target: nil, href: "http://example.com",
templated: false}]}, properties: %{"name" => "Hello!"}}
iex> ExHal.url(doc)
{:ok, "http://example.com"}
iex> ExHal.fetch(doc, "name")
{:ok, "Hello!"}
iex> ExHal.fetch(doc, "non-existent")
:error
iex> ExHal.fetch(doc, "profile")
{:ok,
[%ExHal.Link{name: nil, rel: "profile", target: nil,
href: "http://example.com/normal",
templated: false},
%ExHal.Link{name: nil, rel: "profile", target: nil,
href: "http://example.com/special",
templated: false}]}
iex> ExHal.get_links_lazy(doc, "profile", fn -> [] end)
[%ExHal.Link{name: nil, rel: "profile", target: nil,
href: "http://example.com/normal",
templated: false},
%ExHal.Link{name: nil, rel: "profile", target: nil,
href: "http://example.com/special",
templated: false}]
iex> ExHal.get_links_lazy(doc, "alternate", fn -> [] end)
[]
```
ExHal can also make requests. Continuing the example above:
```elixir
ExHal.follow_link(doc, "profile")
{:error, %ExHal.Error{reason: "multiple choices"}}
ExHal.follow_link(doc, "nonexistent")
{:error, %ExHal.Error{reason: "no such link"}}
ExHal.follow_link("self")
{:ok, %ExHal.Document{...}}
ExHal.follow_link(doc, "profile", pick_volunteer: true)
{:ok, %ExHal.Document{...}}
ExHal.follow_links(doc, "profile")
[{:ok, %ExHal.Document{...}}, {:ok, %ExHal.Document{...}}]
ExHal.follow_links(doc, "profile", headers: ["Content-Type": "application/vnd.custom.json+type"])
[{:ok, %ExHal.Document{...}}, {:ok, %ExHal.Document{...}}]
ExHal.post(doc, "self", ~s|
...> { "name": "http://example.com/new-thing",
...> "_links": {
...> "self": { "href": "http://example.com/new-thing" }
...> }
...> }
...> |)
{:ok, %ExHal.Document{...}}
```
"""
alias ExHal.Link, as: Link
alias ExHal.Document, as: Document
alias ExHal.Error, as: Error
@doc """
Returns a new `%ExHal.Document` representing the HAL document provided.
"""
def parse(hal_str, opts \\ %{}) do
parsed = Poison.Parser.parse!(hal_str)
Document.from_parsed_hal(parsed, opts)
end
@doc """
Follows a link in a HAL document.
Returns `{:ok, %ExHal.Document{...}}` if request is an error
`{:error, %ExHal.Error{...}}` if not
"""
def follow_link(a_doc, name, opts \\ %{tmpl_vars: %{}, pick_volunteer: false, headers: []}) do
opts = Map.new(opts)
pick_volunteer? = Map.get opts, :pick_volunteer, false
case figure_link(a_doc, name, pick_volunteer?) do
{:error, e} -> {:error, e}
{:ok, link} -> Link.follow(link, opts)
end
end
@doc """
Follows all links of a particular rel in a HAL document.
Returns `[{:ok, %ExHal.Document{...}}, {:error, %ExHal.Error{...}, ...]`
"""
def follow_links(a_doc, name, opts \\ %{tmpl_vars: %{}, headers: []}) do
opts = Map.new(opts)
case get_links_lazy(a_doc, name, fn -> :missing end) do
:missing -> {:error, %Error{reason: "no such link: #{name}"}}
links -> Enum.map(links, fn link -> Link.follow(link, opts) end)
end
end
@doc """
Posts data to the named link in a HAL document.
Returns `{:error, %ExHal.Error{...}}` if request is an error
`{:ok, %ExHal.Document{...}}` if not
"""
def post(a_doc, name, body) do
case figure_link(a_doc, name, false) do
{:error, e} -> {:error, e}
{:ok, link} -> Link.post(link, body)
end
end
@doc """
Fetches value of specified property or links whose `rel` matches
Returns `{:ok, <property value>}` if `name` identifies a property;
`{:ok, [%Link{}, ...]}` if `name` identifies a link;
`:error` othewise
"""
def fetch(a_document, name) do
case get_lazy(a_document, name, fn -> :error end) do
:error -> :error
result -> {:ok, result}
end
end
@doc """
Returns link or property of the specified name, or the result of `default_fun`
if neither are found.
"""
def get_lazy(a_doc, name, default_fun) do
get_property_lazy(a_doc, name,
fn -> get_links_lazy(a_doc, name, default_fun) end
)
end
@doc """
Returns `<property value>` when property exists
result of `default_fun` otherwise
"""
def get_property_lazy(a_doc, prop_name, default_fun) do
Map.get_lazy(a_doc.properties, prop_name, default_fun)
end
@doc """
Returns `[%Link{}...]` when link exists
result of `default_fun` otherwise
"""
def get_links_lazy(a_doc, link_name, default_fun) do
Map.get_lazy(a_doc.links, link_name, default_fun)
end
@doc """
Returns `{:ok, <url of specified document>}`
`:error`
"""
def url(a_doc, default_fn \\ fn (_doc) -> :error end) do
case ExHal.fetch(a_doc, "self") do
:error -> default_fn.(a_doc)
{:ok, [link | _]} -> Link.target_url(link)
end
end
defp figure_link(a_doc, name, pick_volunteer?) do
case get_links_lazy(a_doc, name, fn -> :missing end) do
:missing -> {:error, %Error{reason: "no such link: #{name}"}}
(ls = [_|[_|_]]) -> if pick_volunteer? do
{:ok, List.first(ls)}
else
{:error, %Error{reason: "multiple choices"}}
end
[l] -> {:ok, l}
end
end
end