Kronky v0.5.0 Kronky.Payload View Source

Absinthe Middleware to build a mutation payload response.

Kronky 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 Kronky.Payload
  2. import_types Kronky.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 Kronky.Payload
import_types Kronky.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 Kronky payloads in your resolver instead. See success_payload/1 and error_payload/1 for examples.

Link to this section Summary

Functions

Convert resolution errors 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 resolution errors to a mutation payload

The build payload middleware will accept lists of Kronky.ValidationMessage or string errors.

Valid formats are:

[%ValidationMessage{},%ValidationMessage{}]
"This is an error"
["This is an error", "This is another error"]
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 Kronky.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, ["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
  |> Kronky.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 Kronky.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