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 = b.buildTypes()
127
128 sort.Slice(s.Objects, func(i, j int) bool {
129 return s.Objects[i].Definition.Name < s.Objects[j].Definition.Name
130 })
131
132 sort.Slice(s.Inputs, func(i, j int) bool {
133 return s.Inputs[i].Definition.Name < s.Inputs[j].Definition.Name
134 })
135
136 return &s, nil
137}
138
139func (b *builder) injectIntrospectionRoots(s *Data) error {
140 obj := s.Objects.ByName(b.Schema.Query.Name)
141 if obj == nil {
142 return fmt.Errorf("root query type must be defined")
143 }
144
145 __type, err := b.buildField(obj, &ast.FieldDefinition{
146 Name: "__type",
147 Type: ast.NamedType("__Type", nil),
148 Arguments: []*ast.ArgumentDefinition{
149 {
150 Name: "name",
151 Type: ast.NonNullNamedType("String", nil),
152 },
153 },
154 })
155 if err != nil {
156 return err
157 }
158
159 __schema, err := b.buildField(obj, &ast.FieldDefinition{
160 Name: "__schema",
161 Type: ast.NamedType("__Schema", nil),
162 })
163 if err != nil {
164 return err
165 }
166
167 obj.Fields = append(obj.Fields, __type, __schema)
168
169 return nil
170}