defmodule Absinthe.Relay.Mutation.Notation.Modern do @moduledoc """ Convenience macros for Relay Modern mutations. (If you want `clientMutationId` handling, see `Absinthe.Relay.Mutation.Notation.Classic`!) The `payload` macro can be used by schema designers to support mutation fields that receive either: - A single non-null input object argument (using the `input` macro in this module) - Any arguments you want to use (using the normal `arg` macro) More information can be found at https://facebook.github.io/relay/docs/mutations.html ## Example In this example we add a mutation field `:simple_mutation` that accepts an `input` argument of a new type (which is defined for us because we use the `input` macro), which contains an `:input_data` field. We also declare the output will contain a field, `:result`. Notice the `resolve` function doesn't need to know anything about the wrapping `input` argument -- it only concerns itself with the contents. The input fields are passed to the resolver just like they were declared as separate top-level arguments. ``` mutation do payload field :simple_mutation do input do field :input_data, non_null(:integer) end output do field :result, :integer end resolve fn %{input_data: input_data}, _ -> # Some mutation side-effect here {:ok, %{result: input_data * 2}} end end end ``` Here's a query document that would hit this field: ```graphql mutation DoSomethingSimple { simpleMutation(input: {inputData: 2}) { result } } ``` And here's the response: ```json { "data": { "simpleMutation": { "result": 4 } } } ``` Note the above code would create the following types in our schema, ad hoc: - `SimpleMutationInput` - `SimpleMutationPayload` For this reason, the identifier passed to `payload field` must be unique across your schema. ## Using your own arguments You are free to just declare your own arguments instead. The `input` argument and type behavior is only activated for your mutation field if you use the `input` macro. You're free to define your own arguments using `arg`, as usual, with one caveat: don't call one `:input`. ## The Escape Hatch The mutation macros defined here are just for convenience; if you want something that goes against these restrictions, don't worry! You can always just define your types and fields using normal (`field`, `arg`, `input_object`, etc) schema notation macros as usual. """ alias Absinthe.Blueprint alias Absinthe.Blueprint.Schema alias Absinthe.Relay.Schema.Notation @doc """ Define a mutation with a single input and a client mutation ID. See the module documentation for more information. """ defmacro payload({:field, meta, args}, do: block) do Notation.payload(meta, args, [block_private(), block]) end defmacro payload({:field, meta, args}) do Notation.payload(meta, args, block_private()) end defp block_private() do # This indicates to the Relay schema phase that this field should automatically # generate the payload type for this field if it is not explicitly created quote do private(:absinthe_relay, :payload, {:fill, unquote(__MODULE__)}) end end # # INPUT # @doc """ Defines the input type for your payload field. See the module documentation for an example. """ defmacro input(identifier, do: block) do [ # Only if the `input` macro is actually used should we mark the field # as using an input type, autogenerating the `input` argument on the field. quote do private(:absinthe_relay, :input, {:fill, unquote(__MODULE__)}) end, Notation.input(__MODULE__, identifier, block) ] end # # PAYLOAD # @doc """ Defines the output (payload) type for your payload field. See the module documentation for an example. """ defmacro output(identifier, do: block) do Notation.output(__MODULE__, identifier, block) end def additional_types(:payload, %Schema.FieldDefinition{identifier: field_ident}) do %Schema.ObjectTypeDefinition{ name: Notation.ident(field_ident, :payload) |> Atom.to_string() |> Macro.camelize(), identifier: Notation.ident(field_ident, :payload), module: __MODULE__, __private__: [absinthe_relay: [payload: {:fill, __MODULE__}]], __reference__: Absinthe.Schema.Notation.build_reference(__ENV__) } end def additional_types(_, _), do: [] def fillout(:input, %Schema.FieldDefinition{} = field) do add_input_arg(field) end def fillout(_, node) do node end def add_input_arg(field) do arg = %Schema.InputValueDefinition{ identifier: :input, name: "input", type: %Blueprint.TypeReference.NonNull{of_type: Notation.ident(field.identifier, :input)}, module: __MODULE__, __reference__: Absinthe.Schema.Notation.build_reference(__ENV__) } %{ field | arguments: [arg | field.arguments], middleware: [Absinthe.Relay.Mutation | field.middleware] } end end