Skip to content

Commit

Permalink
Merge branch 'master' into next
Browse files Browse the repository at this point in the history
# Conflicts:
#	codegen/config/config.go
#	handler/graphql.go
#	handler/graphql_test.go
  • Loading branch information
hantonelli committed Mar 31, 2019
2 parents 3a6f2fb + bd4aeaa commit bf79bc9
Show file tree
Hide file tree
Showing 89 changed files with 7,012 additions and 2,849 deletions.
9 changes: 7 additions & 2 deletions .github/ISSUE_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
### Expected Behaviour
### What happened?

### Actual Behavior
### What did you expect?

### Minimal graphql.schema and models to reproduce

### versions
- `gqlgen version`?
- `go version`?
- dep or go modules?
27 changes: 27 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Contribution Guidelines

Want to contribute to gqlgen? Here are some guidelines for how we accept help.

## Getting in Touch

Our [gitter](https://gitter.im/gqlgen/Lobby) channel is the best place to ask questions or get advice on using gqlgen.

## Reporting Bugs and Issues

We use [GitHub Issues](https://github.com/99designs/gqlgen/issues) to track bugs, so please do a search before submitting to ensure your problem isn't already tracked.

### New Issues

Please provide the expected and observed behaviours in your issue. A minimal GraphQL schema or configuration file should be provided where appropriate.

## Proposing a Change

If you intend to implement a feature for gqlgen, or make a non-trivial change to the current implementation, we recommend [first filing an issue](https://github.com/99designs/gqlgen/issues/new) marked with the `proposal` tag, so that the engineering team can provide guidance and feedback on the direction of an implementation. This also help ensure that other people aren't also working on the same thing.

Bug fixes are welcome and should come with appropriate test coverage.

New features should be made against the `next` branch.

### License

By contributing to gqlgen, you agree that your contributions will be licensed under its MIT license.
61 changes: 29 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,31 @@
# gqlgen [![CircleCI](https://badgen.net/circleci/github/99designs/gqlgen/master)](https://circleci.com/gh/99designs/gqlgen) [![Read the Docs](https://badgen.net/badge/docs/available/green)](http://gqlgen.com/)

This is a library for quickly creating strictly typed graphql servers in golang.

See the [docs](https://gqlgen.com/) for a getting started guide.

### Feature comparison

| | [gqlgen](https://github.com/99designs/gqlgen) | [gophers](https://github.com/graph-gophers/graphql-go) | [graphql-go](https://github.com/graphql-go/graphql) | [thunder](https://github.com/samsarahq/thunder) |
| --------: | :-------- | :-------- | :-------- | :-------- |
| Kind | schema first | schema first | run time types | struct first |
| Boilerplate | less | more | more | some |
| Docs | [docs](https://gqlgen.com) & [examples](https://github.com/99designs/gqlgen/tree/master/example) | [examples](https://github.com/graph-gophers/graphql-go/tree/master/example/starwars) | [examples](https://github.com/graphql-go/graphql/tree/master/examples) | [examples](https://github.com/samsarahq/thunder/tree/master/example)|
| Query | :+1: | :+1: | :+1: | :+1: |
| Mutation | :+1: | :construction: [pr](https://github.com/graph-gophers/graphql-go/pull/182) | :+1: | :+1: |
| Subscription | :+1: | :construction: [pr](https://github.com/graph-gophers/graphql-go/pull/182) | :+1: | :+1: |
| Type Safety | :+1: | :+1: | :no_entry: | :+1: |
| Type Binding | :+1: | :construction: [pr](https://github.com/graph-gophers/graphql-go/pull/194) | :no_entry: | :+1: |
| Embedding | :+1: | :no_entry: | :construction: [pr](https://github.com/graphql-go/graphql/pull/371) | :no_entry: |
| Interfaces | :+1: | :+1: | :+1: | :no_entry: [is](https://github.com/samsarahq/thunder/issues/78) |
| Generated Enums | :+1: | :no_entry: | :no_entry: | :no_entry: |
| Generated Inputs | :+1: | :no_entry: | :no_entry: | :no_entry: |
| Stitching gql | :clock1: [is](https://github.com/99designs/gqlgen/issues/5) | :no_entry: | :no_entry: | :no_entry: |
| Opentracing | :+1: | :+1: | :no_entry: | :scissors:[pr](https://github.com/samsarahq/thunder/pull/77) |
| Hooks for error logging | :+1: | :no_entry: | :no_entry: | :no_entry: |
| Dataloading | :+1: | :+1: | :+1: | :warning: |
| Concurrency | :+1: | :+1: | :+1: | :+1: |
| Custom errors & error.path | :+1: | :no_entry: [is](https://github.com/graphql-go/graphql/issues/259) | :no_entry: | :no_entry: |
| Query complexity | :+1: | :no_entry: [is](https://github.com/graphql-go/graphql/issues/231) | :no_entry: | :no_entry: |


### Help

Create an issue or join the conversation on [gitter](https://gitter.im/gqlgen)
## What is gqlgen?

[gqlgen](https://github.com/99designs/gqlgen) is a Go library for building GraphQL servers without any fuss. gqlgen is:

- **Schema first** — Define your API using the GraphQL [Schema Definition Language](http://graphql.org/learn/schema/).
- **Type safe** — You should never see `map[string]interface{}` here.
- **Codegen** — Let us generate the boring bits, so you can build your app quickly.

[Feature Comparison](https://gqlgen.com/feature-comparison/)

## Getting Started

First work your way through the [Getting Started](https://gqlgen.com/getting-started/) tutorial.

If you can't find what your looking for, look at our [examples](https://github.com/99designs/gqlgen/tree/master/example) for example usage of gqlgen.

## Reporting Issues

If you think you've found a bug, or something isn't behaving the way you think it should, please raise an [issue](https://github.com/99designs/gqlgen/issues) on GitHub.

## Contributing

Read our [Contribution Guidelines](https://github.com/99designs/gqlgen/blob/master/CONTRIBUTING.md) for information on how you can help out gqlgen.

## Other Resources

- [Christopher Biscardi @ Gophercon UK 2018](https://youtu.be/FdURVezcdcw)
- [Introducing gqlgen: a GraphQL Server Generator for Go](https://99designs.com.au/blog/engineering/gqlgen-a-graphql-server-generator-for-go/)
- [GraphQL workshop for Golang developers by Iván Corrales Solera](https://graphql-go.wesovilabs.com)
2 changes: 1 addition & 1 deletion ambient.go → cmd/ambient.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package cmd

import (
// Import and ignore the ambient imports listed below so dependency managers
Expand Down
2 changes: 1 addition & 1 deletion cmd/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ var genCmd = cli.Command{
Action: func(ctx *cli.Context) {
var cfg *config.Config
var err error
if configFilename := ctx.String("gen"); configFilename != "" {
if configFilename := ctx.String("config"); configFilename != "" {
cfg, err = config.LoadConfig(configFilename)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
Expand Down
2 changes: 1 addition & 1 deletion cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func GenerateGraphServer(cfg *config.Config, serverFilename string) {
func initConfig(ctx *cli.Context) *config.Config {
var cfg *config.Config
var err error
configFilename := ctx.String("cfg")
configFilename := ctx.String("config")
if configFilename != "" {
cfg, err = config.LoadConfig(configFilename)
} else {
Expand Down
2 changes: 1 addition & 1 deletion codegen/args.gotpl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func (ec *executionContext) {{ $name }}(ctx context.Context, rawArgs map[string]
if err != nil {
return nil, err
}
if data, ok := tmp.({{ $arg.TypeReference.GO }}) ; ok {
if data, ok := tmp.({{ $arg.TypeReference.GO | ref }}) ; ok {
arg{{$i}} = data
} else {
return nil, fmt.Errorf(`unexpected type %T from directive, should be {{ $arg.TypeReference.GO }}`, tmp)
Expand Down
11 changes: 11 additions & 0 deletions codegen/complexity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package codegen

func (o *Object) UniqueFields() map[string]*Field {
m := map[string]*Field{}

for _, f := range o.Fields {
m[f.GoFieldName] = f
}

return m
}
137 changes: 82 additions & 55 deletions codegen/config/binder.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@ import (
"fmt"
"go/token"
"go/types"
"regexp"
"strings"

"github.com/99designs/gqlgen/codegen/templates"

"github.com/99designs/gqlgen/internal/code"
"github.com/pkg/errors"
"github.com/vektah/gqlparser/ast"
Expand All @@ -29,6 +26,14 @@ func (c *Config) NewBinder(s *ast.Schema) (*Binder, error) {
return nil, err
}

for _, p := range pkgs {
for _, e := range p.Errors {
if e.Kind == packages.ListError {
return nil, p.Errors[0]
}
}
}

return &Binder{
pkgs: pkgs,
schema: s,
Expand Down Expand Up @@ -71,7 +76,7 @@ func (b *Binder) FindType(pkgName string, typeName string) (types.Type, error) {

func (b *Binder) getPkg(find string) *packages.Package {
for _, p := range b.pkgs {
if normalizeVendor(find) == normalizeVendor(p.PkgPath) {
if code.NormalizeVendor(find) == code.NormalizeVendor(p.PkgPath) {
return p
}
}
Expand Down Expand Up @@ -153,51 +158,50 @@ func (b *Binder) PointerTo(ref *TypeReference) *TypeReference {
newRef := &TypeReference{
GO: types.NewPointer(ref.GO),
GQL: ref.GQL,
CastType: ref.CastType,
Definition: ref.Definition,
Unmarshaler: ref.Unmarshaler,
Marshaler: ref.Marshaler,
IsMarshaler: ref.IsMarshaler,
}

b.References = append(b.References, newRef)
return newRef
}

var modsRegex = regexp.MustCompile(`^(\*|\[\])*`)

func normalizeVendor(pkg string) string {
modifiers := modsRegex.FindAllString(pkg, 1)[0]
pkg = strings.TrimPrefix(pkg, modifiers)
parts := strings.Split(pkg, "/vendor/")
return modifiers + parts[len(parts)-1]
}

// TypeReference is used by args and field types. The Definition can refer to both input and output types.
type TypeReference struct {
Definition *ast.Definition
GQL *ast.Type
GO types.Type
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
IsMarshaler bool // Does the type implement graphql.Marshaler and graphql.Unmarshaler
}

func (ref *TypeReference) Elem() *TypeReference {
if p, isPtr := ref.GO.(*types.Pointer); isPtr {
return &TypeReference{
GO: p.Elem(),
GQL: ref.GQL,
CastType: ref.CastType,
Definition: ref.Definition,
Unmarshaler: ref.Unmarshaler,
Marshaler: ref.Marshaler,
IsMarshaler: ref.IsMarshaler,
}
}

if s, isSlice := ref.GO.(*types.Slice); isSlice {
return &TypeReference{
GO: s.Elem(),
GQL: ref.GQL.Elem,
CastType: ref.CastType,
Definition: ref.Definition,
Unmarshaler: ref.Unmarshaler,
Marshaler: ref.Marshaler,
IsMarshaler: ref.IsMarshaler,
}
}
return nil
Expand All @@ -208,6 +212,13 @@ func (t *TypeReference) IsPtr() bool {
return isPtr
}

func (t *TypeReference) IsNilable() bool {
_, isPtr := t.GO.(*types.Pointer)
_, isMap := t.GO.(*types.Map)
_, isInterface := t.GO.(*types.Interface)
return isPtr || isMap || isInterface
}

func (t *TypeReference) IsSlice() bool {
_, isSlice := t.GO.(*types.Slice)
return isSlice
Expand Down Expand Up @@ -246,44 +257,6 @@ func (t *TypeReference) HasIsZero() bool {
return false
}

func (t *TypeReference) SelfMarshalling() bool {
it := t.GO
if ptr, isPtr := it.(*types.Pointer); isPtr {
it = ptr.Elem()
}
namedType, ok := it.(*types.Named)
if !ok {
return false
}

for i := 0; i < namedType.NumMethods(); i++ {
switch namedType.Method(i).Name() {
case "MarshalGQL":
return true
}
}
return false
}

func (t *TypeReference) SelfUnmarshalling() bool {
it := t.GO
if ptr, isPtr := it.(*types.Pointer); isPtr {
it = ptr.Elem()
}
namedType, ok := it.(*types.Named)
if !ok {
return false
}

for i := 0; i < namedType.NumMethods(); i++ {
switch namedType.Method(i).Name() {
case "UnmarshalGQL":
return true
}
}
return false
}

func (t *TypeReference) UniquenessKey() string {
var nullability = "O"
if t.GQL.NonNull {
Expand Down Expand Up @@ -392,11 +365,27 @@ func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret
ref.GO = fun.Type().(*types.Signature).Params().At(0).Type()
ref.Marshaler = fun
ref.Unmarshaler = types.NewFunc(0, fun.Pkg(), "Unmarshal"+typeName, nil)
} else if hasMethod(obj.Type(), "MarshalGQL") && hasMethod(obj.Type(), "UnmarshalGQL") {
ref.GO = obj.Type()
ref.IsMarshaler = true
} else if underlying := basicUnderlying(obj.Type()); underlying != nil && underlying.Kind() == types.String {
// Special case for named types wrapping strings. Used by default enum implementations.

ref.GO = obj.Type()
ref.CastType = underlying

underlyingRef, err := b.TypeReference(&ast.Type{NamedType: "String"}, nil)
if err != nil {
return nil, err
}

ref.Marshaler = underlyingRef.Marshaler
ref.Unmarshaler = underlyingRef.Unmarshaler
} else {
ref.GO = obj.Type()
}

ref.GO = b.CopyModifiersFromAst(schemaType, def.Kind != ast.Interface, ref.GO)
ref.GO = b.CopyModifiersFromAst(schemaType, ref.GO)

if bindTarget != nil {
if err = code.CompatibleTypes(ref.GO, bindTarget); err != nil {
Expand All @@ -411,14 +400,52 @@ func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret
return nil, fmt.Errorf("%s has type compatible with %s", schemaType.Name(), bindTarget.String())
}

func (b *Binder) CopyModifiersFromAst(t *ast.Type, usePtr bool, base types.Type) types.Type {
func (b *Binder) CopyModifiersFromAst(t *ast.Type, base types.Type) types.Type {
if t.Elem != nil {
return types.NewSlice(b.CopyModifiersFromAst(t.Elem, usePtr, base))
return types.NewSlice(b.CopyModifiersFromAst(t.Elem, base))
}

var isInterface bool
if named, ok := base.(*types.Named); ok {
_, isInterface = named.Underlying().(*types.Interface)
}

if !t.NonNull && usePtr {
if !isInterface && !t.NonNull {
return types.NewPointer(base)
}

return base
}

func hasMethod(it types.Type, name string) bool {
if ptr, isPtr := it.(*types.Pointer); isPtr {
it = ptr.Elem()
}
namedType, ok := it.(*types.Named)
if !ok {
return false
}

for i := 0; i < namedType.NumMethods(); i++ {
if namedType.Method(i).Name() == name {
return true
}
}
return false
}

func basicUnderlying(it types.Type) *types.Basic {
if ptr, isPtr := it.(*types.Pointer); isPtr {
it = ptr.Elem()
}
namedType, ok := it.(*types.Named)
if !ok {
return nil
}

if basic, ok := namedType.Underlying().(*types.Basic); ok {
return basic
}

return nil
}
Loading

0 comments on commit bf79bc9

Please sign in to comment.