Skip to content

Commit 37fa3d6

Browse files
authored
Add support for mapping all nullable types as pointers (#198)
This implements the approach suggested in #178 (comment). See the added documentation for the full behavior.
1 parent 3685f3f commit 37fa3d6

7 files changed

+336
-17
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ When releasing a new version:
3131
- You can now enable `use_extensions` in the configuration file, to receive extensions returned by the GraphQL API server. Generated functions will return extensions as `map[string]interface{}`, if enabled.
3232
- You can now use `graphql.NewClientUsingGet` to create a client that uses query parameters to pass the query to the GraphQL API server.
3333
- In config files, `schema`, `operations`, and `generated` can now be absolute paths.
34+
- You can now configure how nullable types are mapped to Go types in the configuration file. Specifically, you can set `optional: pointer` to have all nullable GraphQL arguments, input fields, and output fields map to pointers.
3435

3536
### Bug fixes:
3637

docs/genqlient.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,23 @@ use_struct_references: boolean
9898
use_extensions: boolean
9999

100100

101+
# Customize how optional fields are handled.
102+
optional:
103+
# Customize how models are generated for optional fields. This can currently
104+
# be set to one of the following values:
105+
# - value (default): optional fields are generated as values, the same as
106+
# non-optional fields. E.g. fields with GraphQL types `String` or `String!`
107+
# will both map to the Go type `string`. When values are absent in
108+
# responses the zero value will be used.
109+
# - pointer: optional fields are generated as pointers. E.g. fields with
110+
# GraphQL type `String` will map to the Go type `*string`. When values are
111+
# absent in responses `nil` will be used. Optional list fields do not use
112+
# pointers-to-slices, so the GraphQL type `[String]` will map to the Go
113+
# type `[]*string`, not `*[]*string`; GraphQL null and empty list simply
114+
# map to Go nil- and empty-slice.
115+
output: value
116+
117+
101118
# A map from GraphQL type name to Go fully-qualified type name to override
102119
# the Go type genqlient will use for this GraphQL type.
103120
#

generate/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type Config struct {
2626
ContextType string `yaml:"context_type"`
2727
ClientGetter string `yaml:"client_getter"`
2828
Bindings map[string]*TypeBinding `yaml:"bindings"`
29+
Optional string `yaml:"optional"`
2930
StructReferences bool `yaml:"use_struct_references"`
3031
Extensions bool `yaml:"use_extensions"`
3132

generate/convert.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ func (g *generator) convertType(
256256
oe := true
257257
options.Omitempty = &oe
258258
}
259-
} else if options.GetPointer() {
259+
} else if options.GetPointer() || (!typ.NonNull && g.Config.Optional == "pointer") {
260260
// Whatever we get, wrap it in a pointer. (Because of the way the
261261
// options work, recursing here isn't as connvenient.)
262262
// Note this does []*T or [][]*T, not e.g. *[][]T. See #16.

generate/generate_test.go

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -147,55 +147,64 @@ func getDefaultConfig(t *testing.T) *Config {
147147
// configurations. It uses snapshots, just like TestGenerate.
148148
func TestGenerateWithConfig(t *testing.T) {
149149
tests := []struct {
150-
name string
151-
baseDir string // relative to dataDir
152-
config *Config // omits Schema and Operations, set below.
150+
name string
151+
baseDir string // relative to dataDir
152+
operations []string // overrides the default set below
153+
config *Config // omits Schema and Operations, set below.
153154
}{
154-
{"DefaultConfig", "", getDefaultConfig(t)},
155-
{"Subpackage", "", &Config{
155+
{"DefaultConfig", "", nil, getDefaultConfig(t)},
156+
{"Subpackage", "", nil, &Config{
156157
Generated: "mypkg/myfile.go",
157158
}},
158-
{"SubpackageConfig", "mypkg", &Config{
159+
{"SubpackageConfig", "mypkg", nil, &Config{
159160
Generated: "myfile.go", // (relative to genqlient.yaml)
160161
}},
161-
{"PackageName", "", &Config{
162+
{"PackageName", "", nil, &Config{
162163
Generated: "myfile.go",
163164
Package: "mypkg",
164165
}},
165-
{"ExportOperations", "", &Config{
166+
{"ExportOperations", "", nil, &Config{
166167
Generated: "generated.go",
167168
ExportOperations: "operations.json",
168169
}},
169-
{"CustomContext", "", &Config{
170+
{"CustomContext", "", nil, &Config{
170171
Generated: "generated.go",
171172
ContextType: "github.com/Khan/genqlient/internal/testutil.MyContext",
172173
}},
173-
{"StructReferences", "", &Config{
174+
{"StructReferences", "", nil, &Config{
174175
StructReferences: true,
175176
Generated: "generated-structrefs.go",
176177
}},
177-
{"NoContext", "", &Config{
178+
{"NoContext", "", nil, &Config{
178179
Generated: "generated.go",
179180
ContextType: "-",
180181
}},
181-
{"ClientGetter", "", &Config{
182+
{"ClientGetter", "", nil, &Config{
182183
Generated: "generated.go",
183184
ClientGetter: "github.com/Khan/genqlient/internal/testutil.GetClientFromContext",
184185
}},
185-
{"ClientGetterCustomContext", "", &Config{
186+
{"ClientGetterCustomContext", "", nil, &Config{
186187
Generated: "generated.go",
187188
ClientGetter: "github.com/Khan/genqlient/internal/testutil.GetClientFromMyContext",
188189
ContextType: "github.com/Khan/genqlient/internal/testutil.MyContext",
189190
}},
190-
{"ClientGetterNoContext", "", &Config{
191+
{"ClientGetterNoContext", "", nil, &Config{
191192
Generated: "generated.go",
192193
ClientGetter: "github.com/Khan/genqlient/internal/testutil.GetClientFromNowhere",
193194
ContextType: "-",
194195
}},
195-
{"Extensions", "", &Config{
196+
{"Extensions", "", nil, &Config{
196197
Generated: "generated.go",
197198
Extensions: true,
198199
}},
200+
{"OptionalValue", "", []string{"ListInput.graphql", "QueryWithSlices.graphql"}, &Config{
201+
Generated: "generated.go",
202+
Optional: "value",
203+
}},
204+
{"OptionalPointer", "", []string{"ListInput.graphql", "QueryWithSlices.graphql"}, &Config{
205+
Generated: "generated.go",
206+
Optional: "pointer",
207+
}},
199208
}
200209

201210
sourceFilename := "SimpleQuery.graphql"
@@ -206,7 +215,14 @@ func TestGenerateWithConfig(t *testing.T) {
206215
t.Run(test.name, func(t *testing.T) {
207216
err := config.ValidateAndFillDefaults(baseDir)
208217
config.Schema = []string{filepath.Join(dataDir, "schema.graphql")}
209-
config.Operations = []string{filepath.Join(dataDir, sourceFilename)}
218+
if test.operations == nil {
219+
config.Operations = []string{filepath.Join(dataDir, sourceFilename)}
220+
} else {
221+
config.Operations = make([]string, len(test.operations))
222+
for i := range test.operations {
223+
config.Operations[i] = filepath.Join(dataDir, test.operations[i])
224+
}
225+
}
210226
if err != nil {
211227
t.Fatal(err)
212228
}

generate/testdata/snapshots/TestGenerateWithConfig-OptionalPointer-testdata-queries-generated.go

Lines changed: 142 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)