github.com/operandinc/gqlgen@v0.16.1/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/operandinc/gqlgen/codegen" 11 "github.com/operandinc/gqlgen/codegen/config" 12 "github.com/operandinc/gqlgen/codegen/templates" 13 "github.com/operandinc/gqlgen/internal/rewrite" 14 "github.com/operandinc/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 for _, o := range data.Objects { 91 if o.HasResolvers() { 92 fn := gqlToResolverName(data.Config.Resolver.Dir(), o.Position.Src.Name, data.Config.Resolver.FilenameTemplate) 93 if files[fn] == nil { 94 files[fn] = &File{} 95 } 96 97 rewriter.MarkStructCopied(templates.LcFirst(o.Name) + templates.UcFirst(data.Config.Resolver.Type)) 98 rewriter.GetMethodBody(data.Config.Resolver.Type, strings.Title(o.Name)) 99 files[fn].Objects = append(files[fn].Objects, o) 100 } 101 for _, f := range o.Fields { 102 if !f.IsResolver { 103 continue 104 } 105 106 structName := templates.LcFirst(o.Name) + templates.UcFirst(data.Config.Resolver.Type) 107 implementation := strings.TrimSpace(rewriter.GetMethodBody(structName, f.GoFieldName)) 108 if implementation == "" { 109 implementation = `panic(fmt.Errorf("not implemented"))` 110 } 111 112 resolver := Resolver{o, f, implementation} 113 fn := gqlToResolverName(data.Config.Resolver.Dir(), f.Position.Src.Name, data.Config.Resolver.FilenameTemplate) 114 if files[fn] == nil { 115 files[fn] = &File{} 116 } 117 118 files[fn].Resolvers = append(files[fn].Resolvers, &resolver) 119 } 120 } 121 122 for filename, file := range files { 123 file.imports = rewriter.ExistingImports(filename) 124 file.RemainingSource = rewriter.RemainingSource(filename) 125 } 126 127 for filename, file := range files { 128 resolverBuild := &ResolverBuild{ 129 File: file, 130 PackageName: data.Config.Resolver.Package, 131 ResolverType: data.Config.Resolver.Type, 132 } 133 134 err := templates.Render(templates.Options{ 135 PackageName: data.Config.Resolver.Package, 136 FileNotice: ` 137 // This file will be automatically regenerated based on the schema, any resolver implementations 138 // will be copied through when generating and any unknown code will be moved to the end.`, 139 Filename: filename, 140 Data: resolverBuild, 141 Packages: data.Config.Packages, 142 }) 143 if err != nil { 144 return err 145 } 146 } 147 148 if _, err := os.Stat(data.Config.Resolver.Filename); errors.Is(err, fs.ErrNotExist) { 149 err := templates.Render(templates.Options{ 150 PackageName: data.Config.Resolver.Package, 151 FileNotice: ` 152 // This file will not be regenerated automatically. 153 // 154 // It serves as dependency injection for your app, add any dependencies you require here.`, 155 Template: `type {{.}} struct {}`, 156 Filename: data.Config.Resolver.Filename, 157 Data: data.Config.Resolver.Type, 158 Packages: data.Config.Packages, 159 }) 160 if err != nil { 161 return err 162 } 163 } 164 return nil 165 } 166 167 type ResolverBuild struct { 168 *File 169 HasRoot bool 170 PackageName string 171 ResolverType string 172 } 173 174 type File struct { 175 // These are separated because the type definition of the resolver object may live in a different file from the 176 // resolver method implementations, for example when extending a type in a different graphql schema file 177 Objects []*codegen.Object 178 Resolvers []*Resolver 179 imports []rewrite.Import 180 RemainingSource string 181 } 182 183 func (f *File) Imports() string { 184 for _, imp := range f.imports { 185 if imp.Alias == "" { 186 _, _ = templates.CurrentImports.Reserve(imp.ImportPath) 187 } else { 188 _, _ = templates.CurrentImports.Reserve(imp.ImportPath, imp.Alias) 189 } 190 } 191 return "" 192 } 193 194 type Resolver struct { 195 Object *codegen.Object 196 Field *codegen.Field 197 Implementation string 198 } 199 200 func gqlToResolverName(base string, gqlname, filenameTmpl string) string { 201 gqlname = filepath.Base(gqlname) 202 ext := filepath.Ext(gqlname) 203 if filenameTmpl == "" { 204 filenameTmpl = "{name}.resolvers.go" 205 } 206 filename := strings.ReplaceAll(filenameTmpl, "{name}", strings.TrimSuffix(gqlname, ext)) 207 return filepath.Join(base, filename) 208 }