graphql-spec
graphql-spec copied to clipboard
Fields that do not return a value
Motivation
Our application defines some mutations that are purely used to trigger side effects and don't return a value.
GraphQL enforces the definition of a return type for those fields. Due to this constraint and lack of a better alternative, I have found all of the following permutations to be present in our codebase:
- type
Boolean!
, resolver always returnstrue
- type
Boolean!
, resolver returns a boolean indicating success - swallowing errors! - type
Boolean
, resolver is defined to returnvoid
, result is serialized tonull
While this inconsistency is not pretty for someone on the server side, it is especially bad for the client. Depending on the quality of the schema documentation, they won't know for sure if the return type is meaningful.
Idea
Introduce an idiomatic way to represent the notion of a field that returns no value.
Possible Solutions
No Type
Allow defining the field with no return type at all.
type Mutation {
fireAndForget
}
- terse definition
- affects the grammar of GraphQL, perhaps causes ambiguities (?)
- requires making
__Field.type
nullable in introspection
It is unclear to me how this field would be represented in the serialized result, given it is a map and there has to be something in the place of the value. Perhaps null
?
void
Introduce a new keyword void
that acts as a pseudo-type.
type Mutation {
fireAndForget: void
}
- reserving a keyword might cause breakage
- more explicit than omission
Unit Type
Introduce a new type Unit
that allows only one possible value and thus can hold no information.
type Mutation {
fireAndForget: Unit
}
Some rules:
-
Unit
is implicitly non-nullable, otherwise the value could benull
or whatever the representation ofUnit
is -
Unit
can not be used in lists
Building upon existing types, I can see the definition of this type being either one of:
- a scalar with only a single allowed value - the string
UNIT
- an enum with only a single value
UNIT
I can see this type being useful as an argument too, enabling a binary toggle between not passing the argument at all or passing it.
+1 for void
I have had similar problems, and also went with a boolean response as a workaround. This seems like a valid addition to me.
From the 3 suggestions here, @void
seems to be the only one I can't find any downsides with. It does what you would expect and is hard to misinterprit.
Would the field return null or would it have no representation in the payload? What would happen if the field threw an error?
I think it should be 'no repr in payload', just like with 'skip(if:true)'; if error - just same stuff, error object should appear in errors, path should include the field name at the end, don't see any problem with that
I really like the idea of having no representation in the payload, and it is great to see that there is already an existing construct that does the same.
This seems good for schema evolution: with the field not being present at all, clients will very likely have no code that somehow depends on the result (which might be the case with any pseudo-value). Then, it is easier to introduce any return type once the field has evolved to actually provide useful information.
What would be the best representation in introspection? A very simple solution would be for the field to have no return type at all, thus letting __Field.type
be null
. I wonder if that causes problems for consumers of introspection, such as GraphiQL or various code generation tools. Perhaps they would be better served if the field returned a concrete scalar/enum type.
@spawnia, one option would be to do like c#: Void is an actual type in type system, 'void' is a keyword, equivalent of Void; (same as String type and string keyword). We can just have Void as an actual type (scalar kind of). We do not need 'void' as keyword, intro would return 'Void' as type
@spawnia The selection set validation rules would have to be modified to allow changing from void with no selection set to an object payload type with selection set. One option is to use an “empty” object type, but this comes with a bunch of caveats:
- we don’t support empty object types yet (but it has been discussed a number of times)
- We don’t support empty selection sets
- If it allows __typename you need to ensure it has the same name as your ultimate payload type
- if you don’t allow __typename that’ll confuse a load of clients; also we don’t support empty selection sets
I don’t see a clear path for this kind of schema evolution. 😔