lib/ex_json_schema/validator/items.ex

defmodule ExJsonSchema.Validator.Items do
  @moduledoc """
  `ExJsonSchema.Validator` implementation for `"items"` attributes.

  See:

  """

  alias ExJsonSchema.Validator
  alias ExJsonSchema.Validator.Error

  @behaviour ExJsonSchema.Validator

  @impl ExJsonSchema.Validator
  def validate(root, schema, {"items", _}, items, path) when is_list(items) do
    do_validate(root, schema, items, path)
  end

  def validate(_, _, _, _, _) do
    []
  end

  defp do_validate(_, %{"items" => true}, _, _) do
    []
  end

  defp do_validate(_, %{"items" => false}, [], _) do
    []
  end

  defp do_validate(_, %{"items" => false}, _, _) do
    [%Error{error: %Error.ItemsNotAllowed{}}]
  end

  defp do_validate(root, %{"items" => schema = %{}}, items, path) when is_list(items) do
    items
    |> Enum.with_index()
    |> Enum.flat_map(fn {item, index} ->
      Validator.validation_errors(root, schema, item, path <> "/#{index}")
    end)
  end

  defp do_validate(
         root,
         %{"items" => schemata, "additionalItems" => additional_items},
         items,
         path
       )
       when is_list(items) and is_list(schemata) do
    validate_items(root, {schemata, additional_items}, items, {[], 0}, path)
    |> Enum.reverse()
    |> List.flatten()
  end

  defp validate_items(_root, {_schemata, _additional_items}, [], {errors, _index}, _), do: errors
  defp validate_items(_root, {[], true}, _items, {errors, _index}, _), do: errors

  defp validate_items(_root, {[], false}, items, {errors, index}, _) do
    [
      %Error{
        error: %Error.AdditionalItems{additional_indices: index..(index + Enum.count(items) - 1)}
      }
      | errors
    ]
  end

  defp validate_items(root, {[], additional_items_schema}, [item | items], {errors, index}, path) do
    acc =
      {[
         Validator.validation_errors(root, additional_items_schema, item, path <> "/#{index}")
         | errors
       ], index + 1}

    validate_items(root, {[], additional_items_schema}, items, acc, path)
  end

  defp validate_items(
         root,
         {[schema | schemata], additional_items},
         [item | items],
         {errors, index},
         path
       ) do
    acc = {[Validator.validation_errors(root, schema, item, path <> "/#{index}") | errors], index + 1}

    validate_items(root, {schemata, additional_items}, items, acc, path)
  end
end