1package codegen
2
3import (
4 "fmt"
5 "sort"
6
7 "github.com/99designs/gqlgen/codegen/config"
8 "github.com/pkg/errors"
9 "github.com/vektah/gqlparser/ast"
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)
14type Data struct {
15 Config *config.Config
16 Schema *ast.Schema
17 SchemaStr map[string]string
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
30type builder struct {
31 Config *config.Config
32 Schema *ast.Schema
33 SchemaStr map[string]string
34 Binder *config.Binder
35 Directives map[string]*Directive
36}
37
38func BuildData(cfg *config.Config) (*Data, error) {
39 b := builder{
40 Config: cfg,
41 }
42
43 var err error
44 b.Schema, b.SchemaStr, err = cfg.LoadSchema()
45 if err != nil {
46 return nil, err
47 }
48
49 err = cfg.Check()
50 if err != nil {
51 return nil, err
52 }
53
54 err = cfg.Autobind(b.Schema)
55 if err != nil {
56 return nil, err
57 }
58
59 cfg.InjectBuiltins(b.Schema)
60
61 b.Binder, err = b.Config.NewBinder(b.Schema)
62 if err != nil {
63 return nil, err
64 }
65
66 b.Directives, err = b.buildDirectives()
67 if err != nil {
68 return nil, err
69 }
70
71 dataDirectives := make(map[string]*Directive)
72 for name, d := range b.Directives {
73 if !d.Builtin {
74 dataDirectives[name] = d
75 }
76 }
77
78 s := Data{
79 Config: cfg,
80 Directives: dataDirectives,
81 Schema: b.Schema,
82 SchemaStr: b.SchemaStr,
83 Interfaces: map[string]*Interface{},
84 }
85
86 for _, schemaType := range b.Schema.Types {
87 switch schemaType.Kind {
88 case ast.Object:
89 obj, err := b.buildObject(schemaType)
90 if err != nil {
91 return nil, errors.Wrap(err, "unable to build object definition")
92 }
93
94 s.Objects = append(s.Objects, obj)
95 case ast.InputObject:
96 input, err := b.buildObject(schemaType)
97 if err != nil {
98 return nil, errors.Wrap(err, "unable to build input definition")
99 }
100
101 s.Inputs = append(s.Inputs, input)
102
103 case ast.Union, ast.Interface:
104 s.Interfaces[schemaType.Name] = b.buildInterface(schemaType)
105 }
106 }
107
108 if s.Schema.Query != nil {
109 s.QueryRoot = s.Objects.ByName(s.Schema.Query.Name)
110 } else {
111 return nil, fmt.Errorf("query entry point missing")
112 }
113
114 if s.Schema.Mutation != nil {
115 s.MutationRoot = s.Objects.ByName(s.Schema.Mutation.Name)
116 }
117
118 if s.Schema.Subscription != nil {
119 s.SubscriptionRoot = s.Objects.ByName(s.Schema.Subscription.Name)
120 }
121
122 if err := b.injectIntrospectionRoots(&s); err != nil {
123 return nil, err
124 }
125
126 s.ReferencedTypes, err = b.buildTypes()
127 if err != nil {
128 return nil, err
129 }
130
131 sort.Slice(s.Objects, func(i, j int) bool {
132 return s.Objects[i].Definition.Name < s.Objects[j].Definition.Name
133 })
134
135 sort.Slice(s.Inputs, func(i, j int) bool {
136 return s.Inputs[i].Definition.Name < s.Inputs[j].Definition.Name
137 })
138
139 return &s, nil
140}
141
142func (b *builder) injectIntrospectionRoots(s *Data) error {
143 obj := s.Objects.ByName(b.Schema.Query.Name)
144 if obj == nil {
145 return fmt.Errorf("root query type must be defined")
146 }
147
148 __type, err := b.buildField(obj, &ast.FieldDefinition{
149 Name: "__type",
150 Type: ast.NamedType("__Type", nil),
151 Arguments: []*ast.ArgumentDefinition{
152 {
153 Name: "name",
154 Type: ast.NonNullNamedType("String", nil),
155 },
156 },
157 })
158 if err != nil {
159 return err
160 }
161
162 __schema, err := b.buildField(obj, &ast.FieldDefinition{
163 Name: "__schema",
164 Type: ast.NamedType("__Schema", nil),
165 })
166 if err != nil {
167 return err
168 }
169
170 obj.Fields = append(obj.Fields, __type, __schema)
171
172 return nil
173}