AbsintheErrorPayload.Payload (AbsintheErrorPayload v1.1.4) View Source

Absinthe Middleware to build a mutation payload response.

AbsintheErrorPayload mutation responses (aka "payloads") have three fields

  • successful - Indicates if the mutation completed successfully or not. Boolean.
  • messages - a list of validation errors. Always empty on success
  • result - the data object that was created/updated/deleted on success. Always nil when unsuccesful

Usage

In your schema file

  1. import AbsintheErrorPayload.Payload
  2. import_types AbsintheErrorPayload.ValidationMessageTypes
  3. create a payload object for each object using payload_object(payload_name, object_name)
  4. create a mutation that returns the payload object. Add the payload middleware after the resolver.
    field :create_user, type: :user_payload, description: "add a user" do
    arg :user, :create_user_params
    resolve &UserResolver.create/2
    middleware &build_payload/2
    end

Example Schema

Object Schema:


defmodule MyApp.Schema.User do
@moduledoc false

use Absinthe.Schema.Notation
import AbsintheErrorPayload.Payload
import_types AbsintheErrorPayload.ValidationMessageTypes

alias MyApp.Resolvers.User, as: UserResolver

object :user, description: "Someone on our planet" do
  field :id, non_null(:id), description: "unique identifier"
  field :first_name, non_null(:string), description: "User's first name"
  field :last_name, :string, description: "Optional Last Name"
  field :age, :integer, description: "Age in Earth years"
  field :inserted_at, :time, description: "Created at"
  field :updated_at, :time, description: "Last updated at"
end

input_object :create_user_params, description: "create a user" do
  field :first_name, non_null(:string), description: "Required first name"
  field :last_name, :string, description: "Optional last name"
  field :age, :integer, description: "Age in Earth years"
end

payload_object(:user_payload, :user)

object :user_mutations do

  field :create_user, type: :user_payload, description: "Create a new user" do
    arg :user, :create_user_params
    resolve &UserResolver.create/2
    middleware &build_payload/2
  end
end

In your main schema file

import_types MyApp.Schema.User

mutation do
 ...
 import_fields :user_mutations
end

Alternate Use

If you'd prefer not to use the middleware style, you can generate AbsintheErrorPayload payloads in your resolver instead. See success_payload/1 and error_payload/1 for examples.

Link to this section Summary

Functions

Convert a resolution value to a mutation payload

convert validation message field to camelCase format used by graphQL

Direct converter from value to a Payload struct.

Generates a mutation error payload.

Create a payload object definition

Generates a success payload.

Link to this section Functions

Link to this function

build_payload(resolution, config)

View Source

Convert a resolution value to a mutation payload

To be used as middleware by Absinthe.Graphql. It should be placed immediatly after the resolver.

The middleware will automatically transform an invalid changeset into validation errors.

Your resolver could then look like:

@doc "
Creates a new user

Results are wrapped in a result monad as expected by absinthe.
"
def create(%{user: attrs}, _resolution) do
  case UserContext.create_user(attrs) do
    {:ok, user} -> {:ok, user}
    {:error, %Ecto.Changeset{} = changeset} -> {:ok, changeset}
  end
end

The build payload middleware will also accept error tuples with single or lists of AbsintheErrorPayload.ValidationMessage or string errors. However, lists and strings will need to be wrapped in an :ok tuple or they will be seen as errors by graphql.

An example resolver could look like:

@doc "
updates an existing user.

Results are wrapped in a result monad as expected by absinthe.
"
def update(%{id: id, user: attrs}, _resolution) do
  case UserContext.get_user(id) do
    nil -> {:ok, %ValidationMessage{field: :id, code: "not found", message: "does not exist"}}
    user -> do_update_user(user, attrs)
  end
end

defp do_update_user(user, attrs) do
  case UserContext.update_user(user, attrs) do
    {:ok, user} -> {:ok, user}
    {:error, %Ecto.Changeset{} = changeset} -> {:ok, changeset}
  end
end

Valid formats are:

