Skip to content

Commit

Permalink
allow primitive scalars to be redefined
Browse files Browse the repository at this point in the history
  • Loading branch information
vektah committed Feb 16, 2018
1 parent 402e073 commit d94cfb1
Show file tree
Hide file tree
Showing 14 changed files with 138 additions and 172 deletions.
2 changes: 2 additions & 0 deletions codegen/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ func Bind(schema *schema.Schema, userTypes map[string]string, destDir string) (*
return nil, err
}

bindTypes(imports, namedTypes, prog)

b := &Build{
PackageName: filepath.Base(destDir),
Objects: buildObjects(namedTypes, schema, prog),
Expand Down
2 changes: 1 addition & 1 deletion codegen/import_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func buildImports(types NamedTypes, destDir string) Imports {
{"query", "github.com/vektah/gqlgen/neelance/query"},
{"schema", "github.com/vektah/gqlgen/neelance/schema"},
{"validation", "github.com/vektah/gqlgen/neelance/validation"},
{"jsonw", "github.com/vektah/gqlgen/jsonw"},
{"graphql", "github.com/vektah/gqlgen/graphql"},
}

for _, t := range types {
Expand Down
8 changes: 4 additions & 4 deletions codegen/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func (f *Field) doWriteJson(res string, val string, remainingMods []string, isPt
case len(remainingMods) > 0 && remainingMods[0] == modPtr:
return tpl(`
if {{.val}} == nil {
{{.res}} = jsonw.Null
{{.res}} = graphql.Null
} else {
{{.next}}
}`, map[string]interface{}{
Expand All @@ -123,9 +123,9 @@ func (f *Field) doWriteJson(res string, val string, remainingMods []string, isPt
var index = "idx" + strconv.Itoa(depth)

return tpl(`
{{.arr}} := jsonw.Array{}
{{.arr}} := graphql.Array{}
for {{.index}} := range {{.val}} {
var {{.tmp}} jsonw.Writer
var {{.tmp}} graphql.Marshaler
{{.next}}
{{.arr}} = append({{.arr}}, {{.tmp}})
}
Expand All @@ -142,7 +142,7 @@ func (f *Field) doWriteJson(res string, val string, remainingMods []string, isPt
if isPtr {
val = "*" + val
}
return fmt.Sprintf("%s = jsonw.%s(%s)", res, ucFirst(f.GoType), val)
return f.Marshal(res, val)

default:
if !isPtr {
Expand Down
46 changes: 4 additions & 42 deletions codegen/object_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ func buildObjects(types NamedTypes, s *schema.Schema, prog *loader.Program) Obje
switch typ := typ.(type) {
case *schema.Object:
obj := buildObject(types, typ)
bindObject(prog, obj)

if def := findGoType(prog, obj.Package, obj.GoType); def != nil {
findBindTargets(def.Type(), obj)
}

objects = append(objects, obj)
}
Expand Down Expand Up @@ -76,33 +79,6 @@ func buildObject(types NamedTypes, typ *schema.Object) *Object {
return obj
}

func bindObject(prog *loader.Program, obj *Object) {
if obj.Package == "" {
return
}
pkgName, err := resolvePkg(obj.Package)
if err != nil {
fmt.Fprintf(os.Stderr, "unable to resolve package for %s: %s\n", obj.GQLType, err.Error())
return
}

pkg := prog.Imported[pkgName]
if pkg == nil {
fmt.Fprintf(os.Stderr, "required package was not loaded: %s", pkgName)
return
}

for astNode, object := range pkg.Defs {
if astNode.Name != obj.GoType {
continue
}

if findBindTargets(object.Type(), obj) {
return
}
}
}

func findBindTargets(t types.Type, object *Object) bool {
switch t := t.(type) {
case *types.Named:
Expand Down Expand Up @@ -168,20 +144,6 @@ func findBindTargets(t types.Type, object *Object) bool {
return false
}

func mutationRoot(schema *schema.Schema) string {
if mu, ok := schema.EntryPoints["mutation"]; ok {
return mu.TypeName()
}
return ""
}

func queryRoot(schema *schema.Schema) string {
if mu, ok := schema.EntryPoints["mutation"]; ok {
return mu.TypeName()
}
return ""
}

func modifiersFromGoType(t types.Type) []string {
var modifiers []string
for {
Expand Down
45 changes: 38 additions & 7 deletions codegen/type.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
package codegen

import "strings"
import (
"strings"
)

type NamedTypes map[string]*NamedType

type NamedType struct {
Ref
IsScalar bool
IsInterface bool
GQLType string // Name of the graphql type
GoType string // Name of the go type
Package string // the package the go type lives in
Import *Import
Marshaler *Ref // If this type has an external marshaler this will be set
}

type Ref struct {
GoType string // Name of the go type
Package string // the package the go type lives in
Import *Import // the resolved import with alias
}

type Type struct {
Expand All @@ -24,11 +31,15 @@ const (
modPtr = "*"
)

func (t NamedType) FullName() string {
func (t Ref) FullName() string {
return t.pkgDot() + t.GoType
}

func (t Ref) pkgDot() string {
if t.Import == nil || t.Import.Name == "" {
return t.GoType
return ""
}
return t.Import.Name + "." + t.GoType
return t.Import.Name + "."
}

func (t Type) Signature() string {
Expand All @@ -42,3 +53,23 @@ func (t Type) IsPtr() bool {
func (t Type) IsSlice() bool {
return len(t.Modifiers) > 0 && t.Modifiers[0] == modList
}

func (t Type) Unmarshal(result, raw string) string {
if t.Marshaler != nil {
return result + ", err := " + t.Marshaler.pkgDot() + "Unmarshal" + t.Marshaler.GoType + "(" + raw + ")"
}
return tpl(`var {{.result}} {{.type}}
err := (&{{.result}}).Unmarshal({{.raw}})`, map[string]interface{}{
"result": result,
"raw": raw,
"type": t.FullName(),
})
}

func (t Type) Marshal(result, val string) string {
if t.Marshaler != nil {
return result + " = " + t.Marshaler.pkgDot() + "" /* Marshal */ + t.Marshaler.GoType + "(" + val + ")"
}

return result + " = " + val
}
33 changes: 31 additions & 2 deletions codegen/type_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package codegen

import (
"fmt"
"go/types"
"strings"

"github.com/vektah/gqlgen/neelance/common"
"github.com/vektah/gqlgen/neelance/schema"
"golang.org/x/tools/go/loader"
)

// namedTypeFromSchema objects for every graphql type, including scalars. There should only be one instance of Type for each thing
Expand All @@ -17,10 +19,9 @@ func buildNamedTypes(s *schema.Schema, userTypes map[string]string) NamedTypes {
userType := userTypes[t.GQLType]
if userType == "" {
if t.IsScalar {
userType = "string"
userType = "github.com/vektah/gqlgen/graphql.String"
} else {
userType = "interface{}"
userTypes[t.GQLType] = "interface{}"
}
}
t.Package, t.GoType = pkgAndType(userType)
Expand All @@ -30,6 +31,34 @@ func buildNamedTypes(s *schema.Schema, userTypes map[string]string) NamedTypes {
return types
}

func bindTypes(imports Imports, namedTypes NamedTypes, prog *loader.Program) {
fmt.Println(namedTypes)
for _, t := range namedTypes {
if t.Package == "" {
fmt.Println("NO PKG", t)
continue
}

def := findGoType(prog, t.Package, t.GoType)
if def == nil {

}
fmt.Println("Looking at " + t.FullName())
switch def := def.(type) {
case *types.Func:

fmt.Println(def.String())
sig := def.Type().(*types.Signature)
cpy := t.Ref
t.Marshaler = &cpy

fmt.Println("sig: " + sig.Params().At(0).Type().String())
t.Package, t.GoType = pkgAndType(sig.Params().At(0).Type().String())
t.Import = imports.findByName(t.Package)
}
}
}

// namedTypeFromSchema objects for every graphql type, including primitives.
// don't recurse into object fields or interfaces yet, lets make sure we have collected everything first.
func namedTypeFromSchema(schemaType schema.NamedType) *NamedType {
Expand Down
47 changes: 47 additions & 0 deletions codegen/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package codegen

import (
"fmt"
"go/types"
"os"

"golang.org/x/tools/go/loader"
)

func findGoType(prog *loader.Program, pkgName string, typeName string) types.Object {
fullName := typeName
if pkgName != "" {
fullName = pkgName + "." + typeName
}

pkgName, err := resolvePkg(pkgName)
if err != nil {
fmt.Fprintf(os.Stderr, "unable to resolve package for %s: %s\n", fullName, err.Error())
return nil
}

pkg := prog.Imported[pkgName]
if pkg == nil {
fmt.Fprintf(os.Stderr, "required package was not loaded: %s", fullName)
return nil
}

for astNode, def := range pkg.Defs {
if astNode.Name != typeName || isMethod(def) {
continue
}

return def
}
fmt.Fprintf(os.Stderr, "unable to find type %s\n", fullName)
return nil
}

func isMethod(t types.Object) bool {
f, isFunc := t.(*types.Func)
if !isFunc {
return false
}

return f.Type().(*types.Signature).Recv() != nil
}
1 change: 1 addition & 0 deletions example/dataloader/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ type Order {
type Item {
name: String
}
scalar Time
1 change: 1 addition & 0 deletions example/starwars/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,4 @@ type Starship {
history: [[Int]]
}
union SearchResult = Human | Droid | Starship
scalar Time
12 changes: 6 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,12 @@ func loadTypeMap() map[string]string {
"__EnumValue": "github.com/vektah/gqlgen/neelance/introspection.EnumValue",
"__InputValue": "github.com/vektah/gqlgen/neelance/introspection.InputValue",
"__Schema": "github.com/vektah/gqlgen/neelance/introspection.Schema",
"Int": "int",
"Float": "float64",
"String": "string",
"Boolean": "bool",
"ID": "string",
"Time": "time.Time",
"Int": "github.com/vektah/gqlgen/graphql.Int",
"Float": "github.com/vektah/gqlgen/graphql.Float",
"String": "github.com/vektah/gqlgen/graphql.String",
"Boolean": "github.com/vektah/gqlgen/graphql.Boolean",
"ID": "github.com/vektah/gqlgen/graphql.ID",
"Time": "github.com/vektah/gqlgen/graphql.Time",
}
b, err := ioutil.ReadFile(*typemap)
if err != nil {
Expand Down
3 changes: 0 additions & 3 deletions neelance/schema/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ var metaSrc = `
# The ` + "`" + `ID` + "`" + ` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as ` + "`" + `"4"` + "`" + `) or integer (such as ` + "`" + `4` + "`" + `) input value will be accepted as an ID.
scalar ID
# The ` + "`" + `Time` + "`" + ` scalar type represents an RFC3339 encoded date time object
scalar Time
# Directs the executor to include this field or fragment only when the ` + "`" + `if` + "`" + ` argument is true.
directive @include(
# Included when true.
Expand Down
2 changes: 1 addition & 1 deletion neelance/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,7 @@ func validateBasicLit(v *common.BasicLit, t common.Type) bool {
case "ID":
return v.Type == scanner.Int || v.Type == scanner.String
default:
//TODO: Type-check against expected type by Unmarshalling
//TODO: Type-check against expected type by Unmarshaling
return true
}

Expand Down
18 changes: 2 additions & 16 deletions templates/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,7 @@ var argsTpl = `
{{- define "args" }}
{{- range $i, $arg := . }}
var arg{{$i}} {{$arg.Signature }}
{{- if eq $arg.FullName "time.Time" }}
if tmp, ok := field.Args[{{$arg.GQLName|quote}}]; ok {
if tmpStr, ok := tmp.(string); ok {
tmpDate, err := time.Parse(time.RFC3339, tmpStr)
if err != nil {
ec.Error(err)
continue
}
arg{{$i}} = {{if $arg.Type.IsPtr}}&{{end}}tmpDate
} else {
ec.Errorf("Time '{{$arg.GQLName}}' should be RFC3339 formatted string")
continue
}
}
{{- else if eq $arg.GoType "map[string]interface{}" }}
{{- if eq $arg.GoType "map[string]interface{}" }}
if tmp, ok := field.Args[{{$arg.GQLName|quote}}]; ok {
{{- if $arg.Type.IsPtr }}
tmp2 := tmp.({{$arg.GoType}})
Expand All @@ -29,7 +15,7 @@ var argsTpl = `
}
{{- else if $arg.IsScalar }}
if tmp, ok := field.Args[{{$arg.GQLName|quote}}]; ok {
tmp2, err := coerce{{$arg.GoType|ucFirst}}(tmp)
{{$arg.Unmarshal "tmp2" "tmp" }}
if err != nil {
ec.Error(err)
continue
Expand Down
Loading

0 comments on commit d94cfb1

Please sign in to comment.