## Summary: In practice, at Khan at least, this is easy to mess up when writing mocks, because you write the mock by looking at the query, and the query doesn't say it's asking for `__typename` (because genqlient automatically adds that). A sufficiently-smart mocking library might be able to fix that, or detect it at least, but in any case, we can give a clearer error. I also removed an unrelated TODO that was done. Issue: https://khanacademy.slack.com/archives/C01120CNCS0/p1630019788014000 ## Test plan: make check Author: benjaminjkraft Reviewers: dnerdy, aberkan, csilvers, MiguelCastillo Required Reviewers: Approved by: dnerdy Checks: ✅ Test (1.17), ✅ Test (1.16), ✅ Test (1.15), ✅ Test (1.14), ✅ Test (1.13), ✅ Lint, ✅ Test (1.17), ✅ Test (1.16), ✅ Test (1.15), ✅ Test (1.14), ✅ Test (1.13), ✅ Lint Pull request URL: https://github.com/Khan/genqlient/pull/68
99 lines
4.4 KiB
Cheetah
99 lines
4.4 KiB
Cheetah
{{/* (the blank lines at the start are intentional, to separate
|
|
UnmarshalJSON from the function it follows) */}}
|
|
|
|
func (v *{{.GoName}}) UnmarshalJSON(b []byte) error {
|
|
{{/* We want to specially handle the abstract fields (.AbstractFields),
|
|
but unmarshal everything else normally. To handle abstract fields,
|
|
first we unmarshal them into a json.RawMessage, and then handle those
|
|
further, below. For the rest, we just want to call json.Unmarshal.
|
|
But if we do that naively on a value of type `.Type`, it will call
|
|
this function again, and recurse infinitely. So we make a wrapper
|
|
type -- with a different name, thus different methods, but the same
|
|
fields, and unmarshal into that. For more on why this is so
|
|
difficult, see
|
|
https://github.com/benjaminjkraft/notes/blob/master/go-json-interfaces.md
|
|
TODO(benkraft)): Ensure `{{.Type}}Wrapper` won't collide with any
|
|
other type we need. (For the most part it being locally-scoped saves
|
|
us; it's not clear if this can be a problem in practice.)
|
|
*/}}
|
|
type {{.GoName}}Wrapper {{.GoName}}
|
|
|
|
var firstPass struct{
|
|
*{{.GoName}}Wrapper
|
|
{{range .AbstractFields -}}
|
|
{{.GoName}} {{repeat .GoType.SliceDepth "[]"}}{{ref "encoding/json.RawMessage"}} `json:"{{.JSONName}}"`
|
|
{{end}}
|
|
}
|
|
firstPass.{{.GoName}}Wrapper = (*{{.GoName}}Wrapper)(v)
|
|
|
|
err := {{ref "encoding/json.Unmarshal"}}(b, &firstPass)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
{{/* Now, for each field, call out to the unmarshal-helper.
|
|
This gets a little complicated because we may have a slice field.
|
|
So what we do is basically, for each field of type `[][]...[]MyType`:
|
|
|
|
target := &v.MyField // *[][]...[]MyType
|
|
raw := firstPass.MyField // [][]...[]json.RawMessage
|
|
|
|
// repeat the following three lines n times; each time, inside
|
|
// the loop we have one less layer of slice on raw and target
|
|
*target = make([][]...[]MyType, len(raw))
|
|
for i, raw := range raw {
|
|
// We need the &(*target)[i] because at each stage we want to
|
|
// keep target as a pointer. (It only really has to be a
|
|
// pointer at the innermost level, but it's easiest to be
|
|
// consistent.)
|
|
target := &(*target)[i]
|
|
|
|
// (now we have `target *MyType` and `raw json.RawMessage`)
|
|
__unmarshalMyType(target, raw)
|
|
|
|
} // (also n times)
|
|
|
|
Note that if the field also uses a pointer (`[][]...[]*MyType`), we
|
|
now pass around `*[][]...[]*MyType`; again in principle
|
|
`[][]...[]*MyType` would work but require more special-casing. Thus
|
|
in the innermost loop, `target` is of type `**MyType`, so we have to
|
|
pass `*target` to the unmarshal-helper. Of course, since MyType is an
|
|
interface, I'm not sure why you'd any of that anyway.
|
|
|
|
One additional trick is we wrap everything above in a block ({ ... }),
|
|
so that the variables target and raw may take on different types for
|
|
each field we are handling, which would otherwise conflict. (We could
|
|
instead suffix the names, but that makes things much harder to read.)
|
|
*/}}
|
|
{{range $field := .AbstractFields -}}
|
|
{
|
|
target := &v.{{$field.GoName}}
|
|
raw := firstPass.{{$field.GoName}}
|
|
{{range $i := intRange $field.GoType.SliceDepth -}}
|
|
*target = make(
|
|
{{repeat (sub $field.GoType.SliceDepth $i) "[]"}}{{if $field.GoType.IsPointer}}*{{end}}{{$field.GoType.Unwrap.Reference}},
|
|
len(raw))
|
|
for i, raw := range raw {
|
|
target := &(*target)[i]
|
|
{{end -}}
|
|
{{if $field.GoType.IsPointer -}}
|
|
{{/* In this case, the parent for loop did `make([]*MyType, ...)` and
|
|
we have a pointer into that list. But we actually still need to
|
|
initialize the *elements* of the list. */ -}}
|
|
*target = new({{$field.GoType.Unwrap.Reference}})
|
|
{{end -}}
|
|
err = __unmarshal{{$field.GoType.Unwrap.Reference}}(
|
|
{{if $field.GoType.IsPointer}}*{{end}}target, raw)
|
|
if err != nil {
|
|
return fmt.Errorf(
|
|
"Unable to unmarshal {{$.GoName}}.{{$field.GoName}}: %w", err)
|
|
}
|
|
{{range $i := intRange $field.GoType.SliceDepth -}}
|
|
}
|
|
{{end -}}
|
|
}
|
|
{{end -}}
|
|
|
|
return nil
|
|
}
|