genqlient icon indicating copy to clipboard operation
genqlient copied to clipboard

Correctly handle self-recursive types automatically

Open benjaminjkraft opened this issue 4 years ago • 2 comments
trafficstars

(Split from #149.)

If you have in your schema

input T {
  f: T  # or `f: T!`
}

then genqlient by default generates

struct T {
  F T
}

which does not compile. Of course you can manually add pointer: true, but we could just do that automatically because it's the only valid way to represent this. Apparently this comes up a lot with Hasura-generated schemas, so although it seems a bit special-casey it's probably worth handling.

benjaminjkraft avatar Nov 02 '21 20:11 benjaminjkraft

@benjaminjkraft, Was this issue resolved by #155 ?

nedredmond avatar Jan 26 '22 15:01 nedredmond

#155 is one way to handle it, but this is a case where we can do even better without configuration, so that's what this issue tracks. (More discussion is in #149.)

benjaminjkraft avatar Jan 26 '22 21:01 benjaminjkraft

I think automatically making self-referential fields use pointers would be awesome! 🎉

Other benefits

TL;DR: At least one other benefit I see relates to developer ergonomics 😄

Why?

Not having to specify the optional: pointer attribute in genqlient.yaml would also be beneficial for lots of fields that aren't self-referential.

Optional fields are very common with Hasura. Suppose you had a foobar table with a non-nullable, default-is-now() timestamp column. The input type that Hasura provides lets you optionally set the timestamp.

input foobar_insert_input {
  created_at: timestamptz
}

When we use the optional: pointer attribute in genqlient.yaml, the resulting Go struct that genqlient gives us:

type Foobar_insert_input struct {
	// the time at which this record was originally created
	Created_at *time.Time `json:"created_at"`
}

I think this means in our code, we can't actually let those fields get the zero-value (nil), lest we get the unexpected null value for type 'timestamptz' error:

input := graphql.Foobar_insert_input{
	Created_at: ptr.Time(now),
	Updated_at: ptr.Time(now),
}

func Time(t time.Time) *time.Time {
	return &t
}

This adds up, especially in Hasura, where an insert_input can have nested inputs, and leads to a Cambrian explosion of fields.

In short, this issue could lead to better developer ergonomics 😄

kevinmichaelchen avatar Nov 17 '22 20:11 kevinmichaelchen