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