Current section
Files
Jump to
Current section
Files
lib/boundary/classifier.ex
defmodule Boundary.Classifier do
@moduledoc false
@type t :: %{boundaries: %{Boundary.name() => Boundary.t()}, modules: %{module() => Boundary.name()}}
@spec new :: t
def new, do: %{boundaries: %{}, modules: %{}}
@spec classify(t, [module], [Boundary.t()]) :: t
def classify(classifier, modules, boundaries) do
trie = build_trie(boundaries)
boundaries = Enum.reduce(boundaries, classifier.boundaries, &Map.put_new(&2, &1.name, &1))
classifier = %{classifier | boundaries: boundaries}
for module <- modules,
boundary = find_boundary(trie, module),
reduce: classifier do
classifier -> Map.update!(classifier, :modules, &Map.put(&1, module, boundary.name))
end
end
defp build_trie(boundaries), do: Enum.reduce(boundaries, new_trie(), &add_boundary(&2, &1))
defp new_trie, do: %{boundary: nil, children: %{}}
defp find_boundary(trie, module) when is_atom(module) do
case Boundary.Definition.classified_to(module) do
nil ->
if Boundary.protocol_impl?(module),
do: nil,
else: find_boundary(trie, Module.split(module))
classified_to ->
boundary = find_boundary(trie, classified_to.boundary)
unless boundary do
message = "invalid boundary #{inspect(classified_to.boundary)}"
raise Boundary.Error, message: message, file: classified_to.file, line: classified_to.line
end
boundary
end
end
defp find_boundary(_trie, []), do: nil
defp find_boundary(trie, [part | rest]) do
case Map.fetch(trie.children, part) do
{:ok, child_trie} -> find_boundary(child_trie, rest) || child_trie.boundary
:error -> nil
end
end
defp add_boundary(trie, boundary),
do: add_boundary(trie, Module.split(boundary.name), boundary)
defp add_boundary(trie, [], boundary), do: %{trie | boundary: boundary}
defp add_boundary(trie, [part | rest], boundary) do
Map.update!(
trie,
:children,
fn children ->
children
|> Map.put_new_lazy(part, &new_trie/0)
|> Map.update!(part, &add_boundary(&1, rest, boundary))
end
)
end
end