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  }