defmodule Mecto.SchemaValidator do @moduledoc """ Validates fields extracted with `Mecto.MarkupParser` exist based on the schema given from `Mecto.MarkupParser`. """ alias Mecto.SchemaValidator.Result def check(schema, nodes) do result = check(schema, nodes, []) if result.error == [] do result.ok else {:error, result.error} end end defp check(schema, nodes, path), do: Enum.reduce(nodes, %Result{path: path}, &check_node(schema, &1, &2)) 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