graphql-go: Cannot Represent Optional Fields with Defaults

Overview

We encountered a bug in production that has uncovered a mismatch in the relationship between optional fields with defaults and the Go types used to represent them. The parser should be corrected to add support for setting defaults on optional InputValues.

Current State

Consider this simple schema with one object:

schema {
  query: Query
}

type Query {
  buyIceCream(args: QueryInput): Boolean!
}

input QueryInput {
  flavor: String = "VANILLA"
}

and the corresponding resolver:

func (r *resolver) BuyIceCream(args struct{ Args *struct{ Flavor *string } }) bool {
	return true
}

The parsing method will reject this schema with this error could not unmarshal "VANILLA" (string) into *string: incompatible type.

Expected Behaviour

The expectation is that the parser is able to represent this schema and resolver combination and that all users of this library are able to set defaults on optional InputValues.

Further Notes & Workarounds

The common way to work around this is to use a non-pointer type to represent the optional argument. While the parser allows this, it can lead to runtime errors if a client supplies a (legal) null value for the field. This error message in this case is graphql: got null for non-null.

This also hopefully explains why we’d want optional fields with defaults in the first place. Including null as a possible input is sometimes the best way to represent our business domain.

I hope this write-up is sufficient. I can open a PR with some failing tests as a next step if that would be helpful. I’m going to continue working on solving the issue, but I thought that having the issue described here might help someone else who is seeing the same problem.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 16 (11 by maintainers)

Most upvoted comments

The schema parsing is done by the lexer and by default it parses a nullable type, however, if it encounters an ! symbol, then it wraps the type in an ast.NonNull struct. Check here: https://github.com/graph-gophers/graphql-go/blob/master/internal/common/types.go#L10 You can find all possible types in the ast package. Then during query execution/validation the library validates the incoming executable definition (i.e. GraphQL query) against the AST schema which is usually parsed only once during application start time.

@pavelnikolov I would like to take up this bug.

@rudle Did you tried to use string instead of *string in your corresponding resolver?

I tested this behavior now and it works perfectly with following setup:

input QueryInput { flavor: String = “VANILLA” }

func (r *Resolver) BuyIceCream(args struct{ Args *struct{ Flavor string } }) bool { if args.Args.Flavor == “VANILLA” { return true } return false }

I think there is no bug in this library and the issue can be closed.