github.com/apipluspower/gqlgen@v0.15.2/codegen/data.go (about)

     1  package codegen
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  
     7  	"github.com/vektah/gqlparser/v2/ast"
     8  
     9  	"github.com/apipluspower/gqlgen/codegen/config"
    10  )
    11  
    12  // Data is a unified model of the code to be generated. Plugins may modify this structure to do things like implement
    13  // resolvers or directives automatically (eg grpc, validation)
    14  type Data struct {
    15  	Config *config.Config
    16  	Schema *ast.Schema
    17  	// If a schema is broken up into multiple Data instance, each representing part of the schema,
    18  	// AllDirectives should contain the directives for the entire schema. Directives() can
    19  	// then be used to get the directives that were defined in this Data instance's sources.
    20  	// If a single Data instance is used for the entire schema, AllDirectives and Directives()
    21  	// will be identical.
    22  	// AllDirectives should rarely be used directly.
    23  	AllDirectives   DirectiveList
    24  	Objects         Objects
    25  	Inputs          Objects
    26  	Interfaces      map[string]*Interface
    27  	ReferencedTypes map[string]*config.TypeReference
    28  	ComplexityRoots map[string]*Object
    29  
    30  	QueryRoot        *Object
    31  	MutationRoot     *Object
    32  	SubscriptionRoot *Object
    33  }
    34  
    35  type builder struct {
    36  	Config     *config.Config
    37  	Schema     *ast.Schema
    38  	Binder     *config.Binder
    39  	Directives map[string]*Directive
    40  }
    41  
    42  // Get only the directives which are defined in the config's sources.
    43  func (d *Data) Directives() DirectiveList {
    44  	res := DirectiveList{}
    45  	for k, directive := range d.AllDirectives {
    46  		for _, s := range d.Config.Sources {
    47  			if directive.Position.Src.Name == s.Name {
    48  				res[k] = directive
    49  				break
    50  			}
    51  		}
    52  	}
    53  	return res
    54  }
    55  
    56  func BuildData(cfg *config.Config) (*Data, error) {
    57  	// We reload all packages to allow packages to be compared correctly.
    58  	cfg.ReloadAllPackages()
    59  
    60  	b := builder{
    61  		Config: cfg,
    62  		Schema: cfg.Schema,
    63  	}
    64  
    65  	b.Binder = b.Config.NewBinder()
    66  
    67  	var err error
    68  	b.Directives, err = b.buildDirectives()
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  
    73  	dataDirectives := make(map[string]*Directive)
    74  	for name, d := range b.Directives {
    75  		if !d.Builtin {
    76  			dataDirectives[name] = d
    77  		}
    78  	}
    79  
    80  	s := Data{
    81  		Config:        cfg,
    82  		AllDirectives: dataDirectives,
    83  		Schema:        b.Schema,
    84  		Interfaces:    map[string]*Interface{},
    85  	}
    86  
    87  	for _, schemaType := range b.Schema.Types {
    88  		switch schemaType.Kind {
    89  		case ast.Object:
    90  			obj, err := b.buildObject(schemaType)
    91  			if err != nil {
    92  				return nil, fmt.Errorf("unable to build object definition: %w", err)
    93  			}
    94  
    95  			s.Objects = append(s.Objects, obj)
    96  		case ast.InputObject:
    97  			input, err := b.buildObject(schemaType)
    98  			if err != nil {
    99  				return nil, fmt.Errorf("unable to build input definition: %w", err)
   100  			}
   101  
   102  			s.Inputs = append(s.Inputs, input)
   103  
   104  		case ast.Union, ast.Interface:
   105  			s.Interfaces[schemaType.Name], err = b.buildInterface(schemaType)
   106  			if err != nil {
   107  				return nil, fmt.Errorf("unable to bind to interface: %w", err)
   108  			}
   109  		}
   110  	}
   111  
   112  	if s.Schema.Query != nil {
   113  		s.QueryRoot = s.Objects.ByName(s.Schema.Query.Name)
   114  	} else {
   115  		return nil, fmt.Errorf("query entry point missing")
   116  	}
   117  
   118  	if s.Schema.Mutation != nil {
   119  		s.MutationRoot = s.Objects.ByName(s.Schema.Mutation.Name)
   120  	}
   121  
   122  	if s.Schema.Subscription != nil {
   123  		s.SubscriptionRoot = s.Objects.ByName(s.Schema.Subscription.Name)
   124  	}
   125  
   126  	if err := b.injectIntrospectionRoots(&s); err != nil {
   127  		return nil, err
   128  	}
   129  
   130  	s.ReferencedTypes = b.buildTypes()
   131  
   132  	sort.Slice(s.Objects, func(i, j int) bool {
   133  		return s.Objects[i].Definition.Name < s.Objects[j].Definition.Name
   134  	})
   135  
   136  	sort.Slice(s.Inputs, func(i, j int) bool {
   137  		return s.Inputs[i].Definition.Name < s.Inputs[j].Definition.Name
   138  	})
   139  
   140  	if b.Binder.SawInvalid {
   141  		// if we have a syntax error, show it
   142  		err := cfg.Packages.Errors()
   143  		if len(err) > 0 {
   144  			return nil, err
   145  		}
   146  
   147  		// otherwise show a generic error message
   148  		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")
   149  	}
   150  
   151  	return &s, nil
   152  }
   153  
   154  func (b *builder) injectIntrospectionRoots(s *Data) error {
   155  	obj := s.Objects.ByName(b.Schema.Query.Name)
   156  	if obj == nil {
   157  		return fmt.Errorf("root query type must be defined")
   158  	}
   159  
   160  	__type, err := b.buildField(obj, &ast.FieldDefinition{
   161  		Name: "__type",
   162  		Type: ast.NamedType("__Type", nil),
   163  		Arguments: []*ast.ArgumentDefinition{
   164  			{
   165  				Name: "name",
   166  				Type: ast.NonNullNamedType("String", nil),
   167  			},
   168  		},
   169  	})
   170  	if err != nil {
   171  		return err
   172  	}
   173  
   174  	__schema, err := b.buildField(obj, &ast.FieldDefinition{
   175  		Name: "__schema",
   176  		Type: ast.NamedType("__Schema", nil),
   177  	})
   178  	if err != nil {
   179  		return err
   180  	}
   181  
   182  	obj.Fields = append(obj.Fields, __type, __schema)
   183  
   184  	return nil
   185  }