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  }