From b332879455e0b909cb8c5e5ab6467c9891c13218 Mon Sep 17 00:00:00 2001 From: Steve Coffman Date: Fri, 8 Nov 2024 19:44:38 -0500 Subject: [PATCH] Config option to preserve single file resolvers instead of always rewriting (#3359) Signed-off-by: Steve Coffman --- TESTING.md | 2 +- codegen/config/resolver.go | 4 ++ plugin/resolvergen/resolver.go | 15 ++++++- plugin/resolvergen/resolver_test.go | 39 ++++++++++++++++++- .../filetemplate/out/schema.custom.go.txt | 38 ++++++++++++++++++ .../followschema/out/schema.resolvers.go.txt | 38 ++++++++++++++++++ .../testdata/singlefile/gqlgen.yml | 1 + .../testdata/singlefile/out/resolver.go | 5 +-- .../testdata/singlefile_preserve/gqlgen.yml | 17 ++++++++ .../testdata/singlefile_preserve/out/model.go | 13 +++++++ .../singlefile_preserve/out/resolver.go | 28 +++++++++++++ 11 files changed, 193 insertions(+), 7 deletions(-) create mode 100644 plugin/resolvergen/testdata/filetemplate/out/schema.custom.go.txt create mode 100644 plugin/resolvergen/testdata/followschema/out/schema.resolvers.go.txt create mode 100644 plugin/resolvergen/testdata/singlefile_preserve/gqlgen.yml create mode 100644 plugin/resolvergen/testdata/singlefile_preserve/out/model.go create mode 100644 plugin/resolvergen/testdata/singlefile_preserve/out/resolver.go diff --git a/TESTING.md b/TESTING.md index 2677b33e486..bdb56f40f57 100644 --- a/TESTING.md +++ b/TESTING.md @@ -1,7 +1,7 @@ How to write tests for gqlgen === -Testing generated code is a little tricky, heres how its currently set up. +Testing generated code is a little tricky, here's how its currently set up. ### Testing responses from a server diff --git a/codegen/config/resolver.go b/codegen/config/resolver.go index 1901fd2d35f..05bdee0863e 100644 --- a/codegen/config/resolver.go +++ b/codegen/config/resolver.go @@ -19,6 +19,7 @@ type ResolverConfig struct { DirName string `yaml:"dir"` OmitTemplateComment bool `yaml:"omit_template_comment,omitempty"` ResolverTemplate string `yaml:"resolver_template,omitempty"` + PreserveResolver bool `yaml:"preserve_resolver,omitempty"` } type ResolverLayout string @@ -55,6 +56,9 @@ func (r *ResolverConfig) Check() error { } else { r.Filename = abs(r.Filename) } + if r.PreserveResolver { + return fmt.Errorf("preserve_resolver=true cannot be used with layout=%s", r.Layout) + } default: return fmt.Errorf("invalid layout %s. must be %s or %s", r.Layout, LayoutSingleFile, LayoutFollowSchema) } diff --git a/plugin/resolvergen/resolver.go b/plugin/resolvergen/resolver.go index c0e040890b8..83717f961ee 100644 --- a/plugin/resolvergen/resolver.go +++ b/plugin/resolvergen/resolver.go @@ -53,6 +53,14 @@ func (m *Plugin) GenerateCode(data *codegen.Data) error { func (m *Plugin) generateSingleFile(data *codegen.Data) error { file := File{} + + if _, err := os.Stat(data.Config.Resolver.Filename); err == nil && + data.Config.Resolver.PreserveResolver { + // file already exists and config says not to update resolver + // with layout = single so just return + return nil + } + rewriter, err := rewrite.New(data.Config.Resolver.Dir()) if err != nil { return err @@ -104,9 +112,14 @@ func (m *Plugin) generateSingleFile(data *codegen.Data) error { newResolverTemplate = readResolverTemplate(data.Config.Resolver.ResolverTemplate) } + fileNotice := `// THIS CODE WILL BE UPDATED WITH SCHEMA CHANGES. PREVIOUS IMPLEMENTATION FOR SCHEMA CHANGES WILL BE KEPT IN THE COMMENT SECTION. IMPLEMENTATION FOR UNCHANGED SCHEMA WILL BE KEPT.` + if data.Config.Resolver.PreserveResolver { + fileNotice = `// THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES.` + } + return templates.Render(templates.Options{ PackageName: data.Config.Resolver.Package, - FileNotice: `// THIS CODE WILL BE UPDATED WITH SCHEMA CHANGES. PREVIOUS IMPLEMENTATION FOR SCHEMA CHANGES WILL BE KEPT IN THE COMMENT SECTION. IMPLEMENTATION FOR UNCHANGED SCHEMA WILL BE KEPT.`, + FileNotice: fileNotice, Filename: data.Config.Resolver.Filename, Data: resolverBuild, Packages: data.Config.Packages, diff --git a/plugin/resolvergen/resolver_test.go b/plugin/resolvergen/resolver_test.go index 831596e1425..bfb107b99a4 100644 --- a/plugin/resolvergen/resolver_test.go +++ b/plugin/resolvergen/resolver_test.go @@ -29,10 +29,35 @@ func TestLayoutSingleFile(t *testing.T) { assertNoErrors(t, "github.com/99designs/gqlgen/plugin/resolvergen/testdata/singlefile/out") } +func TestLayoutSingleFileWithEnableRewrite(t *testing.T) { + // Ensure the resolver file exists before running the test + resolverFilePath := "testdata/singlefile_preserve/out/resolver.go" + _, err := os.Stat(resolverFilePath) + if os.IsNotExist(err) { + t.Fatalf("Expected resolver file does not exist: %s", resolverFilePath) + } + require.NoError(t, err) + + cfg, err := config.LoadConfig("testdata/singlefile_preserve/gqlgen.yml") + require.NoError(t, err) + p := Plugin{} + + require.NoError(t, cfg.Init()) + + data, err := codegen.BuildData(cfg) + require.NoError(t, err) + + require.NoError(t, p.GenerateCode(data)) + assertNoErrors(t, "github.com/99designs/gqlgen/plugin/resolvergen/testdata/singlefile_preserve/out") +} + func TestLayoutFollowSchema(t *testing.T) { testFollowSchemaPersistence(t, "testdata/followschema") - b, err := os.ReadFile("testdata/followschema/out/schema.resolvers.go") + resolverFilePath := "testdata/followschema/out/schema.resolvers.go" + overWriteFile(t, resolverFilePath+".txt", resolverFilePath) + + b, err := os.ReadFile(resolverFilePath) require.NoError(t, err) source := string(b) @@ -45,7 +70,9 @@ func TestLayoutFollowSchema(t *testing.T) { func TestLayoutFollowSchemaWithCustomFilename(t *testing.T) { testFollowSchemaPersistence(t, "testdata/filetemplate") - b, err := os.ReadFile("testdata/filetemplate/out/schema.custom.go") + resolverFilePath := "testdata/filetemplate/out/schema.custom.go" + overWriteFile(t, resolverFilePath+".txt", resolverFilePath) + b, err := os.ReadFile(resolverFilePath) require.NoError(t, err) source := string(b) @@ -126,6 +153,14 @@ func testFollowSchemaPersistence(t *testing.T, dir string) { assertNoErrors(t, "github.com/99designs/gqlgen/plugin/resolvergen/"+dir+"/out") } +func overWriteFile(t *testing.T, sourceFile, destinationFile string) { + input, err := os.ReadFile(sourceFile) + require.NoError(t, err) + + err = os.WriteFile(destinationFile, input, 0644) + require.NoError(t, err) +} + func assertNoErrors(t *testing.T, pkg string) { pkgs, err := packages.Load(&packages.Config{ Mode: packages.NeedName | diff --git a/plugin/resolvergen/testdata/filetemplate/out/schema.custom.go.txt b/plugin/resolvergen/testdata/filetemplate/out/schema.custom.go.txt new file mode 100644 index 00000000000..166fc59f3be --- /dev/null +++ b/plugin/resolvergen/testdata/filetemplate/out/schema.custom.go.txt @@ -0,0 +1,38 @@ +package customresolver + +// This file will be automatically regenerated based on the schema, any resolver implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen + +import ( + "context" + + customresolver "github.com/99designs/gqlgen/plugin/resolvergen/testdata/singlefile/out" +) + +// Resolver is the resolver for the resolver field. +func (r *queryCustomResolverType) Resolver(ctx context.Context) (*customresolver.Resolver, error) { + // CustomerResolverType.Resolver implementation + return nil, nil +} + +// Name is the resolver for the name field. +func (r *resolverCustomResolverType) Name(ctx context.Context, obj *customresolver.Resolver) (string, error) { + // CustomerResolverType.Name implementation + return "", nil +} + +// Query returns customresolver.QueryResolver implementation. +func (r *CustomResolverType) Query() customresolver.QueryResolver { return &queryCustomResolverType{r} } + +// Resolver returns customresolver.ResolverResolver implementation. +func (r *CustomResolverType) Resolver() customresolver.ResolverResolver { + return &resolverCustomResolverType{r} +} + +type queryCustomResolverType struct{ *CustomResolverType } +type resolverCustomResolverType struct{ *CustomResolverType } + +func AUserHelperFunction() { + // AUserHelperFunction implementation +} diff --git a/plugin/resolvergen/testdata/followschema/out/schema.resolvers.go.txt b/plugin/resolvergen/testdata/followschema/out/schema.resolvers.go.txt new file mode 100644 index 00000000000..27661bed710 --- /dev/null +++ b/plugin/resolvergen/testdata/followschema/out/schema.resolvers.go.txt @@ -0,0 +1,38 @@ +package customresolver + +// This file will be automatically regenerated based on the schema, any resolver implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen + +import ( + "context" + + customresolver "github.com/99designs/gqlgen/plugin/resolvergen/testdata/singlefile/out" +) + +// Resolver is the resolver for the resolver field. +func (r *queryCustomResolverType) Resolver(ctx context.Context) (_ *customresolver.Resolver, err error) { + // Named return values are supported. + return +} + +// Name is the resolver for the name field. +func (r *resolverCustomResolverType) Name(ctx context.Context, obj *customresolver.Resolver) (string, error) { + // CustomerResolverType.Name implementation + return "", nil +} + +// Query returns customresolver.QueryResolver implementation. +func (r *CustomResolverType) Query() customresolver.QueryResolver { return &queryCustomResolverType{r} } + +// Resolver returns customresolver.ResolverResolver implementation. +func (r *CustomResolverType) Resolver() customresolver.ResolverResolver { + return &resolverCustomResolverType{r} +} + +type queryCustomResolverType struct{ *CustomResolverType } +type resolverCustomResolverType struct{ *CustomResolverType } + +func AUserHelperFunction() { + // AUserHelperFunction implementation +} diff --git a/plugin/resolvergen/testdata/singlefile/gqlgen.yml b/plugin/resolvergen/testdata/singlefile/gqlgen.yml index 41187687600..ce46b812126 100644 --- a/plugin/resolvergen/testdata/singlefile/gqlgen.yml +++ b/plugin/resolvergen/testdata/singlefile/gqlgen.yml @@ -8,6 +8,7 @@ model: resolver: filename: testdata/singlefile/out/resolver.go type: CustomResolverType + preserve_resolver: false models: Resolver: diff --git a/plugin/resolvergen/testdata/singlefile/out/resolver.go b/plugin/resolvergen/testdata/singlefile/out/resolver.go index 614c66cf18b..2081df53c58 100644 --- a/plugin/resolvergen/testdata/singlefile/out/resolver.go +++ b/plugin/resolvergen/testdata/singlefile/out/resolver.go @@ -4,19 +4,18 @@ package customresolver import ( "context" - "fmt" ) type CustomResolverType struct{} // Resolver is the resolver for the resolver field. func (r *queryCustomResolverType) Resolver(ctx context.Context) (*Resolver, error) { - panic(fmt.Errorf("not implemented: Resolver - resolver")) + panic("not implemented") } // Name is the resolver for the name field. func (r *resolverCustomResolverType) Name(ctx context.Context, obj *Resolver) (string, error) { - panic(fmt.Errorf("not implemented: Name - name")) + panic("not implemented") } // Query returns QueryResolver implementation. diff --git a/plugin/resolvergen/testdata/singlefile_preserve/gqlgen.yml b/plugin/resolvergen/testdata/singlefile_preserve/gqlgen.yml new file mode 100644 index 00000000000..cb406c8b50f --- /dev/null +++ b/plugin/resolvergen/testdata/singlefile_preserve/gqlgen.yml @@ -0,0 +1,17 @@ +schema: + - "testdata/schema.graphql" + +exec: + filename: testdata/singlefile_preserve/out/ignored.go +model: + filename: testdata/singlefile_preserve/out/generated.go +resolver: + filename: testdata/singlefile_preserve/out/resolver.go + type: CustomResolverType + preserve_resolver: true + +models: + Resolver: + model: github.com/99designs/gqlgen/plugin/resolvergen/testdata/singlefile_preserve/out.Resolver + +omit_gqlgen_version_in_file_notice: true diff --git a/plugin/resolvergen/testdata/singlefile_preserve/out/model.go b/plugin/resolvergen/testdata/singlefile_preserve/out/model.go new file mode 100644 index 00000000000..47712a16bdb --- /dev/null +++ b/plugin/resolvergen/testdata/singlefile_preserve/out/model.go @@ -0,0 +1,13 @@ +package customresolver + +import "context" + +type Resolver struct{} + +type QueryResolver interface { + Resolver(ctx context.Context) (*Resolver, error) +} + +type ResolverResolver interface { + Name(ctx context.Context, obj *Resolver) (string, error) +} diff --git a/plugin/resolvergen/testdata/singlefile_preserve/out/resolver.go b/plugin/resolvergen/testdata/singlefile_preserve/out/resolver.go new file mode 100644 index 00000000000..844b077c4ac --- /dev/null +++ b/plugin/resolvergen/testdata/singlefile_preserve/out/resolver.go @@ -0,0 +1,28 @@ +package customresolver + +// THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES. + +import ( + "context" +) + +type CustomResolverType struct{} + +// Resolver is the resolver for the resolver field. +func (r *queryCustomResolverType) Resolver(ctx context.Context) (*Resolver, error) { + panic("not implemented") +} + +// Name is the resolver for the name field. +func (r *resolverCustomResolverType) Name(ctx context.Context, obj *Resolver) (string, error) { + panic("not implemented") +} + +// Query returns QueryResolver implementation. +func (r *CustomResolverType) Query() QueryResolver { return &queryCustomResolverType{r} } + +// Resolver returns ResolverResolver implementation. +func (r *CustomResolverType) Resolver() ResolverResolver { return &resolverCustomResolverType{r} } + +type queryCustomResolverType struct{ *CustomResolverType } +type resolverCustomResolverType struct{ *CustomResolverType }