github.com/senomas/gqlgen@v0.17.11-0.20220626120754-9aee61b0716a/plugin/resolvergen/resolver.go (about) 1 package resolvergen 2 3 import ( 4 "errors" 5 "io/fs" 6 "os" 7 "path/filepath" 8 "strings" 9 10 "github.com/99designs/gqlgen/codegen" 11 "github.com/99designs/gqlgen/codegen/config" 12 "github.com/99designs/gqlgen/codegen/templates" 13 "github.com/99designs/gqlgen/internal/rewrite" 14 "github.com/99designs/gqlgen/plugin" 15 ) 16 17 func New() plugin.Plugin { 18 return &Plugin{} 19 } 20 21 type Plugin struct{} 22 23 var _ plugin.CodeGenerator = &Plugin{} 24 25 func (m *Plugin) Name() string { 26 return "resolvergen" 27 } 28 29 func (m *Plugin) GenerateCode(data *codegen.Data) error { 30 if !data.Config.Resolver.IsDefined() { 31 return nil 32 } 33 34 switch data.Config.Resolver.Layout { 35 case config.LayoutSingleFile: 36 return m.generateSingleFile(data) 37 case config.LayoutFollowSchema: 38 return m.generatePerSchema(data) 39 } 40 41 return nil 42 } 43 44 func (m *Plugin) generateSingleFile(data *codegen.Data) error { 45 file := File{} 46 47 if _, err := os.Stat(data.Config.Resolver.Filename); err == nil { 48 // file already exists and we dont support updating resolvers with layout = single so just return 49 return nil 50 } 51 52 for _, o := range data.Objects { 53 if o.HasResolvers() { 54 file.Objects = append(file.Objects, o) 55 } 56 for _, f := range o.Fields { 57 if !f.IsResolver { 58 continue 59 } 60 61 resolver := Resolver{o, f, `panic("not implemented")`} 62 file.Resolvers = append(file.Resolvers, &resolver) 63 } 64 } 65 66 resolverBuild := &ResolverBuild{ 67 File: &file, 68 PackageName: data.Config.Resolver.Package, 69 ResolverType: data.Config.Resolver.Type, 70 HasRoot: true, 71 } 72 73 return templates.Render(templates.Options{ 74 PackageName: data.Config.Resolver.Package, 75 FileNotice: `// THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES.`, 76 Filename: data.Config.Resolver.Filename, 77 Data: resolverBuild, 78 Packages: data.Config.Packages, 79 }) 80 } 81 82 func (m *Plugin) generatePerSchema(data *codegen.Data) error { 83 rewriter, err := rewrite.New(data.Config.Resolver.Dir()) 84 if err != nil { 85 return err 86 } 87 88 files := map[string]*File{} 89 90 objects := make(codegen.Objects, len(data.Objects)+len(data.Inputs)) 91 copy(objects, data.Objects) 92 copy(objects[len(data.Objects):], data.Inputs) 93 94 for _, o := range objects { 95 if o.HasResolvers() { 96 fn := gqlToResolverName(data.Config.Resolver.Dir(), o.Position.Src.Name, data.Config.Resolver.FilenameTemplate) 97 if files[fn] == nil { 98 files[fn] = &File{} 99 } 100 101 rewriter.MarkStructCopied(templates.LcFirst(o.Name) + templates.UcFirst(data.Config.Resolver.Type)) 102 rewriter.GetMethodBody(data.Config.Resolver.Type, strings.Title(o.Name)) 103 files[fn].Objects = append(files[fn].Objects, o) 104 } 105 for _, f := range o.Fields { 106 if !f.IsResolver { 107 continue 108 } 109 110 structName := templates.LcFirst(o.Name) + templates.UcFirst(data.Config.Resolver.Type) 111 implementation := strings.TrimSpace(rewriter.GetMethodBody(structName, f.GoFieldName)) 112 if implementation == "" { 113 implementation = `panic(fmt.Errorf("not implemented"))` 114 } 115 116 resolver := Resolver{o, f, implementation} 117 fn := gqlToResolverName(data.Config.Resolver.Dir(), f.Position.Src.Name, data.Config.Resolver.FilenameTemplate) 118 if files[fn] == nil { 119 files[fn] = &File{} 120 } 121 122 files[fn].Resolvers = append(files[fn].Resolvers, &resolver) 123 } 124 } 125 126 for filename, file := range files { 127 file.imports = rewriter.ExistingImports(filename) 128 file.RemainingSource = rewriter.RemainingSource(filename) 129 } 130 131 for filename, file := range files { 132 resolverBuild := &ResolverBuild{ 133 File: file, 134 PackageName: data.Config.Resolver.Package, 135 ResolverType: data.Config.Resolver.Type, 136 } 137 138 err := templates.Render(templates.Options{ 139 PackageName: data.Config.Resolver.Package, 140 FileNotice: ` 141 // This file will be automatically regenerated based on the schema, any resolver implementations 142 // will be copied through when generating and any unknown code will be moved to the end.`, 143 Filename: filename, 144 Data: resolverBuild, 145 Packages: data.Config.Packages, 146 }) 147 if err != nil { 148 return err 149 } 150 } 151 152 if _, err := os.Stat(data.Config.Resolver.Filename); errors.Is(err, fs.ErrNotExist) { 153 err := templates.Render(templates.Options{ 154 PackageName: data.Config.Resolver.Package, 155 FileNotice: ` 156 // This file will not be regenerated automatically. 157 // 158 // It serves as dependency injection for your app, add any dependencies you require here.`, 159 Template: `type {{.}} struct {}`, 160 Filename: data.Config.Resolver.Filename, 161 Data: data.Config.Resolver.Type, 162 Packages: data.Config.Packages, 163 }) 164 if err != nil { 165 return err 166 } 167 } 168 return nil 169 } 170 171 type ResolverBuild struct { 172 *File 173 HasRoot bool 174 PackageName string 175 ResolverType string 176 } 177 178 type File struct { 179 // These are separated because the type definition of the resolver object may live in a different file from the 180 // resolver method implementations, for example when extending a type in a different graphql schema file 181 Objects []*codegen.Object 182 Resolvers []*Resolver 183 imports []rewrite.Import 184 RemainingSource string 185 } 186 187 func (f *File) Imports() string { 188 for _, imp := range f.imports { 189 if imp.Alias == "" { 190 _, _ = templates.CurrentImports.Reserve(imp.ImportPath) 191 } else { 192 _, _ = templates.CurrentImports.Reserve(imp.ImportPath, imp.Alias) 193 } 194 } 195 return "" 196 } 197 198 type Resolver struct { 199 Object *codegen.Object 200 Field *codegen.Field 201 Implementation string 202 } 203 204 func gqlToResolverName(base string, gqlname, filenameTmpl string) string { 205 gqlname = filepath.Base(gqlname) 206 ext := filepath.Ext(gqlname) 207 if filenameTmpl == "" { 208 filenameTmpl = "{name}.resolvers.go" 209 } 210 filename := strings.ReplaceAll(filenameTmpl, "{name}", strings.TrimSuffix(gqlname, ext)) 211 return filepath.Join(base, filename) 212 }