Skip to content

Commit

Permalink
Add support for mapping all nullable types as pointers (Khan#198)
Browse files Browse the repository at this point in the history
This implements the approach suggested in
Khan#178 (comment).
See the added documentation for the full behavior.
  • Loading branch information
connec authored May 24, 2022
1 parent 3685f3f commit 37fa3d6
Show file tree
Hide file tree
Showing 7 changed files with 336 additions and 17 deletions.
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ When releasing a new version:
- 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.
- You can now use `graphql.NewClientUsingGet` to create a client that uses query parameters to pass the query to the GraphQL API server.
- In config files, `schema`, `operations`, and `generated` can now be absolute paths.
- 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.

### Bug fixes:

Expand Down
17 changes: 17 additions & 0 deletions docs/genqlient.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,23 @@ use_struct_references: boolean
use_extensions: boolean


# Customize how optional fields are handled.
optional:
# Customize how models are generated for optional fields. This can currently
# be set to one of the following values:
# - value (default): optional fields are generated as values, the same as
# non-optional fields. E.g. fields with GraphQL types `String` or `String!`
# will both map to the Go type `string`. When values are absent in
# responses the zero value will be used.
# - pointer: optional fields are generated as pointers. E.g. fields with
# GraphQL type `String` will map to the Go type `*string`. When values are
# absent in responses `nil` will be used. Optional list fields do not use
# pointers-to-slices, so the GraphQL type `[String]` will map to the Go
# type `[]*string`, not `*[]*string`; GraphQL null and empty list simply
# map to Go nil- and empty-slice.
output: value


# A map from GraphQL type name to Go fully-qualified type name to override
# the Go type genqlient will use for this GraphQL type.
#
Expand Down
1 change: 1 addition & 0 deletions generate/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type Config struct {
ContextType string `yaml:"context_type"`
ClientGetter string `yaml:"client_getter"`
Bindings map[string]*TypeBinding `yaml:"bindings"`
Optional string `yaml:"optional"`
StructReferences bool `yaml:"use_struct_references"`
Extensions bool `yaml:"use_extensions"`

Expand Down
2 changes: 1 addition & 1 deletion generate/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ func (g *generator) convertType(
oe := true
options.Omitempty = &oe
}
} else if options.GetPointer() {
} else if options.GetPointer() || (!typ.NonNull && g.Config.Optional == "pointer") {
// Whatever we get, wrap it in a pointer. (Because of the way the
// options work, recursing here isn't as connvenient.)
// Note this does []*T or [][]*T, not e.g. *[][]T. See #16.
Expand Down
48 changes: 32 additions & 16 deletions generate/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,55 +147,64 @@ func getDefaultConfig(t *testing.T) *Config {
// configurations. It uses snapshots, just like TestGenerate.
func TestGenerateWithConfig(t *testing.T) {
tests := []struct {
name string
baseDir string // relative to dataDir
config *Config // omits Schema and Operations, set below.
name string
baseDir string // relative to dataDir
operations []string // overrides the default set below
config *Config // omits Schema and Operations, set below.
}{
{"DefaultConfig", "", getDefaultConfig(t)},
{"Subpackage", "", &Config{
{"DefaultConfig", "", nil, getDefaultConfig(t)},
{"Subpackage", "", nil, &Config{
Generated: "mypkg/myfile.go",
}},
{"SubpackageConfig", "mypkg", &Config{
{"SubpackageConfig", "mypkg", nil, &Config{
Generated: "myfile.go", // (relative to genqlient.yaml)
}},
{"PackageName", "", &Config{
{"PackageName", "", nil, &Config{
Generated: "myfile.go",
Package: "mypkg",
}},
{"ExportOperations", "", &Config{
{"ExportOperations", "", nil, &Config{
Generated: "generated.go",
ExportOperations: "operations.json",
}},
{"CustomContext", "", &Config{
{"CustomContext", "", nil, &Config{
Generated: "generated.go",
ContextType: "github.com/Khan/genqlient/internal/testutil.MyContext",
}},
{"StructReferences", "", &Config{
{"StructReferences", "", nil, &Config{
StructReferences: true,
Generated: "generated-structrefs.go",
}},
{"NoContext", "", &Config{
{"NoContext", "", nil, &Config{
Generated: "generated.go",
ContextType: "-",
}},
{"ClientGetter", "", &Config{
{"ClientGetter", "", nil, &Config{
Generated: "generated.go",
ClientGetter: "github.com/Khan/genqlient/internal/testutil.GetClientFromContext",
}},
{"ClientGetterCustomContext", "", &Config{
{"ClientGetterCustomContext", "", nil, &Config{
Generated: "generated.go",
ClientGetter: "github.com/Khan/genqlient/internal/testutil.GetClientFromMyContext",
ContextType: "github.com/Khan/genqlient/internal/testutil.MyContext",
}},
{"ClientGetterNoContext", "", &Config{
{"ClientGetterNoContext", "", nil, &Config{
Generated: "generated.go",
ClientGetter: "github.com/Khan/genqlient/internal/testutil.GetClientFromNowhere",
ContextType: "-",
}},
{"Extensions", "", &Config{
{"Extensions", "", nil, &Config{
Generated: "generated.go",
Extensions: true,
}},
{"OptionalValue", "", []string{"ListInput.graphql", "QueryWithSlices.graphql"}, &Config{
Generated: "generated.go",
Optional: "value",
}},
{"OptionalPointer", "", []string{"ListInput.graphql", "QueryWithSlices.graphql"}, &Config{
Generated: "generated.go",
Optional: "pointer",
}},
}

sourceFilename := "SimpleQuery.graphql"
Expand All @@ -206,7 +215,14 @@ func TestGenerateWithConfig(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
err := config.ValidateAndFillDefaults(baseDir)
config.Schema = []string{filepath.Join(dataDir, "schema.graphql")}
config.Operations = []string{filepath.Join(dataDir, sourceFilename)}
if test.operations == nil {
config.Operations = []string{filepath.Join(dataDir, sourceFilename)}
} else {
config.Operations = make([]string, len(test.operations))
for i := range test.operations {
config.Operations[i] = filepath.Join(dataDir, test.operations[i])
}
}
if err != nil {
t.Fatal(err)
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 37fa3d6

Please sign in to comment.