Packages

Domain parser for Elixir using the Public Suffix List.

Current section

Files

Jump to
handle_domain lib handle domain components.ex
Raw

lib/handle/domain/components.ex

defmodule Handle.Domain.Components do
@moduledoc """
Provides functionality to build a structured domain representation from parsed rules and domain parts.
The module defines a struct for a domain, including:
- `:domain` – the main domain name
- `:subdomain` – any subdomain portions
- `:tld` – top-level domain portion
- `:type` – domain type (`:public`, `:private`, or `:test`)
## Building Domains
The primary function `build/2` takes a parsed rule (`Handle.Domain.Rule.t()`) and a list of domain parts,
then attempts to construct a `%Handle.Domain.Components{}` struct based on the rule type.
- For wildcard rules (`:wildcard`), it splits the domain parts to extract the TLD, domain, and subdomain.
- For other rule types, it handles the special case where the domain is "localhost"
(treated as a test domain without a TLD), and standard domain parsing for other cases.
If the domain parts do not meet expectations (e.g., top-level domain only),
it returns an error tuple with an appropriate message.
## Examples
iex> rule = %Handle.Domain.Rule{rule_type: :standard, length: 1, parts: ["com"], domain_type: :public}
iex> Handle.Domain.Components.build(rule, ["com", "example", "sub"])
{:ok, %Handle.Domain.Components{
domain: "example",
subdomain: "sub",
tld: "com",
type: :public
}}
"""
defstruct [:domain, :subdomain, :tld, :type]
alias Handle.Domain.Rule
@type t :: %__MODULE__{
domain: String.t(),
subdomain: String.t(),
tld: String.t(),
type: :public | :private | :test
}
@spec build(Rule.t(), list(String.t())) :: {:ok, t()} | {:error, String.t()}
def build(%Rule{rule_type: :wildcard} = rule, domain_parts) do
case Enum.split(domain_parts, rule.length - 1) do
{tld, [domain | subdomain]} ->
{:ok,
%__MODULE__{
domain: domain,
subdomain: merge_parts(subdomain),
tld: merge_parts(tld),
type: rule.domain_type
}}
_ ->
{:error, "Top level domain only"}
end
end
def build(%Rule{} = rule, domain_parts) do
case Enum.split(domain_parts, rule.length) do
# Special case localhost as a domain without a TLD
{["localhost"], subdomain} ->
{:ok,
%__MODULE__{
domain: "localhost",
subdomain: merge_parts(subdomain),
tld: "",
type: :test
}}
{tld, [domain | subdomain]} ->
{:ok,
%__MODULE__{
domain: domain,
subdomain: merge_parts(subdomain),
tld: merge_parts(tld),
type: rule.domain_type
}}
_ ->
{:error, "Top level domain only"}
end
end
defp merge_parts(parts), do: parts |> Enum.reverse() |> Enum.join(".")
end