Alembic v4.0.0 Alembic.Source View Source

The source of an error.

Link to this section Summary

Types

A single error field key in the Ecto.Changeset.t :errors Keyword.t

A pointer path is composed of the parent pointer and the final child name

Options for Alembic.Source.pointer_path_from_ecto_changeset_error_field_options

t()

An object containing references to the source of the error, optionally including any of the following members

Functions

Descends pointer to child of current pointer

Converts JSON object to t

Converts an ecto_changeset_error_field to an Alembic.Source.pointer_path that can be used to generate both the Alembic.Source.t :pointer and Alembic.Error.t :detail

Link to this section Types

Link to this type ecto_changeset_error_field() View Source
ecto_changeset_error_field() :: atom()

A single error field key in the Ecto.Changeset.t :errors Keyword.t

Link to this type pointer_path() View Source
pointer_path() :: {parent :: Api.json_pointer(), child :: String.t()}

A pointer path is composed of the parent pointer and the final child name.

Link to this type pointer_path_from_ecto_changeset_error_field_options() View Source
pointer_path_from_ecto_changeset_error_field_options() :: %{
  association_set: MapSet.t(atom()),
  association_by_foreign_key: %{optional(atom()) => atom()},
  attribute_set: MapSet.t(atom()),
  format_key: (atom() -> String.t())
}

Options for Alembic.Source.pointer_path_from_ecto_changeset_error_field_options

Link to this type t() View Source
t() ::
  %Alembic.Source{parameter: String.t(), pointer: nil}
  | %Alembic.Source{parameter: nil, pointer: Api.json_pointer()}

An object containing references to the source of the error, optionally including any of the following members:

  • pointer - JSON Pointer (RFC6901) to the associated entity in the request document (e.g. "/data" for a primary data object, or "/data/attributes/title" for a specific attribute).
  • parameter - URL query parameter caused the error.

Link to this section Functions

Link to this function descend(source, child) View Source
descend(t(), String.t() | integer()) :: t()

Descends pointer to child of current pointer

iex> Alembic.Source.descend(
...>   %Alembic.Source{
...>     pointer: "/data"
...>   },
...>   1
...> )
%Alembic.Source{
  pointer: "/data/1"
}
Link to this function from_json(json, error_template) View Source
from_json(%{optional(String.t()) => String.t()}, Alembic.Error.t()) ::
  Alembic.FromJson.error()

Converts JSON object to t.

Valid Input

A parameter can be the source of an error

iex> Alembic.Source.from_json(
...>   %{
...>     "parameter" => "q",
...>   },
...>   %Alembic.Error{
...>     source: %Alembic.Source{
...>       pointer: "/errors/0/source"
...>     }
...>   }
...> )
{
  :ok,
  %Alembic.Source{
    parameter: "q"
  }
}

A member of a JSON object can be the source of an error, in which case a pointer to the location in the object will be given

iex> Alembic.Source.from_json(
...>   %{
...>     "pointer" => "/data"
...>   },
...>   %Alembic.Error{
...>     source: %Alembic.Source{
...>       pointer: "/errors/0/source"
...>     }
...>   }
...> )
{
  :ok,
  %Alembic.Source{
    pointer: "/data"
  }
}

Invalid Input

It is assumed that only "parameter" or "pointer" can be set in a single error source (although that’s not explicit in the JSON API specification), so setting both is an error

iex> Alembic.Source.from_json(
...>   %{
...>     "parameter" => "q",
...>     "pointer" => "/data"
...>   },
...>   %Alembic.Error{
...>     source: %Alembic.Source{
...>       pointer: "/errors/0/source"
...>     }
...>   }
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "The following members conflict with each other (only one can be present):\nparameter\npointer",
        meta: %{
          "children" => [
            "parameter",
            "pointer"
          ]
        },
        source: %Alembic.Source{
          pointer: "/errors/0/source"
        },
        status: "422",
        title: "Children conflicting"
      }
    ]
  }
}

A parameter MUST be a string

iex> Alembic.Source.from_json(
...>   %{
...>     "parameter" => true,
...>   },
...>   %Alembic.Error{
...>     source: %Alembic.Source{
...>       pointer: "/errors/0/source"
...>     }
...>   }
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "`/errors/0/source/parameter` type is not string",
        meta: %{
          "type" => "string"
        },
        source: %Alembic.Source{
          pointer: "/errors/0/source/parameter"
        },
        status: "422",
        title: "Type is wrong"
      }
    ]
  }
}

