defmodule Spark.Dsl.Verifiers.VerifyEntityUniqueness do @moduledoc """ Verifies that each entity that has an identifier is unique at each path. """ use Spark.Dsl.Verifier alias Spark.Dsl.Verifier def verify(dsl_state) do module = Verifier.get_persisted(dsl_state, :module) dsl_state |> Verifier.get_persisted(:extensions) |> Enum.each(fn extension -> extension.sections |> Enum.each(fn section -> verify_entity_uniqueness(module, section, dsl_state) end) end) :ok end defp verify_entity_uniqueness(module, section, dsl_state, path \\ []) do section_path = path ++ [section.name] section.entities |> Enum.each(fn entity -> do_verify_entity_uniqueness(module, entity, section_path, dsl_state) end) Enum.each(section.sections, fn section -> verify_entity_uniqueness(module, section, dsl_state, section_path) end) section.entities |> Enum.each(fn entity -> entities_to_check = Verifier.get_entities(dsl_state, section_path) entity.entities |> Enum.flat_map(fn {key, nested_entities} -> Enum.map(nested_entities, &{key, &1}) end) |> Enum.each(fn {key, nested_entity} -> verify_nested_entity_uniqueness( module, nested_entity, section_path, entities_to_check, [key] ) end) end) end defp verify_nested_entity_uniqueness( module, nested_entity, section_path, entities_to_check, nested_entity_path ) do unique_entities_or_error( entities_to_check, nested_entity.identifier, module, section_path ++ nested_entity_path ) entities_to_check |> Enum.each(fn entity_to_check -> nested_entity.entities |> Enum.flat_map(fn {key, nested_entities} -> Enum.map(nested_entities, &{key, &1}) end) |> Enum.filter(fn {_, nested_entity} -> nested_entity.identifier end) |> Enum.each(fn {key, nested_entity} -> nested_entities_to_check = entity_to_check |> Map.get(key) |> List.wrap() verify_nested_entity_uniqueness( module, nested_entity, section_path, nested_entities_to_check, nested_entity_path ++ [key] ) end) end) end defp do_verify_entity_uniqueness(module, entity, section_path, dsl_state) do dsl_state |> Verifier.get_entities(section_path) |> Enum.filter(&(&1.__struct__ == entity.target)) |> unique_entities_or_error(entity.identifier, module, section_path) end defp unique_entities_or_error(_, nil, _, _), do: :ok defp unique_entities_or_error(entities_to_check, identifier, module, path) do entities_to_check |> Enum.frequencies_by(&{get_identifier(&1, identifier), &1.__struct__}) |> Enum.find_value(fn {key, value} -> if value > 1 do key end end) |> case do nil -> :ok {identifier, target} -> raise Spark.Error.DslError, module: module, path: path ++ [identifier], message: """ Got duplicate #{inspect(target)}: #{identifier} """ end end defp get_identifier(record, {:auto, _}), do: record.__identifier__ defp get_identifier(record, identifier), do: Map.get(record, identifier) end