generics based optional type doesn't go well with graphql unions
- Dominant language
- Go
- Stars
- 1.3k
- Forks
- 143
- Avg merge
- 4h 50m
- Merged PRs (30d)
- 1
Description
**Describe the bug**
Generated MarshalJSON and UnmarshalJSON functions for type union-based fragments are invalid and need manual intervention.
The documentation about the optional_generic_type gives a possibility to specify the type, but doesn't require and specific contract so the generator doesn't know anything about the internal structure. When UnmarshalJSON and __premarshalJSON functions are generated, the code does't count, with the fact, that the value is wrapped inside of struct.
**MarshalJSON**
https://github.com/Khan/genqlient/blob/7c070dd710caf1c753ce317664db4fa06c5553fa/generate/marshal.go.tmpl#L57
The code needs to be aware how to get value from
src := v.Source => src := v.Source.Value()
**UnmarshalJSON**
Similar issue, but with an extra step, the wrapper usually has an internal bool attribute that needs to be set to true to signal that the value was set.
``golang
var v Source
err = __unmarshalSource(
src, &v) // unmarchal to variable
if err != nil {
return fmt.Errorf(
"unable to unmarshal PersonalizedSnippetPreviewFragment.Source: %w", err)
}
dst.SetValue(v) // set to omittable
``
https://github.com/Khan/genqlient/blob/7c070dd710caf1c753ce317664db4fa06c5553fa/generate/unmarshal.go.tmpl#L139
**To Reproduce**
Steps to reproduce the behavior. Please include whether the problem happens at code-generation time or at runtime.
We are using our own generic type for optional types
optional_generic_type: "our package /model.Omittable"
Then we have a following union type using type union in graphql schema and fragments
```graphql
Source =
SourceA
| SourceB
| SourceN
type Message {
source: Source
}
fragment FragmentMessage on Message {
source {
...FragmentSource
}
}
fragment FragmentSource on Source {
... on SourceA {
... some fields
}
... on SourceB {
... some fields
}
... on SourceN {
... some fields
}
}
```
**Expected behavior**
The union type based fragments should be working without manual intervention in generated code.
**genqlient version**
github.com/Khan/genqlient v0.7.0 but i haven't found any trace that latest would work
**Additional context**
I guess that some contract on optional wrappers is required or some extra settings that would allow to specify functions that would return or set values for example:
``yaml
optional_generic_type_methods:
getter: Value
setter: SetValue
``
where functions signatures are:
``golang
type Omittable[T any] struct {
value T
set bool
}
func (o Omittable[T]) Value() T {
if !o.set {
var zero T
return zero
}
return o.value
}
func (o *Omittable[T]) SetValue(v T) {
o.set = true
o.value = v
}
``
Contributor guide
Research direction
Reproduce the union-fragment case using the custom optional_generic_type, then inspect generate/marshal.go.tmpl around line 57 and generate/unmarshal.go.tmpl around line 139. Determine the required getter and setter contract for the wrapper, and verify that generated MarshalJSON and UnmarshalJSON code works without manual intervention.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, graphql
- Domain
- backend-api-design, tooling
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100