git.sr.ht/~sircmpwn/gqlgen@v0.0.0-20200522192042-c84d29a1c940/codegen/data.go (about)

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