Current section

Files

Jump to
indieweb lib indieweb link_rel.ex
Raw

lib/indieweb/link_rel.ex

defmodule IndieWeb.LinkRel do
@moduledoc """
An interface for obtaining link relationships for a URL.
This provides an interface for extracting and working on the link relationships
available to a page.
"""
alias IndieWeb.{Http, MF2}
@doc """
Extracts the provided relationship from the page.
NOTE: This defaults to returning an empty list if it fails in any way.
"""
@spec find(binary(), binary()) :: list()
def find(url, value) do
with(
{:ok, %Http.Response{code: code} = resp} when code < 499 <- Http.get(url),
header_rels <- do_extract_rels_from_request(resp, value),
page_rels <- do_extract_rels_from_body(resp, value)
) do
header_rels
|> Enum.concat(page_rels)
|> Enum.map(&do_normalize_url(&1, url))
else
_ -> []
end
end
defp do_normalize_url(url, page_url) when is_binary(url) do
cond do
# Relative to the site itself.
String.starts_with?(url, "/") ->
%{authority: authority} = URI.parse(page_url)
authority <> url
# Relative to the current page's path.
is_nil(Map.get(URI.parse(url), :authority)) and
!String.starts_with?(url, "/") ->
page_url <> "/" <> url
# Empty path is the same URL.
url == "" ->
page_url
# It's good enough!
true ->
url
end
end
defp do_extract_rels_from_request(%Http.Response{rels: rels}, value) do
matching_rel_keys =
rels
|> Map.keys()
|> Enum.filter(fn rel_str ->
rel_str |> String.starts_with?(value)
end)
rels
|> Map.take(matching_rel_keys)
|> Map.values()
|> List.flatten()
end
defp do_extract_rels_from_body(resp, value) do
%{body: body, url: url} = resp
case MF2.parse(body, url) do
{:ok, %{"rel_urls" => rel_url_map}} ->
rel_url_map
|> Enum.filter(fn {_url, %{"rels" => rels}} -> value in rels end)
|> Enum.map(fn {key, _} -> key end)
|> Enum.uniq()
{:error, _} ->
[]
end
end
end