A pointer MUST be a string

iex> Alembic.Source.from_json(
...>   %{
...>     "pointer" => ["data"],
...>   },
...>   %Alembic.Error{
...>     source: %Alembic.Source{
...>       pointer: "/errors/0/source"
...>     }
...>   }
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "`/errors/0/source/pointer` type is not string",
        meta: %{
          "type" => "string"
        },
        source: %Alembic.Source{
          pointer: "/errors/0/source/pointer"
        },
        status: "422",
        title: "Type is wrong"
      }
    ]
  }
}
Link to this function pointer_path_from_ecto_changeset_error_field(ecto_changeset_error_field, map) View Source
pointer_path_from_ecto_changeset_error_field(
  ecto_changeset_error_field(),
  pointer_path_from_ecto_changeset_error_field_options()
) :: {:ok, pointer_path()} | :error

Converts an ecto_changeset_error_field to an Alembic.Source.pointer_path that can be used to generate both the Alembic.Source.t :pointer and Alembic.Error.t :detail

If ecto_changeset_error_field is in the association_set, then the pointer_path will be under /data/relationships and the child String.t will be formatted with format_key, so that the expected underscore and hypenation rules are followed.

iex> format_key = fn key ->
...>   key |> Atom.to_string() |> String.replace("_", "-")
...> end
iex> Alembic.Source.pointer_path_from_ecto_changeset_error_field(
...>   :favorite_posts,
...>   %{
...>     association_set: MapSet.new([:designated_editor, :favorite_posts]),
...>     association_by_foreign_key: %{designated_editor_id: :designated_editor},
...>     attribute_set: MapSet.new([:first_name, :last_name]),
...>     format_key: format_key
...>   }
...> )
{:ok, {"/data/relationships", "favorite-posts"}}

If ecto_changeset_error_field is a key in association_by_foreign_key, then the associated association is used for child and the parent is /data/relationships the same as if the ecto_cahgneset_error_field were directly an associaton name.

iex> format_key = fn key ->
...>   key |> Atom.to_string() |> String.replace("_", "-")
...> end
iex> Alembic.Source.pointer_path_from_ecto_changeset_error_field(
...>   :designated_editor_id,
...>   %{
...>     association_set: MapSet.new([:designated_editor, :favorite_posts]),
...>     association_by_foreign_key: %{designated_editor_id: :designated_editor},
...>     attribute_set: MapSet.new([:first_name, :last_name]),
...>     format_key: format_key
...>   }
...> )
{:ok, {"/data/relationships", "designated-editor"}}

If ecto_changeset_error_field is in the attribute_set, then the pointer_path will be under /data/attributes and the child String.t will be formated with format_key, so that the expected underscore and hypenation rules are followed.

iex> format_key = fn key ->
...>   key |> Atom.to_string() |> String.replace("_", "-")
...> end
iex> Alembic.Source.pointer_path_from_ecto_changeset_error_field(
...>   :first_name,
...>   %{
...>     association_set: MapSet.new([:designated_editor, :favorite_posts]),
...>     association_by_foreign_key: %{designated_editor_id: :designated_editor},
...>     attribute_set: MapSet.new([:first_name, :last_name]),
...>     format_key: format_key
...>   }
...> )
{:ok, {"/data/attributes", "first-name"}}

If ecto_changeset_error_field is not in association_set, attribute_set or a foreign key in association_by_foreign_key, then :error is returned. Callers should treat this as indicating the error has no source pointer and the Alembic.Error.t :source should be left nil.

iex> format_key = fn key ->
...>   key |> Atom.to_string() |> String.replace("_", "-")
...> end
iex> Alembic.Source.pointer_path_from_ecto_changeset_error_field(
...>   :name,
...>   %{
...>     association_set: MapSet.new([:designated_editor, :favorite_posts]),
...>     association_by_foreign_key: %{designated_editor_id: :designated_editor},
...>     attribute_set: MapSet.new([:first_name, :last_name]),
...>     format_key: format_key
...>   }
...> )
:error