Description
Is your feature request related to a problem? Please describe.
I have a proposal that I think would make it easier to take advantage of the useful getter functions this library generates for fields. Consider this simple example, using optional: pointer
(or individual pointer: true
directives on optional fields):
Schema
type Food {
name: String!
# Zero is a valid value if someone really loves celery, so using value optionals isn't appropriate
calories: Int!
}
type Person {
name: String!
# If the Person doesn't have a favourite food, the API returns null for this entire field
favouriteFood: Food
}
type Query {
person(name: String): Person
}
Query
query FindPerson($name: String!) {
person(name: $name) {
name
favouriteFood {
name
}
}
}
One of the things genqlient will generate is a getter method for favouriteFood.name
:
// GetName returns FindPersonPersonFavouriteFood.Name, and is useful for accessing the field via an interface.
func (v *FindPersonPersonFavouriteFood) GetName() string { return v.Name }
My interpretation of the comment on the getter is that over time, I may find myself with many types having a name
field, and might want to start writing functions that can deal with any of them. So, I might want to write something like:
type named interface {
GetName() string
}
func processName(f named) *string {
if f == nil { return nil }
name := f.GetName()
// Do some processing involving the name
return &name
}
Of course, I've now shot myself in the foot, because f
is a typed nil; the f == nil
comparison will return false, and I'll get a nil pointer dereference when I call GetName
.
Describe the solution you'd like
I think this could be made smoother if the getters generated for fields of a pointer-optional nested type themselves performed a nil-check and returned pointers (while leaving the actual field in the struct a value, unless it itself was optional). i.e., the generated getter from above would instead be:
// GetName returns FindPersonPersonFavouriteFood.Name, and is useful for accessing the field via an interface.
func (v *FindPersonPersonFavouriteFood) GetName() *string {
if v == nil { return nil }
return &v.Name
}
Now, the call to f.GetName()
would return a nil pointer instead of panicking, and I could return that from my function directly. I would probably also want to remove the f == nil
check as it never really served a purpose anyway:
func processName(f named) *string {
name := f.GetName()
// Do some processing involving the name
return name
}
As far as I can tell, genqlient always generates separate Go types per-query per-type, so I don't believe this would have any negative effects in cases where the nested type is optional in one parent type and non-optional in another.
This would also support the common response when this behaviour of Go is questioned as a design flaw, which is that the implementer of the methods on an interface should be responsible for handling nil pointers. Or, to say it another way, the getter says it can return a string given any pointer to FindPersonPersonFavouriteFood
, but currently if that pointer is nil it cannot.
Of course, there's the fairly big caveat that genqclient hasn't said it implements any interfaces; I have 😁 But I believe such a use case was the intent behind the feature?
Describe alternatives you've considered
I appreciate that there are several reasons the maintainers may not want to implement this, for example:
- It's 100% a breaking change
- The library already supports unmarshalling nulls to a user-provided generic type, which in an ideal world is preferable to the imperfect modelling of missing values using Go nils
- Reflection can be used to check if the concrete value is nil rather than
== nil
- I could nil-check the pointer before I pass it to my function
Nevertheless, because it's perfectly valid in Go to call a method on a (typed) nil pointer, I think my suggestion is overall more type-safe if the getters are pointer receivers rather than value receivers.
Additional context
While Go is not my first language, I would be happy to look into implementing this change if there is any interest in it!