github.com/niko0xdev/gqlgen@v0.17.55-0.20240120102243-2ecff98c3e37/codegen/data.go (about)

     1  package codegen
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"sort"
     8  	"strings"
     9  
    10  	"github.com/vektah/gqlparser/v2/ast"
    11  
    12  	"github.com/niko0xdev/gqlgen/codegen/config"
    13  )
    14  
    15  // Data is a unified model of the code to be generated. Plugins may modify this structure to do things like implement
    16  // resolvers or directives automatically (eg grpc, validation)
    17  type Data struct {
    18  	Config *config.Config
    19  	Schema *ast.Schema
    20  	// If a schema is broken up into multiple Data instance, each representing part of the schema,
    21  	// AllDirectives should contain the directives for the entire schema. Directives() can
    22  	// then be used to get the directives that were defined in this Data instance's sources.
    23  	// If a single Data instance is used for the entire schema, AllDirectives and Directives()
    24  	// will be identical.
    25  	// AllDirectives should rarely be used directly.
    26  	AllDirectives   DirectiveList
    27  	Objects         Objects
    28  	Inputs          Objects
    29  	Interfaces      map[string]*Interface
    30  	ReferencedTypes map[string]*config.TypeReference
    31  	ComplexityRoots map[string]*Object
    32  
    33  	QueryRoot        *Object
    34  	MutationRoot     *Object
    35  	SubscriptionRoot *Object
    36  	AugmentedSources []AugmentedSource
    37  	Plugins          []interface{}
    38  }
    39  
    40  func (d *Data) HasEmbeddableSources() bool {
    41  	hasEmbeddableSources := false
    42  	for _, s := range d.AugmentedSources {
    43  		if s.Embeddable {
    44  			hasEmbeddableSources = true
    45  		}
    46  	}
    47  	return hasEmbeddableSources
    48  }
    49  
    50  // AugmentedSource contains extra information about graphql schema files which is not known directly from the Config.Sources data
    51  type AugmentedSource struct {
    52  	// path relative to Config.Exec.Filename
    53  	RelativePath string
    54  	Embeddable   bool
    55  	BuiltIn      bool
    56  	Source       string
    57  }
    58  
    59  type builder struct {
    60  	Config     *config.Config
    61  	Schema     *ast.Schema
    62  	Binder     *config.Binder
    63  	Directives map[string]*Directive
    64  }
    65  
    66  // Get only the directives which are defined in the config's sources.
    67  func (d *Data) Directives() DirectiveList {
    68  	res := DirectiveList{}
    69  	for k, directive := range d.AllDirectives {
    70  		for _, s := range d.Config.Sources {
    71  			if directive.Position.Src.Name == s.Name {
    72  				res[k] = directive
    73  				break
    74  			}
    75  		}
    76  	}
    77  	return res
    78  }
    79  
    80  func BuildData(cfg *config.Config, plugins ...interface{}) (*Data, error) {
    81  	// We reload all packages to allow packages to be compared correctly.
    82  	cfg.ReloadAllPackages()
    83  
    84  	b := builder{
    85  		Config: cfg,
    86  		Schema: cfg.Schema,
    87  	}
    88  
    89  	b.Binder = b.Config.NewBinder()
    90  
    91  	var err error
    92  	b.Directives, err = b.buildDirectives()
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	dataDirectives := make(map[string]*Directive)
    98  	for name, d := range b.Directives {
    99  		if !d.Builtin {
   100  			dataDirectives[name] = d
   101  		}
   102  	}
   103  
   104  	s := Data{
   105  		Config:        cfg,
   106  		AllDirectives: dataDirectives,
   107  		Schema:        b.Schema,
   108  		Interfaces:    map[string]*Interface{},
   109  		Plugins:       plugins,
   110  	}
   111  
   112  	for _, schemaType := range b.Schema.Types {
   113  		switch schemaType.Kind {
   114  		case ast.Object:
   115  			obj, err := b.buildObject(schemaType)
   116  			if err != nil {
   117  				return nil, fmt.Errorf("unable to build object definition: %w", err)
   118  			}
   119  
   120  			s.Objects = append(s.Objects, obj)
   121  		case ast.InputObject:
   122  			input, err := b.buildObject(schemaType)
   123  			if err != nil {
   124  				return nil, fmt.Errorf("unable to build input definition: %w", err)
   125  			}
   126  
   127  			s.Inputs = append(s.Inputs, input)
   128  
   129  		case ast.Union, ast.Interface:
   130  			s.Interfaces[schemaType.Name], err = b.buildInterface(schemaType)
   131  			if err != nil {
   132  				return nil, fmt.Errorf("unable to bind to interface: %w", err)
   133  			}
   134  		}
   135  	}
   136  
   137  	if s.Schema.Query != nil {
   138  		s.QueryRoot = s.Objects.ByName(s.Schema.Query.Name)
   139  	} else {
   140  		return nil, fmt.Errorf("query entry point missing")
   141  	}
   142  
   143  	if s.Schema.Mutation != nil {
   144  		s.MutationRoot = s.Objects.ByName(s.Schema.Mutation.Name)
   145  	}
   146  
   147  	if s.Schema.Subscription != nil {
   148  		s.SubscriptionRoot = s.Objects.ByName(s.Schema.Subscription.Name)
   149  	}
   150  
   151  	if err := b.injectIntrospectionRoots(&s); err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	s.ReferencedTypes = b.buildTypes()
   156  
   157  	sort.Slice(s.Objects, func(i, j int) bool {
   158  		return s.Objects[i].Definition.Name < s.Objects[j].Definition.Name
   159  	})
   160  
   161  	sort.Slice(s.Inputs, func(i, j int) bool {
   162  		return s.Inputs[i].Definition.Name < s.Inputs[j].Definition.Name
   163  	})
   164  
   165  	if b.Binder.SawInvalid {
   166  		// if we have a syntax error, show it
   167  		err := cfg.Packages.Errors()
   168  		if len(err) > 0 {
   169  			return nil, err
   170  		}
   171  
   172  		// otherwise show a generic error message
   173  		return nil, fmt.Errorf("invalid types were encountered while traversing the go source code, this probably means the invalid code generated isnt correct. add try adding -v to debug")
   174  	}
   175  	aSources := []AugmentedSource{}
   176  	for _, s := range cfg.Sources {
   177  		wd, err := os.Getwd()
   178  		if err != nil {
   179  			return nil, fmt.Errorf("failed to get working directory: %w", err)
   180  		}
   181  		outputDir := cfg.Exec.Dir()
   182  		sourcePath := filepath.Join(wd, s.Name)
   183  		relative, err := filepath.Rel(outputDir, sourcePath)
   184  		if err != nil {
   185  			return nil, fmt.Errorf("failed to compute path of %s relative to %s: %w", sourcePath, outputDir, err)
   186  		}
   187  		relative = filepath.ToSlash(relative)
   188  		embeddable := true
   189  		if strings.HasPrefix(relative, "..") || s.BuiltIn {
   190  			embeddable = false
   191  		}
   192  		aSources = append(aSources, AugmentedSource{
   193  			RelativePath: relative,
   194  			Embeddable:   embeddable,
   195  			BuiltIn:      s.BuiltIn,
   196  			Source:       s.Input,
   197  		})
   198  	}
   199  	s.AugmentedSources = aSources
   200  
   201  	return &s, nil
   202  }
   203  
   204  func (b *builder) injectIntrospectionRoots(s *Data) error {
   205  	obj := s.Objects.ByName(b.Schema.Query.Name)
   206  	if obj == nil {
   207  		return fmt.Errorf("root query type must be defined")
   208  	}
   209  
   210  	__type, err := b.buildField(obj, &ast.FieldDefinition{
   211  		Name: "__type",
   212  		Type: ast.NamedType("__Type", nil),
   213  		Arguments: []*ast.ArgumentDefinition{
   214  			{
   215  				Name: "name",
   216  				Type: ast.NonNullNamedType("String", nil),
   217  			},
   218  		},
   219  	})
   220  	if err != nil {
   221  		return err
   222  	}
   223  
   224  	__schema, err := b.buildField(obj, &ast.FieldDefinition{
   225  		Name: "__schema",
   226  		Type: ast.NamedType("__Schema", nil),
   227  	})
   228  	if err != nil {
   229  		return err
   230  	}
   231  
   232  	obj.Fields = append(obj.Fields, __type, __schema)
   233  
   234  	return nil
   235  }