Refactor argument-handling to use a struct (#103)
## Summary: In this commit I refactor the argument-generation logic to move most of the code out of the template and into the type-generator. This logic predates #51, and I didn't think to update it there, but I think it benefits from similar treatment, for similar reasons. Specifically, the main change is to treat variables as another struct type we can generate, rather than handling them inline as a `map[string]interface{}`. Users still pass them the same way, but instead of putting them into a `map[string]interface{}` and JSONifying that, we generate a struct and put them there. This turns out to simplify things quite a lot, because we already have a lot of code to generate types. Notably, the omitempty code goes from a dozen lines to basically two, and fixes a bug (#43) in the process, because now that we have a struct, `json.Marshal` will do our work for us! (And, once we have syntax for it (#14), we'll be able to handle field-level omitempty basically for free.) More importantly, it will simplify custom marshalers (#38, forthcoming) significantly, since we do all that logic at the containing-struct level, but will need to apply it to arguments. It does require two breaking changes: 1. For folks implementing the `graphql.Client` API (rather than just calling `NewClient`): we now pass them variables as an `interface{}` rather than a `map[string]interface{}`. For most callers, including Khan/webapp, this is basically a one-line change to the signature of their `MakeRequest`, and it should be a lot more future-proof. 2. genqlient's handling of the `omitempty` option has changed to match that of `encoding/json`, in particular it now never considers structs "empty". The difference was never intentional (I just didn't realize that behavior of `encoding/json`); arguably our behavior was more useful but I think that's outweighed by the value of consistency with `encoding/json` as well as the simpler and more correct implementation (fixing #43 is actually quite nontrivial otherwise). Once we have custom unmarshaler support (#38), users will be able to map a zero value to JSON null if they wish, which is mostly if not entirely equivalent for GraphQL's purposes. Issue: https://github.com/Khan/genqlient/issues/38 Issue: https://github.com/Khan/genqlient/issues/43 ## Test plan: make check Author: benjaminjkraft Reviewers: StevenACoffman, dnerdy, aberkan, jvoll, mahtabsabet, MiguelCastillo Required Reviewers: Approved By: StevenACoffman, dnerdy Checks: ✅ Test (1.17), ✅ Test (1.16), ✅ Test (1.15), ✅ Test (1.14), ✅ Lint, ✅ Lint, ✅ Test (1.17), ✅ Test (1.16), ✅ Test (1.15), ✅ Test (1.14) Pull Request URL: https://github.com/Khan/genqlient/pull/103
This commit is contained in:
+10
-5
@@ -8,6 +8,12 @@ import (
|
||||
"github.com/Khan/genqlient/graphql"
|
||||
)
|
||||
|
||||
// __convertTimezoneInput is used internally by genqlient
|
||||
type __convertTimezoneInput struct {
|
||||
Dt time.Time `json:"dt"`
|
||||
Tz string `json:"tz"`
|
||||
}
|
||||
|
||||
// convertTimezoneResponse is returned by convertTimezone on success.
|
||||
type convertTimezoneResponse struct {
|
||||
Convert time.Time `json:"convert"`
|
||||
@@ -18,11 +24,10 @@ func convertTimezone(
|
||||
dt time.Time,
|
||||
tz string,
|
||||
) (*convertTimezoneResponse, error) {
|
||||
variables := map[string]interface{}{
|
||||
"dt": dt,
|
||||
"tz": tz,
|
||||
__input := __convertTimezoneInput{
|
||||
Dt: dt,
|
||||
Tz: tz,
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
var retval convertTimezoneResponse
|
||||
@@ -35,7 +40,7 @@ query convertTimezone ($dt: DateTime!, $tz: String) {
|
||||
}
|
||||
`,
|
||||
&retval,
|
||||
variables,
|
||||
&__input,
|
||||
)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
+8
-4
@@ -38,14 +38,18 @@ const (
|
||||
RoleTeacher Role = "TEACHER"
|
||||
)
|
||||
|
||||
// __InputEnumQueryInput is used internally by genqlient
|
||||
type __InputEnumQueryInput struct {
|
||||
Role Role `json:"role"`
|
||||
}
|
||||
|
||||
func InputEnumQuery(
|
||||
client graphql.Client,
|
||||
role Role,
|
||||
) (*InputEnumQueryResponse, error) {
|
||||
variables := map[string]interface{}{
|
||||
"role": role,
|
||||
__input := __InputEnumQueryInput{
|
||||
Role: role,
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
var retval InputEnumQueryResponse
|
||||
@@ -60,7 +64,7 @@ query InputEnumQuery ($role: Role!) {
|
||||
}
|
||||
`,
|
||||
&retval,
|
||||
variables,
|
||||
&__input,
|
||||
)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
+8
-4
@@ -56,14 +56,18 @@ type UserQueryInput struct {
|
||||
HasPokemon testutil.Pokemon `json:"hasPokemon"`
|
||||
}
|
||||
|
||||
// __InputObjectQueryInput is used internally by genqlient
|
||||
type __InputObjectQueryInput struct {
|
||||
Query UserQueryInput `json:"query"`
|
||||
}
|
||||
|
||||
func InputObjectQuery(
|
||||
client graphql.Client,
|
||||
query UserQueryInput,
|
||||
) (*InputObjectQueryResponse, error) {
|
||||
variables := map[string]interface{}{
|
||||
"query": query,
|
||||
__input := __InputObjectQueryInput{
|
||||
Query: query,
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
var retval InputObjectQueryResponse
|
||||
@@ -78,7 +82,7 @@ query InputObjectQuery ($query: UserQueryInput) {
|
||||
}
|
||||
`,
|
||||
&retval,
|
||||
variables,
|
||||
&__input,
|
||||
)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
+8
-4
@@ -27,14 +27,18 @@ type ListInputQueryUser struct {
|
||||
Id testutil.ID `json:"id"`
|
||||
}
|
||||
|
||||
// __ListInputQueryInput is used internally by genqlient
|
||||
type __ListInputQueryInput struct {
|
||||
Names []string `json:"names"`
|
||||
}
|
||||
|
||||
func ListInputQuery(
|
||||
client graphql.Client,
|
||||
names []string,
|
||||
) (*ListInputQueryResponse, error) {
|
||||
variables := map[string]interface{}{
|
||||
"names": names,
|
||||
__input := __ListInputQueryInput{
|
||||
Names: names,
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
var retval ListInputQueryResponse
|
||||
@@ -49,7 +53,7 @@ query ListInputQuery ($names: [String]) {
|
||||
}
|
||||
`,
|
||||
&retval,
|
||||
variables,
|
||||
&__input,
|
||||
)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
+16
-23
@@ -72,6 +72,15 @@ type UserQueryInput struct {
|
||||
HasPokemon testutil.Pokemon `json:"hasPokemon"`
|
||||
}
|
||||
|
||||
// __OmitEmptyQueryInput is used internally by genqlient
|
||||
type __OmitEmptyQueryInput struct {
|
||||
Query UserQueryInput `json:"query,omitempty"`
|
||||
Queries []UserQueryInput `json:"queries,omitempty"`
|
||||
Dt time.Time `json:"dt,omitempty"`
|
||||
Tz string `json:"tz,omitempty"`
|
||||
TzNoOmitEmpty string `json:"tzNoOmitEmpty"`
|
||||
}
|
||||
|
||||
func OmitEmptyQuery(
|
||||
client graphql.Client,
|
||||
query UserQueryInput,
|
||||
@@ -80,29 +89,13 @@ func OmitEmptyQuery(
|
||||
tz string,
|
||||
tzNoOmitEmpty string,
|
||||
) (*OmitEmptyQueryResponse, error) {
|
||||
variables := map[string]interface{}{
|
||||
"tzNoOmitEmpty": tzNoOmitEmpty,
|
||||
__input := __OmitEmptyQueryInput{
|
||||
Query: query,
|
||||
Queries: queries,
|
||||
Dt: dt,
|
||||
Tz: tz,
|
||||
TzNoOmitEmpty: tzNoOmitEmpty,
|
||||
}
|
||||
|
||||
var zero_query UserQueryInput
|
||||
if query != zero_query {
|
||||
variables["query"] = query
|
||||
}
|
||||
|
||||
if len(queries) > 0 {
|
||||
variables["queries"] = queries
|
||||
}
|
||||
|
||||
var zero_dt time.Time
|
||||
if dt != zero_dt {
|
||||
variables["dt"] = dt
|
||||
}
|
||||
|
||||
var zero_tz string
|
||||
if tz != zero_tz {
|
||||
variables["tz"] = tz
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
var retval OmitEmptyQueryResponse
|
||||
@@ -122,7 +115,7 @@ query OmitEmptyQuery ($query: UserQueryInput, $queries: [UserQueryInput], $dt: D
|
||||
}
|
||||
`,
|
||||
&retval,
|
||||
variables,
|
||||
&__input,
|
||||
)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
+14
-8
@@ -79,18 +79,24 @@ type UserQueryInput struct {
|
||||
HasPokemon *testutil.Pokemon `json:"hasPokemon"`
|
||||
}
|
||||
|
||||
// __PointersQueryInput is used internally by genqlient
|
||||
type __PointersQueryInput struct {
|
||||
Query *UserQueryInput `json:"query"`
|
||||
Dt time.Time `json:"dt"`
|
||||
Tz *string `json:"tz"`
|
||||
}
|
||||
|
||||
func PointersQuery(
|
||||
client graphql.Client,
|
||||
query UserQueryInput,
|
||||
query *UserQueryInput,
|
||||
dt time.Time,
|
||||
tz string,
|
||||
tz *string,
|
||||
) (*PointersQueryResponse, error) {
|
||||
variables := map[string]interface{}{
|
||||
"query": query,
|
||||
"dt": dt,
|
||||
"tz": tz,
|
||||
__input := __PointersQueryInput{
|
||||
Query: query,
|
||||
Dt: dt,
|
||||
Tz: tz,
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
var retval PointersQueryResponse
|
||||
@@ -113,7 +119,7 @@ query PointersQuery ($query: UserQueryInput, $dt: DateTime, $tz: String) {
|
||||
}
|
||||
`,
|
||||
&retval,
|
||||
variables,
|
||||
&__input,
|
||||
)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
+12
-6
@@ -79,18 +79,24 @@ type UserQueryInput struct {
|
||||
HasPokemon testutil.Pokemon `json:"hasPokemon"`
|
||||
}
|
||||
|
||||
// __PointersQueryInput is used internally by genqlient
|
||||
type __PointersQueryInput struct {
|
||||
Query *UserQueryInput `json:"query"`
|
||||
Dt *time.Time `json:"dt"`
|
||||
Tz string `json:"tz"`
|
||||
}
|
||||
|
||||
func PointersQuery(
|
||||
client graphql.Client,
|
||||
query *UserQueryInput,
|
||||
dt *time.Time,
|
||||
tz string,
|
||||
) (*PointersQueryResponse, error) {
|
||||
variables := map[string]interface{}{
|
||||
"query": query,
|
||||
"dt": dt,
|
||||
"tz": tz,
|
||||
__input := __PointersQueryInput{
|
||||
Query: query,
|
||||
Dt: dt,
|
||||
Tz: tz,
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
var retval PointersQueryResponse
|
||||
@@ -113,7 +119,7 @@ query PointersQuery ($query: UserQueryInput, $dt: DateTime, $tz: String) {
|
||||
}
|
||||
`,
|
||||
&retval,
|
||||
variables,
|
||||
&__input,
|
||||
)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
+8
-4
@@ -37,14 +37,18 @@ type GetPokemonSiblingsUserGenqlientPokemon struct {
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
// __GetPokemonSiblingsInput is used internally by genqlient
|
||||
type __GetPokemonSiblingsInput struct {
|
||||
Input testutil.Pokemon `json:"input"`
|
||||
}
|
||||
|
||||
func GetPokemonSiblings(
|
||||
client graphql.Client,
|
||||
input testutil.Pokemon,
|
||||
) (*GetPokemonSiblingsResponse, error) {
|
||||
variables := map[string]interface{}{
|
||||
"input": input,
|
||||
__input := __GetPokemonSiblingsInput{
|
||||
Input: input,
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
var retval GetPokemonSiblingsResponse
|
||||
@@ -69,7 +73,7 @@ query GetPokemonSiblings ($input: PokemonInput!) {
|
||||
}
|
||||
`,
|
||||
&retval,
|
||||
variables,
|
||||
&__input,
|
||||
)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
+8
-4
@@ -36,14 +36,18 @@ type RecursiveInput struct {
|
||||
Rec []RecursiveInput `json:"rec"`
|
||||
}
|
||||
|
||||
// __RecursionInput is used internally by genqlient
|
||||
type __RecursionInput struct {
|
||||
Input RecursiveInput `json:"input"`
|
||||
}
|
||||
|
||||
func Recursion(
|
||||
client graphql.Client,
|
||||
input RecursiveInput,
|
||||
) (*RecursionResponse, error) {
|
||||
variables := map[string]interface{}{
|
||||
"input": input,
|
||||
__input := __RecursionInput{
|
||||
Input: input,
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
var retval RecursionResponse
|
||||
@@ -64,7 +68,7 @@ query Recursion ($input: RecursiveInput!) {
|
||||
}
|
||||
`,
|
||||
&retval,
|
||||
variables,
|
||||
&__input,
|
||||
)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
+8
-4
@@ -27,14 +27,18 @@ type SimpleInputQueryUser struct {
|
||||
Id testutil.ID `json:"id"`
|
||||
}
|
||||
|
||||
// __SimpleInputQueryInput is used internally by genqlient
|
||||
type __SimpleInputQueryInput struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func SimpleInputQuery(
|
||||
client graphql.Client,
|
||||
name string,
|
||||
) (*SimpleInputQueryResponse, error) {
|
||||
variables := map[string]interface{}{
|
||||
"name": name,
|
||||
__input := __SimpleInputQueryInput{
|
||||
Name: name,
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
var retval SimpleInputQueryResponse
|
||||
@@ -49,7 +53,7 @@ query SimpleInputQuery ($name: String!) {
|
||||
}
|
||||
`,
|
||||
&retval,
|
||||
variables,
|
||||
&__input,
|
||||
)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
+8
-4
@@ -24,6 +24,11 @@ type SimpleMutationResponse struct {
|
||||
CreateUser SimpleMutationCreateUser `json:"createUser"`
|
||||
}
|
||||
|
||||
// __SimpleMutationInput is used internally by genqlient
|
||||
type __SimpleMutationInput struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// SimpleMutation creates a user.
|
||||
//
|
||||
// It has a long doc-comment, to test that we handle that correctly.
|
||||
@@ -32,10 +37,9 @@ func SimpleMutation(
|
||||
client graphql.Client,
|
||||
name string,
|
||||
) (*SimpleMutationResponse, error) {
|
||||
variables := map[string]interface{}{
|
||||
"name": name,
|
||||
__input := __SimpleMutationInput{
|
||||
Name: name,
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
var retval SimpleMutationResponse
|
||||
@@ -51,7 +55,7 @@ mutation SimpleMutation ($name: String!) {
|
||||
}
|
||||
`,
|
||||
&retval,
|
||||
variables,
|
||||
&__input,
|
||||
)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
+8
-4
@@ -36,6 +36,11 @@ type UserQueryInput struct {
|
||||
HasPokemon testutil.Pokemon `json:"hasPokemon"`
|
||||
}
|
||||
|
||||
// __unexportedInput is used internally by genqlient
|
||||
type __unexportedInput struct {
|
||||
Query UserQueryInput `json:"query"`
|
||||
}
|
||||
|
||||
// unexportedResponse is returned by unexported on success.
|
||||
type unexportedResponse struct {
|
||||
// user looks up a user by some stuff.
|
||||
@@ -60,10 +65,9 @@ func unexported(
|
||||
client graphql.Client,
|
||||
query UserQueryInput,
|
||||
) (*unexportedResponse, error) {
|
||||
variables := map[string]interface{}{
|
||||
"query": query,
|
||||
__input := __unexportedInput{
|
||||
Query: query,
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
var retval unexportedResponse
|
||||
@@ -78,7 +82,7 @@ query unexported ($query: UserQueryInput) {
|
||||
}
|
||||
`,
|
||||
&retval,
|
||||
variables,
|
||||
&__input,
|
||||
)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user