improvement: make mutation arguments non-null (#111)

* improvement: make mutation arguments non-null

As discussed in #105 and #110, put this behind an opt-in configuration to avoid
breaking existing code.
The ID in update mutations is always non-null if non-null mutation arguments are
allowed, while input is non-null if it's allowed _and_ there is at least a
non-null field in the input.

Document the newly added config variable in the getting started guide.

* chore: enable non-null mutation arguments in tests
This commit is contained in:
Riccardo Binetti 2024-01-31 23:52:01 +01:00 committed by GitHub
parent af4193feca
commit 8fff0d361d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 42 additions and 16 deletions

View file

@ -6,6 +6,7 @@ config :ash, :validate_api_resource_inclusion?, false
config :ash, :validate_api_config_inclusion?, false
config :ash_graphql, :default_managed_relationship_type_name_template, :action_name
config :ash_graphql, :allow_non_null_mutation_arguments?, true
if Mix.env() == :dev do
config :git_ops,

View file

@ -21,6 +21,7 @@ in `config/config.exs`
```elixir
config :ash_graphql, :default_managed_relationship_type_name_template, :action_name
config :ash_graphql, :allow_non_null_mutation_arguments?, true
```
This won't be necessary after the next major release, where this new configuration will be the default.

View file

@ -552,14 +552,14 @@ defmodule AshGraphql.Resource do
[] ->
[]
_ ->
fields ->
[
%Absinthe.Blueprint.Schema.InputValueDefinition{
identifier: :input,
module: schema,
name: "input",
placement: :argument_definition,
type: String.to_atom("#{name}_input")
type: mutation_input_type(name, fields)
}
]
end
@ -621,14 +621,14 @@ defmodule AshGraphql.Resource do
[] ->
[]
_ ->
fields ->
[
%Absinthe.Blueprint.Schema.InputValueDefinition{
identifier: :input,
module: schema,
name: "input",
placement: :argument_definition,
type: String.to_atom("#{mutation.name}_input")
type: mutation_input_type(mutation.name, fields)
}
]
end
@ -672,7 +672,7 @@ defmodule AshGraphql.Resource do
[] ->
mutation_args(mutation, resource, schema)
_ ->
fields ->
mutation_args(mutation, resource, schema) ++
[
%Absinthe.Blueprint.Schema.InputValueDefinition{
@ -680,7 +680,7 @@ defmodule AshGraphql.Resource do
module: schema,
name: "input",
placement: :argument_definition,
type: String.to_atom("#{mutation.name}_input"),
type: mutation_input_type(mutation.name, fields),
__reference__: ref(__ENV__)
}
]
@ -730,6 +730,12 @@ defmodule AshGraphql.Resource do
|> Enum.concat(mutation_read_args(mutation, resource, schema))
end
@allow_non_null_mutation_arguments? Application.compile_env(
:ash_graphql,
:allow_non_null_mutation_arguments?,
false
)
defp mutation_args(mutation, resource, schema) do
[
%Absinthe.Blueprint.Schema.InputValueDefinition{
@ -737,13 +743,31 @@ defmodule AshGraphql.Resource do
module: schema,
name: "id",
placement: :argument_definition,
type: :id,
type: maybe_wrap_non_null(:id, @allow_non_null_mutation_arguments?),
__reference__: ref(__ENV__)
}
| mutation_read_args(mutation, resource, schema)
]
end
# sobelow_skip ["DOS.StringToAtom"]
defp mutation_input_type(mutation_name, mutation_fields) do
any_non_null_field? =
mutation_fields
|> Enum.any?(fn
%Absinthe.Blueprint.Schema.FieldDefinition{
type: %Absinthe.Blueprint.TypeReference.NonNull{}
} ->
true
_ ->
false
end)
String.to_atom("#{mutation_name}_input")
|> maybe_wrap_non_null(any_non_null_field? and @allow_non_null_mutation_arguments?)
end
defp mutation_read_args(%{read_action: read_action}, resource, schema) do
read_action =
cond do

View file

@ -376,7 +376,7 @@ defmodule AshGraphql.CreateTest do
test "a create with a managed relationship works with many_to_many and [on_lookup: :relate, on_match: :relate]" do
resp =
"""
mutation CreatePostWithCommentsAndTags($input: CreatePostWithCommentsAndTagsInput) {
mutation CreatePostWithCommentsAndTags($input: CreatePostWithCommentsAndTagsInput!) {
createPostWithCommentsAndTags(input: $input) {
result{
text

View file

@ -14,7 +14,7 @@ defmodule AshGraphql.DestroyTest do
resp =
"""
mutation DeletePost($id: ID) {
mutation DeletePost($id: ID!) {
deletePost(id: $id) {
result{
text
@ -42,7 +42,7 @@ defmodule AshGraphql.DestroyTest do
resp =
"""
mutation ArchivePost($id: ID) {
mutation ArchivePost($id: ID!) {
deletePost(id: $id) {
result{
text
@ -96,7 +96,7 @@ defmodule AshGraphql.DestroyTest do
resp =
"""
mutation DeleteWithError($id: ID) {
mutation DeleteWithError($id: ID!) {
deletePostWithError(id: $id) {
result{
text
@ -128,7 +128,7 @@ defmodule AshGraphql.DestroyTest do
test "destroying a non-existent record returns a not found error" do
resp =
"""
mutation DeletePost($id: ID) {
mutation DeletePost($id: ID!) {
deletePost(id: $id) {
result{
text
@ -162,7 +162,7 @@ defmodule AshGraphql.DestroyTest do
resp =
"""
mutation DeletePost($id: ID) {
mutation DeletePost($id: ID!) {
deletePostWithError(id: $id) {
result{
text

View file

@ -19,7 +19,7 @@ defmodule AshGraphql.UpdateTest do
resp =
"""
mutation UpdatePost($id: ID, $input: UpdatePostInput) {
mutation UpdatePost($id: ID!, $input: UpdatePostInput) {
updatePost(id: $id, input: $input) {
result{
text
@ -209,7 +209,7 @@ defmodule AshGraphql.UpdateTest do
resp =
"""
mutation UpdatePostConfirm($input: UpdatePostConfirmInput, $id: ID) {
mutation UpdatePostConfirm($input: UpdatePostConfirmInput, $id: ID!) {
updatePostConfirm(input: $input, id: $id) {
result{
text
@ -253,7 +253,7 @@ defmodule AshGraphql.UpdateTest do
resp =
"""
mutation UpdatePostConfirm($input: UpdatePostConfirmInput, $id: ID) {
mutation UpdatePostConfirm($input: UpdatePostConfirmInput, $id: ID!) {
updatePostConfirm(input: $input, id: $id) {
result{
text