Current section
Files
Jump to
Current section
Files
lib/ex_link_header.ex
defmodule ExLinkHeader do
@moduledoc """
HTTP link header parser
"""
@regex_format ~r{<?(.+)>; (.+)}
defmodule ParseError do
defexception [:message]
def exception(msg), do: %__MODULE__{message: msg}
end
def parse(""), do: :error
def parse(nil), do: :error
def parse(link_header), do: parse_links(link_header)
def parse!(link_header) do
case parse(link_header) do
{:ok, result} -> result
:error -> raise ParseError, "Parse error: no valid links to parse"
end
end
defp extract(links) do
Enum.filter_map(links, fn(link) ->
Regex.match?(@regex_format, link)
end, fn(link) ->
[_, url, params] = Regex.run(@regex_format, link)
{url, parse_params(params)}
end)
end
defp extract_relationships(links) when links == [], do: nil
defp extract_relationships(links) do
links
|> Enum.flat_map(fn(link) ->
{url, params} = link
%{rel: rels} = params
rels
|> String.split(" ", trim: true)
|> Enum.map(fn(rel) ->
{url, Map.merge(params, %{rel: rel})}
end)
end)
end
defp filter(nil), do: nil
defp filter(links) do
Enum.filter(links, fn(link) ->
{url, params} = link
valid_url?(url) && has_rel?(params)
end)
end
defp format(nil), do: parse(nil)
defp format(links) do
formatted_links = links
|> Enum.map(fn(link) ->
{url, params} = link
%{rel: rel} = params
page = case Regex.run(~r{.+[\?|\&]page=(\d+)\??}, url) do
[_, page] -> page
nil -> nil
end
per_page = case Regex.run(~r{.+[\?|\&]per_page=(\d+)\??}, url) do
[_, per_page] -> per_page
nil -> nil
end
{rel, Map.merge(%{page: page, per_page: per_page, url: url}, params)}
end)
|> Enum.into(%{})
{:ok, formatted_links}
end
defp has_rel?(params) do
Map.has_key?(params, :rel)
end
defp parse_links(links) do
links
|> String.split(~r{,\s*<}, trim: true)
|> extract
|> filter
|> extract_relationships
|> format
end
defp parse_params(params) do
params
|> String.split(";", trim: true)
|> Enum.map(fn(param) ->
[_, name, value] = Regex.run(~r{(\w+)=\"?([\w\s]+)\"?}, param)
{String.to_atom(name), value}
end)
|> Enum.into(%{})
end
defp valid_url?(url) do
case URI.parse(url) do
%URI{host: nil} -> false
%URI{scheme: nil} -> false
%URI{path: nil} -> false
_ -> true
end
end
end