%ValidationMessage{}
{:error, %ValidationMessage{}}
{:error, [%ValidationMessage{},%ValidationMessage{}]}
{:error, "This is an error"}
{:error, :error_atom}
{:error, ["This is an error", "This is another error"]}

Alternate Use

If you'd prefer not to use the middleware style, you can generate AbsintheErrorPayload payloads in your resolver instead. See convert_to_payload/1, success_payload/1 and error_payload/1 for examples.

Link to this function

convert_field_name(message)

View Source

convert validation message field to camelCase format used by graphQL

Link to this function

convert_to_payload(message)

View Source

Direct converter from value to a Payload struct.

This function will automatically transform an invalid changeset into validation errors.

Changesets, error tuples and lists of AbsintheErrorPayload.ValidationMessage will be identified as errors and will generate an error payload.

Error formats are:

%Ecto.Changeset{valid?: false}
%ValidationMessage{}
{:error, %ValidationMessage{}}
{:error, [%ValidationMessage{},%ValidationMessage{}]}
{:error, "This is an error"}
{:error, :error_atom}
{:error, ["This is an error", "This is another error"]}

All other values will be converted to a success payload. or string errors. However, lists and strings will need to be wrapped in an :ok tuple or they will be seen as errors by graphql.

An example use could look like:

@doc "
Load a user matching an id

Results are wrapped in a result monad as expected by absinthe.
"
def get_user(%{id: id}, _resolution) do
  case UserContext.get_user(id) do
    nil -> %ValidationMessage{field: :id, code: "not found", message: "does not exist"}}
    user -> user
  end
  |> AbsintheErrorPayload.Payload.convert_to_payload()
end

Generates a mutation error payload.

Examples

iex> error_payload(%ValidationMessage{code: "required", field: "name"})
%Payload{successful: false, messages: [%ValidationMessage{code: "required", field: "name"}]}

iex> error_payload([%ValidationMessage{code: "required", field: "name"}])
%Payload{successful: false, messages: [%ValidationMessage{code: "required", field: "name"}]}

Usage

If you prefer not to use the Payload.middleware, you can use this method in your resolvers instead.


@doc "
updates an existing user.

Results are wrapped in a result monad as expected by absinthe.
"
def update(%{id: id, user: attrs}, _resolution) do
  case UserContext.get_user(id) do
    nil -> {:ok, error_payload([%ValidationMessage{field: :id, code: "not found", message: "does not exist"}])}
    user -> do_update_user(user, attrs)
  end
end

defp do_update_user(user, attrs) do
  case UserContext.update_user(user, attrs) do
    {:ok, user} -> {:ok, success_payload(user)}
    {:error, %Ecto.Changeset{} = changeset} -> {:ok, error_payload(changeset)}
  end
end
Link to this macro

payload_object(payload_name, result_object_name)

View Source (macro)

Create a payload object definition

Each object that can be mutated will need its own graphql response object in order to return typed responses. This is a helper method to generate a custom payload object

Usage

payload_object(:user_payload, :user)

is the equivalent of

object :user_payload do
  field :successful, non_null(:boolean), description: "Indicates if the mutation completed successfully or not. "
  field :messages, list_of(:validation_message), description: "A list of failed validations. May be blank or null if mutation succeeded."
  field :result, :user, description: "The object created/updated/deleted by the mutation"
end

This method must be called after import_types AbsintheErrorPayload.MutationTypes or it will fail due to :validation_message not being defined.

Generates a success payload.

Examples

iex> success_payload(%User{first_name: "Stich", last_name: "Pelekai", id: 626})
%Payload{successful: true, result: %User{first_name: "Stich", last_name: "Pelekai", id: 626}}

Usage

If you prefer not to use the build_payload/2 middleware, you can use this method in your resolvers instead.

@doc "
Creates a new user

Results are wrapped in a result monad as expected by absinthe.
"
def create(%{user: attrs}, _resolution) do
  case UserContext.create_user(attrs) do
    {:ok, user} -> {:ok, success_payload(user)}
    {:error, %Ecto.Changeset{} = changeset} -> {:ok, error_payload(changeset)}
  end
end