Potential improvement to generated getters for pointer-optionals
- Dominant language
- Go
- Stars
- 1.3k
- Forks
- 143
- Avg merge
- 4h 50m
- Merged PRs (30d)
- 1
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
```graphql
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
```graphql
query FindPerson($name: String!) {
person(name: $name) {
name
favouriteFood {
name
}
}
}
```
One of the things genqlient will generate is a getter method for `favouriteFood.name`:
```go
// 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:
```go
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:
```go
// 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:
```go
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](https://groups.google.com/g/golang-nuts/c/wnH302gBa4I/m/qws8QeidBAAJ). 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!
Contributor guide
Research direction
Start by tracing how genqlient generates getters for pointer-optional nested types, using the Food/Person schema and FindPerson query as the entry point. Define and test the intended nil-pointer behavior, returned type, and compatibility impact before deciding whether the proposed change is complete.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, graphql
- Domain
- tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100