Stash box client interface (#751)

* Add gql client generation files
* Update dependencies
* Add stash-box client generation to the makefile
* Move scraped scene object matchers to models
* Add stash-box to scrape with dropdown
* Add scrape scene from fingerprint in UI
This commit is contained in:
WithoutPants
2020-09-17 19:57:18 +10:00
committed by GitHub
parent b0b5621337
commit 7a45943e8e
324 changed files with 34978 additions and 17323 deletions

View File

@@ -8,37 +8,24 @@ import (
"github.com/99designs/gqlgen/codegen/templates"
"github.com/99designs/gqlgen/internal/code"
"github.com/pkg/errors"
"github.com/vektah/gqlparser/ast"
"golang.org/x/tools/go/packages"
"github.com/vektah/gqlparser/v2/ast"
)
// Binder connects graphql types to golang types using static analysis
type Binder struct {
pkgs []*packages.Package
pkgs *code.Packages
schema *ast.Schema
cfg *Config
References []*TypeReference
SawInvalid bool
}
func (c *Config) NewBinder(s *ast.Schema) (*Binder, error) {
pkgs, err := packages.Load(&packages.Config{Mode: packages.LoadTypes | packages.LoadSyntax}, c.Models.ReferencedPackages()...)
if err != nil {
return nil, err
}
for _, p := range pkgs {
for _, e := range p.Errors {
if e.Kind == packages.ListError {
return nil, p.Errors[0]
}
}
}
func (c *Config) NewBinder() *Binder {
return &Binder{
pkgs: pkgs,
schema: s,
pkgs: c.Packages,
schema: c.Schema,
cfg: c,
}, nil
}
}
func (b *Binder) TypePosition(typ types.Type) token.Position {
@@ -58,11 +45,26 @@ func (b *Binder) ObjectPosition(typ types.Object) token.Position {
Filename: "unknown",
}
}
pkg := b.getPkg(typ.Pkg().Path())
pkg := b.pkgs.Load(typ.Pkg().Path())
return pkg.Fset.Position(typ.Pos())
}
func (b *Binder) FindTypeFromName(name string) (types.Type, error) {
pkgName, typeName := code.PkgAndType(name)
return b.FindType(pkgName, typeName)
}
func (b *Binder) FindType(pkgName string, typeName string) (types.Type, error) {
if pkgName == "" {
if typeName == "map[string]interface{}" {
return MapType, nil
}
if typeName == "interface{}" {
return InterfaceType, nil
}
}
obj, err := b.FindObject(pkgName, typeName)
if err != nil {
return nil, err
@@ -74,15 +76,6 @@ func (b *Binder) FindType(pkgName string, typeName string) (types.Type, error) {
return obj.Type(), nil
}
func (b *Binder) getPkg(find string) *packages.Package {
for _, p := range b.pkgs {
if code.NormalizeVendor(find) == code.NormalizeVendor(p.PkgPath) {
return p
}
}
return nil
}
var MapType = types.NewMap(types.Typ[types.String], types.NewInterfaceType(nil, nil).Complete())
var InterfaceType = types.NewInterfaceType(nil, nil)
@@ -122,7 +115,7 @@ func (b *Binder) FindObject(pkgName string, typeName string) (types.Object, erro
fullName = pkgName + "." + typeName
}
pkg := b.getPkg(pkgName)
pkg := b.pkgs.LoadWithTypes(pkgName)
if pkg == nil {
return nil, errors.Errorf("required package was not loaded: %s", fullName)
}
@@ -173,7 +166,8 @@ func (b *Binder) PointerTo(ref *TypeReference) *TypeReference {
type TypeReference struct {
Definition *ast.Definition
GQL *ast.Type
GO types.Type
GO types.Type // Type of the field being bound. Could be a pointer or a value type of Target.
Target types.Type // The actual type that we know how to bind to. May require pointer juggling when traversing to fields.
CastType types.Type // Before calling marshalling functions cast from/to this base type
Marshaler *types.Func // When using external marshalling functions this will point to the Marshal function
Unmarshaler *types.Func // When using external marshalling functions this will point to the Unmarshal function
@@ -184,6 +178,7 @@ func (ref *TypeReference) Elem() *TypeReference {
if p, isPtr := ref.GO.(*types.Pointer); isPtr {
return &TypeReference{
GO: p.Elem(),
Target: ref.Target,
GQL: ref.GQL,
CastType: ref.CastType,
Definition: ref.Definition,
@@ -196,6 +191,7 @@ func (ref *TypeReference) Elem() *TypeReference {
if ref.IsSlice() {
return &TypeReference{
GO: ref.GO.(*types.Slice).Elem(),
Target: ref.Target,
GQL: ref.GQL.Elem,
CastType: ref.CastType,
Definition: ref.Definition,
@@ -213,10 +209,7 @@ func (t *TypeReference) IsPtr() bool {
}
func (t *TypeReference) IsNilable() bool {
_, isPtr := t.GO.(*types.Pointer)
_, isMap := t.GO.(*types.Map)
_, isInterface := t.GO.(*types.Interface)
return isPtr || isMap || isInterface
return IsNilable(t.GO)
}
func (t *TypeReference) IsSlice() bool {
@@ -244,7 +237,12 @@ func (t *TypeReference) UniquenessKey() string {
nullability = "N"
}
return nullability + t.Definition.Name + "2" + templates.TypeIdentifier(t.GO)
var elemNullability = ""
if t.GQL.Elem != nil && t.GQL.Elem.NonNull {
// Fix for #896
elemNullability = "ᚄ"
}
return nullability + t.Definition.Name + "2" + templates.TypeIdentifier(t.GO) + elemNullability
}
func (t *TypeReference) MarshalFunc() string {
@@ -271,6 +269,10 @@ func (t *TypeReference) UnmarshalFunc() string {
return "unmarshal" + t.UniquenessKey()
}
func (t *TypeReference) IsTargetNilable() bool {
return IsNilable(t.Target)
}
func (b *Binder) PushRef(ret *TypeReference) {
b.References = append(b.References, ret)
}
@@ -292,6 +294,11 @@ func isIntf(t types.Type) bool {
}
func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret *TypeReference, err error) {
if !isValid(bindTarget) {
b.SawInvalid = true
return nil, fmt.Errorf("%s has an invalid type", schemaType.Name())
}
var pkgName, typeName string
def := b.schema.Types[schemaType.Name()]
defer func() {
@@ -350,7 +357,7 @@ func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret
ref.GO = obj.Type()
ref.IsMarshaler = true
} else if underlying := basicUnderlying(obj.Type()); def.IsLeafType() && underlying != nil && underlying.Kind() == types.String {
// Special case for named types wrapping strings. Used by default enum implementations.
// TODO delete before v1. Backwards compatibility case for named types wrapping strings (see #595)
ref.GO = obj.Type()
ref.CastType = underlying
@@ -366,6 +373,7 @@ func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret
ref.GO = obj.Type()
}
ref.Target = ref.GO
ref.GO = b.CopyModifiersFromAst(schemaType, ref.GO)
if bindTarget != nil {
@@ -378,13 +386,21 @@ func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret
return ref, nil
}
return nil, fmt.Errorf("%s has type compatible with %s", schemaType.Name(), bindTarget.String())
return nil, fmt.Errorf("%s is incompatible with %s", schemaType.Name(), bindTarget.String())
}
func isValid(t types.Type) bool {
basic, isBasic := t.(*types.Basic)
if !isBasic {
return true
}
return basic.Kind() != types.Invalid
}
func (b *Binder) CopyModifiersFromAst(t *ast.Type, base types.Type) types.Type {
if t.Elem != nil {
child := b.CopyModifiersFromAst(t.Elem, base)
if _, isStruct := child.Underlying().(*types.Struct); isStruct {
if _, isStruct := child.Underlying().(*types.Struct); isStruct && !b.cfg.OmitSliceElementPointers {
child = types.NewPointer(child)
}
return types.NewSlice(child)
@@ -395,13 +411,25 @@ func (b *Binder) CopyModifiersFromAst(t *ast.Type, base types.Type) types.Type {
_, isInterface = named.Underlying().(*types.Interface)
}
if !isInterface && !t.NonNull {
if !isInterface && !IsNilable(base) && !t.NonNull {
return types.NewPointer(base)
}
return base
}
func IsNilable(t types.Type) bool {
if namedType, isNamed := t.(*types.Named); isNamed {
return IsNilable(namedType.Underlying())
}
_, isPtr := t.(*types.Pointer)
_, isMap := t.(*types.Map)
_, isInterface := t.(*types.Interface)
_, isSlice := t.(*types.Slice)
_, isChan := t.(*types.Chan)
return isPtr || isMap || isInterface || isSlice || isChan
}
func hasMethod(it types.Type, name string) bool {
if ptr, isPtr := it.(*types.Pointer); isPtr {
it = ptr.Elem()

View File

@@ -2,27 +2,38 @@ package config
import (
"fmt"
"go/types"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"github.com/99designs/gqlgen/internal/code"
"github.com/pkg/errors"
"github.com/vektah/gqlparser"
"github.com/vektah/gqlparser/ast"
yaml "gopkg.in/yaml.v2"
"github.com/vektah/gqlparser/v2"
"github.com/vektah/gqlparser/v2/ast"
"gopkg.in/yaml.v2"
)
type Config struct {
SchemaFilename StringList `yaml:"schema,omitempty"`
Exec PackageConfig `yaml:"exec"`
Model PackageConfig `yaml:"model"`
Resolver PackageConfig `yaml:"resolver,omitempty"`
Models TypeMap `yaml:"models,omitempty"`
StructTag string `yaml:"struct_tag,omitempty"`
SchemaFilename StringList `yaml:"schema,omitempty"`
Exec PackageConfig `yaml:"exec"`
Model PackageConfig `yaml:"model,omitempty"`
Federation PackageConfig `yaml:"federation,omitempty"`
Resolver ResolverConfig `yaml:"resolver,omitempty"`
AutoBind []string `yaml:"autobind"`
Models TypeMap `yaml:"models,omitempty"`
StructTag string `yaml:"struct_tag,omitempty"`
Directives map[string]DirectiveConfig `yaml:"directives,omitempty"`
OmitSliceElementPointers bool `yaml:"omit_slice_element_pointers,omitempty"`
SkipValidation bool `yaml:"skip_validation,omitempty"`
Sources []*ast.Source `yaml:"-"`
Packages *code.Packages `yaml:"-"`
Schema *ast.Schema `yaml:"-"`
// Deprecated use Federation instead. Will be removed next release
Federated bool `yaml:"federated,omitempty"`
}
var cfgFilenames = []string{".gqlgen.yml", "gqlgen.yml", "gqlgen.yaml"}
@@ -33,9 +44,30 @@ func DefaultConfig() *Config {
SchemaFilename: StringList{"schema.graphql"},
Model: PackageConfig{Filename: "models_gen.go"},
Exec: PackageConfig{Filename: "generated.go"},
Directives: map[string]DirectiveConfig{},
Models: TypeMap{},
}
}
// LoadDefaultConfig loads the default config so that it is ready to be used
func LoadDefaultConfig() (*Config, error) {
config := DefaultConfig()
for _, filename := range config.SchemaFilename {
filename = filepath.ToSlash(filename)
var err error
var schemaRaw []byte
schemaRaw, err = ioutil.ReadFile(filename)
if err != nil {
return nil, errors.Wrap(err, "unable to open schema")
}
config.Sources = append(config.Sources, &ast.Source{Name: filename, Input: string(schemaRaw)})
}
return config, nil
}
// LoadConfigFromDefaultLocations looks for a config file in the current directory, and all parent directories
// walking up the tree. The closest config file will be returned.
func LoadConfigFromDefaultLocations() (*Config, error) {
@@ -51,6 +83,13 @@ func LoadConfigFromDefaultLocations() (*Config, error) {
return LoadConfig(cfgFile)
}
var path2regex = strings.NewReplacer(
`.`, `\.`,
`*`, `.+`,
`\`, `[\\/]`,
`/`, `[\\/]`,
)
// LoadConfig reads the gqlgen.yml config file
func LoadConfig(filename string) (*Config, error) {
config := DefaultConfig()
@@ -64,12 +103,50 @@ func LoadConfig(filename string) (*Config, error) {
return nil, errors.Wrap(err, "unable to parse config")
}
defaultDirectives := map[string]DirectiveConfig{
"skip": {SkipRuntime: true},
"include": {SkipRuntime: true},
"deprecated": {SkipRuntime: true},
}
for key, value := range defaultDirectives {
if _, defined := config.Directives[key]; !defined {
config.Directives[key] = value
}
}
preGlobbing := config.SchemaFilename
config.SchemaFilename = StringList{}
for _, f := range preGlobbing {
matches, err := filepath.Glob(f)
if err != nil {
return nil, errors.Wrapf(err, "failed to glob schema filename %s", f)
var matches []string
// for ** we want to override default globbing patterns and walk all
// subdirectories to match schema files.
if strings.Contains(f, "**") {
pathParts := strings.SplitN(f, "**", 2)
rest := strings.TrimPrefix(strings.TrimPrefix(pathParts[1], `\`), `/`)
// turn the rest of the glob into a regex, anchored only at the end because ** allows
// for any number of dirs in between and walk will let us match against the full path name
globRe := regexp.MustCompile(path2regex.Replace(rest) + `$`)
if err := filepath.Walk(pathParts[0], func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if globRe.MatchString(strings.TrimPrefix(path, pathParts[0])) {
matches = append(matches, path)
}
return nil
}); err != nil {
return nil, errors.Wrapf(err, "failed to walk schema at root %s", pathParts[0])
}
} else {
matches, err = filepath.Glob(f)
if err != nil {
return nil, errors.Wrapf(err, "failed to glob schema filename %s", f)
}
}
for _, m := range matches {
@@ -80,13 +157,126 @@ func LoadConfig(filename string) (*Config, error) {
}
}
for _, filename := range config.SchemaFilename {
filename = filepath.ToSlash(filename)
var err error
var schemaRaw []byte
schemaRaw, err = ioutil.ReadFile(filename)
if err != nil {
return nil, errors.Wrap(err, "unable to open schema")
}
config.Sources = append(config.Sources, &ast.Source{Name: filename, Input: string(schemaRaw)})
}
return config, nil
}
type PackageConfig struct {
Filename string `yaml:"filename,omitempty"`
Package string `yaml:"package,omitempty"`
Type string `yaml:"type,omitempty"`
func (c *Config) Init() error {
if c.Packages == nil {
c.Packages = &code.Packages{}
}
if c.Schema == nil {
if err := c.LoadSchema(); err != nil {
return err
}
}
err := c.injectTypesFromSchema()
if err != nil {
return err
}
err = c.autobind()
if err != nil {
return err
}
c.injectBuiltins()
// prefetch all packages in one big packages.Load call
pkgs := []string{
"github.com/99designs/gqlgen/graphql",
"github.com/99designs/gqlgen/graphql/introspection",
}
pkgs = append(pkgs, c.Models.ReferencedPackages()...)
pkgs = append(pkgs, c.AutoBind...)
c.Packages.LoadAll(pkgs...)
// check everything is valid on the way out
err = c.check()
if err != nil {
return err
}
return nil
}
func (c *Config) injectTypesFromSchema() error {
c.Directives["goModel"] = DirectiveConfig{
SkipRuntime: true,
}
c.Directives["goField"] = DirectiveConfig{
SkipRuntime: true,
}
for _, schemaType := range c.Schema.Types {
if schemaType == c.Schema.Query || schemaType == c.Schema.Mutation || schemaType == c.Schema.Subscription {
continue
}
if bd := schemaType.Directives.ForName("goModel"); bd != nil {
if ma := bd.Arguments.ForName("model"); ma != nil {
if mv, err := ma.Value.Value(nil); err == nil {
c.Models.Add(schemaType.Name, mv.(string))
}
}
if ma := bd.Arguments.ForName("models"); ma != nil {
if mvs, err := ma.Value.Value(nil); err == nil {
for _, mv := range mvs.([]interface{}) {
c.Models.Add(schemaType.Name, mv.(string))
}
}
}
}
if schemaType.Kind == ast.Object || schemaType.Kind == ast.InputObject {
for _, field := range schemaType.Fields {
if fd := field.Directives.ForName("goField"); fd != nil {
forceResolver := c.Models[schemaType.Name].Fields[field.Name].Resolver
fieldName := c.Models[schemaType.Name].Fields[field.Name].FieldName
if ra := fd.Arguments.ForName("forceResolver"); ra != nil {
if fr, err := ra.Value.Value(nil); err == nil {
forceResolver = fr.(bool)
}
}
if na := fd.Arguments.ForName("name"); na != nil {
if fr, err := na.Value.Value(nil); err == nil {
fieldName = fr.(string)
}
}
if c.Models[schemaType.Name].Fields == nil {
c.Models[schemaType.Name] = TypeMapEntry{
Model: c.Models[schemaType.Name].Model,
Fields: map[string]TypeMapField{},
}
}
c.Models[schemaType.Name].Fields[field.Name] = TypeMapField{
FieldName: fieldName,
Resolver: forceResolver,
}
}
}
}
}
return nil
}
type TypeMapEntry struct {
@@ -95,8 +285,9 @@ type TypeMapEntry struct {
}
type TypeMapField struct {
Resolver bool `yaml:"resolver"`
FieldName string `yaml:"fieldName"`
Resolver bool `yaml:"resolver"`
FieldName string `yaml:"fieldName"`
GeneratedMethod string `yaml:"-"`
}
type StringList []string
@@ -128,90 +319,85 @@ func (a StringList) Has(file string) bool {
return false
}
func (c *PackageConfig) normalize() error {
if c.Filename == "" {
return errors.New("Filename is required")
}
c.Filename = abs(c.Filename)
// If Package is not set, first attempt to load the package at the output dir. If that fails
// fallback to just the base dir name of the output filename.
if c.Package == "" {
c.Package = code.NameForDir(c.Dir())
func (c *Config) check() error {
if c.Models == nil {
c.Models = TypeMap{}
}
return nil
}
func (c *PackageConfig) ImportPath() string {
return code.ImportPathForDir(c.Dir())
}
func (c *PackageConfig) Dir() string {
return filepath.Dir(c.Filename)
}
func (c *PackageConfig) Check() error {
if strings.ContainsAny(c.Package, "./\\") {
return fmt.Errorf("package should be the output package name only, do not include the output filename")
}
if c.Filename != "" && !strings.HasSuffix(c.Filename, ".go") {
return fmt.Errorf("filename should be path to a go source file")
type FilenamePackage struct {
Filename string
Package string
Declaree string
}
return c.normalize()
}
fileList := map[string][]FilenamePackage{}
func (c *PackageConfig) Pkg() *types.Package {
return types.NewPackage(c.ImportPath(), c.Dir())
}
func (c *PackageConfig) IsDefined() bool {
return c.Filename != ""
}
func (c *Config) Check() error {
if err := c.Models.Check(); err != nil {
return errors.Wrap(err, "config.models")
}
if err := c.Exec.Check(); err != nil {
return errors.Wrap(err, "config.exec")
}
if err := c.Model.Check(); err != nil {
return errors.Wrap(err, "config.model")
fileList[c.Exec.ImportPath()] = append(fileList[c.Exec.ImportPath()], FilenamePackage{
Filename: c.Exec.Filename,
Package: c.Exec.Package,
Declaree: "exec",
})
if c.Model.IsDefined() {
if err := c.Model.Check(); err != nil {
return errors.Wrap(err, "config.model")
}
fileList[c.Model.ImportPath()] = append(fileList[c.Model.ImportPath()], FilenamePackage{
Filename: c.Model.Filename,
Package: c.Model.Package,
Declaree: "model",
})
}
if c.Resolver.IsDefined() {
if err := c.Resolver.Check(); err != nil {
return errors.Wrap(err, "config.resolver")
}
fileList[c.Resolver.ImportPath()] = append(fileList[c.Resolver.ImportPath()], FilenamePackage{
Filename: c.Resolver.Filename,
Package: c.Resolver.Package,
Declaree: "resolver",
})
}
// check packages names against conflict, if present in the same dir
// and check filenames for uniqueness
packageConfigList := []PackageConfig{
c.Model,
c.Exec,
c.Resolver,
}
filesMap := make(map[string]bool)
pkgConfigsByDir := make(map[string]PackageConfig)
for _, current := range packageConfigList {
_, fileFound := filesMap[current.Filename]
if fileFound {
return fmt.Errorf("filename %s defined more than once", current.Filename)
if c.Federation.IsDefined() {
if err := c.Federation.Check(); err != nil {
return errors.Wrap(err, "config.federation")
}
filesMap[current.Filename] = true
previous, inSameDir := pkgConfigsByDir[current.Dir()]
if inSameDir && current.Package != previous.Package {
return fmt.Errorf("filenames %s and %s are in the same directory but have different package definitions", stripPath(current.Filename), stripPath(previous.Filename))
fileList[c.Federation.ImportPath()] = append(fileList[c.Federation.ImportPath()], FilenamePackage{
Filename: c.Federation.Filename,
Package: c.Federation.Package,
Declaree: "federation",
})
if c.Federation.ImportPath() != c.Exec.ImportPath() {
return fmt.Errorf("federation and exec must be in the same package")
}
pkgConfigsByDir[current.Dir()] = current
}
if c.Federated {
return fmt.Errorf("federated has been removed, instead use\nfederation:\n filename: path/to/federated.go")
}
return c.normalize()
}
for importPath, pkg := range fileList {
for _, file1 := range pkg {
for _, file2 := range pkg {
if file1.Package != file2.Package {
return fmt.Errorf("%s and %s define the same import path (%s) with different package names (%s vs %s)",
file1.Declaree,
file2.Declaree,
importPath,
file1.Package,
file2.Package,
)
}
}
}
}
func stripPath(path string) string {
return filepath.Base(path)
return nil
}
type TypeMap map[string]TypeMapEntry
@@ -259,10 +445,14 @@ func (tm TypeMap) ReferencedPackages() []string {
return pkgs
}
func (tm TypeMap) Add(Name string, goType string) {
modelCfg := tm[Name]
func (tm TypeMap) Add(name string, goType string) {
modelCfg := tm[name]
modelCfg.Model = append(modelCfg.Model, goType)
tm[Name] = modelCfg
tm[name] = modelCfg
}
type DirectiveConfig struct {
SkipRuntime bool `yaml:"skip_runtime"`
}
func inStrSlice(haystack []string, needle string) bool {
@@ -307,29 +497,54 @@ func findCfgInDir(dir string) string {
return ""
}
func (c *Config) normalize() error {
if err := c.Model.normalize(); err != nil {
return errors.Wrap(err, "model")
func (c *Config) autobind() error {
if len(c.AutoBind) == 0 {
return nil
}
if err := c.Exec.normalize(); err != nil {
return errors.Wrap(err, "exec")
}
ps := c.Packages.LoadAll(c.AutoBind...)
if c.Resolver.IsDefined() {
if err := c.Resolver.normalize(); err != nil {
return errors.Wrap(err, "resolver")
for _, t := range c.Schema.Types {
if c.Models.UserDefined(t.Name) {
continue
}
for i, p := range ps {
if p == nil {
return fmt.Errorf("unable to load %s - make sure you're using an import path to a package that exists", c.AutoBind[i])
}
if t := p.Types.Scope().Lookup(t.Name); t != nil {
c.Models.Add(t.Name(), t.Pkg().Path()+"."+t.Name())
break
}
}
}
if c.Models == nil {
c.Models = TypeMap{}
for i, t := range c.Models {
for j, m := range t.Model {
pkg, typename := code.PkgAndType(m)
// skip anything that looks like an import path
if strings.Contains(pkg, "/") {
continue
}
for _, p := range ps {
if p.Name != pkg {
continue
}
if t := p.Types.Scope().Lookup(typename); t != nil {
c.Models[i].Model[j] = t.Pkg().Path() + "." + t.Name()
break
}
}
}
}
return nil
}
func (c *Config) InjectBuiltins(s *ast.Schema) {
func (c *Config) injectBuiltins() {
builtins := TypeMap{
"__Directive": {Model: StringList{"github.com/99designs/gqlgen/graphql/introspection.Directive"}},
"__DirectiveLocation": {Model: StringList{"github.com/99designs/gqlgen/graphql.String"}},
@@ -370,35 +585,36 @@ func (c *Config) InjectBuiltins(s *ast.Schema) {
}
for typeName, entry := range extraBuiltins {
if t, ok := s.Types[typeName]; !c.Models.Exists(typeName) && ok && t.Kind == ast.Scalar {
if t, ok := c.Schema.Types[typeName]; !c.Models.Exists(typeName) && ok && t.Kind == ast.Scalar {
c.Models[typeName] = entry
}
}
}
func (c *Config) LoadSchema() (*ast.Schema, map[string]string, error) {
schemaStrings := map[string]string{}
var sources []*ast.Source
for _, filename := range c.SchemaFilename {
filename = filepath.ToSlash(filename)
var err error
var schemaRaw []byte
schemaRaw, err = ioutil.ReadFile(filename)
if err != nil {
fmt.Fprintln(os.Stderr, "unable to open schema: "+err.Error())
os.Exit(1)
}
schemaStrings[filename] = string(schemaRaw)
sources = append(sources, &ast.Source{Name: filename, Input: schemaStrings[filename]})
func (c *Config) LoadSchema() error {
if c.Packages != nil {
c.Packages = &code.Packages{}
}
schema, err := gqlparser.LoadSchema(sources...)
if err := c.check(); err != nil {
return err
}
schema, err := gqlparser.LoadSchema(c.Sources...)
if err != nil {
return nil, nil, err
return err
}
return schema, schemaStrings, nil
if schema.Query == nil {
schema.Query = &ast.Definition{
Kind: ast.Object,
Name: "Query",
}
schema.Types["Query"] = schema.Query
}
c.Schema = schema
return nil
}
func abs(path string) string {

View File

@@ -0,0 +1,62 @@
package config
import (
"fmt"
"go/types"
"path/filepath"
"strings"
"github.com/99designs/gqlgen/internal/code"
)
type PackageConfig struct {
Filename string `yaml:"filename,omitempty"`
Package string `yaml:"package,omitempty"`
}
func (c *PackageConfig) ImportPath() string {
if !c.IsDefined() {
return ""
}
return code.ImportPathForDir(c.Dir())
}
func (c *PackageConfig) Dir() string {
if !c.IsDefined() {
return ""
}
return filepath.Dir(c.Filename)
}
func (c *PackageConfig) Pkg() *types.Package {
if !c.IsDefined() {
return nil
}
return types.NewPackage(c.ImportPath(), c.Package)
}
func (c *PackageConfig) IsDefined() bool {
return c.Filename != ""
}
func (c *PackageConfig) Check() error {
if strings.ContainsAny(c.Package, "./\\") {
return fmt.Errorf("package should be the output package name only, do not include the output filename")
}
if c.Filename == "" {
return fmt.Errorf("filename must be specified")
}
if !strings.HasSuffix(c.Filename, ".go") {
return fmt.Errorf("filename should be path to a go source file")
}
c.Filename = abs(c.Filename)
// If Package is not set, first attempt to load the package at the output dir. If that fails
// fallback to just the base dir name of the output filename.
if c.Package == "" {
c.Package = code.NameForDir(c.Dir())
}
return nil
}

View File

@@ -0,0 +1,100 @@
package config
import (
"fmt"
"go/types"
"path/filepath"
"strings"
"github.com/99designs/gqlgen/internal/code"
)
type ResolverConfig struct {
Filename string `yaml:"filename,omitempty"`
FilenameTemplate string `yaml:"filename_template,omitempty"`
Package string `yaml:"package,omitempty"`
Type string `yaml:"type,omitempty"`
Layout ResolverLayout `yaml:"layout,omitempty"`
DirName string `yaml:"dir"`
}
type ResolverLayout string
var (
LayoutSingleFile ResolverLayout = "single-file"
LayoutFollowSchema ResolverLayout = "follow-schema"
)
func (r *ResolverConfig) Check() error {
if r.Layout == "" {
r.Layout = LayoutSingleFile
}
if r.Type == "" {
r.Type = "Resolver"
}
switch r.Layout {
case LayoutSingleFile:
if r.Filename == "" {
return fmt.Errorf("filename must be specified with layout=%s", r.Layout)
}
if !strings.HasSuffix(r.Filename, ".go") {
return fmt.Errorf("filename should be path to a go source file with layout=%s", r.Layout)
}
r.Filename = abs(r.Filename)
case LayoutFollowSchema:
if r.DirName == "" {
return fmt.Errorf("dirname must be specified with layout=%s", r.Layout)
}
r.DirName = abs(r.DirName)
if r.Filename == "" {
r.Filename = filepath.Join(r.DirName, "resolver.go")
} else {
r.Filename = abs(r.Filename)
}
default:
return fmt.Errorf("invalid layout %s. must be %s or %s", r.Layout, LayoutSingleFile, LayoutFollowSchema)
}
if strings.ContainsAny(r.Package, "./\\") {
return fmt.Errorf("package should be the output package name only, do not include the output filename")
}
if r.Package == "" && r.Dir() != "" {
r.Package = code.NameForDir(r.Dir())
}
return nil
}
func (r *ResolverConfig) ImportPath() string {
if r.Dir() == "" {
return ""
}
return code.ImportPathForDir(r.Dir())
}
func (r *ResolverConfig) Dir() string {
switch r.Layout {
case LayoutSingleFile:
if r.Filename == "" {
return ""
}
return filepath.Dir(r.Filename)
case LayoutFollowSchema:
return r.DirName
default:
panic("invalid layout " + r.Layout)
}
}
func (r *ResolverConfig) Pkg() *types.Package {
if r.Dir() == "" {
return nil
}
return types.NewPackage(r.ImportPath(), r.Package)
}
func (r *ResolverConfig) IsDefined() bool {
return r.Filename != "" || r.DirName != ""
}