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