graphql-spec icon indicating copy to clipboard operation
graphql-spec copied to clipboard

Fields that do not return a value

Open spawnia opened this issue 2 years ago • 7 comments

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 returns true
  • type Boolean!, resolver returns a boolean indicating success - swallowing errors!
  • type Boolean, resolver is defined to return void, result is serialized to null

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 be null or whatever the representation of Unit 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.

spawnia avatar Dec 01 '21 17:12 spawnia

+1 for void

rivantsov avatar Dec 01 '21 21:12 rivantsov

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.

gregory-claeyssens avatar Dec 08 '21 16:12 gregory-claeyssens

Would the field return null or would it have no representation in the payload? What would happen if the field threw an error?

benjie avatar Dec 08 '21 23:12 benjie

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

rivantsov avatar Dec 09 '21 00:12 rivantsov

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 avatar Dec 12 '21 16:12 spawnia

@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

rivantsov avatar Dec 12 '21 16:12 rivantsov

@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. 😔

benjie avatar Dec 13 '21 00:12 benjie