github.com/niko0xdev/gqlgen@v0.17.55-0.20240120102243-2ecff98c3e37/codegen/config/config.go (about) 1 package config 2 3 import ( 4 "bytes" 5 "fmt" 6 "go/types" 7 "io" 8 "os" 9 "path/filepath" 10 "regexp" 11 "sort" 12 "strings" 13 14 "github.com/vektah/gqlparser/v2" 15 "github.com/vektah/gqlparser/v2/ast" 16 "golang.org/x/tools/go/packages" 17 "gopkg.in/yaml.v3" 18 19 "github.com/niko0xdev/gqlgen/codegen/templates" 20 "github.com/niko0xdev/gqlgen/internal/code" 21 ) 22 23 type Config struct { 24 SchemaFilename StringList `yaml:"schema,omitempty"` 25 Exec ExecConfig `yaml:"exec"` 26 Model PackageConfig `yaml:"model,omitempty"` 27 Federation PackageConfig `yaml:"federation,omitempty"` 28 Resolver ResolverConfig `yaml:"resolver,omitempty"` 29 AutoBind []string `yaml:"autobind"` 30 Models TypeMap `yaml:"models,omitempty"` 31 StructTag string `yaml:"struct_tag,omitempty"` 32 Directives map[string]DirectiveConfig `yaml:"directives,omitempty"` 33 GoBuildTags StringList `yaml:"go_build_tags,omitempty"` 34 GoInitialisms GoInitialismsConfig `yaml:"go_initialisms,omitempty"` 35 OmitSliceElementPointers bool `yaml:"omit_slice_element_pointers,omitempty"` 36 OmitGetters bool `yaml:"omit_getters,omitempty"` 37 OmitInterfaceChecks bool `yaml:"omit_interface_checks,omitempty"` 38 OmitComplexity bool `yaml:"omit_complexity,omitempty"` 39 OmitGQLGenFileNotice bool `yaml:"omit_gqlgen_file_notice,omitempty"` 40 OmitGQLGenVersionInFileNotice bool `yaml:"omit_gqlgen_version_in_file_notice,omitempty"` 41 OmitRootModels bool `yaml:"omit_root_models,omitempty"` 42 StructFieldsAlwaysPointers bool `yaml:"struct_fields_always_pointers,omitempty"` 43 ReturnPointersInUmarshalInput bool `yaml:"return_pointers_in_unmarshalinput,omitempty"` 44 ResolversAlwaysReturnPointers bool `yaml:"resolvers_always_return_pointers,omitempty"` 45 NullableInputOmittable bool `yaml:"nullable_input_omittable,omitempty"` 46 EnableModelJsonOmitemptyTag *bool `yaml:"enable_model_json_omitempty_tag,omitempty"` 47 SkipValidation bool `yaml:"skip_validation,omitempty"` 48 SkipModTidy bool `yaml:"skip_mod_tidy,omitempty"` 49 Sources []*ast.Source `yaml:"-"` 50 Packages *code.Packages `yaml:"-"` 51 Schema *ast.Schema `yaml:"-"` 52 53 // Deprecated: use Federation instead. Will be removed next release 54 Federated bool `yaml:"federated,omitempty"` 55 } 56 57 var cfgFilenames = []string{".gqlgen.yml", "gqlgen.yml", "gqlgen.yaml"} 58 59 // DefaultConfig creates a copy of the default config 60 func DefaultConfig() *Config { 61 return &Config{ 62 SchemaFilename: StringList{"schema.graphql"}, 63 Model: PackageConfig{Filename: "models_gen.go"}, 64 Exec: ExecConfig{Filename: "generated.go"}, 65 Directives: map[string]DirectiveConfig{}, 66 Models: TypeMap{}, 67 StructFieldsAlwaysPointers: true, 68 ReturnPointersInUmarshalInput: false, 69 ResolversAlwaysReturnPointers: true, 70 NullableInputOmittable: false, 71 } 72 } 73 74 // LoadDefaultConfig loads the default config so that it is ready to be used 75 func LoadDefaultConfig() (*Config, error) { 76 config := DefaultConfig() 77 78 for _, filename := range config.SchemaFilename { 79 filename = filepath.ToSlash(filename) 80 var err error 81 var schemaRaw []byte 82 schemaRaw, err = os.ReadFile(filename) 83 if err != nil { 84 return nil, fmt.Errorf("unable to open schema: %w", err) 85 } 86 87 config.Sources = append(config.Sources, &ast.Source{Name: filename, Input: string(schemaRaw)}) 88 } 89 90 return config, nil 91 } 92 93 // LoadConfigFromDefaultLocations looks for a config file in the current directory, and all parent directories 94 // walking up the tree. The closest config file will be returned. 95 func LoadConfigFromDefaultLocations() (*Config, error) { 96 cfgFile, err := findCfg() 97 if err != nil { 98 return nil, err 99 } 100 101 err = os.Chdir(filepath.Dir(cfgFile)) 102 if err != nil { 103 return nil, fmt.Errorf("unable to enter config dir: %w", err) 104 } 105 return LoadConfig(cfgFile) 106 } 107 108 var path2regex = strings.NewReplacer( 109 `.`, `\.`, 110 `*`, `.+`, 111 `\`, `[\\/]`, 112 `/`, `[\\/]`, 113 ) 114 115 // LoadConfig reads the gqlgen.yml config file 116 func LoadConfig(filename string) (*Config, error) { 117 b, err := os.ReadFile(filename) 118 if err != nil { 119 return nil, fmt.Errorf("unable to read config: %w", err) 120 } 121 122 return ReadConfig(bytes.NewReader(b)) 123 } 124 125 func ReadConfig(cfgFile io.Reader) (*Config, error) { 126 config := DefaultConfig() 127 128 dec := yaml.NewDecoder(cfgFile) 129 dec.KnownFields(true) 130 131 if err := dec.Decode(config); err != nil { 132 return nil, fmt.Errorf("unable to parse config: %w", err) 133 } 134 135 if err := CompleteConfig(config); err != nil { 136 return nil, err 137 } 138 139 return config, nil 140 } 141 142 // CompleteConfig fills in the schema and other values to a config loaded from 143 // YAML. 144 func CompleteConfig(config *Config) error { 145 defaultDirectives := map[string]DirectiveConfig{ 146 "skip": {SkipRuntime: true}, 147 "include": {SkipRuntime: true}, 148 "deprecated": {SkipRuntime: true}, 149 "specifiedBy": {SkipRuntime: true}, 150 } 151 152 for key, value := range defaultDirectives { 153 if _, defined := config.Directives[key]; !defined { 154 config.Directives[key] = value 155 } 156 } 157 158 preGlobbing := config.SchemaFilename 159 config.SchemaFilename = StringList{} 160 for _, f := range preGlobbing { 161 var matches []string 162 163 // for ** we want to override default globbing patterns and walk all 164 // subdirectories to match schema files. 165 if strings.Contains(f, "**") { 166 pathParts := strings.SplitN(f, "**", 2) 167 rest := strings.TrimPrefix(strings.TrimPrefix(pathParts[1], `\`), `/`) 168 // turn the rest of the glob into a regex, anchored only at the end because ** allows 169 // for any number of dirs in between and walk will let us match against the full path name 170 globRe := regexp.MustCompile(path2regex.Replace(rest) + `$`) 171 172 if err := filepath.Walk(pathParts[0], func(path string, info os.FileInfo, err error) error { 173 if err != nil { 174 return err 175 } 176 177 if globRe.MatchString(strings.TrimPrefix(path, pathParts[0])) { 178 matches = append(matches, path) 179 } 180 181 return nil 182 }); err != nil { 183 return fmt.Errorf("failed to walk schema at root %s: %w", pathParts[0], err) 184 } 185 } else { 186 var err error 187 matches, err = filepath.Glob(f) 188 if err != nil { 189 return fmt.Errorf("failed to glob schema filename %s: %w", f, err) 190 } 191 } 192 193 for _, m := range matches { 194 if config.SchemaFilename.Has(m) { 195 continue 196 } 197 config.SchemaFilename = append(config.SchemaFilename, m) 198 } 199 } 200 201 for _, filename := range config.SchemaFilename { 202 filename = filepath.ToSlash(filename) 203 var err error 204 var schemaRaw []byte 205 schemaRaw, err = os.ReadFile(filename) 206 if err != nil { 207 return fmt.Errorf("unable to open schema: %w", err) 208 } 209 210 config.Sources = append(config.Sources, &ast.Source{Name: filename, Input: string(schemaRaw)}) 211 } 212 213 config.GoInitialisms.setInitialisms() 214 215 return nil 216 } 217 218 func (c *Config) Init() error { 219 if c.Packages == nil { 220 c.Packages = code.NewPackages( 221 code.WithBuildTags(c.GoBuildTags...), 222 ) 223 } 224 225 if c.Schema == nil { 226 if err := c.LoadSchema(); err != nil { 227 return err 228 } 229 } 230 231 err := c.injectTypesFromSchema() 232 if err != nil { 233 return err 234 } 235 236 err = c.autobind() 237 if err != nil { 238 return err 239 } 240 241 c.injectBuiltins() 242 // prefetch all packages in one big packages.Load call 243 c.Packages.LoadAll(c.packageList()...) 244 245 // check everything is valid on the way out 246 err = c.check() 247 if err != nil { 248 return err 249 } 250 251 return nil 252 } 253 254 func (c *Config) packageList() []string { 255 pkgs := []string{ 256 "github.com/niko0xdev/gqlgen/graphql", 257 "github.com/niko0xdev/gqlgen/graphql/introspection", 258 } 259 pkgs = append(pkgs, c.Models.ReferencedPackages()...) 260 pkgs = append(pkgs, c.AutoBind...) 261 return pkgs 262 } 263 264 func (c *Config) ReloadAllPackages() { 265 c.Packages.ReloadAll(c.packageList()...) 266 } 267 268 func (c *Config) IsRoot(def *ast.Definition) bool { 269 return def == c.Schema.Query || def == c.Schema.Mutation || def == c.Schema.Subscription 270 } 271 272 func (c *Config) injectTypesFromSchema() error { 273 c.Directives["goModel"] = DirectiveConfig{ 274 SkipRuntime: true, 275 } 276 277 c.Directives["goField"] = DirectiveConfig{ 278 SkipRuntime: true, 279 } 280 281 c.Directives["goTag"] = DirectiveConfig{ 282 SkipRuntime: true, 283 } 284 285 for _, schemaType := range c.Schema.Types { 286 if c.IsRoot(schemaType) { 287 continue 288 } 289 290 if bd := schemaType.Directives.ForName("goModel"); bd != nil { 291 if ma := bd.Arguments.ForName("model"); ma != nil { 292 if mv, err := ma.Value.Value(nil); err == nil { 293 c.Models.Add(schemaType.Name, mv.(string)) 294 } 295 } 296 297 if ma := bd.Arguments.ForName("models"); ma != nil { 298 if mvs, err := ma.Value.Value(nil); err == nil { 299 for _, mv := range mvs.([]interface{}) { 300 c.Models.Add(schemaType.Name, mv.(string)) 301 } 302 } 303 } 304 305 if fg := bd.Arguments.ForName("forceGenerate"); fg != nil { 306 if mv, err := fg.Value.Value(nil); err == nil { 307 c.Models.ForceGenerate(schemaType.Name, mv.(bool)) 308 } 309 } 310 } 311 312 if schemaType.Kind == ast.Object || schemaType.Kind == ast.InputObject { 313 for _, field := range schemaType.Fields { 314 if fd := field.Directives.ForName("goField"); fd != nil { 315 forceResolver := c.Models[schemaType.Name].Fields[field.Name].Resolver 316 fieldName := c.Models[schemaType.Name].Fields[field.Name].FieldName 317 318 if ra := fd.Arguments.ForName("forceResolver"); ra != nil { 319 if fr, err := ra.Value.Value(nil); err == nil { 320 forceResolver = fr.(bool) 321 } 322 } 323 324 if na := fd.Arguments.ForName("name"); na != nil { 325 if fr, err := na.Value.Value(nil); err == nil { 326 fieldName = fr.(string) 327 } 328 } 329 330 if c.Models[schemaType.Name].Fields == nil { 331 c.Models[schemaType.Name] = TypeMapEntry{ 332 Model: c.Models[schemaType.Name].Model, 333 ExtraFields: c.Models[schemaType.Name].ExtraFields, 334 Fields: map[string]TypeMapField{}, 335 } 336 } 337 338 c.Models[schemaType.Name].Fields[field.Name] = TypeMapField{ 339 FieldName: fieldName, 340 Resolver: forceResolver, 341 } 342 } 343 } 344 } 345 } 346 347 return nil 348 } 349 350 type TypeMapEntry struct { 351 Model StringList `yaml:"model,omitempty"` 352 ForceGenerate bool `yaml:"forceGenerate,omitempty"` 353 Fields map[string]TypeMapField `yaml:"fields,omitempty"` 354 355 // Key is the Go name of the field. 356 ExtraFields map[string]ModelExtraField `yaml:"extraFields,omitempty"` 357 } 358 359 type TypeMapField struct { 360 Resolver bool `yaml:"resolver"` 361 FieldName string `yaml:"fieldName"` 362 GeneratedMethod string `yaml:"-"` 363 } 364 365 type ModelExtraField struct { 366 // Type is the Go type of the field. 367 // 368 // It supports the builtin basic types (like string or int64), named types 369 // (qualified by the full package path), pointers to those types (prefixed 370 // with `*`), and slices of those types (prefixed with `[]`). 371 // 372 // For example, the following are valid types: 373 // string 374 // *github.com/author/package.Type 375 // []string 376 // []*github.com/author/package.Type 377 // 378 // Note that the type will be referenced from the generated/graphql, which 379 // means the package it lives in must not reference the generated/graphql 380 // package to avoid circular imports. 381 // restrictions. 382 Type string `yaml:"type"` 383 384 // OverrideTags is an optional override of the Go field tag. 385 OverrideTags string `yaml:"overrideTags"` 386 387 // Description is an optional the Go field doc-comment. 388 Description string `yaml:"description"` 389 } 390 391 type StringList []string 392 393 func (a *StringList) UnmarshalYAML(unmarshal func(interface{}) error) error { 394 var single string 395 err := unmarshal(&single) 396 if err == nil { 397 *a = []string{single} 398 return nil 399 } 400 401 var multi []string 402 err = unmarshal(&multi) 403 if err != nil { 404 return err 405 } 406 407 *a = multi 408 return nil 409 } 410 411 func (a StringList) Has(file string) bool { 412 for _, existing := range a { 413 if existing == file { 414 return true 415 } 416 } 417 return false 418 } 419 420 func (c *Config) check() error { 421 if c.Models == nil { 422 c.Models = TypeMap{} 423 } 424 425 type FilenamePackage struct { 426 Filename string 427 Package string 428 Declaree string 429 } 430 431 fileList := map[string][]FilenamePackage{} 432 433 if err := c.Models.Check(); err != nil { 434 return fmt.Errorf("config.models: %w", err) 435 } 436 if err := c.Exec.Check(); err != nil { 437 return fmt.Errorf("config.exec: %w", err) 438 } 439 fileList[c.Exec.ImportPath()] = append(fileList[c.Exec.ImportPath()], FilenamePackage{ 440 Filename: c.Exec.Filename, 441 Package: c.Exec.Package, 442 Declaree: "exec", 443 }) 444 445 if c.Model.IsDefined() { 446 if err := c.Model.Check(); err != nil { 447 return fmt.Errorf("config.model: %w", err) 448 } 449 fileList[c.Model.ImportPath()] = append(fileList[c.Model.ImportPath()], FilenamePackage{ 450 Filename: c.Model.Filename, 451 Package: c.Model.Package, 452 Declaree: "model", 453 }) 454 } 455 if c.Resolver.IsDefined() { 456 if err := c.Resolver.Check(); err != nil { 457 return fmt.Errorf("config.resolver: %w", err) 458 } 459 fileList[c.Resolver.ImportPath()] = append(fileList[c.Resolver.ImportPath()], FilenamePackage{ 460 Filename: c.Resolver.Filename, 461 Package: c.Resolver.Package, 462 Declaree: "resolver", 463 }) 464 } 465 if c.Federation.IsDefined() { 466 if err := c.Federation.Check(); err != nil { 467 return fmt.Errorf("config.federation: %w", err) 468 } 469 fileList[c.Federation.ImportPath()] = append(fileList[c.Federation.ImportPath()], FilenamePackage{ 470 Filename: c.Federation.Filename, 471 Package: c.Federation.Package, 472 Declaree: "federation", 473 }) 474 if c.Federation.ImportPath() != c.Exec.ImportPath() { 475 return fmt.Errorf("federation and exec must be in the same package") 476 } 477 } 478 if c.Federated { 479 return fmt.Errorf("federated has been removed, instead use\nfederation:\n filename: path/to/federated.go") 480 } 481 482 for importPath, pkg := range fileList { 483 for _, file1 := range pkg { 484 for _, file2 := range pkg { 485 if file1.Package != file2.Package { 486 return fmt.Errorf("%s and %s define the same import path (%s) with different package names (%s vs %s)", 487 file1.Declaree, 488 file2.Declaree, 489 importPath, 490 file1.Package, 491 file2.Package, 492 ) 493 } 494 } 495 } 496 } 497 498 return nil 499 } 500 501 type TypeMap map[string]TypeMapEntry 502 503 func (tm TypeMap) Exists(typeName string) bool { 504 _, ok := tm[typeName] 505 return ok 506 } 507 508 func (tm TypeMap) UserDefined(typeName string) bool { 509 m, ok := tm[typeName] 510 return ok && len(m.Model) > 0 511 } 512 513 func (tm TypeMap) Check() error { 514 for typeName, entry := range tm { 515 for _, model := range entry.Model { 516 if strings.LastIndex(model, ".") < strings.LastIndex(model, "/") { 517 return fmt.Errorf("model %s: invalid type specifier \"%s\" - you need to specify a struct to map to", typeName, entry.Model) 518 } 519 } 520 } 521 return nil 522 } 523 524 func (tm TypeMap) ReferencedPackages() []string { 525 var pkgs []string 526 527 for _, typ := range tm { 528 for _, model := range typ.Model { 529 if model == "map[string]interface{}" || model == "interface{}" { 530 continue 531 } 532 pkg, _ := code.PkgAndType(model) 533 if pkg == "" || inStrSlice(pkgs, pkg) { 534 continue 535 } 536 pkgs = append(pkgs, code.QualifyPackagePath(pkg)) 537 } 538 } 539 540 sort.Slice(pkgs, func(i, j int) bool { 541 return pkgs[i] > pkgs[j] 542 }) 543 return pkgs 544 } 545 546 func (tm TypeMap) Add(name string, goType string) { 547 modelCfg := tm[name] 548 modelCfg.Model = append(modelCfg.Model, goType) 549 tm[name] = modelCfg 550 } 551 552 func (tm TypeMap) ForceGenerate(name string, forceGenerate bool) { 553 modelCfg := tm[name] 554 modelCfg.ForceGenerate = forceGenerate 555 tm[name] = modelCfg 556 } 557 558 type DirectiveConfig struct { 559 SkipRuntime bool `yaml:"skip_runtime"` 560 } 561 562 func inStrSlice(haystack []string, needle string) bool { 563 for _, v := range haystack { 564 if needle == v { 565 return true 566 } 567 } 568 569 return false 570 } 571 572 // findCfg searches for the config file in this directory and all parents up the tree 573 // looking for the closest match 574 func findCfg() (string, error) { 575 dir, err := os.Getwd() 576 if err != nil { 577 return "", fmt.Errorf("unable to get working dir to findCfg: %w", err) 578 } 579 580 cfg := findCfgInDir(dir) 581 582 for cfg == "" && dir != filepath.Dir(dir) { 583 dir = filepath.Dir(dir) 584 cfg = findCfgInDir(dir) 585 } 586 587 if cfg == "" { 588 return "", os.ErrNotExist 589 } 590 591 return cfg, nil 592 } 593 594 func findCfgInDir(dir string) string { 595 for _, cfgName := range cfgFilenames { 596 path := filepath.Join(dir, cfgName) 597 if _, err := os.Stat(path); err == nil { 598 return path 599 } 600 } 601 return "" 602 } 603 604 func (c *Config) autobind() error { 605 if len(c.AutoBind) == 0 { 606 return nil 607 } 608 609 ps := c.Packages.LoadAll(c.AutoBind...) 610 611 for _, t := range c.Schema.Types { 612 if c.Models.UserDefined(t.Name) || c.Models[t.Name].ForceGenerate { 613 continue 614 } 615 616 for i, p := range ps { 617 if p == nil || p.Module == nil { 618 return fmt.Errorf("unable to load %s - make sure you're using an import path to a package that exists", c.AutoBind[i]) 619 } 620 621 autobindType := c.lookupAutobindType(p, t) 622 if autobindType != nil { 623 c.Models.Add(t.Name, autobindType.Pkg().Path()+"."+autobindType.Name()) 624 break 625 } 626 } 627 } 628 629 for i, t := range c.Models { 630 if t.ForceGenerate { 631 continue 632 } 633 634 for j, m := range t.Model { 635 pkg, typename := code.PkgAndType(m) 636 637 // skip anything that looks like an import path 638 if strings.Contains(pkg, "/") { 639 continue 640 } 641 642 for _, p := range ps { 643 if p.Name != pkg { 644 continue 645 } 646 if t := p.Types.Scope().Lookup(typename); t != nil { 647 c.Models[i].Model[j] = t.Pkg().Path() + "." + t.Name() 648 break 649 } 650 } 651 } 652 } 653 654 return nil 655 } 656 657 func (c *Config) lookupAutobindType(p *packages.Package, schemaType *ast.Definition) types.Object { 658 // Try binding to either the original schema type name, or the normalized go type name 659 for _, lookupName := range []string{schemaType.Name, templates.ToGo(schemaType.Name)} { 660 if t := p.Types.Scope().Lookup(lookupName); t != nil { 661 return t 662 } 663 } 664 665 return nil 666 } 667 668 func (c *Config) injectBuiltins() { 669 builtins := TypeMap{ 670 "__Directive": {Model: StringList{"github.com/niko0xdev/gqlgen/graphql/introspection.Directive"}}, 671 "__DirectiveLocation": {Model: StringList{"github.com/niko0xdev/gqlgen/graphql.String"}}, 672 "__Type": {Model: StringList{"github.com/niko0xdev/gqlgen/graphql/introspection.Type"}}, 673 "__TypeKind": {Model: StringList{"github.com/niko0xdev/gqlgen/graphql.String"}}, 674 "__Field": {Model: StringList{"github.com/niko0xdev/gqlgen/graphql/introspection.Field"}}, 675 "__EnumValue": {Model: StringList{"github.com/niko0xdev/gqlgen/graphql/introspection.EnumValue"}}, 676 "__InputValue": {Model: StringList{"github.com/niko0xdev/gqlgen/graphql/introspection.InputValue"}}, 677 "__Schema": {Model: StringList{"github.com/niko0xdev/gqlgen/graphql/introspection.Schema"}}, 678 "Float": {Model: StringList{"github.com/niko0xdev/gqlgen/graphql.FloatContext"}}, 679 "String": {Model: StringList{"github.com/niko0xdev/gqlgen/graphql.String"}}, 680 "Boolean": {Model: StringList{"github.com/niko0xdev/gqlgen/graphql.Boolean"}}, 681 "Int": {Model: StringList{ 682 "github.com/niko0xdev/gqlgen/graphql.Int", 683 "github.com/niko0xdev/gqlgen/graphql.Int32", 684 "github.com/niko0xdev/gqlgen/graphql.Int64", 685 }}, 686 "ID": { 687 Model: StringList{ 688 "github.com/niko0xdev/gqlgen/graphql.ID", 689 "github.com/niko0xdev/gqlgen/graphql.IntID", 690 }, 691 }, 692 } 693 694 for typeName, entry := range builtins { 695 if !c.Models.Exists(typeName) { 696 c.Models[typeName] = entry 697 } 698 } 699 700 // These are additional types that are injected if defined in the schema as scalars. 701 extraBuiltins := TypeMap{ 702 "Time": {Model: StringList{"github.com/niko0xdev/gqlgen/graphql.Time"}}, 703 "Map": {Model: StringList{"github.com/niko0xdev/gqlgen/graphql.Map"}}, 704 "Upload": {Model: StringList{"github.com/niko0xdev/gqlgen/graphql.Upload"}}, 705 "Any": {Model: StringList{"github.com/niko0xdev/gqlgen/graphql.Any"}}, 706 } 707 708 for typeName, entry := range extraBuiltins { 709 if t, ok := c.Schema.Types[typeName]; !c.Models.Exists(typeName) && ok && t.Kind == ast.Scalar { 710 c.Models[typeName] = entry 711 } 712 } 713 } 714 715 func (c *Config) LoadSchema() error { 716 if c.Packages != nil { 717 c.Packages = code.NewPackages( 718 code.WithBuildTags(c.GoBuildTags...), 719 ) 720 } 721 722 if err := c.check(); err != nil { 723 return err 724 } 725 726 schema, err := gqlparser.LoadSchema(c.Sources...) 727 if err != nil { 728 return err 729 } 730 731 if schema.Query == nil { 732 schema.Query = &ast.Definition{ 733 Kind: ast.Object, 734 Name: "Query", 735 } 736 schema.Types["Query"] = schema.Query 737 } 738 739 c.Schema = schema 740 return nil 741 } 742 743 func abs(path string) string { 744 absPath, err := filepath.Abs(path) 745 if err != nil { 746 panic(err) 747 } 748 return filepath.ToSlash(absPath) 749 }