defmodule AshGeo.Validation.Topo do @opts_schema [ function: [ type: {:in, AshGeo.topo_functions()}, required: true, doc: "Topo function to use for comparison" ], geometry_a: [ type: :atom, required: true, doc: "Geometry A" ], geometry_b: [ type: :atom, required: true, doc: "Geometry B" ] ] @moduledoc """ Validate that the specified `Topo` function return true. ### Options #{Spark.Options.docs(@opts_schema)} ### See also - `AshGeo.topo_functions/0` """ @moduledoc since: "0.1.0" use Ash.Resource.Validation alias Ash.Error.Changes.InvalidArgument @impl Ash.Resource.Validation def init(opts) do case Spark.Options.validate(opts, @opts_schema) do {:ok, opts} -> {:ok, opts} {:error, error} -> {:error, Exception.message(error)} end end @impl Ash.Resource.Validation def validate(cs, opts, _context) do geometry_a = attribute_value(cs, opts[:geometry_a]) geometry_b = attribute_value(cs, opts[:geometry_b]) case apply(Topo, opts[:function], [geometry_a, geometry_b]) do true -> :ok false -> invalid_argument_error(opts, geometry_a, geometry_b) end end defp attribute_value(_cs, attribute) when is_function(attribute, 0) do attribute.() end defp attribute_value(cs, attribute) when is_atom(attribute) do Ash.Changeset.get_argument_or_attribute(cs, attribute) end defp attribute_value(_, attribute), do: attribute defp invalid_argument_error(opts, geometry_a, geometry_b) do {:error, InvalidArgument.exception( field: opts[:geometry_a], value: geometry_a, message: case opts[:function] do :contains? -> "#{geometry_a} does not contain #{geometry_b}" :disjoint? -> "#{geometry_a} is not disjoint with #{geometry_b}" :equals? -> "#{geometry_a} does not equal #{geometry_b}" :intersects? -> "#{geometry_a} does not intersect #{geometry_b}" :within? -> "#{geometry_a} is not within #{geometry_b}" end )} end end