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 }