Current section
Files
Jump to
Current section
Files
lib/parser/parser.ex
defmodule ExLinkHeader.Parser do
alias ExLinkHeader.ParseError
require ExLinkHeaderEntry
@moduledoc """
HTTP link header parser
"""
@regex_format ~r{<?(.+)>; (.+)}
def parse(header, defaults \\ %{})
def parse("", _defaults), do: :error
def parse(nil, _defaults), do: :error
def parse(link_header, defaults), do: parse_links(link_header, defaults)
def parse!(link_header, defaults \\ %{}) do
case parse(link_header, defaults) 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 extract_and_parse_query(url, defaults) do
extracted_query = Regex.run(~r{.+\?(.+)}, url)
extracted_query
|> parse_query(defaults)
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, defaults) do
formatted_links = struct(ExLinkHeader, format_links(links, defaults))
{:ok, formatted_links}
end
defp format_links(links, defaults) do
links
|> Enum.map(fn(link) ->
{url, params} = link
%{rel: rel} = params
attrs = Map.delete(params, :rel)
uri = URI.parse(url)
{
String.to_atom(rel), %ExLinkHeaderEntry{
attributes: attrs,
host: uri.host,
params: extract_and_parse_query(url, defaults),
path: uri.path,
scheme: uri.scheme,
url: url
}
}
end)
end
defp has_rel?(params) do
Map.has_key?(params, :rel)
end
defp parse_links(links, defaults) do
links
|> String.split(~r{,\s*<}, trim: true)
|> extract
|> filter
|> extract_relationships
|> format(defaults)
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 parse_query(nil, _), do: %{}
defp parse_query([_, query], defaults) do
{_, params} = Enum.map_reduce(decode_query_to_list(query), defaults, fn({k, v}, acc) ->
acc = Map.put(acc, String.to_atom(k), v)
{nil, acc}
end
)
params
end
defp decode_query_to_list(query) do
query
|> URI.decode_query
|> Map.to_list
end
defp valid_url?(url) do
case URI.parse(url) do
%URI{host: nil} -> false
%URI{scheme: nil} -> false
_ -> true
end
end
end