Khan / Khan/genqlient

generics based optional type doesn't go well with graphql unions

Open
#400 1 comment 0 reactions 0 assignees View on GitHub
bug
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.