Skip to content

Commit ef288a3

Browse files
authored
Annotated optional fields with omitempty by using new optional config value (#308)
It could be useful to annotate optional fields annotate with `omitempty` when using pointers. By allowing this option, input fields that get removed (and not used is the executed queries) will not break. This gives the library an advantage to be more resilient against break API changes. To introduce this option, a new `optional` config value is introduced `pointer_omitempty`. If `optional: pointer_omitempty` is set. We generate ``` type Task_insert_input struct { Id: *int `json:"id,omitempty"` } ``` based on ``` input task_insert_input { id: Int } ``` The `@genqlient(omitempty)` config will override the previous defined config.
1 parent 35bfe03 commit ef288a3

10 files changed

+1231
-4
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ In addition to several new features and bugfixes, along with this release comes
7878
### New features:
7979

8080
- The new `optional: generic` allows using a generic type to represent optionality. See the [documentation](genqlient.yaml) for details.
81+
- The new `optional: pointer_omitempty` allows using a pointer that is also annotated with `omitempty`. See the [documentation](genqlient.yaml) for details.
8182
- For schemas with enum values that differ only in casing, it's now possible to disable smart-casing in genqlient.yaml; see the [documentation](genqlient.yaml) for `casing` for details.
8283
- genqlient now supports .graphqls and .gql file extensions for schemas and queries.
8384
- More accurately guess the package name for generated code (and warn if the config option -- now almost never needed -- looks wrong).

docs/genqlient.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ use_extensions: boolean
116116
# pointers-to-slices, so the GraphQL type `[String]` will map to the Go
117117
# type `[]*string`, not `*[]*string`; GraphQL null and empty list simply
118118
# map to Go nil- and empty-slice.
119+
# - pointer_omitempty: optional fields are generated as pointers, as described above.
120+
# Additionally, the fields are annotated with omitempty. Fields that are not defined,
121+
# will not be exposed.
119122
# - generic: optional fields are generated as type parameters to a generic type
120123
# specified by `optional_generic_type`. E.g. fields with GraphQL type `String`
121124
# will map to the Go type `generic.Type[string]`. This is useful if you have a

generate/config.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,8 @@ func (c *Config) ValidateAndFillDefaults(baseDir string) error {
212212
c.ContextType = "context.Context"
213213
}
214214

215-
if c.Optional != "" && c.Optional != "value" && c.Optional != "pointer" && c.Optional != "generic" {
216-
return errorf(nil, "optional must be one of: 'value' (default), 'pointer', or 'generic'")
215+
if c.Optional != "" && c.Optional != "value" && c.Optional != "pointer" && c.Optional != "pointer_omitempty" && c.Optional != "generic" {
216+
return errorf(nil, "optional must be one of: 'value' (default), 'pointer', 'pointer_omitempty' or 'generic'")
217217
}
218218

219219
if c.Optional == "generic" && c.OptionalGenericType == "" {

generate/convert.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,15 @@ func (g *generator) convertType(
258258
oe := true
259259
options.Omitempty = &oe
260260
}
261+
} else if g.Config.Optional == "pointer_omitempty" && (options.GetPointer() || !typ.NonNull) {
262+
if !options.PointerIsFalse() {
263+
goTyp = &goPointerType{Elem: goTyp}
264+
}
265+
266+
if options.Omitempty == nil {
267+
oe := true
268+
options.Omitempty = &oe
269+
}
261270
} else if !options.PointerIsFalse() && (options.GetPointer() || (!typ.NonNull && g.Config.Optional == "pointer")) {
262271
// Whatever we get, wrap it in a pointer. (Because of the way the
263272
// options work, recursing here isn't as connvenient.)

generate/generate_test.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import (
99
"strings"
1010
"testing"
1111

12-
"github.com/Khan/genqlient/internal/testutil"
1312
"gopkg.in/yaml.v2"
13+
14+
"github.com/Khan/genqlient/internal/testutil"
1415
)
1516

1617
const (
@@ -239,6 +240,26 @@ func TestGenerateWithConfig(t *testing.T) {
239240
Enums: map[string]CasingAlgorithm{"Role": CasingRaw},
240241
},
241242
}},
243+
{"OptionalPointerOmitEmpty", "", []string{
244+
"InputObject.graphql",
245+
"PointersOmitEmpty.graphql",
246+
"Omitempty.graphql",
247+
"ListInput.graphql",
248+
}, &Config{
249+
Optional: "pointer_omitempty",
250+
Bindings: map[string]*TypeBinding{
251+
"Date": {
252+
Type: "time.Time",
253+
Marshaler: "github.com/Khan/genqlient/internal/testutil.MarshalDate",
254+
Unmarshaler: "github.com/Khan/genqlient/internal/testutil.UnmarshalDate",
255+
},
256+
"DateTime": {
257+
Type: "time.Time",
258+
Marshaler: "github.com/Khan/genqlient/internal/testutil.MarshalDate",
259+
Unmarshaler: "github.com/Khan/genqlient/internal/testutil.UnmarshalDate",
260+
},
261+
},
262+
}},
242263
{
243264
"UseStructReference", "", []string{"UseStructReference.graphql"}, &Config{
244265
StructReferences: true,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# @genqlient(for: "UserQueryInput.id", pointer: false)
2+
# @genqlient(for: "User.id", pointer: false)
3+
query PointersOmitEmptyQuery(
4+
$query: UserQueryInput,
5+
$dt: DateTime,
6+
# @genqlient(pointer: false)
7+
$tz: String,
8+
) {
9+
user(query: $query) {
10+
# @genqlient(pointer: true)
11+
id
12+
roles
13+
name
14+
emails
15+
# @genqlient(pointer: false)
16+
emailsNoPtr: emails
17+
}
18+
otherUser: user(query: $query) {
19+
id
20+
}
21+
maybeConvert(dt: $dt, tz: $tz)
22+
}

generate/testdata/snapshots/TestGenerate-PointersOmitEmpty.graphql-PointersOmitEmpty.graphql.go

Lines changed: 281 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"operations": [
3+
{
4+
"operationName": "PointersOmitEmptyQuery",
5+
"query": "\nquery PointersOmitEmptyQuery ($query: UserQueryInput, $dt: DateTime, $tz: String) {\n\tuser(query: $query) {\n\t\tid\n\t\troles\n\t\tname\n\t\temails\n\t\temailsNoPtr: emails\n\t}\n\totherUser: user(query: $query) {\n\t\tid\n\t}\n\tmaybeConvert(dt: $dt, tz: $tz)\n}\n",
6+
"sourceLocation": "testdata/queries/PointersOmitEmpty.graphql"
7+
}
8+
]
9+
}

0 commit comments

Comments
 (0)