github.com/shish-dev/gqlgen@v0.99.0/codegen/generate.go (about) 1 package codegen 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "path/filepath" 8 "runtime" 9 "strings" 10 11 "github.com/vektah/gqlparser/v2/ast" 12 13 "github.com/shish-dev/gqlgen/codegen/config" 14 "github.com/shish-dev/gqlgen/codegen/templates" 15 ) 16 17 func GenerateCode(data *Data) error { 18 if !data.Config.Exec.IsDefined() { 19 return fmt.Errorf("missing exec config") 20 } 21 22 switch data.Config.Exec.Layout { 23 case config.ExecLayoutSingleFile: 24 return generateSingleFile(data) 25 case config.ExecLayoutFollowSchema: 26 return generatePerSchema(data) 27 } 28 29 return fmt.Errorf("unrecognized exec layout %s", data.Config.Exec.Layout) 30 } 31 32 func generateSingleFile(data *Data) error { 33 return templates.Render(templates.Options{ 34 PackageName: data.Config.Exec.Package, 35 Filename: data.Config.Exec.Filename, 36 Data: data, 37 RegionTags: true, 38 GeneratedHeader: true, 39 Packages: data.Config.Packages, 40 }) 41 } 42 43 func generatePerSchema(data *Data) error { 44 err := generateRootFile(data) 45 if err != nil { 46 return err 47 } 48 49 builds := map[string]*Data{} 50 51 err = addObjects(data, &builds) 52 if err != nil { 53 return err 54 } 55 56 err = addInputs(data, &builds) 57 if err != nil { 58 return err 59 } 60 61 err = addInterfaces(data, &builds) 62 if err != nil { 63 return err 64 } 65 66 err = addReferencedTypes(data, &builds) 67 if err != nil { 68 return err 69 } 70 71 for filename, build := range builds { 72 if filename == "" { 73 continue 74 } 75 76 dir := data.Config.Exec.DirName 77 path := filepath.Join(dir, filename) 78 79 err = templates.Render(templates.Options{ 80 PackageName: data.Config.Exec.Package, 81 Filename: path, 82 Data: build, 83 RegionTags: true, 84 GeneratedHeader: true, 85 Packages: data.Config.Packages, 86 }) 87 if err != nil { 88 return err 89 } 90 } 91 92 return nil 93 } 94 95 func filename(p *ast.Position, config *config.Config) string { 96 name := "common!" 97 if p != nil && p.Src != nil { 98 gqlname := filepath.Base(p.Src.Name) 99 ext := filepath.Ext(p.Src.Name) 100 name = strings.TrimSuffix(gqlname, ext) 101 } 102 103 filenameTempl := config.Exec.FilenameTemplate 104 if filenameTempl == "" { 105 filenameTempl = "{name}.generated.go" 106 } 107 108 return strings.ReplaceAll(filenameTempl, "{name}", name) 109 } 110 111 func addBuild(filename string, p *ast.Position, data *Data, builds *map[string]*Data) { 112 buildConfig := *data.Config 113 if p != nil { 114 buildConfig.Sources = []*ast.Source{p.Src} 115 } 116 117 (*builds)[filename] = &Data{ 118 Config: &buildConfig, 119 QueryRoot: data.QueryRoot, 120 MutationRoot: data.MutationRoot, 121 SubscriptionRoot: data.SubscriptionRoot, 122 AllDirectives: data.AllDirectives, 123 } 124 } 125 126 // Root file contains top-level definitions that should not be duplicated across the generated 127 // files for each schema file. 128 func generateRootFile(data *Data) error { 129 dir := data.Config.Exec.DirName 130 path := filepath.Join(dir, "root_.generated.go") 131 132 _, thisFile, _, _ := runtime.Caller(0) 133 rootDir := filepath.Dir(thisFile) 134 templatePath := filepath.Join(rootDir, "root_.gotpl") 135 templateBytes, err := ioutil.ReadFile(templatePath) 136 if err != nil { 137 return err 138 } 139 template := string(templateBytes) 140 141 return templates.Render(templates.Options{ 142 PackageName: data.Config.Exec.Package, 143 Template: template, 144 Filename: path, 145 Data: data, 146 RegionTags: false, 147 GeneratedHeader: true, 148 Packages: data.Config.Packages, 149 }) 150 } 151 152 func addObjects(data *Data, builds *map[string]*Data) error { 153 for _, o := range data.Objects { 154 filename := filename(o.Position, data.Config) 155 if (*builds)[filename] == nil { 156 addBuild(filename, o.Position, data, builds) 157 } 158 159 (*builds)[filename].Objects = append((*builds)[filename].Objects, o) 160 } 161 return nil 162 } 163 164 func addInputs(data *Data, builds *map[string]*Data) error { 165 for _, in := range data.Inputs { 166 filename := filename(in.Position, data.Config) 167 if (*builds)[filename] == nil { 168 addBuild(filename, in.Position, data, builds) 169 } 170 171 (*builds)[filename].Inputs = append((*builds)[filename].Inputs, in) 172 } 173 return nil 174 } 175 176 func addInterfaces(data *Data, builds *map[string]*Data) error { 177 for k, inf := range data.Interfaces { 178 filename := filename(inf.Position, data.Config) 179 if (*builds)[filename] == nil { 180 addBuild(filename, inf.Position, data, builds) 181 } 182 build := (*builds)[filename] 183 184 if build.Interfaces == nil { 185 build.Interfaces = map[string]*Interface{} 186 } 187 if build.Interfaces[k] != nil { 188 return errors.New("conflicting interface keys") 189 } 190 191 build.Interfaces[k] = inf 192 } 193 return nil 194 } 195 196 func addReferencedTypes(data *Data, builds *map[string]*Data) error { 197 for k, rt := range data.ReferencedTypes { 198 filename := filename(rt.Definition.Position, data.Config) 199 if (*builds)[filename] == nil { 200 addBuild(filename, rt.Definition.Position, data, builds) 201 } 202 build := (*builds)[filename] 203 204 if build.ReferencedTypes == nil { 205 build.ReferencedTypes = map[string]*config.TypeReference{} 206 } 207 if build.ReferencedTypes[k] != nil { 208 return errors.New("conflicting referenced type keys") 209 } 210 211 build.ReferencedTypes[k] = rt 212 } 213 return nil 214 }