defmodule Mecto.SchemaValidator do @moduledoc """ Validates fields extracted with `Mecto.MarkupParser` exist based on the schema given from `Mecto.SchemaExtractor`. """ alias Mecto.SchemaValidator.Result @spec check(map(), map()) :: map() | {:error, [String.t()]} def check(schema, nodes) do result = check(schema, nodes, []) if result.error == [] do result.ok else {:error, result.error} end end @spec check(map(), map(), list()) :: map() | {:error, [String.t()]} defp check(schema, nodes, path), do: Enum.reduce(nodes, %Result{path: path}, &check_node(schema, &1, &2)) @spec check_node(map(), {atom(), map()}, Result.t()) :: map() | {:error, [String.t()]} defp check_node(schema, {node, nested_nodes}, %Result{path: path} = result) do case Map.get(schema, node) do nil -> Result.add_error(result, node, "does not exist") value when is_atom(value) -> Result.merge(result, node, value) enum when is_map(enum) -> nested_result = check(enum, nested_nodes, path ++ [node]) Result.merge(result, node, nested_result) {cardinality, enum} when is_map(enum) -> integer_keys? = nested_nodes |> Map.keys() |> Enum.all?(&is_integer/1) case {cardinality, integer_keys?} do {:one, false} -> nested_result = check(enum, nested_nodes, path ++ [node]) Result.merge(result, node, nested_result) {:many, true} -> nested_result = Enum.map(nested_nodes, fn {index, nested_nodes} -> {index, check(enum, nested_nodes, path ++ [node, index])} end) Result.merge(result, node, nested_result) {:one, true} -> Result.add_error( result, node, "has a cardinality of :one, but is being used like a list" ) {:many, false} -> Result.add_error( result, node, "have a cardinality of :many, but is being used like a single element" ) end end end end