package codegen
import (
"log"
"sort"
"github.com/pkg/errors"
"github.com/vektah/gqlparser/ast"
"golang.org/x/tools/go/loader"
)
func (cfg *Config) buildObjects(types NamedTypes, prog *loader.Program, imports *Imports) (Objects, error) {
var objects Objects
for _, typ := range cfg.schema.Types {
if typ.Kind != ast.Object {
continue
}
obj, err := cfg.buildObject(types, typ, imports)
if err != nil {
return nil, err
}
def, err := findGoType(prog, obj.Package, obj.GoType)
if err != nil {
return nil, err
}
if def != nil {
for _, bindErr := range bindObject(def.Type(), obj, imports, cfg.StructTag) {
log.Println(bindErr.Error())
log.Println(" Adding resolver method")
}
}
objects = append(objects, obj)
}
sort.Slice(objects, func(i, j int) bool {
return objects[i].GQLType < objects[j].GQLType
})
return objects, nil
}
var keywords = []string{
"break",
"default",
"func",
"interface",
"select",
"case",
"defer",
"go",
"map",
"struct",
"chan",
"else",
"goto",
"package",
"switch",
"const",
"fallthrough",
"if",
"range",
"type",
"continue",
"for",
"import",
"return",
"var",
}
// sanitizeArgName prevents collisions with go keywords for arguments to resolver functions
func sanitizeArgName(name string) string {
for _, k := range keywords {
if name == k {
return name + "Arg"
}
}
return name
}
func (cfg *Config) buildObject(types NamedTypes, typ *ast.Definition, imports *Imports) (*Object, error) {
obj := &Object{NamedType: types[typ.Name]}
typeEntry, entryExists := cfg.Models[typ.Name]
imp := imports.findByPath(cfg.Exec.ImportPath())
obj.ResolverInterface = &Ref{GoType: obj.GQLType + "Resolver", Import: imp}
if typ == cfg.schema.Query {
obj.Root = true
}
if typ == cfg.schema.Mutation {
obj.Root = true
obj.DisableConcurrency = true
}
if typ == cfg.schema.Subscription {
obj.Root = true
obj.Stream = true
}
obj.Satisfies = append(obj.Satisfies, typ.Interfaces...)
for _, field := range typ.Fields {
if typ == cfg.schema.Query && field.Name == "__type" {
obj.Fields = append(obj.Fields, Field{
Type: &Type{types["__Schema"], []string{modPtr}, ast.NamedType("__Schema", nil), nil},
GQLName: "__schema",
NoErr: true,
GoFieldType: GoFieldMethod,
GoReceiverName: "ec",
GoFieldName: "introspectSchema",
Object: obj,
Description: field.Description,
})
continue
}
if typ == cfg.schema.Query && field.Name == "__schema" {
obj.Fields = append(obj.Fields, Field{
Type: &Type{types["__Type"], []string{modPtr}, ast.NamedType("__Schema", nil), nil},
GQLName: "__type",
NoErr: true,
GoFieldType: GoFieldMethod,
GoReceiverName: "ec",
GoFieldName: "introspectType",
Args: []FieldArgument{
{GQLName: "name", Type: &Type{types["String"], []string{}, ast.NamedType("String", nil), nil}, Object: &Object{}},
},
Object: obj,
})
continue
}
var forceResolver bool
var goName string
if entryExists {
if typeField, ok := typeEntry.Fields[field.Name]; ok {
goName = typeField.FieldName
forceResolver = typeField.Resolver
}
}
var args []FieldArgument
for _, arg := range field.Arguments {
newArg := FieldArgument{
GQLName: arg.Name,
Type: types.getType(arg.Type),
Object: obj,
GoVarName: sanitizeArgName(arg.Name),
}
if !newArg.Type.IsInput && !newArg.Type.IsScalar {
return nil, errors.Errorf("%s cannot be used as argument of %s.%s. only input and scalar types are allowed", arg.Type, obj.GQLType, field.Name)
}
if arg.DefaultValue != nil {
var err error
newArg.Default, err = arg.DefaultValue.Value(nil)
if err != nil {
return nil, errors.Errorf("default value for %s.%s is not valid: %s", typ.Name, field.Name, err.Error())
}
newArg.StripPtr()
}
args = append(args, newArg)
}
obj.Fields = append(obj.Fields, Field{
GQLName: field.Name,
Type: types.getType(field.Type),
Args: args,
Object: obj,
GoFieldName: goName,
ForceResolver: forceResolver,
})
}
return obj, nil
}