github.com/operandinc/gqlgen@v0.16.1/plugin/resolvergen/resolver.go (about)

     1  package resolvergen
     2  
     3  import (
     4  	"errors"
     5  	"io/fs"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/operandinc/gqlgen/codegen"
    11  	"github.com/operandinc/gqlgen/codegen/config"
    12  	"github.com/operandinc/gqlgen/codegen/templates"
    13  	"github.com/operandinc/gqlgen/internal/rewrite"
    14  	"github.com/operandinc/gqlgen/plugin"
    15  )
    16  
    17  func New() plugin.Plugin {
    18  	return &Plugin{}
    19  }
    20  
    21  type Plugin struct{}
    22  
    23  var _ plugin.CodeGenerator = &Plugin{}
    24  
    25  func (m *Plugin) Name() string {
    26  	return "resolvergen"
    27  }
    28  
    29  func (m *Plugin) GenerateCode(data *codegen.Data) error {
    30  	if !data.Config.Resolver.IsDefined() {
    31  		return nil
    32  	}
    33  
    34  	switch data.Config.Resolver.Layout {
    35  	case config.LayoutSingleFile:
    36  		return m.generateSingleFile(data)
    37  	case config.LayoutFollowSchema:
    38  		return m.generatePerSchema(data)
    39  	}
    40  
    41  	return nil
    42  }
    43  
    44  func (m *Plugin) generateSingleFile(data *codegen.Data) error {
    45  	file := File{}
    46  
    47  	if _, err := os.Stat(data.Config.Resolver.Filename); err == nil {
    48  		// file already exists and we dont support updating resolvers with layout = single so just return
    49  		return nil
    50  	}
    51  
    52  	for _, o := range data.Objects {
    53  		if o.HasResolvers() {
    54  			file.Objects = append(file.Objects, o)
    55  		}
    56  		for _, f := range o.Fields {
    57  			if !f.IsResolver {
    58  				continue
    59  			}
    60  
    61  			resolver := Resolver{o, f, `panic("not implemented")`}
    62  			file.Resolvers = append(file.Resolvers, &resolver)
    63  		}
    64  	}
    65  
    66  	resolverBuild := &ResolverBuild{
    67  		File:         &file,
    68  		PackageName:  data.Config.Resolver.Package,
    69  		ResolverType: data.Config.Resolver.Type,
    70  		HasRoot:      true,
    71  	}
    72  
    73  	return templates.Render(templates.Options{
    74  		PackageName: data.Config.Resolver.Package,
    75  		FileNotice:  `// THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES.`,
    76  		Filename:    data.Config.Resolver.Filename,
    77  		Data:        resolverBuild,
    78  		Packages:    data.Config.Packages,
    79  	})
    80  }
    81  
    82  func (m *Plugin) generatePerSchema(data *codegen.Data) error {
    83  	rewriter, err := rewrite.New(data.Config.Resolver.Dir())
    84  	if err != nil {
    85  		return err
    86  	}
    87  
    88  	files := map[string]*File{}
    89  
    90  	for _, o := range data.Objects {
    91  		if o.HasResolvers() {
    92  			fn := gqlToResolverName(data.Config.Resolver.Dir(), o.Position.Src.Name, data.Config.Resolver.FilenameTemplate)
    93  			if files[fn] == nil {
    94  				files[fn] = &File{}
    95  			}
    96  
    97  			rewriter.MarkStructCopied(templates.LcFirst(o.Name) + templates.UcFirst(data.Config.Resolver.Type))
    98  			rewriter.GetMethodBody(data.Config.Resolver.Type, strings.Title(o.Name))
    99  			files[fn].Objects = append(files[fn].Objects, o)
   100  		}
   101  		for _, f := range o.Fields {
   102  			if !f.IsResolver {
   103  				continue
   104  			}
   105  
   106  			structName := templates.LcFirst(o.Name) + templates.UcFirst(data.Config.Resolver.Type)
   107  			implementation := strings.TrimSpace(rewriter.GetMethodBody(structName, f.GoFieldName))
   108  			if implementation == "" {
   109  				implementation = `panic(fmt.Errorf("not implemented"))`
   110  			}
   111  
   112  			resolver := Resolver{o, f, implementation}
   113  			fn := gqlToResolverName(data.Config.Resolver.Dir(), f.Position.Src.Name, data.Config.Resolver.FilenameTemplate)
   114  			if files[fn] == nil {
   115  				files[fn] = &File{}
   116  			}
   117  
   118  			files[fn].Resolvers = append(files[fn].Resolvers, &resolver)
   119  		}
   120  	}
   121  
   122  	for filename, file := range files {
   123  		file.imports = rewriter.ExistingImports(filename)
   124  		file.RemainingSource = rewriter.RemainingSource(filename)
   125  	}
   126  
   127  	for filename, file := range files {
   128  		resolverBuild := &ResolverBuild{
   129  			File:         file,
   130  			PackageName:  data.Config.Resolver.Package,
   131  			ResolverType: data.Config.Resolver.Type,
   132  		}
   133  
   134  		err := templates.Render(templates.Options{
   135  			PackageName: data.Config.Resolver.Package,
   136  			FileNotice: `
   137  				// This file will be automatically regenerated based on the schema, any resolver implementations
   138  				// will be copied through when generating and any unknown code will be moved to the end.`,
   139  			Filename: filename,
   140  			Data:     resolverBuild,
   141  			Packages: data.Config.Packages,
   142  		})
   143  		if err != nil {
   144  			return err
   145  		}
   146  	}
   147  
   148  	if _, err := os.Stat(data.Config.Resolver.Filename); errors.Is(err, fs.ErrNotExist) {
   149  		err := templates.Render(templates.Options{
   150  			PackageName: data.Config.Resolver.Package,
   151  			FileNotice: `
   152  				// This file will not be regenerated automatically.
   153  				//
   154  				// It serves as dependency injection for your app, add any dependencies you require here.`,
   155  			Template: `type {{.}} struct {}`,
   156  			Filename: data.Config.Resolver.Filename,
   157  			Data:     data.Config.Resolver.Type,
   158  			Packages: data.Config.Packages,
   159  		})
   160  		if err != nil {
   161  			return err
   162  		}
   163  	}
   164  	return nil
   165  }
   166  
   167  type ResolverBuild struct {
   168  	*File
   169  	HasRoot      bool
   170  	PackageName  string
   171  	ResolverType string
   172  }
   173  
   174  type File struct {
   175  	// These are separated because the type definition of the resolver object may live in a different file from the
   176  	// resolver method implementations, for example when extending a type in a different graphql schema file
   177  	Objects         []*codegen.Object
   178  	Resolvers       []*Resolver
   179  	imports         []rewrite.Import
   180  	RemainingSource string
   181  }
   182  
   183  func (f *File) Imports() string {
   184  	for _, imp := range f.imports {
   185  		if imp.Alias == "" {
   186  			_, _ = templates.CurrentImports.Reserve(imp.ImportPath)
   187  		} else {
   188  			_, _ = templates.CurrentImports.Reserve(imp.ImportPath, imp.Alias)
   189  		}
   190  	}
   191  	return ""
   192  }
   193  
   194  type Resolver struct {
   195  	Object         *codegen.Object
   196  	Field          *codegen.Field
   197  	Implementation string
   198  }
   199  
   200  func gqlToResolverName(base string, gqlname, filenameTmpl string) string {
   201  	gqlname = filepath.Base(gqlname)
   202  	ext := filepath.Ext(gqlname)
   203  	if filenameTmpl == "" {
   204  		filenameTmpl = "{name}.resolvers.go"
   205  	}
   206  	filename := strings.ReplaceAll(filenameTmpl, "{name}", strings.TrimSuffix(gqlname, ext))
   207  	return filepath.Join(base, filename)
   208  }