github.com/apipluspower/gqlgen@v0.15.2/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/apipluspower/gqlgen/codegen/config"
    14  	"github.com/apipluspower/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  }