From 6e70eb10def2e62f4537e967c05dba2d4eae63aa Mon Sep 17 00:00:00 2001 From: Ben Kraft Date: Sat, 17 Feb 2024 17:59:40 -0800 Subject: [PATCH] Improve package-sniffing and bind correctly to types in the same package The original motivation here is to fix #283, which is that if you try to use `bindings` to bind to a type in the same package as the generated code, we generate a self-import, which Go doesn't allow. Fixing that is easy -- the three lines in `imports.go` -- once you know the package-path of the generated code. (The test that that all fits together is in integration-tests because that was the easiest place to set up the right situation.) Determining the package-path is not too much harder: you ask `go/packages`, which we already use for `package_bindings`. But once we're doing that, and handling errors, it's kinda silly that we ask you to specify the package your generated code will use, because we have that too, usually. So I rewrote that handling too, making `package` now rarely necessary (see for example the `example` config), and warning if it looks wrong. This is the changes in `config.go`, and is the more substantial non-test change. (I also renamed some of the testdata dirs to be valid package-names, to exercise more of that code, or in the case of `find-config`, just for consistency.) --- docs/CHANGELOG.md | 2 + docs/genqlient.yaml | 8 +- example/genqlient.yaml | 2 - generate/config.go | 93 +++++++++++++++---- generate/config_test.go | 6 +- generate/imports.go | 3 + .../current/genqlient.yaml | 0 .../filenames/dotyaml/.genqlient.yaml | 0 .../filenames/dotyml/.genqlient.yml | 0 .../filenames/none/.gitkeep | 0 .../filenames/yaml/genqlient.yaml | 0 .../filenames/yml/genqlient.yml | 0 .../{find-config => findConfig}/none/.gitkeep | 0 .../none/child/.gitkeep | 0 .../parent/child/.gitkeep | 0 .../parent/genqlient.yaml | 0 .../InvalidCasing.yaml | 0 .../InvalidOptional.yaml | 0 .../InvalidPackage.yaml | 0 .../TestInvalidConfigs-CantGuessPackage.yaml | 3 - .../TestInvalidConfigs-InvalidCasing.yaml | 2 +- .../TestInvalidConfigs-InvalidOptional.yaml | 2 +- .../TestInvalidConfigs-InvalidPackage.yaml | 2 +- ...Simple.yaml => TestValidConfigs-Empty.yml} | 5 +- .../snapshots/TestValidConfigs-Lists.yaml | 13 +-- .../snapshots/TestValidConfigs-Strings.yaml | 9 +- generate/testdata/valid-config/Simple.yaml | 1 - .../Empty.yml} | 0 .../{valid-config => validConfig}/Lists.yaml | 2 - .../Strings.yaml | 1 - internal/integration/generated.go | 11 ++- internal/integration/genqlient.yaml | 2 + internal/integration/integration_test.go | 2 +- internal/integration/schema.graphql | 2 + internal/integration/server/gqlgen_exec.go | 83 +++++++++++++++++ internal/integration/server/gqlgen_models.go | 1 + internal/integration/server/server.go | 9 +- internal/integration/util.go | 6 ++ 38 files changed, 213 insertions(+), 57 deletions(-) rename generate/testdata/{find-config => findConfig}/current/genqlient.yaml (100%) rename generate/testdata/{find-config => findConfig}/filenames/dotyaml/.genqlient.yaml (100%) rename generate/testdata/{find-config => findConfig}/filenames/dotyml/.genqlient.yml (100%) rename generate/testdata/{find-config => findConfig}/filenames/none/.gitkeep (100%) rename generate/testdata/{find-config => findConfig}/filenames/yaml/genqlient.yaml (100%) rename generate/testdata/{find-config => findConfig}/filenames/yml/genqlient.yml (100%) rename generate/testdata/{find-config => findConfig}/none/.gitkeep (100%) rename generate/testdata/{find-config => findConfig}/none/child/.gitkeep (100%) rename generate/testdata/{find-config => findConfig}/parent/child/.gitkeep (100%) rename generate/testdata/{find-config => findConfig}/parent/genqlient.yaml (100%) rename generate/testdata/{invalid-config => invalidConfig}/InvalidCasing.yaml (100%) rename generate/testdata/{invalid-config => invalidConfig}/InvalidOptional.yaml (100%) rename generate/testdata/{invalid-config => invalidConfig}/InvalidPackage.yaml (100%) delete mode 100644 generate/testdata/snapshots/TestInvalidConfigs-CantGuessPackage.yaml rename generate/testdata/snapshots/{TestValidConfigs-Simple.yaml => TestValidConfigs-Empty.yml} (76%) delete mode 100644 generate/testdata/valid-config/Simple.yaml rename generate/testdata/{invalid-config/CantGuessPackage.yaml => validConfig/Empty.yml} (100%) rename generate/testdata/{valid-config => validConfig}/Lists.yaml (84%) rename generate/testdata/{valid-config => validConfig}/Strings.yaml (72%) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index be0c36f9..91538b5f 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -27,11 +27,13 @@ When releasing a new version: - The new `optional: generic` allows using a generic type to represent optionality. See the [documentation](genqlient.yaml) for details. - 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. - Support .graphqls and .gql file extensions +- More accurately guess the package name for generated code (and warn if the config option -- now almost never needed -- looks wrong). ### Bug fixes: - The presence of negative pointer directives, i.e., `# @genqlient(pointer: false)` are now respected even in the when `optional: pointer` is set in the configuration file. - Made name collisions between query/mutation arguments and local function variables less likely. - Fix generation issue related to golang type implementation of complex graphql union fragments +- Bind correctly to types in the same package as the generated code. ## v0.6.0 diff --git a/docs/genqlient.yaml b/docs/genqlient.yaml index ddfb760b..f31b251a 100644 --- a/docs/genqlient.yaml +++ b/docs/genqlient.yaml @@ -29,8 +29,12 @@ operations: # genqlient.yaml. Default: generated.go. generated: generated/genqlient.go -# The package name for the output code; defaults to the directory name of -# the generated-code file. +# The package name for the output code; defaults to the package-name +# corresponding to the setting of `generated`, above. +# +# This is rarely needed: only if you want the package-name to differ from the +# suffix of the package-path, and there are no other Go files in the package +# already. package: mygenerated # If set, a file at this path (relative to genqlient.yaml) will be generated diff --git a/example/genqlient.yaml b/example/genqlient.yaml index 11cd0348..1fe772a5 100644 --- a/example/genqlient.yaml +++ b/example/genqlient.yaml @@ -2,8 +2,6 @@ schema: schema.graphql operations: - genqlient.graphql generated: generated.go -# needed since it doesn't match the directory name: -package: main # We bind github's DateTime scalar type to Go's time.Time (which conveniently # already defines MarshalJSON and UnmarshalJSON). This means genqlient will diff --git a/generate/config.go b/generate/config.go index 94ee12c4..7116d4ed 100644 --- a/generate/config.go +++ b/generate/config.go @@ -47,6 +47,8 @@ type Config struct { // The directory of the config-file (relative to which all the other paths // are resolved). Set by ValidateAndFillDefaults. baseDir string + // The package-path into which we are generating. + pkgPath string } // A TypeBinding represents a Go type to which genqlient will bind a particular @@ -132,6 +134,46 @@ func pathJoin(a, b string) string { return filepath.Join(a, b) } +func (c *Config) getPackageNameAndPath() (pkgName, pkgPath string, err error) { + abs, err := filepath.Abs(c.Generated) + if err != nil { + return "", "", err + } + + dir := filepath.Dir(abs) + pkgNameGuess := filepath.Base(dir) + if !token.IsIdentifier(pkgNameGuess) { + pkgNameGuess = "" + } + + pkgs, err := packages.Load(&packages.Config{Mode: packages.NeedName}, dir) + if err != nil { + return pkgNameGuess, "", err + } else if len(pkgs) != 1 { + return pkgNameGuess, "", fmt.Errorf("found %v packages in %v, expected 1", len(pkgs), dir) + } + + pkg := pkgs[0] + // TODO(benkraft): Can PkgPath ever be empty without error? If so, we could + // warn. + if pkg.Name != "" { + return pkg.Name, pkg.PkgPath, nil + } + + // e.g. empty package yet to be created, see if we can just guess a + // reasonable name. + pathSuffix := filepath.Base(pkg.PkgPath) + if token.IsIdentifier(pathSuffix) { + pkgNameGuess = pathSuffix + } + + if pkgNameGuess != "" { + return pkgNameGuess, pkg.PkgPath, nil + } else { + return "", "", fmt.Errorf("no package found in %v", dir) + } +} + // ValidateAndFillDefaults ensures that the configuration is valid, and fills // in any options that were unspecified. // @@ -167,29 +209,40 @@ func (c *Config) ValidateAndFillDefaults(baseDir string) error { "\nExample: \"github.com/Org/Repo/optional.Value\"") } - if c.Package != "" { - if !token.IsIdentifier(c.Package) { - // No need for link here -- if you're already setting the package - // you know where to set the package. - return errorf(nil, "invalid package in genqlient.yaml: '%v' is not a valid identifier", c.Package) - } - } else { - abs, err := filepath.Abs(c.Generated) - if err != nil { - return errorf(nil, "unable to guess package-name: %v"+ - "\nSet package name in genqlient.yaml"+ - "\nExample: https://github.com/Khan/genqlient/blob/main/example/genqlient.yaml#L6", err) - } + if c.Package != "" && !token.IsIdentifier(c.Package) { + // No need for link here -- if you're already setting the package + // you know where to set the package. + return errorf(nil, "invalid package in genqlient.yaml: '%v' is not a valid identifier", c.Package) + } - base := filepath.Base(filepath.Dir(abs)) - if !token.IsIdentifier(base) { - return errorf(nil, "unable to guess package-name: '%v' is not a valid identifier"+ - "\nSet package name in genqlient.yaml"+ - "\nExample: https://github.com/Khan/genqlient/blob/main/example/genqlient.yaml#L6", base) + pkgName, pkgPath, err := c.getPackageNameAndPath() + if err == nil { + if c.Package == pkgName || c.Package == "" { + c.Package = pkgName + } else { + fmt.Printf("warning: package setting in genqlient.yaml '%v' looks wrong "+ + "('%v' is in package '%v') but proceeding with '%v' anyway\n", + c.Package, c.Generated, pkgName, c.Package) } - - c.Package = base + } else if c.Package != "" { + // If you specified a valid package, at least try to use that. + // But we can't set pkgPath, which means you'll run into trouble + // binding against the generated package, so at least warn. + fmt.Printf("warning: unable to identify current package-path (using 'package' config '%v'): %v\n", c.Package, err) + } else if pkgName != "" { + // If the directory-name is valid, use that. This is useful if you + // somehow can't build, and especially for tests. + fmt.Printf("warning: unable to identify current package-path (using directory name '%v': %v\n", pkgName, err) + c.Package = pkgName + } else { + return errorf(nil, "unable to guess package-name: %v"+ + "\nSet package name in genqlient.yaml"+ + "\nExample: https://github.com/Khan/genqlient/blob/main/example/genqlient.yaml#L6", err) } + // This is not likely to work if we got an error, especially if we did the + // c.Package fallback. But it's more likely to work than nothing, so we may + // as well. + c.pkgPath = pkgPath if len(c.PackageBindings) > 0 { for _, binding := range c.PackageBindings { diff --git a/generate/config_test.go b/generate/config_test.go index 004c8f49..d3ea4dbe 100644 --- a/generate/config_test.go +++ b/generate/config_test.go @@ -11,9 +11,9 @@ import ( ) const ( - findConfigDir = "testdata/find-config" - validConfigDir = "testdata/valid-config" - invalidConfigDir = "testdata/invalid-config" + findConfigDir = "testdata/findConfig" + validConfigDir = "testdata/validConfig" + invalidConfigDir = "testdata/invalidConfig" ) func TestFindCfg(t *testing.T) { diff --git a/generate/imports.go b/generate/imports.go index 5d6f0a29..5daee2cd 100644 --- a/generate/imports.go +++ b/generate/imports.go @@ -99,6 +99,9 @@ func (g *generator) ref(fullyQualifiedName string) (qualifiedName string, err er pkgPath := nameToImport[:i] localName := nameToImport[i+1:] + if pkgPath == g.Config.pkgPath { + return prefix + localName, nil + } alias, ok := g.imports[pkgPath] if !ok { if g.importsLocked { diff --git a/generate/testdata/find-config/current/genqlient.yaml b/generate/testdata/findConfig/current/genqlient.yaml similarity index 100% rename from generate/testdata/find-config/current/genqlient.yaml rename to generate/testdata/findConfig/current/genqlient.yaml diff --git a/generate/testdata/find-config/filenames/dotyaml/.genqlient.yaml b/generate/testdata/findConfig/filenames/dotyaml/.genqlient.yaml similarity index 100% rename from generate/testdata/find-config/filenames/dotyaml/.genqlient.yaml rename to generate/testdata/findConfig/filenames/dotyaml/.genqlient.yaml diff --git a/generate/testdata/find-config/filenames/dotyml/.genqlient.yml b/generate/testdata/findConfig/filenames/dotyml/.genqlient.yml similarity index 100% rename from generate/testdata/find-config/filenames/dotyml/.genqlient.yml rename to generate/testdata/findConfig/filenames/dotyml/.genqlient.yml diff --git a/generate/testdata/find-config/filenames/none/.gitkeep b/generate/testdata/findConfig/filenames/none/.gitkeep similarity index 100% rename from generate/testdata/find-config/filenames/none/.gitkeep rename to generate/testdata/findConfig/filenames/none/.gitkeep diff --git a/generate/testdata/find-config/filenames/yaml/genqlient.yaml b/generate/testdata/findConfig/filenames/yaml/genqlient.yaml similarity index 100% rename from generate/testdata/find-config/filenames/yaml/genqlient.yaml rename to generate/testdata/findConfig/filenames/yaml/genqlient.yaml diff --git a/generate/testdata/find-config/filenames/yml/genqlient.yml b/generate/testdata/findConfig/filenames/yml/genqlient.yml similarity index 100% rename from generate/testdata/find-config/filenames/yml/genqlient.yml rename to generate/testdata/findConfig/filenames/yml/genqlient.yml diff --git a/generate/testdata/find-config/none/.gitkeep b/generate/testdata/findConfig/none/.gitkeep similarity index 100% rename from generate/testdata/find-config/none/.gitkeep rename to generate/testdata/findConfig/none/.gitkeep diff --git a/generate/testdata/find-config/none/child/.gitkeep b/generate/testdata/findConfig/none/child/.gitkeep similarity index 100% rename from generate/testdata/find-config/none/child/.gitkeep rename to generate/testdata/findConfig/none/child/.gitkeep diff --git a/generate/testdata/find-config/parent/child/.gitkeep b/generate/testdata/findConfig/parent/child/.gitkeep similarity index 100% rename from generate/testdata/find-config/parent/child/.gitkeep rename to generate/testdata/findConfig/parent/child/.gitkeep diff --git a/generate/testdata/find-config/parent/genqlient.yaml b/generate/testdata/findConfig/parent/genqlient.yaml similarity index 100% rename from generate/testdata/find-config/parent/genqlient.yaml rename to generate/testdata/findConfig/parent/genqlient.yaml diff --git a/generate/testdata/invalid-config/InvalidCasing.yaml b/generate/testdata/invalidConfig/InvalidCasing.yaml similarity index 100% rename from generate/testdata/invalid-config/InvalidCasing.yaml rename to generate/testdata/invalidConfig/InvalidCasing.yaml diff --git a/generate/testdata/invalid-config/InvalidOptional.yaml b/generate/testdata/invalidConfig/InvalidOptional.yaml similarity index 100% rename from generate/testdata/invalid-config/InvalidOptional.yaml rename to generate/testdata/invalidConfig/InvalidOptional.yaml diff --git a/generate/testdata/invalid-config/InvalidPackage.yaml b/generate/testdata/invalidConfig/InvalidPackage.yaml similarity index 100% rename from generate/testdata/invalid-config/InvalidPackage.yaml rename to generate/testdata/invalidConfig/InvalidPackage.yaml diff --git a/generate/testdata/snapshots/TestInvalidConfigs-CantGuessPackage.yaml b/generate/testdata/snapshots/TestInvalidConfigs-CantGuessPackage.yaml deleted file mode 100644 index 2571f712..00000000 --- a/generate/testdata/snapshots/TestInvalidConfigs-CantGuessPackage.yaml +++ /dev/null @@ -1,3 +0,0 @@ -invalid config file testdata/invalid-config/CantGuessPackage.yaml: unable to guess package-name: 'invalid-config' is not a valid identifier -Set package name in genqlient.yaml -Example: https://github.com/Khan/genqlient/blob/main/example/genqlient.yaml#L6 diff --git a/generate/testdata/snapshots/TestInvalidConfigs-InvalidCasing.yaml b/generate/testdata/snapshots/TestInvalidConfigs-InvalidCasing.yaml index 2bcb5e06..6b2cf919 100644 --- a/generate/testdata/snapshots/TestInvalidConfigs-InvalidCasing.yaml +++ b/generate/testdata/snapshots/TestInvalidConfigs-InvalidCasing.yaml @@ -1 +1 @@ -invalid config file testdata/invalid-config/InvalidCasing.yaml: unknown casing algorithm: bogus +invalid config file testdata/invalidConfig/InvalidCasing.yaml: unknown casing algorithm: bogus diff --git a/generate/testdata/snapshots/TestInvalidConfigs-InvalidOptional.yaml b/generate/testdata/snapshots/TestInvalidConfigs-InvalidOptional.yaml index fc5394bf..b6addeb0 100644 --- a/generate/testdata/snapshots/TestInvalidConfigs-InvalidOptional.yaml +++ b/generate/testdata/snapshots/TestInvalidConfigs-InvalidOptional.yaml @@ -1 +1 @@ -invalid config file testdata/invalid-config/InvalidOptional.yaml: optional must be one of: 'value' (default), 'pointer', or 'generic' +invalid config file testdata/invalidConfig/InvalidOptional.yaml: optional must be one of: 'value' (default), 'pointer', or 'generic' diff --git a/generate/testdata/snapshots/TestInvalidConfigs-InvalidPackage.yaml b/generate/testdata/snapshots/TestInvalidConfigs-InvalidPackage.yaml index a3d047a2..28af6380 100644 --- a/generate/testdata/snapshots/TestInvalidConfigs-InvalidPackage.yaml +++ b/generate/testdata/snapshots/TestInvalidConfigs-InvalidPackage.yaml @@ -1 +1 @@ -invalid config file testdata/invalid-config/InvalidPackage.yaml: invalid package in genqlient.yaml: 'bogus-package-name' is not a valid identifier +invalid config file testdata/invalidConfig/InvalidPackage.yaml: invalid package in genqlient.yaml: 'bogus-package-name' is not a valid identifier diff --git a/generate/testdata/snapshots/TestValidConfigs-Simple.yaml b/generate/testdata/snapshots/TestValidConfigs-Empty.yml similarity index 76% rename from generate/testdata/snapshots/TestValidConfigs-Simple.yaml rename to generate/testdata/snapshots/TestValidConfigs-Empty.yml index 77cc1b32..709dea13 100644 --- a/generate/testdata/snapshots/TestValidConfigs-Simple.yaml +++ b/generate/testdata/snapshots/TestValidConfigs-Empty.yml @@ -1,7 +1,7 @@ (*generate.Config)({ Schema: (generate.StringList) , Operations: (generate.StringList) , - Generated: (string) (len=34) "testdata/valid-config/generated.go", + Generated: (string) (len=33) "testdata/validConfig/generated.go", Package: (string) (len=11) "validConfig", ExportOperations: (string) "", ContextType: (string) (len=15) "context.Context", @@ -17,5 +17,6 @@ StructReferences: (bool) false, Extensions: (bool) false, AllowBrokenFeatures: (bool) false, - baseDir: (string) (len=21) "testdata/valid-config" + baseDir: (string) (len=20) "testdata/validConfig", + pkgPath: (string) (len=55) "github.com/Khan/genqlient/generate/testdata/validConfig" }) diff --git a/generate/testdata/snapshots/TestValidConfigs-Lists.yaml b/generate/testdata/snapshots/TestValidConfigs-Lists.yaml index 7ff70462..1c10e82a 100644 --- a/generate/testdata/snapshots/TestValidConfigs-Lists.yaml +++ b/generate/testdata/snapshots/TestValidConfigs-Lists.yaml @@ -1,13 +1,13 @@ (*generate.Config)({ Schema: (generate.StringList) (len=2) { - (string) (len=42) "testdata/valid-config/first_schema.graphql", - (string) (len=43) "testdata/valid-config/second_schema.graphql" + (string) (len=41) "testdata/validConfig/first_schema.graphql", + (string) (len=42) "testdata/validConfig/second_schema.graphql" }, Operations: (generate.StringList) (len=2) { - (string) (len=46) "testdata/valid-config/first_operations.graphql", - (string) (len=47) "testdata/valid-config/second_operations.graphql" + (string) (len=45) "testdata/validConfig/first_operations.graphql", + (string) (len=46) "testdata/validConfig/second_operations.graphql" }, - Generated: (string) (len=34) "testdata/valid-config/generated.go", + Generated: (string) (len=33) "testdata/validConfig/generated.go", Package: (string) (len=11) "validConfig", ExportOperations: (string) "", ContextType: (string) (len=15) "context.Context", @@ -23,5 +23,6 @@ StructReferences: (bool) false, Extensions: (bool) false, AllowBrokenFeatures: (bool) false, - baseDir: (string) (len=21) "testdata/valid-config" + baseDir: (string) (len=20) "testdata/validConfig", + pkgPath: (string) (len=55) "github.com/Khan/genqlient/generate/testdata/validConfig" }) diff --git a/generate/testdata/snapshots/TestValidConfigs-Strings.yaml b/generate/testdata/snapshots/TestValidConfigs-Strings.yaml index 1638e8da..c47d4ad2 100644 --- a/generate/testdata/snapshots/TestValidConfigs-Strings.yaml +++ b/generate/testdata/snapshots/TestValidConfigs-Strings.yaml @@ -1,11 +1,11 @@ (*generate.Config)({ Schema: (generate.StringList) (len=1) { - (string) (len=36) "testdata/valid-config/schema.graphql" + (string) (len=35) "testdata/validConfig/schema.graphql" }, Operations: (generate.StringList) (len=1) { - (string) (len=40) "testdata/valid-config/operations.graphql" + (string) (len=39) "testdata/validConfig/operations.graphql" }, - Generated: (string) (len=34) "testdata/valid-config/generated.go", + Generated: (string) (len=33) "testdata/validConfig/generated.go", Package: (string) (len=11) "validConfig", ExportOperations: (string) "", ContextType: (string) (len=15) "context.Context", @@ -21,5 +21,6 @@ StructReferences: (bool) false, Extensions: (bool) false, AllowBrokenFeatures: (bool) false, - baseDir: (string) (len=21) "testdata/valid-config" + baseDir: (string) (len=20) "testdata/validConfig", + pkgPath: (string) (len=55) "github.com/Khan/genqlient/generate/testdata/validConfig" }) diff --git a/generate/testdata/valid-config/Simple.yaml b/generate/testdata/valid-config/Simple.yaml deleted file mode 100644 index 3084ef55..00000000 --- a/generate/testdata/valid-config/Simple.yaml +++ /dev/null @@ -1 +0,0 @@ -package: validConfig diff --git a/generate/testdata/invalid-config/CantGuessPackage.yaml b/generate/testdata/validConfig/Empty.yml similarity index 100% rename from generate/testdata/invalid-config/CantGuessPackage.yaml rename to generate/testdata/validConfig/Empty.yml diff --git a/generate/testdata/valid-config/Lists.yaml b/generate/testdata/validConfig/Lists.yaml similarity index 84% rename from generate/testdata/valid-config/Lists.yaml rename to generate/testdata/validConfig/Lists.yaml index 8348498b..e3a8af11 100644 --- a/generate/testdata/valid-config/Lists.yaml +++ b/generate/testdata/validConfig/Lists.yaml @@ -5,5 +5,3 @@ schema: operations: - first_operations.graphql - second_operations.graphql - -package: validConfig diff --git a/generate/testdata/valid-config/Strings.yaml b/generate/testdata/validConfig/Strings.yaml similarity index 72% rename from generate/testdata/valid-config/Strings.yaml rename to generate/testdata/validConfig/Strings.yaml index 7830e0f9..d4a4957f 100644 --- a/generate/testdata/valid-config/Strings.yaml +++ b/generate/testdata/validConfig/Strings.yaml @@ -1,3 +1,2 @@ schema: schema.graphql operations: operations.graphql -package: validConfig diff --git a/internal/integration/generated.go b/internal/integration/generated.go index 1f0e079d..2183ce82 100644 --- a/internal/integration/generated.go +++ b/internal/integration/generated.go @@ -3056,9 +3056,10 @@ func (v *simpleQueryExtResponse) GetMe() simpleQueryExtMeUser { return v.Me } // simpleQueryMeUser includes the requested fields of the GraphQL type User. type simpleQueryMeUser struct { - Id string `json:"id"` - Name string `json:"name"` - LuckyNumber int `json:"luckyNumber"` + Id string `json:"id"` + Name string `json:"name"` + LuckyNumber int `json:"luckyNumber"` + GreatScalar MyGreatScalar `json:"greatScalar"` } // GetId returns simpleQueryMeUser.Id, and is useful for accessing the field via an interface. @@ -3070,6 +3071,9 @@ func (v *simpleQueryMeUser) GetName() string { return v.Name } // GetLuckyNumber returns simpleQueryMeUser.LuckyNumber, and is useful for accessing the field via an interface. func (v *simpleQueryMeUser) GetLuckyNumber() int { return v.LuckyNumber } +// GetGreatScalar returns simpleQueryMeUser.GreatScalar, and is useful for accessing the field via an interface. +func (v *simpleQueryMeUser) GetGreatScalar() MyGreatScalar { return v.GreatScalar } + // simpleQueryResponse is returned by simpleQuery on success. type simpleQueryResponse struct { Me simpleQueryMeUser `json:"me"` @@ -3656,6 +3660,7 @@ query simpleQuery { id name luckyNumber + greatScalar } } ` diff --git a/internal/integration/genqlient.yaml b/internal/integration/genqlient.yaml index db6f988f..8f57602a 100644 --- a/internal/integration/genqlient.yaml +++ b/internal/integration/genqlient.yaml @@ -8,3 +8,5 @@ bindings: type: time.Time marshaler: "github.com/Khan/genqlient/internal/testutil.MarshalDate" unmarshaler: "github.com/Khan/genqlient/internal/testutil.UnmarshalDate" + MyGreatScalar: + type: github.com/Khan/genqlient/internal/integration.MyGreatScalar diff --git a/internal/integration/integration_test.go b/internal/integration/integration_test.go index 0e056fab..37b02425 100644 --- a/internal/integration/integration_test.go +++ b/internal/integration/integration_test.go @@ -20,7 +20,7 @@ import ( func TestSimpleQuery(t *testing.T) { _ = `# @genqlient - query simpleQuery { me { id name luckyNumber } }` + query simpleQuery { me { id name luckyNumber greatScalar } }` ctx := context.Background() server := server.RunServer() diff --git a/internal/integration/schema.graphql b/internal/integration/schema.graphql index 7857a4a7..17e20e2d 100644 --- a/internal/integration/schema.graphql +++ b/internal/integration/schema.graphql @@ -1,4 +1,5 @@ scalar Date +scalar MyGreatScalar type Query { me: User @@ -23,6 +24,7 @@ type User implements Being & Lucky { hair: Hair birthdate: Date friends: [User!]! + greatScalar: MyGreatScalar } input NewUser { diff --git a/internal/integration/server/gqlgen_exec.go b/internal/integration/server/gqlgen_exec.go index 10b07b03..e1dce252 100644 --- a/internal/integration/server/gqlgen_exec.go +++ b/internal/integration/server/gqlgen_exec.go @@ -78,6 +78,7 @@ type ComplexityRoot struct { User struct { Birthdate func(childComplexity int) int Friends func(childComplexity int) int + GreatScalar func(childComplexity int) int Hair func(childComplexity int) int ID func(childComplexity int) int LuckyNumber func(childComplexity int) int @@ -288,6 +289,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.User.Friends(childComplexity), true + case "User.greatScalar": + if e.complexity.User.GreatScalar == nil { + break + } + + return e.complexity.User.GreatScalar(childComplexity), true + case "User.hair": if e.complexity.User.Hair == nil { break @@ -423,6 +431,7 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er var sources = []*ast.Source{ {Name: "../schema.graphql", Input: `scalar Date +scalar MyGreatScalar type Query { me: User @@ -447,6 +456,7 @@ type User implements Being & Lucky { hair: Hair birthdate: Date friends: [User!]! + greatScalar: MyGreatScalar } input NewUser { @@ -1022,6 +1032,8 @@ func (ec *executionContext) fieldContext_Mutation_createUser(ctx context.Context return ec.fieldContext_User_birthdate(ctx, field) case "friends": return ec.fieldContext_User_friends(ctx, field) + case "greatScalar": + return ec.fieldContext_User_greatScalar(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type User", field.Name) }, @@ -1088,6 +1100,8 @@ func (ec *executionContext) fieldContext_Query_me(ctx context.Context, field gra return ec.fieldContext_User_birthdate(ctx, field) case "friends": return ec.fieldContext_User_friends(ctx, field) + case "greatScalar": + return ec.fieldContext_User_greatScalar(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type User", field.Name) }, @@ -1143,6 +1157,8 @@ func (ec *executionContext) fieldContext_Query_user(ctx context.Context, field g return ec.fieldContext_User_birthdate(ctx, field) case "friends": return ec.fieldContext_User_friends(ctx, field) + case "greatScalar": + return ec.fieldContext_User_greatScalar(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type User", field.Name) }, @@ -1371,6 +1387,8 @@ func (ec *executionContext) fieldContext_Query_usersBornOn(ctx context.Context, return ec.fieldContext_User_birthdate(ctx, field) case "friends": return ec.fieldContext_User_friends(ctx, field) + case "greatScalar": + return ec.fieldContext_User_greatScalar(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type User", field.Name) }, @@ -1440,6 +1458,8 @@ func (ec *executionContext) fieldContext_Query_usersBornOnDates(ctx context.Cont return ec.fieldContext_User_birthdate(ctx, field) case "friends": return ec.fieldContext_User_friends(ctx, field) + case "greatScalar": + return ec.fieldContext_User_greatScalar(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type User", field.Name) }, @@ -1506,6 +1526,8 @@ func (ec *executionContext) fieldContext_Query_userSearch(ctx context.Context, f return ec.fieldContext_User_birthdate(ctx, field) case "friends": return ec.fieldContext_User_friends(ctx, field) + case "greatScalar": + return ec.fieldContext_User_greatScalar(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type User", field.Name) }, @@ -1960,6 +1982,8 @@ func (ec *executionContext) fieldContext_User_friends(ctx context.Context, field return ec.fieldContext_User_birthdate(ctx, field) case "friends": return ec.fieldContext_User_friends(ctx, field) + case "greatScalar": + return ec.fieldContext_User_greatScalar(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type User", field.Name) }, @@ -1967,6 +1991,47 @@ func (ec *executionContext) fieldContext_User_friends(ctx context.Context, field return fc, nil } +func (ec *executionContext) _User_greatScalar(ctx context.Context, field graphql.CollectedField, obj *User) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_User_greatScalar(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.GreatScalar, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOMyGreatScalar2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_User_greatScalar(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "User", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type MyGreatScalar does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { fc, err := ec.fieldContext___Directive_name(ctx, field) if err != nil { @@ -4255,6 +4320,8 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj if out.Values[i] == graphql.Null { out.Invalids++ } + case "greatScalar": + out.Values[i] = ec._User_greatScalar(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -5209,6 +5276,22 @@ func (ec *executionContext) marshalOLucky2githubᚗcomᚋKhanᚋgenqlientᚋinte return ec._Lucky(ctx, sel, v) } +func (ec *executionContext) unmarshalOMyGreatScalar2ᚖstring(ctx context.Context, v interface{}) (*string, error) { + if v == nil { + return nil, nil + } + res, err := graphql.UnmarshalString(v) + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalOMyGreatScalar2ᚖstring(ctx context.Context, sel ast.SelectionSet, v *string) graphql.Marshaler { + if v == nil { + return graphql.Null + } + res := graphql.MarshalString(*v) + return res +} + func (ec *executionContext) unmarshalOString2ᚖstring(ctx context.Context, v interface{}) (*string, error) { if v == nil { return nil, nil diff --git a/internal/integration/server/gqlgen_models.go b/internal/integration/server/gqlgen_models.go index 1ac2e113..0c28bc55 100644 --- a/internal/integration/server/gqlgen_models.go +++ b/internal/integration/server/gqlgen_models.go @@ -50,6 +50,7 @@ type User struct { Hair *Hair `json:"hair,omitempty"` Birthdate *string `json:"birthdate,omitempty"` Friends []*User `json:"friends"` + GreatScalar *string `json:"greatScalar,omitempty"` } func (User) IsBeing() {} diff --git a/internal/integration/server/server.go b/internal/integration/server/server.go index 00dbb308..a8a45a72 100644 --- a/internal/integration/server/server.go +++ b/internal/integration/server/server.go @@ -17,10 +17,11 @@ func intptr(v int) *int { return &v } var users = []*User{ { ID: "1", Name: "Yours Truly", LuckyNumber: intptr(17), - Birthdate: strptr("2025-01-01"), - Hair: &Hair{Color: strptr("Black")}, + Birthdate: strptr("2025-01-01"), + Hair: &Hair{Color: strptr("Black")}, + GreatScalar: strptr("cool value"), }, - {ID: "2", Name: "Raven", LuckyNumber: intptr(-1), Hair: nil}, + {ID: "2", Name: "Raven", LuckyNumber: intptr(-1), Hair: nil, GreatScalar: strptr("cool value")}, } func init() { @@ -177,4 +178,4 @@ func (r *resolver) Mutation() MutationResolver { func (r *resolver) Query() QueryResolver { return &queryResolver{} } -//go:generate go run github.com/99designs/gqlgen +//go:generate go run github.com/99designs/gqlgen@v0.17.35 diff --git a/internal/integration/util.go b/internal/integration/util.go index 0e0a5127..b55e9b60 100644 --- a/internal/integration/util.go +++ b/internal/integration/util.go @@ -62,3 +62,9 @@ func RunGenerateTest(t *testing.T, relConfigFilename string) { } } } + +// Used for a binding in genqlient.yaml. +// +// This is here rather than in testutil to test the case where the generated +// code and the bound type are in the same package. +type MyGreatScalar string