github.com/senomas/gqlgen@v0.17.11-0.20220626120754-9aee61b0716a/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/99designs/gqlgen/codegen"
    11  	"github.com/99designs/gqlgen/codegen/config"
    12  	"github.com/99designs/gqlgen/codegen/templates"
    13  	"github.com/99designs/gqlgen/internal/rewrite"
    14  	"github.com/99designs/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  	objects := make(codegen.Objects, len(data.Objects)+len(data.Inputs))
    91  	copy(objects, data.Objects)
    92  	copy(objects[len(data.Objects):], data.Inputs)
    93  
    94  	for _, o := range objects {
    95  		if o.HasResolvers() {
    96  			fn := gqlToResolverName(data.Config.Resolver.Dir(), o.Position.Src.Name, data.Config.Resolver.FilenameTemplate)
    97  			if files[fn] == nil {
    98  				files[fn] = &File{}
    99  			}
   100  
   101  			rewriter.MarkStructCopied(templates.LcFirst(o.Name) + templates.UcFirst(data.Config.Resolver.Type))
   102  			rewriter.GetMethodBody(data.Config.Resolver.Type, strings.Title(o.Name))
   103  			files[fn].Objects = append(files[fn].Objects, o)
   104  		}
   105  		for _, f := range o.Fields {
   106  			if !f.IsResolver {
   107  				continue
   108  			}
   109  
   110  			structName := templates.LcFirst(o.Name) + templates.UcFirst(data.Config.Resolver.Type)
   111  			implementation := strings.TrimSpace(rewriter.GetMethodBody(structName, f.GoFieldName))
   112  			if implementation == "" {
   113  				implementation = `panic(fmt.Errorf("not implemented"))`
   114  			}
   115  
   116  			resolver := Resolver{o, f, implementation}
   117  			fn := gqlToResolverName(data.Config.Resolver.Dir(), f.Position.Src.Name, data.Config.Resolver.FilenameTemplate)
   118  			if files[fn] == nil {
   119  				files[fn] = &File{}
   120  			}
   121  
   122  			files[fn].Resolvers = append(files[fn].Resolvers, &resolver)
   123  		}
   124  	}
   125  
   126  	for filename, file := range files {
   127  		file.imports = rewriter.ExistingImports(filename)
   128  		file.RemainingSource = rewriter.RemainingSource(filename)
   129  	}
   130  
   131  	for filename, file := range files {
   132  		resolverBuild := &ResolverBuild{
   133  			File:         file,
   134  			PackageName:  data.Config.Resolver.Package,
   135  			ResolverType: data.Config.Resolver.Type,
   136  		}
   137  
   138  		err := templates.Render(templates.Options{
   139  			PackageName: data.Config.Resolver.Package,
   140  			FileNotice: `
   141  				// This file will be automatically regenerated based on the schema, any resolver implementations
   142  				// will be copied through when generating and any unknown code will be moved to the end.`,
   143  			Filename: filename,
   144  			Data:     resolverBuild,
   145  			Packages: data.Config.Packages,
   146  		})
   147  		if err != nil {
   148  			return err
   149  		}
   150  	}
   151  
   152  	if _, err := os.Stat(data.Config.Resolver.Filename); errors.Is(err, fs.ErrNotExist) {
   153  		err := templates.Render(templates.Options{
   154  			PackageName: data.Config.Resolver.Package,
   155  			FileNotice: `
   156  				// This file will not be regenerated automatically.
   157  				//
   158  				// It serves as dependency injection for your app, add any dependencies you require here.`,
   159  			Template: `type {{.}} struct {}`,
   160  			Filename: data.Config.Resolver.Filename,
   161  			Data:     data.Config.Resolver.Type,
   162  			Packages: data.Config.Packages,
   163  		})
   164  		if err != nil {
   165  			return err
   166  		}
   167  	}
   168  	return nil
   169  }
   170  
   171  type ResolverBuild struct {
   172  	*File
   173  	HasRoot      bool
   174  	PackageName  string
   175  	ResolverType string
   176  }
   177  
   178  type File struct {
   179  	// These are separated because the type definition of the resolver object may live in a different file from the
   180  	// resolver method implementations, for example when extending a type in a different graphql schema file
   181  	Objects         []*codegen.Object
   182  	Resolvers       []*Resolver
   183  	imports         []rewrite.Import
   184  	RemainingSource string
   185  }
   186  
   187  func (f *File) Imports() string {
   188  	for _, imp := range f.imports {
   189  		if imp.Alias == "" {
   190  			_, _ = templates.CurrentImports.Reserve(imp.ImportPath)
   191  		} else {
   192  			_, _ = templates.CurrentImports.Reserve(imp.ImportPath, imp.Alias)
   193  		}
   194  	}
   195  	return ""
   196  }
   197  
   198  type Resolver struct {
   199  	Object         *codegen.Object
   200  	Field          *codegen.Field
   201  	Implementation string
   202  }
   203  
   204  func gqlToResolverName(base string, gqlname, filenameTmpl string) string {
   205  	gqlname = filepath.Base(gqlname)
   206  	ext := filepath.Ext(gqlname)
   207  	if filenameTmpl == "" {
   208  		filenameTmpl = "{name}.resolvers.go"
   209  	}
   210  	filename := strings.ReplaceAll(filenameTmpl, "{name}", strings.TrimSuffix(gqlname, ext))
   211  	return filepath.Join(base, filename)
   212  }