/
modern.ex
177 lines (143 loc) · 5.06 KB
/
modern.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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