From bd9657f3e50b7b9642c05039a5364ce2262faaf4 Mon Sep 17 00:00:00 2001 From: Ron <38083777+roneli@users.noreply.github.com> Date: Sun, 3 Dec 2023 18:24:28 +0200 Subject: [PATCH] Improve ResolverImplementer.Implment (#2850) * improve resolver implement render * add error when multiple implementors * add initial test --- plugin/plugin.go | 5 +- plugin/resolvergen/resolver.go | 51 ++++++++++--------- plugin/resolvergen/resolver_test.go | 24 +++++++++ .../followschema/out/schema.resolvers.go | 2 +- .../testdata/resolver_implementor/gqlgen.yml | 15 ++++++ .../resolver_implementor/out/model.go | 13 +++++ .../resolver_implementor/out/resolver.go | 7 +++ .../out/schema.resolvers.go | 28 ++++++++++ 8 files changed, 117 insertions(+), 28 deletions(-) create mode 100644 plugin/resolvergen/testdata/resolver_implementor/gqlgen.yml create mode 100644 plugin/resolvergen/testdata/resolver_implementor/out/model.go create mode 100644 plugin/resolvergen/testdata/resolver_implementor/out/resolver.go create mode 100644 plugin/resolvergen/testdata/resolver_implementor/out/schema.resolvers.go diff --git a/plugin/plugin.go b/plugin/plugin.go index 214c58d9e18..1284e04906b 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -3,10 +3,9 @@ package plugin import ( - "github.com/vektah/gqlparser/v2/ast" - "github.com/99designs/gqlgen/codegen" "github.com/99designs/gqlgen/codegen/config" + "github.com/vektah/gqlparser/v2/ast" ) type Plugin interface { @@ -31,7 +30,7 @@ type LateSourceInjector interface { InjectSourceLate(schema *ast.Schema) *ast.Source } -// Implementer is used to generate code inside resolvers +// ResolverImplementer is used to generate code inside resolvers type ResolverImplementer interface { Implement(field *codegen.Field) string } diff --git a/plugin/resolvergen/resolver.go b/plugin/resolvergen/resolver.go index c3a2c408227..085c2958470 100644 --- a/plugin/resolvergen/resolver.go +++ b/plugin/resolvergen/resolver.go @@ -68,7 +68,7 @@ func (m *Plugin) generateSingleFile(data *codegen.Data) error { continue } - resolver := Resolver{o, f, nil, "", `panic("not implemented")`} + resolver := Resolver{o, f, nil, "", `panic("not implemented")`, nil} file.Resolvers = append(file.Resolvers, &resolver) } } @@ -130,29 +130,24 @@ func (m *Plugin) generatePerSchema(data *codegen.Data) error { structName := templates.LcFirst(o.Name) + templates.UcFirst(data.Config.Resolver.Type) comment := strings.TrimSpace(strings.TrimLeft(rewriter.GetMethodComment(structName, f.GoFieldName), `\`)) - implementation := strings.TrimSpace(rewriter.GetMethodBody(structName, f.GoFieldName)) if implementation == "" { - // Check for Implementer Plugin - var resolver_implementer plugin.ResolverImplementer - var exists bool - for _, p := range data.Plugins { - if p_cast, ok := p.(plugin.ResolverImplementer); ok { - resolver_implementer = p_cast - exists = true - break - } + // use default implementation, if no implementation was previously used + implementation = fmt.Sprintf("panic(fmt.Errorf(\"not implemented: %v - %v\"))", f.GoFieldName, f.Name) + } + resolver := Resolver{o, f, rewriter.GetPrevDecl(structName, f.GoFieldName), comment, implementation, nil} + var implExists bool + for _, p := range data.Plugins { + rImpl, ok := p.(plugin.ResolverImplementer) + if !ok { + continue } - - if exists { - implementation = resolver_implementer.Implement(f) - } else { - implementation = fmt.Sprintf("panic(fmt.Errorf(\"not implemented: %v - %v\"))", f.GoFieldName, f.Name) + if implExists { + return fmt.Errorf("multiple plugins implement ResolverImplementer") } - + implExists = true + resolver.ImplementationRender = rImpl.Implement } - - resolver := Resolver{o, f, rewriter.GetPrevDecl(structName, f.GoFieldName), comment, implementation} fnCase := gqlToResolverName(data.Config.Resolver.Dir(), f.Position.Src.Name, data.Config.Resolver.FilenameTemplate) fn := strings.ToLower(fnCase) if files[fn] == nil { @@ -257,11 +252,19 @@ func (f *File) Imports() string { } type Resolver struct { - Object *codegen.Object - Field *codegen.Field - PrevDecl *ast.FuncDecl - Comment string - Implementation string + Object *codegen.Object + Field *codegen.Field + PrevDecl *ast.FuncDecl + Comment string + ImplementationStr string + ImplementationRender func(r *codegen.Field) string +} + +func (r *Resolver) Implementation() string { + if r.ImplementationRender != nil { + return r.ImplementationRender(r.Field) + } + return r.ImplementationStr } func gqlToResolverName(base string, gqlname, filenameTmpl string) string { diff --git a/plugin/resolvergen/resolver_test.go b/plugin/resolvergen/resolver_test.go index 17650e8f200..7c590b9e4c2 100644 --- a/plugin/resolvergen/resolver_test.go +++ b/plugin/resolvergen/resolver_test.go @@ -84,6 +84,24 @@ func TestOmitTemplateComment(t *testing.T) { assertNoErrors(t, "github.com/99designs/gqlgen/plugin/resolvergen/testdata/omit_template_comment/out") } +func TestResolver_Implementation(t *testing.T) { + _ = syscall.Unlink("testdata/resolver_implementor/resolver.go") + + cfg, err := config.LoadConfig("testdata/resolver_implementor/gqlgen.yml") + require.NoError(t, err) + p := Plugin{} + + require.NoError(t, cfg.Init()) + + data, err := codegen.BuildData(cfg, &implementorTest{}) + if err != nil { + panic(err) + } + + require.NoError(t, p.GenerateCode(data)) + assertNoErrors(t, "github.com/99designs/gqlgen/plugin/resolvergen/testdata/resolver_implementor/out") +} + func TestCustomResolverTemplate(t *testing.T) { _ = syscall.Unlink("testdata/resolvertemplate/out/resolver.go") cfg, err := config.LoadConfig("testdata/resolvertemplate/gqlgen.yml") @@ -142,3 +160,9 @@ func assertNoErrors(t *testing.T, pkg string) { t.Fatal("see compilation errors above") } } + +type implementorTest struct{} + +func (i *implementorTest) Implement(field *codegen.Field) string { + return "panic(\"implementor implemented me\")" +} diff --git a/plugin/resolvergen/testdata/followschema/out/schema.resolvers.go b/plugin/resolvergen/testdata/followschema/out/schema.resolvers.go index c2b7384156c..81c2aa9ceb3 100644 --- a/plugin/resolvergen/testdata/followschema/out/schema.resolvers.go +++ b/plugin/resolvergen/testdata/followschema/out/schema.resolvers.go @@ -2,7 +2,7 @@ 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 version v0.17.39-dev +// Code generated by github.com/99designs/gqlgen version v0.17.40-dev import ( "context" diff --git a/plugin/resolvergen/testdata/resolver_implementor/gqlgen.yml b/plugin/resolvergen/testdata/resolver_implementor/gqlgen.yml new file mode 100644 index 00000000000..833f78cf096 --- /dev/null +++ b/plugin/resolvergen/testdata/resolver_implementor/gqlgen.yml @@ -0,0 +1,15 @@ +schema: + - "testdata/schema.graphql" + +exec: + filename: testdata/resolver_implementor/out/ignored.go +model: + filename: testdata/resolver_implementor/out/generated.go +resolver: + type: CustomResolverType + layout: follow-schema + dir: testdata/resolver_implementor/out + +models: + Resolver: + model: github.com/99designs/gqlgen/plugin/resolvergen/testdata/resolver_implementor/out.Resolver diff --git a/plugin/resolvergen/testdata/resolver_implementor/out/model.go b/plugin/resolvergen/testdata/resolver_implementor/out/model.go new file mode 100644 index 00000000000..47712a16bdb --- /dev/null +++ b/plugin/resolvergen/testdata/resolver_implementor/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/resolver_implementor/out/resolver.go b/plugin/resolvergen/testdata/resolver_implementor/out/resolver.go new file mode 100644 index 00000000000..fbe00ecff89 --- /dev/null +++ b/plugin/resolvergen/testdata/resolver_implementor/out/resolver.go @@ -0,0 +1,7 @@ +package customresolver + +// This file will not be regenerated automatically. +// +// It serves as dependency injection for your app, add any dependencies you require here. + +type CustomResolverType struct{} diff --git a/plugin/resolvergen/testdata/resolver_implementor/out/schema.resolvers.go b/plugin/resolvergen/testdata/resolver_implementor/out/schema.resolvers.go new file mode 100644 index 00000000000..2b5ba462eb7 --- /dev/null +++ b/plugin/resolvergen/testdata/resolver_implementor/out/schema.resolvers.go @@ -0,0 +1,28 @@ +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 version v0.17.40-dev + +import ( + "context" +) + +// Resolver is the resolver for the resolver field. +func (r *queryCustomResolverType) Resolver(ctx context.Context) (*Resolver, error) { + panic("implementor implemented me") +} + +// Name is the resolver for the name field. +func (r *resolverCustomResolverType) Name(ctx context.Context, obj *Resolver) (string, error) { + panic("implementor implemented me") +} + +// 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 }