aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Muré <batolettre@gmail.com>2018-07-29 18:11:33 +0200
committerMichael Muré <batolettre@gmail.com>2018-07-29 18:51:56 +0200
commit6363518c85cbd8247a5f6507b8a1dd3903cfb71d (patch)
treeaa51652e9881196b3637247988cbd5155f42b5e2
parentff2fd14e3f10a7206d4ec86f07e524cfa290e0fc (diff)
downloadgit-bug-6363518c85cbd8247a5f6507b8a1dd3903cfb71d.tar.gz
relay connection working with gqlgen
-rw-r--r--Gopkg.lock29
-rw-r--r--Gopkg.toml12
-rw-r--r--cache/cache.go10
-rw-r--r--commands/webui.go12
-rw-r--r--graphql2/gqlgen.yml11
-rw-r--r--graphql2/handler.go18
-rw-r--r--graphql2/relay.go39
-rw-r--r--graphql2/resolvers.go82
-rw-r--r--graphql2/resolvers/bug.go61
-rw-r--r--graphql2/resolvers/generated_graph.go (renamed from graphql2/gen/graph.go)946
-rw-r--r--graphql2/resolvers/generated_model.go (renamed from graphql2/gen/model.go)40
-rw-r--r--graphql2/resolvers/operations.go54
-rw-r--r--graphql2/resolvers/pager_bug.go225
-rw-r--r--graphql2/resolvers/pager_comment.go225
-rw-r--r--graphql2/resolvers/pager_operation.go225
-rw-r--r--graphql2/resolvers/pagers.go51
-rw-r--r--graphql2/resolvers/pagers_template.go224
-rw-r--r--graphql2/resolvers/query.go36
-rw-r--r--graphql2/resolvers/repo.go26
-rw-r--r--graphql2/resolvers/root.go53
-rw-r--r--graphql2/schema.graphql120
-rw-r--r--vendor/github.com/cheekybits/genny/LICENSE22
-rw-r--r--vendor/github.com/cheekybits/genny/generic/doc.go2
-rw-r--r--vendor/github.com/cheekybits/genny/generic/generic.go13
-rw-r--r--vendor/github.com/gorilla/websocket/.gitignore25
-rw-r--r--vendor/github.com/gorilla/websocket/.travis.yml19
-rw-r--r--vendor/github.com/gorilla/websocket/AUTHORS8
-rw-r--r--vendor/github.com/gorilla/websocket/LICENSE22
-rw-r--r--vendor/github.com/gorilla/websocket/README.md64
-rw-r--r--vendor/github.com/gorilla/websocket/client.go392
-rw-r--r--vendor/github.com/gorilla/websocket/client_clone.go16
-rw-r--r--vendor/github.com/gorilla/websocket/client_clone_legacy.go38
-rw-r--r--vendor/github.com/gorilla/websocket/compression.go148
-rw-r--r--vendor/github.com/gorilla/websocket/conn.go1149
-rw-r--r--vendor/github.com/gorilla/websocket/conn_read.go18
-rw-r--r--vendor/github.com/gorilla/websocket/conn_read_legacy.go21
-rw-r--r--vendor/github.com/gorilla/websocket/doc.go180
-rw-r--r--vendor/github.com/gorilla/websocket/json.go55
-rw-r--r--vendor/github.com/gorilla/websocket/mask.go55
-rw-r--r--vendor/github.com/gorilla/websocket/mask_safe.go15
-rw-r--r--vendor/github.com/gorilla/websocket/prepared.go103
-rw-r--r--vendor/github.com/gorilla/websocket/server.go291
-rw-r--r--vendor/github.com/gorilla/websocket/util.go214
-rw-r--r--vendor/github.com/vektah/gqlgen/LICENSE19
-rw-r--r--vendor/github.com/vektah/gqlgen/graphql/bool.go30
-rw-r--r--vendor/github.com/vektah/gqlgen/graphql/context.go145
-rw-r--r--vendor/github.com/vektah/gqlgen/graphql/defer.go30
-rw-r--r--vendor/github.com/vektah/gqlgen/graphql/error.go46
-rw-r--r--vendor/github.com/vektah/gqlgen/graphql/exec.go118
-rw-r--r--vendor/github.com/vektah/gqlgen/graphql/float.go26
-rw-r--r--vendor/github.com/vektah/gqlgen/graphql/id.go33
-rw-r--r--vendor/github.com/vektah/gqlgen/graphql/int.go26
-rw-r--r--vendor/github.com/vektah/gqlgen/graphql/jsonw.go83
-rw-r--r--vendor/github.com/vektah/gqlgen/graphql/map.go24
-rw-r--r--vendor/github.com/vektah/gqlgen/graphql/oneshot.go14
-rw-r--r--vendor/github.com/vektah/gqlgen/graphql/recovery.go19
-rw-r--r--vendor/github.com/vektah/gqlgen/graphql/response.go18
-rw-r--r--vendor/github.com/vektah/gqlgen/graphql/string.go63
-rw-r--r--vendor/github.com/vektah/gqlgen/graphql/time.go21
-rw-r--r--vendor/github.com/vektah/gqlgen/handler/graphql.go235
-rw-r--r--vendor/github.com/vektah/gqlgen/handler/playground.go51
-rw-r--r--vendor/github.com/vektah/gqlgen/handler/stub.go45
-rw-r--r--vendor/github.com/vektah/gqlgen/handler/websocket.go245
-rw-r--r--vendor/github.com/vektah/gqlgen/neelance/LICENSE24
-rw-r--r--vendor/github.com/vektah/gqlgen/neelance/common/directive.go32
-rw-r--r--vendor/github.com/vektah/gqlgen/neelance/common/lexer.go122
-rw-r--r--vendor/github.com/vektah/gqlgen/neelance/common/literals.go206
-rw-r--r--vendor/github.com/vektah/gqlgen/neelance/common/types.go80
-rw-r--r--vendor/github.com/vektah/gqlgen/neelance/common/values.go77
-rw-r--r--vendor/github.com/vektah/gqlgen/neelance/errors/errors.go41
-rw-r--r--vendor/github.com/vektah/gqlgen/neelance/introspection/introspection.go313
-rw-r--r--vendor/github.com/vektah/gqlgen/neelance/introspection/query.go104
-rw-r--r--vendor/github.com/vektah/gqlgen/neelance/query/query.go261
-rw-r--r--vendor/github.com/vektah/gqlgen/neelance/schema/meta.go193
-rw-r--r--vendor/github.com/vektah/gqlgen/neelance/schema/schema.go489
-rw-r--r--vendor/github.com/vektah/gqlgen/neelance/tests/testdata/LICENSE33
-rw-r--r--vendor/github.com/vektah/gqlgen/neelance/validation/suggestion.go71
-rw-r--r--vendor/github.com/vektah/gqlgen/neelance/validation/validation.go861
78 files changed, 8738 insertions, 829 deletions
diff --git a/Gopkg.lock b/Gopkg.lock
index fea2499b..b6f9df80 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -2,6 +2,12 @@
[[projects]]
+ branch = "master"
+ name = "github.com/cheekybits/genny"
+ packages = ["generic"]
+ revision = "9127e812e1e9e501ce899a18121d316ecb52e4ba"
+
+[[projects]]
name = "github.com/cpuguy83/go-md2man"
packages = ["md2man"]
revision = "20f5889cbdc3c73dbd2862796665e7c465ade7d1"
@@ -32,6 +38,12 @@
version = "v1.6.2"
[[projects]]
+ name = "github.com/gorilla/websocket"
+ packages = ["."]
+ revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
+ version = "v1.2.0"
+
+[[projects]]
name = "github.com/graphql-go/graphql"
packages = [
".",
@@ -119,6 +131,21 @@
version = "v1.0.1"
[[projects]]
+ name = "github.com/vektah/gqlgen"
+ packages = [
+ "graphql",
+ "handler",
+ "neelance/common",
+ "neelance/errors",
+ "neelance/introspection",
+ "neelance/query",
+ "neelance/schema",
+ "neelance/validation"
+ ]
+ revision = "381b34691fd93829e50ba8821412dc3467ec4821"
+ version = "0.3.0"
+
+[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix"]
@@ -133,6 +160,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
- inputs-digest = "bbcfc01c18bb3703bea4b5d08a015a6bcc74f7ea2e9eb130c36e551745f2ec06"
+ inputs-digest = "c70340117a5b5a1d50ad4e8c20e51b01ff6cbec9e3c49911a066e6fd1115b854"
solver-name = "gps-cdcl"
solver-version = 1
diff --git a/Gopkg.toml b/Gopkg.toml
index 9af5a30b..66ca072c 100644
--- a/Gopkg.toml
+++ b/Gopkg.toml
@@ -53,13 +53,9 @@
version = "v0.0.3"
[[constraint]]
- name = "github.com/graphql-go/graphql"
- version = "v0.7.5"
-
-[[constraint]]
- name = "github.com/graphql-go/handler"
- version = "v0.2.1"
-
-[[constraint]]
branch = "master"
name = "github.com/dustin/go-humanize"
+
+[[constraint]]
+ name = "github.com/vektah/gqlgen"
+ version = "0.3.0"
diff --git a/cache/cache.go b/cache/cache.go
index c440646e..779b95b0 100644
--- a/cache/cache.go
+++ b/cache/cache.go
@@ -27,7 +27,7 @@ type RepoCacher interface {
}
type BugCacher interface {
- Snapshot() bug.Snapshot
+ Snapshot() *bug.Snapshot
ClearSnapshot()
}
@@ -37,8 +37,8 @@ type RootCache struct {
repos map[string]RepoCacher
}
-func NewCache() Cacher {
- return &RootCache{
+func NewCache() RootCache {
+ return RootCache{
repos: make(map[string]RepoCacher),
}
}
@@ -172,12 +172,12 @@ func NewBugCache(b *bug.Bug) BugCacher {
}
}
-func (c BugCache) Snapshot() bug.Snapshot {
+func (c BugCache) Snapshot() *bug.Snapshot {
if c.snap == nil {
snap := c.bug.Compile()
c.snap = &snap
}
- return *c.snap
+ return c.snap
}
func (c BugCache) ClearSnapshot() {
diff --git a/commands/webui.go b/commands/webui.go
index 802fc5df..80bda980 100644
--- a/commands/webui.go
+++ b/commands/webui.go
@@ -2,12 +2,13 @@ package commands
import (
"fmt"
- "github.com/MichaelMure/git-bug/graphql"
+ "github.com/MichaelMure/git-bug/graphql2"
"github.com/MichaelMure/git-bug/webui"
"github.com/gorilla/mux"
"github.com/phayes/freeport"
"github.com/skratchdot/open-golang/open"
"github.com/spf13/cobra"
+ "github.com/vektah/gqlgen/handler"
"log"
"net/http"
)
@@ -28,16 +29,11 @@ func runWebUI(cmd *cobra.Command, args []string) error {
fmt.Printf("Web UI available at %s\n", webUiAddr)
- graphqlHandler, err := graphql.NewHandler(repo)
-
- if err != nil {
- return err
- }
-
router := mux.NewRouter()
// Routes
- router.Path("/graphql").Handler(graphqlHandler)
+ router.Path("/playground").Handler(handler.Playground("git-bug", "/graphql"))
+ router.Path("/graphql").Handler(graphql2.NewHandler(repo))
router.PathPrefix("/").Handler(http.FileServer(webui.WebUIAssets))
open.Run(webUiAddr)
diff --git a/graphql2/gqlgen.yml b/graphql2/gqlgen.yml
index 60ea0c49..d7c096bf 100644
--- a/graphql2/gqlgen.yml
+++ b/graphql2/gqlgen.yml
@@ -1,10 +1,17 @@
schema: schema.graphql
exec:
- filename: gen/graph.go
+ filename: resolvers/generated_graph.go
model:
- filename: gen/model.go
+ filename: resolvers/generated_model.go
models:
+ Repository:
+ fields:
+ bug:
+ resolver: true
+ allBugs:
+ resolver: true
+# model: github.com/MichaelMure/git-bug/graphql2/resolvers.repoResolver
Bug:
model: github.com/MichaelMure/git-bug/bug.Snapshot
Comment:
diff --git a/graphql2/handler.go b/graphql2/handler.go
new file mode 100644
index 00000000..2bf6df8c
--- /dev/null
+++ b/graphql2/handler.go
@@ -0,0 +1,18 @@
+//go:generate gorunpkg github.com/vektah/gqlgen
+
+package graphql2
+
+import (
+ "github.com/MichaelMure/git-bug/graphql2/resolvers"
+ "github.com/MichaelMure/git-bug/repository"
+ "github.com/vektah/gqlgen/handler"
+ "net/http"
+)
+
+func NewHandler(repo repository.Repo) http.Handler {
+ backend := resolvers.NewRootResolver()
+
+ backend.RegisterDefaultRepository(repo)
+
+ return handler.GraphQL(resolvers.NewExecutableSchema(backend))
+}
diff --git a/graphql2/relay.go b/graphql2/relay.go
new file mode 100644
index 00000000..b037f28b
--- /dev/null
+++ b/graphql2/relay.go
@@ -0,0 +1,39 @@
+package graphql2
+
+import (
+ "encoding/base64"
+ "strings"
+)
+
+
+type ResolvedGlobalID struct {
+ Type string `json:"type"`
+ ID string `json:"id"`
+}
+
+// Takes a type name and an ID specific to that type name, and returns a
+// "global ID" that is unique among all types.
+func ToGlobalID(ttype string, id string) string {
+ str := ttype + ":" + id
+ encStr := base64.StdEncoding.EncodeToString([]byte(str))
+ return encStr
+}
+
+// Takes the "global ID" created by toGlobalID, and returns the type name and ID
+// used to create it.
+func FromGlobalID(globalID string) *ResolvedGlobalID {
+ strID := ""
+ b, err := base64.StdEncoding.DecodeString(globalID)
+ if err == nil {
+ strID = string(b)
+ }
+ tokens := strings.Split(strID, ":")
+ if len(tokens) < 2 {
+ return nil
+ }
+ return &ResolvedGlobalID{
+ Type: tokens[0],
+ ID: tokens[1],
+ }
+}
+
diff --git a/graphql2/resolvers.go b/graphql2/resolvers.go
deleted file mode 100644
index fa47f222..00000000
--- a/graphql2/resolvers.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package graphql2
-
-import (
- "context"
- "fmt"
- "github.com/MichaelMure/git-bug/bug"
- "github.com/MichaelMure/git-bug/bug/operations"
- "github.com/MichaelMure/git-bug/cache"
- "github.com/MichaelMure/git-bug/graphql2/gen"
- "time"
-)
-
-type Backend struct {
- cache cache.RootCache
-}
-
-func (*Backend) Bug_labels(ctx context.Context, obj *bug.Snapshot) ([]*bug.Label, error) {
- return obj.Labels
-}
-
-func (*Backend) LabelChangeOperation_added(ctx context.Context, obj *operations.LabelChangeOperation) ([]*bug.Label, error) {
- panic("implement me")
-}
-
-func (*Backend) LabelChangeOperation_removed(ctx context.Context, obj *operations.LabelChangeOperation) ([]*bug.Label, error) {
- panic("implement me")
-}
-
-func (*Backend) AddCommentOperation_date(ctx context.Context, obj *operations.AddCommentOperation) (time.Time, error) {
- return obj.Time(), nil
-}
-
-func (*Backend) Bug_status(ctx context.Context, obj *bug.Snapshot) (gen.Status, error) {
- return convertStatus(obj.Status)
-}
-
-func (*Backend) Bug_comments(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int, query *string) (gen.CommentConnection, error) {
- panic("implement me")
-}
-
-func (*Backend) Bug_operations(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int, query *string) (gen.OperationConnection, error) {
- panic("implement me")
-}
-
-func (*Backend) CreateOperation_date(ctx context.Context, obj *operations.CreateOperation) (time.Time, error) {
- return obj.Time(), nil
-}
-
-func (*Backend) LabelChangeOperation_date(ctx context.Context, obj *operations.LabelChangeOperation) (time.Time, error) {
- return obj.Time(), nil
-}
-
-func (*Backend) RootQuery_allBugs(ctx context.Context, after *string, before *string, first *int, last *int, query *string) (gen.BugConnection, error) {
- panic("implement me")
-}
-
-func (*Backend) RootQuery_bug(ctx context.Context, id string) (*bug.Snapshot, error) {
- panic("implement me")
-}
-
-func (*Backend) SetStatusOperation_date(ctx context.Context, obj *operations.SetStatusOperation) (time.Time, error) {
- return obj.Time(), nil
-}
-
-func (*Backend) SetStatusOperation_status(ctx context.Context, obj *operations.SetStatusOperation) (gen.Status, error) {
- return convertStatus(obj.Status)
-}
-
-func (*Backend) SetTitleOperation_date(ctx context.Context, obj *operations.SetTitleOperation) (time.Time, error) {
- return obj.Time(), nil
-}
-
-func convertStatus(status bug.Status) (gen.Status, error) {
- switch status {
- case bug.OpenStatus:
- return gen.StatusOpen, nil
- case bug.ClosedStatus:
- return gen.StatusClosed, nil
- }
-
- return "", fmt.Errorf("Unknown status")
-}
diff --git a/graphql2/resolvers/bug.go b/graphql2/resolvers/bug.go
new file mode 100644
index 00000000..ad6c288b
--- /dev/null
+++ b/graphql2/resolvers/bug.go
@@ -0,0 +1,61 @@
+package resolvers
+
+import (
+ "context"
+ "github.com/MichaelMure/git-bug/bug"
+ "github.com/MichaelMure/git-bug/cache"
+)
+
+type bugResolver struct {
+ cache cache.Cacher
+}
+
+func (bugResolver) Status(ctx context.Context, obj *bug.Snapshot) (Status, error) {
+ return convertStatus(obj.Status)
+}
+
+func (bugResolver) Comments(ctx context.Context, obj *bug.Snapshot, input ConnectionInput) (CommentConnection, error) {
+ var connection CommentConnection
+
+ edger := func(comment bug.Comment, offset int) Edge {
+ return CommentEdge{
+ Node: comment,
+ Cursor: offsetToCursor(offset),
+ }
+ }
+
+ edges, pageInfo, err := BugCommentPaginate(obj.Comments, edger, input)
+
+ if err != nil {
+ return connection, err
+ }
+
+ connection.Edges = edges
+ connection.PageInfo = pageInfo
+ connection.TotalCount = len(obj.Comments)
+
+ return connection, nil
+}
+
+func (bugResolver) Operations(ctx context.Context, obj *bug.Snapshot, input ConnectionInput) (OperationConnection, error) {
+ var connection OperationConnection
+
+ edger := func(op bug.Operation, offset int) Edge {
+ return OperationEdge{
+ Node: op.(OperationUnion),
+ Cursor: offsetToCursor(offset),
+ }
+ }
+
+ edges, pageInfo, err := BugOperationPaginate(obj.Operations, edger, input)
+
+ if err != nil {
+ return connection, err
+ }
+
+ connection.Edges = edges
+ connection.PageInfo = pageInfo
+ connection.TotalCount = len(obj.Operations)
+
+ return connection, nil
+}
diff --git a/graphql2/gen/graph.go b/graphql2/resolvers/generated_graph.go
index d5249dea..3d752ddc 100644
--- a/graphql2/gen/graph.go
+++ b/graphql2/resolvers/generated_graph.go
@@ -1,6 +1,6 @@
// Code generated by github.com/vektah/gqlgen, DO NOT EDIT.
-package gen
+package resolvers
import (
"bytes"
@@ -31,18 +31,19 @@ type Resolvers interface {
AddCommentOperation_date(ctx context.Context, obj *operations.AddCommentOperation) (time.Time, error)
Bug_status(ctx context.Context, obj *bug.Snapshot) (Status, error)
- Bug_labels(ctx context.Context, obj *bug.Snapshot) ([]*bug.Label, error)
- Bug_comments(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int, query *string) (CommentConnection, error)
- Bug_operations(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int, query *string) (OperationConnection, error)
+
+ Bug_comments(ctx context.Context, obj *bug.Snapshot, input ConnectionInput) (CommentConnection, error)
+ Bug_operations(ctx context.Context, obj *bug.Snapshot, input ConnectionInput) (OperationConnection, error)
CreateOperation_date(ctx context.Context, obj *operations.CreateOperation) (time.Time, error)
LabelChangeOperation_date(ctx context.Context, obj *operations.LabelChangeOperation) (time.Time, error)
- LabelChangeOperation_added(ctx context.Context, obj *operations.LabelChangeOperation) ([]*bug.Label, error)
- LabelChangeOperation_removed(ctx context.Context, obj *operations.LabelChangeOperation) ([]*bug.Label, error)
- RootQuery_allBugs(ctx context.Context, after *string, before *string, first *int, last *int, query *string) (BugConnection, error)
- RootQuery_bug(ctx context.Context, id string) (*bug.Snapshot, error)
+ Query_defaultRepository(ctx context.Context) (*repoResolver, error)
+ Query_repository(ctx context.Context, id string) (*repoResolver, error)
+
+ Repository_allBugs(ctx context.Context, obj *repoResolver, input ConnectionInput) (BugConnection, error)
+ Repository_bug(ctx context.Context, obj *repoResolver, prefix string) (*bug.Snapshot, error)
SetStatusOperation_date(ctx context.Context, obj *operations.SetStatusOperation) (time.Time, error)
SetStatusOperation_status(ctx context.Context, obj *operations.SetStatusOperation) (Status, error)
@@ -55,7 +56,8 @@ type ResolverRoot interface {
Bug() BugResolver
CreateOperation() CreateOperationResolver
LabelChangeOperation() LabelChangeOperationResolver
- RootQuery() RootQueryResolver
+ Query() QueryResolver
+ Repository() RepositoryResolver
SetStatusOperation() SetStatusOperationResolver
SetTitleOperation() SetTitleOperationResolver
}
@@ -64,21 +66,23 @@ type AddCommentOperationResolver interface {
}
type BugResolver interface {
Status(ctx context.Context, obj *bug.Snapshot) (Status, error)
- Labels(ctx context.Context, obj *bug.Snapshot) ([]*bug.Label, error)
- Comments(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int, query *string) (CommentConnection, error)
- Operations(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int, query *string) (OperationConnection, error)
+
+ Comments(ctx context.Context, obj *bug.Snapshot, input ConnectionInput) (CommentConnection, error)
+ Operations(ctx context.Context, obj *bug.Snapshot, input ConnectionInput) (OperationConnection, error)
}
type CreateOperationResolver interface {
Date(ctx context.Context, obj *operations.CreateOperation) (time.Time, error)
}
type LabelChangeOperationResolver interface {
Date(ctx context.Context, obj *operations.LabelChangeOperation) (time.Time, error)
- Added(ctx context.Context, obj *operations.LabelChangeOperation) ([]*bug.Label, error)
- Removed(ctx context.Context, obj *operations.LabelChangeOperation) ([]*bug.Label, error)
}
-type RootQueryResolver interface {
- AllBugs(ctx context.Context, after *string, before *string, first *int, last *int, query *string) (BugConnection, error)
- Bug(ctx context.Context, id string) (*bug.Snapshot, error)
+type QueryResolver interface {
+ DefaultRepository(ctx context.Context) (*repoResolver, error)
+ Repository(ctx context.Context, id string) (*repoResolver, error)
+}
+type RepositoryResolver interface {
+ AllBugs(ctx context.Context, obj *repoResolver, input ConnectionInput) (BugConnection, error)
+ Bug(ctx context.Context, obj *repoResolver, prefix string) (*bug.Snapshot, error)
}
type SetStatusOperationResolver interface {
Date(ctx context.Context, obj *operations.SetStatusOperation) (time.Time, error)
@@ -100,16 +104,12 @@ func (s shortMapper) Bug_status(ctx context.Context, obj *bug.Snapshot) (Status,
return s.r.Bug().Status(ctx, obj)
}
-func (s shortMapper) Bug_labels(ctx context.Context, obj *bug.Snapshot) ([]*bug.Label, error) {
- return s.r.Bug().Labels(ctx, obj)
+func (s shortMapper) Bug_comments(ctx context.Context, obj *bug.Snapshot, input ConnectionInput) (CommentConnection, error) {
+ return s.r.Bug().Comments(ctx, obj, input)
}
-func (s shortMapper) Bug_comments(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int, query *string) (CommentConnection, error) {
- return s.r.Bug().Comments(ctx, obj, after, before, first, last, query)
-}
-
-func (s shortMapper) Bug_operations(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int, query *string) (OperationConnection, error) {
- return s.r.Bug().Operations(ctx, obj, after, before, first, last, query)
+func (s shortMapper) Bug_operations(ctx context.Context, obj *bug.Snapshot, input ConnectionInput) (OperationConnection, error) {
+ return s.r.Bug().Operations(ctx, obj, input)
}
func (s shortMapper) CreateOperation_date(ctx context.Context, obj *operations.CreateOperation) (time.Time, error) {
@@ -120,20 +120,20 @@ func (s shortMapper) LabelChangeOperation_date(ctx context.Context, obj *operati
return s.r.LabelChangeOperation().Date(ctx, obj)
}
-func (s shortMapper) LabelChangeOperation_added(ctx context.Context, obj *operations.LabelChangeOperation) ([]*bug.Label, error) {
- return s.r.LabelChangeOperation().Added(ctx, obj)
+func (s shortMapper) Query_defaultRepository(ctx context.Context) (*repoResolver, error) {
+ return s.r.Query().DefaultRepository(ctx)
}
-func (s shortMapper) LabelChangeOperation_removed(ctx context.Context, obj *operations.LabelChangeOperation) ([]*bug.Label, error) {
- return s.r.LabelChangeOperation().Removed(ctx, obj)
+func (s shortMapper) Query_repository(ctx context.Context, id string) (*repoResolver, error) {
+ return s.r.Query().Repository(ctx, id)
}
-func (s shortMapper) RootQuery_allBugs(ctx context.Context, after *string, before *string, first *int, last *int, query *string) (BugConnection, error) {
- return s.r.RootQuery().AllBugs(ctx, after, before, first, last, query)
+func (s shortMapper) Repository_allBugs(ctx context.Context, obj *repoResolver, input ConnectionInput) (BugConnection, error) {
+ return s.r.Repository().AllBugs(ctx, obj, input)
}
-func (s shortMapper) RootQuery_bug(ctx context.Context, id string) (*bug.Snapshot, error) {
- return s.r.RootQuery().Bug(ctx, id)
+func (s shortMapper) Repository_bug(ctx context.Context, obj *repoResolver, prefix string) (*bug.Snapshot, error) {
+ return s.r.Repository().Bug(ctx, obj, prefix)
}
func (s shortMapper) SetStatusOperation_date(ctx context.Context, obj *operations.SetStatusOperation) (time.Time, error) {
@@ -160,7 +160,7 @@ func (e *executableSchema) Query(ctx context.Context, op *query.Operation) *grap
ec := executionContext{graphql.GetRequestContext(ctx), e.resolvers}
buf := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte {
- data := ec._RootQuery(ctx, op.Selections)
+ data := ec._Query(ctx, op.Selections)
var buf bytes.Buffer
data.MarshalGQL(&buf)
return buf.Bytes()
@@ -265,7 +265,7 @@ func (ec *executionContext) _AddCommentOperation_message(ctx context.Context, fi
return graphql.MarshalString(res)
}
-var bugImplementors = []string{"Bug", "Authored", "Commentable"}
+var bugImplementors = []string{"Bug"}
// nolint: gocyclo, errcheck, gas, goconst
func (ec *executionContext) _Bug(ctx context.Context, sel []query.Selection, obj *bug.Snapshot) graphql.Marshaler {
@@ -364,124 +364,37 @@ func (ec *executionContext) _Bug_status(ctx context.Context, field graphql.Colle
}
func (ec *executionContext) _Bug_labels(ctx context.Context, field graphql.CollectedField, obj *bug.Snapshot) graphql.Marshaler {
- ctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{
- Object: "Bug",
- Args: nil,
- Field: field,
- })
- return graphql.Defer(func() (ret graphql.Marshaler) {
- defer func() {
- if r := recover(); r != nil {
- userErr := ec.Recover(ctx, r)
- ec.Error(ctx, userErr)
- ret = graphql.Null
- }
- }()
-
- resTmp, err := ec.ResolverMiddleware(ctx, func(ctx context.Context) (interface{}, error) {
- return ec.resolvers.Bug_labels(ctx, obj)
- })
- if err != nil {
- ec.Error(ctx, err)
- return graphql.Null
- }
- if resTmp == nil {
- return graphql.Null
- }
- res := resTmp.([]*bug.Label)
- arr1 := graphql.Array{}
- for idx1 := range res {
- arr1 = append(arr1, func() graphql.Marshaler {
- rctx := graphql.GetResolverContext(ctx)
- rctx.PushIndex(idx1)
- defer rctx.Pop()
- if res[idx1] == nil {
- return graphql.Null
- }
- return *res[idx1]
- }())
- }
- return arr1
- })
+ rctx := graphql.GetResolverContext(ctx)
+ rctx.Object = "Bug"
+ rctx.Args = nil
+ rctx.Field = field
+ rctx.PushField(field.Alias)
+ defer rctx.Pop()
+ res := obj.Labels
+ arr1 := graphql.Array{}
+ for idx1 := range res {
+ arr1 = append(arr1, func() graphql.Marshaler {
+ rctx := graphql.GetResolverContext(ctx)
+ rctx.PushIndex(idx1)
+ defer rctx.Pop()
+ return res[idx1]
+ }())
+ }
+ return arr1
}
func (ec *executionContext) _Bug_comments(ctx context.Context, field graphql.CollectedField, obj *bug.Snapshot) graphql.Marshaler {
args := map[string]interface{}{}
- var arg0 *string
- if tmp, ok := field.Args["after"]; ok {
+ var arg0 ConnectionInput
+ if tmp, ok := field.Args["input"]; ok {
var err error
- var ptr1 string
- if tmp != nil {
- ptr1, err = graphql.UnmarshalString(tmp)
- arg0 = &ptr1
- }
-
+ arg0, err = UnmarshalConnectionInput(tmp)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
}
- args["after"] = arg0
- var arg1 *string
- if tmp, ok := field.Args["before"]; ok {
- var err error
- var ptr1 string
- if tmp != nil {
- ptr1, err = graphql.UnmarshalString(tmp)
- arg1 = &ptr1
- }
-
- if err != nil {
- ec.Error(ctx, err)
- return graphql.Null
- }
- }
- args["before"] = arg1
- var arg2 *int
- if tmp, ok := field.Args["first"]; ok {
- var err error
- var ptr1 int
- if tmp != nil {
- ptr1, err = graphql.UnmarshalInt(tmp)
- arg2 = &ptr1
- }
-
- if err != nil {
- ec.Error(ctx, err)
- return graphql.Null
- }
- }
- args["first"] = arg2
- var arg3 *int
- if tmp, ok := field.Args["last"]; ok {
- var err error
- var ptr1 int
- if tmp != nil {
- ptr1, err = graphql.UnmarshalInt(tmp)
- arg3 = &ptr1
- }
-
- if err != nil {
- ec.Error(ctx, err)
- return graphql.Null
- }
- }
- args["last"] = arg3
- var arg4 *string
- if tmp, ok := field.Args["query"]; ok {
- var err error
- var ptr1 string
- if tmp != nil {
- ptr1, err = graphql.UnmarshalString(tmp)
- arg4 = &ptr1
- }
-
- if err != nil {
- ec.Error(ctx, err)
- return graphql.Null
- }
- }
- args["query"] = arg4
+ args["input"] = arg0
ctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{
Object: "Bug",
Args: args,
@@ -497,7 +410,7 @@ func (ec *executionContext) _Bug_comments(ctx context.Context, field graphql.Col
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(ctx context.Context) (interface{}, error) {
- return ec.resolvers.Bug_comments(ctx, obj, args["after"].(*string), args["before"].(*string), args["first"].(*int), args["last"].(*int), args["query"].(*string))
+ return ec.resolvers.Bug_comments(ctx, obj, args["input"].(ConnectionInput))
})
if err != nil {
ec.Error(ctx, err)
@@ -513,81 +426,16 @@ func (ec *executionContext) _Bug_comments(ctx context.Context, field graphql.Col
func (ec *executionContext) _Bug_operations(ctx context.Context, field graphql.CollectedField, obj *bug.Snapshot) graphql.Marshaler {
args := map[string]interface{}{}
- var arg0 *string
- if tmp, ok := field.Args["after"]; ok {
- var err error
- var ptr1 string
- if tmp != nil {
- ptr1, err = graphql.UnmarshalString(tmp)
- arg0 = &ptr1
- }
-
- if err != nil {
- ec.Error(ctx, err)
- return graphql.Null
- }
- }
- args["after"] = arg0
- var arg1 *string
- if tmp, ok := field.Args["before"]; ok {
- var err error
- var ptr1 string
- if tmp != nil {
- ptr1, err = graphql.UnmarshalString(tmp)
- arg1 = &ptr1
- }
-
- if err != nil {
- ec.Error(ctx, err)
- return graphql.Null
- }
- }
- args["before"] = arg1
- var arg2 *int
- if tmp, ok := field.Args["first"]; ok {
- var err error
- var ptr1 int
- if tmp != nil {
- ptr1, err = graphql.UnmarshalInt(tmp)
- arg2 = &ptr1
- }
-
- if err != nil {
- ec.Error(ctx, err)
- return graphql.Null
- }
- }
- args["first"] = arg2
- var arg3 *int
- if tmp, ok := field.Args["last"]; ok {
- var err error
- var ptr1 int
- if tmp != nil {
- ptr1, err = graphql.UnmarshalInt(tmp)
- arg3 = &ptr1
- }
-
- if err != nil {
- ec.Error(ctx, err)
- return graphql.Null
- }
- }
- args["last"] = arg3
- var arg4 *string
- if tmp, ok := field.Args["query"]; ok {
+ var arg0 ConnectionInput
+ if tmp, ok := field.Args["input"]; ok {
var err error
- var ptr1 string
- if tmp != nil {
- ptr1, err = graphql.UnmarshalString(tmp)
- arg4 = &ptr1
- }
-
+ arg0, err = UnmarshalConnectionInput(tmp)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
}
- args["query"] = arg4
+ args["input"] = arg0
ctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{
Object: "Bug",
Args: args,
@@ -603,7 +451,7 @@ func (ec *executionContext) _Bug_operations(ctx context.Context, field graphql.C
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(ctx context.Context) (interface{}, error) {
- return ec.resolvers.Bug_operations(ctx, obj, args["after"].(*string), args["before"].(*string), args["first"].(*int), args["last"].(*int), args["query"].(*string))
+ return ec.resolvers.Bug_operations(ctx, obj, args["input"].(ConnectionInput))
})
if err != nil {
ec.Error(ctx, err)
@@ -632,8 +480,6 @@ func (ec *executionContext) _BugConnection(ctx context.Context, sel []query.Sele
out.Values[i] = graphql.MarshalString("BugConnection")
case "edges":
out.Values[i] = ec._BugConnection_edges(ctx, field, obj)
- case "nodes":
- out.Values[i] = ec._BugConnection_nodes(ctx, field, obj)
case "pageInfo":
out.Values[i] = ec._BugConnection_pageInfo(ctx, field, obj)
case "totalCount":
@@ -669,29 +515,6 @@ func (ec *executionContext) _BugConnection_edges(ctx context.Context, field grap
return arr1
}
-func (ec *executionContext) _BugConnection_nodes(ctx context.Context, field graphql.CollectedField, obj *BugConnection) graphql.Marshaler {
- rctx := graphql.GetResolverContext(ctx)
- rctx.Object = "BugConnection"
- rctx.Args = nil
- rctx.Field = field
- rctx.PushField(field.Alias)
- defer rctx.Pop()
- res := obj.Nodes
- arr1 := graphql.Array{}
- for idx1 := range res {
- arr1 = append(arr1, func() graphql.Marshaler {
- rctx := graphql.GetResolverContext(ctx)
- rctx.PushIndex(idx1)
- defer rctx.Pop()
- if res[idx1] == nil {
- return graphql.Null
- }
- return ec._Bug(ctx, field.Selections, res[idx1])
- }())
- }
- return arr1
-}
-
func (ec *executionContext) _BugConnection_pageInfo(ctx context.Context, field graphql.CollectedField, obj *BugConnection) graphql.Marshaler {
rctx := graphql.GetResolverContext(ctx)
rctx.Object = "BugConnection"
@@ -758,10 +581,7 @@ func (ec *executionContext) _BugEdge_node(ctx context.Context, field graphql.Col
rctx.PushField(field.Alias)
defer rctx.Pop()
res := obj.Node
- if res == nil {
- return graphql.Null
- }
- return ec._Bug(ctx, field.Selections, res)
+ return ec._Bug(ctx, field.Selections, &res)
}
var commentImplementors = []string{"Comment", "Authored"}
@@ -826,8 +646,6 @@ func (ec *executionContext) _CommentConnection(ctx context.Context, sel []query.
out.Values[i] = graphql.MarshalString("CommentConnection")
case "edges":
out.Values[i] = ec._CommentConnection_edges(ctx, field, obj)
- case "nodes":
- out.Values[i] = ec._CommentConnection_nodes(ctx, field, obj)
case "pageInfo":
out.Values[i] = ec._CommentConnection_pageInfo(ctx, field, obj)
case "totalCount":
@@ -854,33 +672,7 @@ func (ec *executionContext) _CommentConnection_edges(ctx context.Context, field
rctx := graphql.GetResolverContext(ctx)
rctx.PushIndex(idx1)
defer rctx.Pop()
- if res[idx1] == nil {
- return graphql.Null
- }
- return ec._CommentEdge(ctx, field.Selections, res[idx1])
- }())
- }
- return arr1
-}
-
-func (ec *executionContext) _CommentConnection_nodes(ctx context.Context, field graphql.CollectedField, obj *CommentConnection) graphql.Marshaler {
- rctx := graphql.GetResolverContext(ctx)
- rctx.Object = "CommentConnection"
- rctx.Args = nil
- rctx.Field = field
- rctx.PushField(field.Alias)
- defer rctx.Pop()
- res := obj.Nodes
- arr1 := graphql.Array{}
- for idx1 := range res {
- arr1 = append(arr1, func() graphql.Marshaler {
- rctx := graphql.GetResolverContext(ctx)
- rctx.PushIndex(idx1)
- defer rctx.Pop()
- if res[idx1] == nil {
- return graphql.Null
- }
- return ec._Comment(ctx, field.Selections, res[idx1])
+ return ec._CommentEdge(ctx, field.Selections, &res[idx1])
}())
}
return arr1
@@ -1118,87 +910,43 @@ func (ec *executionContext) _LabelChangeOperation_date(ctx context.Context, fiel
}
func (ec *executionContext) _LabelChangeOperation_added(ctx context.Context, field graphql.CollectedField, obj *operations.LabelChangeOperation) graphql.Marshaler {
- ctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{
- Object: "LabelChangeOperation",
- Args: nil,
- Field: field,
- })
- return graphql.Defer(func() (ret graphql.Marshaler) {
- defer func() {
- if r := recover(); r != nil {
- userErr := ec.Recover(ctx, r)
- ec.Error(ctx, userErr)
- ret = graphql.Null
- }
- }()
-
- resTmp, err := ec.ResolverMiddleware(ctx, func(ctx context.Context) (interface{}, error) {
- return ec.resolvers.LabelChangeOperation_added(ctx, obj)
- })
- if err != nil {
- ec.Error(ctx, err)
- return graphql.Null
- }
- if resTmp == nil {
- return graphql.Null
- }
- res := resTmp.([]*bug.Label)
- arr1 := graphql.Array{}
- for idx1 := range res {
- arr1 = append(arr1, func() graphql.Marshaler {
- rctx := graphql.GetResolverContext(ctx)
- rctx.PushIndex(idx1)
- defer rctx.Pop()
- if res[idx1] == nil {
- return graphql.Null
- }
- return *res[idx1]
- }())
- }
- return arr1
- })
+ rctx := graphql.GetResolverContext(ctx)
+ rctx.Object = "LabelChangeOperation"
+ rctx.Args = nil
+ rctx.Field = field
+ rctx.PushField(field.Alias)
+ defer rctx.Pop()
+ res := obj.Added
+ arr1 := graphql.Array{}
+ for idx1 := range res {
+ arr1 = append(arr1, func() graphql.Marshaler {
+ rctx := graphql.GetResolverContext(ctx)
+ rctx.PushIndex(idx1)
+ defer rctx.Pop()
+ return res[idx1]
+ }())
+ }
+ return arr1
}
func (ec *executionContext) _LabelChangeOperation_removed(ctx context.Context, field graphql.CollectedField, obj *operations.LabelChangeOperation) graphql.Marshaler {
- ctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{
- Object: "LabelChangeOperation",
- Args: nil,
- Field: field,
- })
- return graphql.Defer(func() (ret graphql.Marshaler) {
- defer func() {
- if r := recover(); r != nil {
- userErr := ec.Recover(ctx, r)
- ec.Error(ctx, userErr)
- ret = graphql.Null
- }
- }()
-
- resTmp, err := ec.ResolverMiddleware(ctx, func(ctx context.Context) (interface{}, error) {
- return ec.resolvers.LabelChangeOperation_removed(ctx, obj)
- })
- if err != nil {
- ec.Error(ctx, err)
- return graphql.Null
- }
- if resTmp == nil {
- return graphql.Null
- }
- res := resTmp.([]*bug.Label)
- arr1 := graphql.Array{}
- for idx1 := range res {
- arr1 = append(arr1, func() graphql.Marshaler {
- rctx := graphql.GetResolverContext(ctx)
- rctx.PushIndex(idx1)
- defer rctx.Pop()
- if res[idx1] == nil {
- return graphql.Null
- }
- return *res[idx1]
- }())
- }
- return arr1
- })
+ rctx := graphql.GetResolverContext(ctx)
+ rctx.Object = "LabelChangeOperation"
+ rctx.Args = nil
+ rctx.Field = field
+ rctx.PushField(field.Alias)
+ defer rctx.Pop()
+ res := obj.Removed
+ arr1 := graphql.Array{}
+ for idx1 := range res {
+ arr1 = append(arr1, func() graphql.Marshaler {
+ rctx := graphql.GetResolverContext(ctx)
+ rctx.PushIndex(idx1)
+ defer rctx.Pop()
+ return res[idx1]
+ }())
+ }
+ return arr1
}
var operationConnectionImplementors = []string{"OperationConnection"}
@@ -1216,8 +964,6 @@ func (ec *executionContext) _OperationConnection(ctx context.Context, sel []quer
out.Values[i] = graphql.MarshalString("OperationConnection")
case "edges":
out.Values[i] = ec._OperationConnection_edges(ctx, field, obj)
- case "nodes":
- out.Values[i] = ec._OperationConnection_nodes(ctx, field, obj)
case "pageInfo":
out.Values[i] = ec._OperationConnection_pageInfo(ctx, field, obj)
case "totalCount":
@@ -1244,33 +990,7 @@ func (ec *executionContext) _OperationConnection_edges(ctx context.Context, fiel
rctx := graphql.GetResolverContext(ctx)
rctx.PushIndex(idx1)
defer rctx.Pop()
- if res[idx1] == nil {
- return graphql.Null
- }
- return ec._OperationEdge(ctx, field.Selections, res[idx1])
- }())
- }
- return arr1
-}
-
-func (ec *executionContext) _OperationConnection_nodes(ctx context.Context, field graphql.CollectedField, obj *OperationConnection) graphql.Marshaler {
- rctx := graphql.GetResolverContext(ctx)
- rctx.Object = "OperationConnection"
- rctx.Args = nil
- rctx.Field = field
- rctx.PushField(field.Alias)
- defer rctx.Pop()
- res := obj.Nodes
- arr1 := graphql.Array{}
- for idx1 := range res {
- arr1 = append(arr1, func() graphql.Marshaler {
- rctx := graphql.GetResolverContext(ctx)
- rctx.PushIndex(idx1)
- defer rctx.Pop()
- if res[idx1] == nil {
- return graphql.Null
- }
- return ec._OperationUnion(ctx, field.Selections, res[idx1])
+ return ec._OperationEdge(ctx, field.Selections, &res[idx1])
}())
}
return arr1
@@ -1362,10 +1082,6 @@ func (ec *executionContext) _PageInfo(ctx context.Context, sel []query.Selection
out.Values[i] = ec._PageInfo_hasNextPage(ctx, field, obj)
case "hasPreviousPage":
out.Values[i] = ec._PageInfo_hasPreviousPage(ctx, field, obj)
- case "startCursor":
- out.Values[i] = ec._PageInfo_startCursor(ctx, field, obj)
- case "endCursor":
- out.Values[i] = ec._PageInfo_endCursor(ctx, field, obj)
default:
panic("unknown field " + strconv.Quote(field.Name))
}
@@ -1396,34 +1112,6 @@ func (ec *executionContext) _PageInfo_hasPreviousPage(ctx context.Context, field
return graphql.MarshalBoolean(res)
}
-func (ec *executionContext) _PageInfo_startCursor(ctx context.Context, field graphql.CollectedField, obj *PageInfo) graphql.Marshaler {
- rctx := graphql.GetResolverContext(ctx)
- rctx.Object = "PageInfo"
- rctx.Args = nil
- rctx.Field = field
- rctx.PushField(field.Alias)
- defer rctx.Pop()
- res := obj.StartCursor
- if res == nil {
- return graphql.Null
- }
- return graphql.MarshalString(*res)
-}
-
-func (ec *executionContext) _PageInfo_endCursor(ctx context.Context, field graphql.CollectedField, obj *PageInfo) graphql.Marshaler {
- rctx := graphql.GetResolverContext(ctx)
- rctx.Object = "PageInfo"
- rctx.Args = nil
- rctx.Field = field
- rctx.PushField(field.Alias)
- defer rctx.Pop()
- res := obj.EndCursor
- if res == nil {
- return graphql.Null
- }
- return graphql.MarshalString(*res)
-}
-
var personImplementors = []string{"Person"}
// nolint: gocyclo, errcheck, gas, goconst
@@ -1471,14 +1159,14 @@ func (ec *executionContext) _Person_name(ctx context.Context, field graphql.Coll
return graphql.MarshalString(res)
}
-var rootQueryImplementors = []string{"RootQuery"}
+var queryImplementors = []string{"Query"}
// nolint: gocyclo, errcheck, gas, goconst
-func (ec *executionContext) _RootQuery(ctx context.Context, sel []query.Selection) graphql.Marshaler {
- fields := graphql.CollectFields(ec.Doc, sel, rootQueryImplementors, ec.Variables)
+func (ec *executionContext) _Query(ctx context.Context, sel []query.Selection) graphql.Marshaler {
+ fields := graphql.CollectFields(ec.Doc, sel, queryImplementors, ec.Variables)
ctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{
- Object: "RootQuery",
+ Object: "Query",
})
out := graphql.NewOrderedMap(len(fields))
@@ -1487,15 +1175,15 @@ func (ec *executionContext) _RootQuery(ctx context.Context, sel []query.Selectio
switch field.Name {
case "__typename":
- out.Values[i] = graphql.MarshalString("RootQuery")
- case "allBugs":
- out.Values[i] = ec._RootQuery_allBugs(ctx, field)
- case "bug":
- out.Values[i] = ec._RootQuery_bug(ctx, field)
+ out.Values[i] = graphql.MarshalString("Query")
+ case "defaultRepository":
+ out.Values[i] = ec._Query_defaultRepository(ctx, field)
+ case "repository":
+ out.Values[i] = ec._Query_repository(ctx, field)
case "__schema":
- out.Values[i] = ec._RootQuery___schema(ctx, field)
+ out.Values[i] = ec._Query___schema(ctx, field)
case "__type":
- out.Values[i] = ec._RootQuery___type(ctx, field)
+ out.Values[i] = ec._Query___type(ctx, field)
default:
panic("unknown field " + strconv.Quote(field.Name))
}
@@ -1504,86 +1192,10 @@ func (ec *executionContext) _RootQuery(ctx context.Context, sel []query.Selectio
return out
}
-func (ec *executionContext) _RootQuery_allBugs(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
- args := map[string]interface{}{}
- var arg0 *string
- if tmp, ok := field.Args["after"]; ok {
- var err error
- var ptr1 string
- if tmp != nil {
- ptr1, err = graphql.UnmarshalString(tmp)
- arg0 = &ptr1
- }
-
- if err != nil {
- ec.Error(ctx, err)
- return graphql.Null
- }
- }
- args["after"] = arg0
- var arg1 *string
- if tmp, ok := field.Args["before"]; ok {
- var err error
- var ptr1 string
- if tmp != nil {
- ptr1, err = graphql.UnmarshalString(tmp)
- arg1 = &ptr1
- }
-
- if err != nil {
- ec.Error(ctx, err)
- return graphql.Null
- }
- }
- args["before"] = arg1
- var arg2 *int
- if tmp, ok := field.Args["first"]; ok {
- var err error
- var ptr1 int
- if tmp != nil {
- ptr1, err = graphql.UnmarshalInt(tmp)
- arg2 = &ptr1
- }
-
- if err != nil {
- ec.Error(ctx, err)
- return graphql.Null
- }
- }
- args["first"] = arg2
- var arg3 *int
- if tmp, ok := field.Args["last"]; ok {
- var err error
- var ptr1 int
- if tmp != nil {
- ptr1, err = graphql.UnmarshalInt(tmp)
- arg3 = &ptr1
- }
-
- if err != nil {
- ec.Error(ctx, err)
- return graphql.Null
- }
- }
- args["last"] = arg3
- var arg4 *string
- if tmp, ok := field.Args["query"]; ok {
- var err error
- var ptr1 string
- if tmp != nil {
- ptr1, err = graphql.UnmarshalString(tmp)
- arg4 = &ptr1
- }
-
- if err != nil {
- ec.Error(ctx, err)
- return graphql.Null
- }
- }
- args["query"] = arg4
+func (ec *executionContext) _Query_defaultRepository(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
ctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{
- Object: "RootQuery",
- Args: args,
+ Object: "Query",
+ Args: nil,
Field: field,
})
return graphql.Defer(func() (ret graphql.Marshaler) {
@@ -1596,7 +1208,7 @@ func (ec *executionContext) _RootQuery_allBugs(ctx context.Context, field graphq
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(ctx context.Context) (interface{}, error) {
- return ec.resolvers.RootQuery_allBugs(ctx, args["after"].(*string), args["before"].(*string), args["first"].(*int), args["last"].(*int), args["query"].(*string))
+ return ec.resolvers.Query_defaultRepository(ctx)
})
if err != nil {
ec.Error(ctx, err)
@@ -1605,12 +1217,15 @@ func (ec *executionContext) _RootQuery_allBugs(ctx context.Context, field graphq
if resTmp == nil {
return graphql.Null
}
- res := resTmp.(BugConnection)
- return ec._BugConnection(ctx, field.Selections, &res)
+ res := resTmp.(*repoResolver)
+ if res == nil {
+ return graphql.Null
+ }
+ return ec._Repository(ctx, field.Selections, res)
})
}
-func (ec *executionContext) _RootQuery_bug(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
+func (ec *executionContext) _Query_repository(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
args := map[string]interface{}{}
var arg0 string
if tmp, ok := field.Args["id"]; ok {
@@ -1623,7 +1238,7 @@ func (ec *executionContext) _RootQuery_bug(ctx context.Context, field graphql.Co
}
args["id"] = arg0
ctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{
- Object: "RootQuery",
+ Object: "Query",
Args: args,
Field: field,
})
@@ -1637,7 +1252,7 @@ func (ec *executionContext) _RootQuery_bug(ctx context.Context, field graphql.Co
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(ctx context.Context) (interface{}, error) {
- return ec.resolvers.RootQuery_bug(ctx, args["id"].(string))
+ return ec.resolvers.Query_repository(ctx, args["id"].(string))
})
if err != nil {
ec.Error(ctx, err)
@@ -1646,17 +1261,17 @@ func (ec *executionContext) _RootQuery_bug(ctx context.Context, field graphql.Co
if resTmp == nil {
return graphql.Null
}
- res := resTmp.(*bug.Snapshot)
+ res := resTmp.(*repoResolver)
if res == nil {
return graphql.Null
}
- return ec._Bug(ctx, field.Selections, res)
+ return ec._Repository(ctx, field.Selections, res)
})
}
-func (ec *executionContext) _RootQuery___schema(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
+func (ec *executionContext) _Query___schema(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
rctx := graphql.GetResolverContext(ctx)
- rctx.Object = "RootQuery"
+ rctx.Object = "Query"
rctx.Args = nil
rctx.Field = field
rctx.PushField(field.Alias)
@@ -1668,7 +1283,7 @@ func (ec *executionContext) _RootQuery___schema(ctx context.Context, field graph
return ec.___Schema(ctx, field.Selections, res)
}
-func (ec *executionContext) _RootQuery___type(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
+func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
args := map[string]interface{}{}
var arg0 string
if tmp, ok := field.Args["name"]; ok {
@@ -1681,7 +1296,7 @@ func (ec *executionContext) _RootQuery___type(ctx context.Context, field graphql
}
args["name"] = arg0
rctx := graphql.GetResolverContext(ctx)
- rctx.Object = "RootQuery"
+ rctx.Object = "Query"
rctx.Args = args
rctx.Field = field
rctx.PushField(field.Alias)
@@ -1693,6 +1308,116 @@ func (ec *executionContext) _RootQuery___type(ctx context.Context, field graphql
return ec.___Type(ctx, field.Selections, res)
}
+var repositoryImplementors = []string{"Repository"}
+
+// nolint: gocyclo, errcheck, gas, goconst
+func (ec *executionContext) _Repository(ctx context.Context, sel []query.Selection, obj *repoResolver) graphql.Marshaler {
+ fields := graphql.CollectFields(ec.Doc, sel, repositoryImplementors, ec.Variables)
+
+ out := graphql.NewOrderedMap(len(fields))
+ for i, field := range fields {
+ out.Keys[i] = field.Alias
+
+ switch field.Name {
+ case "__typename":
+ out.Values[i] = graphql.MarshalString("Repository")
+ case "allBugs":
+ out.Values[i] = ec._Repository_allBugs(ctx, field, obj)
+ case "bug":
+ out.Values[i] = ec._Repository_bug(ctx, field, obj)
+ default:
+ panic("unknown field " + strconv.Quote(field.Name))
+ }
+ }
+
+ return out
+}
+
+func (ec *executionContext) _Repository_allBugs(ctx context.Context, field graphql.CollectedField, obj *repoResolver) graphql.Marshaler {
+ args := map[string]interface{}{}
+ var arg0 ConnectionInput
+ if tmp, ok := field.Args["input"]; ok {
+ var err error
+ arg0, err = UnmarshalConnectionInput(tmp)
+ if err != nil {
+ ec.Error(ctx, err)
+ return graphql.Null
+ }
+ }
+ args["input"] = arg0
+ ctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{
+ Object: "Repository",
+ Args: args,
+ Field: field,
+ })
+ return graphql.Defer(func() (ret graphql.Marshaler) {
+ defer func() {
+ if r := recover(); r != nil {
+ userErr := ec.Recover(ctx, r)
+ ec.Error(ctx, userErr)
+ ret = graphql.Null
+ }
+ }()
+
+ resTmp, err := ec.ResolverMiddleware(ctx, func(ctx context.Context) (interface{}, error) {
+ return ec.resolvers.Repository_allBugs(ctx, obj, args["input"].(ConnectionInput))
+ })
+ if err != nil {
+ ec.Error(ctx, err)
+ return graphql.Null
+ }
+ if resTmp == nil {
+ return graphql.Null
+ }
+ res := resTmp.(BugConnection)
+ return ec._BugConnection(ctx, field.Selections, &res)
+ })
+}
+
+func (ec *executionContext) _Repository_bug(ctx context.Context, field graphql.CollectedField, obj *repoResolver) graphql.Marshaler {
+ args := map[string]interface{}{}
+ var arg0 string
+ if tmp, ok := field.Args["prefix"]; ok {
+ var err error
+ arg0, err = graphql.UnmarshalString(tmp)
+ if err != nil {
+ ec.Error(ctx, err)
+ return graphql.Null
+ }
+ }
+ args["prefix"] = arg0
+ ctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{
+ Object: "Repository",
+ Args: args,
+ Field: field,
+ })
+ return graphql.Defer(func() (ret graphql.Marshaler) {
+ defer func() {
+ if r := recover(); r != nil {
+ userErr := ec.Recover(ctx, r)
+ ec.Error(ctx, userErr)
+ ret = graphql.Null
+ }
+ }()
+
+ resTmp, err := ec.ResolverMiddleware(ctx, func(ctx context.Context) (interface{}, error) {
+ return ec.resolvers.Repository_bug(ctx, obj, args["prefix"].(string))
+ })
+ if err != nil {
+ ec.Error(ctx, err)
+ return graphql.Null
+ }
+ if resTmp == nil {
+ return graphql.Null
+ }
+ res := resTmp.(*bug.Snapshot)
+ if res == nil {
+ return graphql.Null
+ }
+ return ec._Bug(ctx, field.Selections, res)
+ })
+}
+
var setStatusOperationImplementors = []string{"SetStatusOperation", "Operation", "Authored"}
// nolint: gocyclo, errcheck, gas, goconst
@@ -2588,23 +2313,6 @@ func (ec *executionContext) _Authored(ctx context.Context, sel []query.Selection
return ec._LabelChangeOperation(ctx, sel, &obj)
case *operations.LabelChangeOperation:
return ec._LabelChangeOperation(ctx, sel, obj)
- case bug.Snapshot:
- return ec._Bug(ctx, sel, &obj)
- case *bug.Snapshot:
- return ec._Bug(ctx, sel, obj)
- default:
- panic(fmt.Errorf("unexpected type %T", obj))
- }
-}
-
-func (ec *executionContext) _Commentable(ctx context.Context, sel []query.Selection, obj *Commentable) graphql.Marshaler {
- switch obj := (*obj).(type) {
- case nil:
- return graphql.Null
- case bug.Snapshot:
- return ec._Bug(ctx, sel, &obj)
- case *bug.Snapshot:
- return ec._Bug(ctx, sel, obj)
default:
panic(fmt.Errorf("unexpected type %T", obj))
}
@@ -2668,6 +2376,62 @@ func (ec *executionContext) _OperationUnion(ctx context.Context, sel []query.Sel
}
}
+func UnmarshalConnectionInput(v interface{}) (ConnectionInput, error) {
+ var it ConnectionInput
+ var asMap = v.(map[string]interface{})
+
+ for k, v := range asMap {
+ switch k {
+ case "after":
+ var err error
+ var ptr1 string
+ if v != nil {
+ ptr1, err = graphql.UnmarshalString(v)
+ it.After = &ptr1
+ }
+
+ if err != nil {
+ return it, err
+ }
+ case "before":
+ var err error
+ var ptr1 string
+ if v != nil {
+ ptr1, err = graphql.UnmarshalString(v)
+ it.Before = &ptr1
+ }
+
+ if err != nil {
+ return it, err
+ }
+ case "first":
+ var err error
+ var ptr1 int
+ if v != nil {
+ ptr1, err = graphql.UnmarshalInt(v)
+ it.First = &ptr1
+ }
+
+ if err != nil {
+ return it, err
+ }
+ case "last":
+ var err error
+ var ptr1 int
+ if v != nil {
+ ptr1, err = graphql.UnmarshalInt(v)
+ it.Last = &ptr1
+ }
+
+ if err != nil {
+ return it, err
+ }
+ }
+ }
+
+ return it, nil
+}
+
func (ec *executionContext) introspectSchema() *introspection.Schema {
return introspection.WrapSchema(parsedSchema)
}
@@ -2680,11 +2444,7 @@ func (ec *executionContext) introspectType(name string) *introspection.Type {
return introspection.WrapType(t)
}
-var parsedSchema = schema.MustParse(`schema {
- query: RootQuery
-}
-
-scalar Time
+var parsedSchema = schema.MustParse(`scalar Time
scalar Label
# Information about pagination in a connection.
@@ -2696,10 +2456,24 @@ type PageInfo {
hasPreviousPage: Boolean!
# When paginating backwards, the cursor to continue.
- startCursor: String
+# startCursor: String
# When paginating forwards, the cursor to continue.
- endCursor: String
+# endCursor: String
+}
+
+input ConnectionInput {
+ # Returns the elements in the list that come after the specified cursor.
+ after: String
+
+ # Returns the elements in the list that come before the specified cursor.
+ before: String
+
+ # Returns the first _n_ elements from the list.
+ first: Int
+
+ # Returns the last _n_ elements from the list.
+ last: Int
}
# Represents an person in a git object.
@@ -2713,8 +2487,7 @@ type Person {
type CommentConnection {
- edges: [CommentEdge]
- nodes: [Comment]
+ edges: [CommentEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
@@ -2724,23 +2497,6 @@ type CommentEdge {
node: Comment!
}
-interface Commentable {
- # A list of comments associated with the object.
- comments(
- # Returns the elements in the list that come after the specified cursor.
- after: String
-
- # Returns the elements in the list that come before the specified cursor.
- before: String
-
- # Returns the first _n_ elements from the list.
- first: Int
-
- # Returns the last _n_ elements from the list.
- last: Int
- ): CommentConnection!
-}
-
# Represents a comment on a bug.
type Comment implements Authored {
# The author of this comment.
@@ -2762,15 +2518,14 @@ interface Authored {
}
type OperationConnection {
- edges: [OperationEdge]!
- nodes: [OperationUnion]!
+ edges: [OperationEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type OperationEdge {
cursor: String!
- node: OperationUnion
+ node: OperationUnion!
}
# An operation applied to a bug.
@@ -2815,8 +2570,8 @@ type LabelChangeOperation implements Operation, Authored {
author: Person!
date: Time!
- added: [Label]!
- removed: [Label]!
+ added: [Label!]!
+ removed: [Label!]!
}
union OperationUnion =
@@ -2826,14 +2581,11 @@ union OperationUnion =
| SetStatusOperation
| LabelChangeOperation
-# The connection type for Label.
+# The connection type for Bug.
type BugConnection {
# A list of edges.
edges: [BugEdge]!
- # A list of nodes.
- nodes: [Bug]!
-
# Information to aid in pagination.
pageInfo: PageInfo!
@@ -2847,70 +2599,30 @@ type BugEdge {
cursor: String!
# The item at the end of the edge.
- node: Bug
+ node: Bug!
}
-type Bug implements Authored, Commentable {
+type Bug {
id: String!
humanId: String!
title: String!
status: Status!
# A list of labels associated with the repository.
- labels: [Label]!
-
- comments(
- # Returns the elements in the list that come after the specified cursor.
- after: String
-
- # Returns the elements in the list that come before the specified cursor.
- before: String
-
- # Returns the first _n_ elements from the list.
- first: Int
-
- # Returns the last _n_ elements from the list.
- last: Int
-
- # If provided, searches comments by name and description.
- query: String
- ): CommentConnection!
+ labels: [Label!]!
- operations(
- # Returns the elements in the list that come after the specified cursor.
- after: String
+ comments(input: ConnectionInput!): CommentConnection!
- # Returns the elements in the list that come before the specified cursor.
- before: String
-
- # Returns the first _n_ elements from the list.
- first: Int
-
- # Returns the last _n_ elements from the list.
- last: Int
-
- # If provided, searches operations by name and description.
- query: String
- ): OperationConnection!
+ operations(input: ConnectionInput!): OperationConnection!
}
-type RootQuery {
- allBugs(
- # Returns the elements in the list that come after the specified cursor.
- after: String
-
- # Returns the elements in the list that come before the specified cursor.
- before: String
-
- # Returns the first _n_ elements from the list.
- first: Int
-
- # Returns the last _n_ elements from the list.
- last: Int
+type Repository {
+ allBugs(input: ConnectionInput!): BugConnection!
+ bug(prefix: String!): Bug
+}
- # If provided, searches labels by name and description.
- query: String
- ): BugConnection!
- bug(id: String!): Bug
+type Query {
+ defaultRepository: Repository
+ repository(id: String!): Repository
}
`)
diff --git a/graphql2/gen/model.go b/graphql2/resolvers/generated_model.go
index 7609180a..f6d78471 100644
--- a/graphql2/gen/model.go
+++ b/graphql2/resolvers/generated_model.go
@@ -1,6 +1,6 @@
// Code generated by github.com/vektah/gqlgen, DO NOT EDIT.
-package gen
+package resolvers
import (
fmt "fmt"
@@ -12,32 +12,34 @@ import (
type Authored interface{}
type BugConnection struct {
- Edges []*BugEdge `json:"edges"`
- Nodes []*bug.Snapshot `json:"nodes"`
- PageInfo PageInfo `json:"pageInfo"`
- TotalCount int `json:"totalCount"`
+ Edges []*BugEdge `json:"edges"`
+ PageInfo PageInfo `json:"pageInfo"`
+ TotalCount int `json:"totalCount"`
}
type BugEdge struct {
- Cursor string `json:"cursor"`
- Node *bug.Snapshot `json:"node"`
+ Cursor string `json:"cursor"`
+ Node bug.Snapshot `json:"node"`
}
type CommentConnection struct {
- Edges []*CommentEdge `json:"edges"`
- Nodes []*bug.Comment `json:"nodes"`
- PageInfo PageInfo `json:"pageInfo"`
- TotalCount int `json:"totalCount"`
+ Edges []CommentEdge `json:"edges"`
+ PageInfo PageInfo `json:"pageInfo"`
+ TotalCount int `json:"totalCount"`
}
type CommentEdge struct {
Cursor string `json:"cursor"`
Node bug.Comment `json:"node"`
}
-type Commentable interface{}
+type ConnectionInput struct {
+ After *string `json:"after"`
+ Before *string `json:"before"`
+ First *int `json:"first"`
+ Last *int `json:"last"`
+}
type Operation interface{}
type OperationConnection struct {
- Edges []*OperationEdge `json:"edges"`
- Nodes []*OperationUnion `json:"nodes"`
- PageInfo PageInfo `json:"pageInfo"`
- TotalCount int `json:"totalCount"`
+ Edges []OperationEdge `json:"edges"`
+ PageInfo PageInfo `json:"pageInfo"`
+ TotalCount int `json:"totalCount"`
}
type OperationEdge struct {
Cursor string `json:"cursor"`
@@ -45,10 +47,8 @@ type OperationEdge struct {
}
type OperationUnion interface{}
type PageInfo struct {
- HasNextPage bool `json:"hasNextPage"`
- HasPreviousPage bool `json:"hasPreviousPage"`
- StartCursor *string `json:"startCursor"`
- EndCursor *string `json:"endCursor"`
+ HasNextPage bool `json:"hasNextPage"`
+ HasPreviousPage bool `json:"hasPreviousPage"`
}
type Status string
diff --git a/graphql2/resolvers/operations.go b/graphql2/resolvers/operations.go
new file mode 100644
index 00000000..1279a1b4
--- /dev/null
+++ b/graphql2/resolvers/operations.go
@@ -0,0 +1,54 @@
+package resolvers
+
+import (
+ "context"
+ "fmt"
+ "github.com/MichaelMure/git-bug/bug"
+ "github.com/MichaelMure/git-bug/bug/operations"
+ "time"
+)
+
+type addCommentOperationResolver struct{}
+
+func (addCommentOperationResolver) Date(ctx context.Context, obj *operations.AddCommentOperation) (time.Time, error) {
+ return obj.Time(), nil
+}
+
+type createOperationResolver struct{}
+
+func (createOperationResolver) Date(ctx context.Context, obj *operations.CreateOperation) (time.Time, error) {
+ return obj.Time(), nil
+}
+
+type labelChangeOperation struct{}
+
+func (labelChangeOperation) Date(ctx context.Context, obj *operations.LabelChangeOperation) (time.Time, error) {
+ return obj.Time(), nil
+}
+
+type setStatusOperationResolver struct{}
+
+func (setStatusOperationResolver) Date(ctx context.Context, obj *operations.SetStatusOperation) (time.Time, error) {
+ return obj.Time(), nil
+}
+
+func (setStatusOperationResolver) Status(ctx context.Context, obj *operations.SetStatusOperation) (Status, error) {
+ return convertStatus(obj.Status)
+}
+
+type setTitleOperationResolver struct{}
+
+func (setTitleOperationResolver) Date(ctx context.Context, obj *operations.SetTitleOperation) (time.Time, error) {
+ return obj.Time(), nil
+}
+
+func convertStatus(status bug.Status) (Status, error) {
+ switch status {
+ case bug.OpenStatus:
+ return StatusOpen, nil
+ case bug.ClosedStatus:
+ return StatusClosed, nil
+ }
+
+ return "", fmt.Errorf("Unknown status")
+}
diff --git a/graphql2/resolvers/pager_bug.go b/graphql2/resolvers/pager_bug.go
new file mode 100644
index 00000000..55acfcc4
--- /dev/null
+++ b/graphql2/resolvers/pager_bug.go
@@ -0,0 +1,225 @@
+// This file was automatically generated by genny.
+// Any changes will be lost if this file is regenerated.
+// see https://github.com/cheekybits/genny
+
+package resolvers
+
+import (
+ "fmt"
+
+ "github.com/MichaelMure/git-bug/bug"
+)
+
+type BugSnapshotEdger func(value bug.Snapshot, offset int) Edge
+
+func BugSnapshotPaginate(source []bug.Snapshot, edger BugSnapshotEdger, input ConnectionInput) ([]BugEdge, PageInfo, error) {
+ var result []BugEdge
+ var pageInfo PageInfo
+
+ offset := 0
+
+ if input.After != nil {
+ for i, value := range source {
+ edge := edger(value, i)
+ if edge.GetCursor() == *input.After {
+ // remove all previous element including the "after" one
+ source = source[i+1:]
+ offset = i + 1
+ break
+ }
+ }
+ }
+
+ if input.Before != nil {
+ for i, value := range source {
+ edge := edger(value, i+offset)
+
+ if edge.GetCursor() == *input.Before {
+ // remove all after element including the "before" one
+ break
+ }
+
+ result = append(result, edge.(BugEdge))
+ }
+ } else {
+ result = make([]BugEdge, len(source))
+
+ for i, value := range source {
+ result[i] = edger(value, i+offset).(BugEdge)
+ }
+ }
+
+ if input.First != nil {
+ if *input.First < 0 {
+ return nil, PageInfo{}, fmt.Errorf("first less than zero")
+ }
+
+ if len(result) > *input.First {
+ // Slice result to be of length first by removing edges from the end
+ result = result[:*input.First]
+ pageInfo.HasNextPage = true
+ }
+ }
+
+ if input.Last != nil {
+ if *input.Last < 0 {
+ return nil, PageInfo{}, fmt.Errorf("last less than zero")
+ }
+
+ if len(result) > *input.Last {
+ // Slice result to be of length last by removing edges from the start
+ result = result[len(result)-*input.Last:]
+ pageInfo.HasPreviousPage = true
+ }
+ }
+
+ return result, pageInfo, nil
+}
+
+// Apply the before/after cursor params to the source and return an array of edges
+//func ApplyCursorToEdges(source []interface{}, edger Edger, input ConnectionInput) []Edge {
+// var result []Edge
+//
+// if input.After != nil {
+// for i, value := range source {
+// edge := edger(value)
+// if edge.Cursor() == *input.After {
+// // remove all previous element including the "after" one
+// source = source[i+1:]
+// break
+// }
+// }
+// }
+//
+// if input.Before != nil {
+// for _, value := range source {
+// edge := edger(value)
+//
+// if edge.Cursor() == *input.Before {
+// // remove all after element including the "before" one
+// break
+// }
+//
+// result = append(result, edge)
+// }
+// } else {
+// result = make([]Edge, len(source))
+//
+// for i, value := range source {
+// result[i] = edger(value)
+// }
+// }
+//
+// return result
+//}
+
+// Apply the first/last cursor params to the edges
+//func EdgesToReturn(edges []Edge, input ConnectionInput) ([]Edge, PageInfo, error) {
+// hasPreviousPage := false
+// hasNextPage := false
+//
+// if input.First != nil {
+// if *input.First < 0 {
+// return nil, nil, fmt.Errorf("first less than zero")
+// }
+//
+// if len(edges) > *input.First {
+// // Slice result to be of length first by removing edges from the end
+// edges = edges[:*input.First]
+// hasNextPage = true
+// }
+// }
+//
+// if input.Last != nil {
+// if *input.Last < 0 {
+// return nil, nil, fmt.Errorf("last less than zero")
+// }
+//
+// if len(edges) > *input.Last {
+// // Slice result to be of length last by removing edges from the start
+// edges = edges[len(edges)-*input.Last:]
+// hasPreviousPage = true
+// }
+// }
+//
+// pageInfo := PageInfo{
+// HasNextPage: hasNextPage,
+// HasPreviousPage: hasPreviousPage,
+// }
+//
+// return edges, pageInfo, nil
+//}
+
+//func EdgesToReturn(allEdges []Edge, before *cursor, after *cursor, first *int, last *int) ([]Edge, error) {
+// result := ApplyCursorToEdges(allEdges, before, after)
+//
+// if first != nil {
+// if *first < 0 {
+// return nil, fmt.Errorf("first less than zero")
+// }
+//
+// if len(result) > *first {
+// // Slice result to be of length first by removing edges from the end
+// result = result[:*first]
+// }
+// }
+//
+// if last != nil {
+// if *last < 0 {
+// return nil, fmt.Errorf("last less than zero")
+// }
+//
+// if len(result) > *last {
+// // Slice result to be of length last by removing edges from the start
+// result = result[len(result)-*last:]
+// }
+// }
+//
+// return result, nil
+//}
+
+//func ApplyCursorToEdges(allEdges []Edge, before *cursor, after *cursor) []Edge {
+// result := allEdges
+//
+// if after != nil {
+// for i, edge := range result {
+// if edge.Cursor() == *after {
+// // remove all previous element including the "after" one
+// result = result[i+1:]
+// break
+// }
+// }
+// }
+//
+// if before != nil {
+// for i, edge := range result {
+// if edge.Cursor() == *before {
+// // remove all after element including the "before" one
+// result = result[:i]
+// }
+// }
+// }
+//
+// return result
+//}
+
+//func HasPreviousPage(allEdges []Edge, before *cursor, after *cursor, last *int) bool {
+// if last != nil {
+// edges := ApplyCursorToEdges(allEdges, before, after)
+// return len(edges) > *last
+// }
+//
+// // TODO: handle "after", but according to the spec it's ok to return false
+//
+// return false
+//}
+//
+//func HasNextPage(allEdges []Edge, before *cursor, after *cursor, first *int) bool {
+// if first != nil {
+// edges := ApplyCursorToEdges(allEdges, before, after)
+// return len(edges) > *first
+// }
+//
+// // TODO: handle "before", but according to the spec it's ok to return false
+//
+// return false
diff --git a/graphql2/resolvers/pager_comment.go b/graphql2/resolvers/pager_comment.go
new file mode 100644
index 00000000..3dd11757
--- /dev/null
+++ b/graphql2/resolvers/pager_comment.go
@@ -0,0 +1,225 @@
+// This file was automatically generated by genny.
+// Any changes will be lost if this file is regenerated.
+// see https://github.com/cheekybits/genny
+
+package resolvers
+
+import (
+ "fmt"
+
+ "github.com/MichaelMure/git-bug/bug"
+)
+
+type BugCommentEdger func(value bug.Comment, offset int) Edge
+
+func BugCommentPaginate(source []bug.Comment, edger BugCommentEdger, input ConnectionInput) ([]CommentEdge, PageInfo, error) {
+ var result []CommentEdge
+ var pageInfo PageInfo
+
+ offset := 0
+
+ if input.After != nil {
+ for i, value := range source {
+ edge := edger(value, i)
+ if edge.GetCursor() == *input.After {
+ // remove all previous element including the "after" one
+ source = source[i+1:]
+ offset = i + 1
+ break
+ }
+ }
+ }
+
+ if input.Before != nil {
+ for i, value := range source {
+ edge := edger(value, i+offset)
+
+ if edge.GetCursor() == *input.Before {
+ // remove all after element including the "before" one
+ break
+ }
+
+ result = append(result, edge.(CommentEdge))
+ }
+ } else {
+ result = make([]CommentEdge, len(source))
+
+ for i, value := range source {
+ result[i] = edger(value, i+offset).(CommentEdge)
+ }
+ }
+
+ if input.First != nil {
+ if *input.First < 0 {
+ return nil, PageInfo{}, fmt.Errorf("first less than zero")
+ }
+
+ if len(result) > *input.First {
+ // Slice result to be of length first by removing edges from the end
+ result = result[:*input.First]
+ pageInfo.HasNextPage = true
+ }
+ }
+
+ if input.Last != nil {
+ if *input.Last < 0 {
+ return nil, PageInfo{}, fmt.Errorf("last less than zero")
+ }
+
+ if len(result) > *input.Last {
+ // Slice result to be of length last by removing edges from the start
+ result = result[len(result)-*input.Last:]
+ pageInfo.HasPreviousPage = true
+ }
+ }
+
+ return result, pageInfo, nil
+}
+
+// Apply the before/after cursor params to the source and return an array of edges
+//func ApplyCursorToEdges(source []interface{}, edger Edger, input ConnectionInput) []Edge {
+// var result []Edge
+//
+// if input.After != nil {
+// for i, value := range source {
+// edge := edger(value)
+// if edge.Cursor() == *input.After {
+// // remove all previous element including the "after" one
+// source = source[i+1:]
+// break
+// }
+// }
+// }
+//
+// if input.Before != nil {
+// for _, value := range source {
+// edge := edger(value)
+//
+// if edge.Cursor() == *input.Before {
+// // remove all after element including the "before" one
+// break
+// }
+//
+// result = append(result, edge)
+// }
+// } else {
+// result = make([]Edge, len(source))
+//
+// for i, value := range source {
+// result[i] = edger(value)
+// }
+// }
+//
+// return result
+//}
+
+// Apply the first/last cursor params to the edges
+//func EdgesToReturn(edges []Edge, input ConnectionInput) ([]Edge, PageInfo, error) {
+// hasPreviousPage := false
+// hasNextPage := false
+//
+// if input.First != nil {
+// if *input.First < 0 {
+// return nil, nil, fmt.Errorf("first less than zero")
+// }
+//
+// if len(edges) > *input.First {
+// // Slice result to be of length first by removing edges from the end
+// edges = edges[:*input.First]
+// hasNextPage = true
+// }
+// }
+//
+// if input.Last != nil {
+// if *input.Last < 0 {
+// return nil, nil, fmt.Errorf("last less than zero")
+// }
+//
+// if len(edges) > *input.Last {
+// // Slice result to be of length last by removing edges from the start
+// edges = edges[len(edges)-*input.Last:]
+// hasPreviousPage = true
+// }
+// }
+//
+// pageInfo := PageInfo{
+// HasNextPage: hasNextPage,
+// HasPreviousPage: hasPreviousPage,
+// }
+//
+// return edges, pageInfo, nil
+//}
+
+//func EdgesToReturn(allEdges []Edge, before *cursor, after *cursor, first *int, last *int) ([]Edge, error) {
+// result := ApplyCursorToEdges(allEdges, before, after)
+//
+// if first != nil {
+// if *first < 0 {
+// return nil, fmt.Errorf("first less than zero")
+// }
+//
+// if len(result) > *first {
+// // Slice result to be of length first by removing edges from the end
+// result = result[:*first]
+// }
+// }
+//
+// if last != nil {
+// if *last < 0 {
+// return nil, fmt.Errorf("last less than zero")
+// }
+//
+// if len(result) > *last {
+// // Slice result to be of length last by removing edges from the start
+// result = result[len(result)-*last:]
+// }
+// }
+//
+// return result, nil
+//}
+
+//func ApplyCursorToEdges(allEdges []Edge, before *cursor, after *cursor) []Edge {
+// result := allEdges
+//
+// if after != nil {
+// for i, edge := range result {
+// if edge.Cursor() == *after {
+// // remove all previous element including the "after" one
+// result = result[i+1:]
+// break
+// }
+// }
+// }
+//
+// if before != nil {
+// for i, edge := range result {
+// if edge.Cursor() == *before {
+// // remove all after element including the "before" one
+// result = result[:i]
+// }
+// }
+// }
+//
+// return result
+//}
+
+//func HasPreviousPage(allEdges []Edge, before *cursor, after *cursor, last *int) bool {
+// if last != nil {
+// edges := ApplyCursorToEdges(allEdges, before, after)
+// return len(edges) > *last
+// }
+//
+// // TODO: handle "after", but according to the spec it's ok to return false
+//
+// return false
+//}
+//
+//func HasNextPage(allEdges []Edge, before *cursor, after *cursor, first *int) bool {
+// if first != nil {
+// edges := ApplyCursorToEdges(allEdges, before, after)
+// return len(edges) > *first
+// }
+//
+// // TODO: handle "before", but according to the spec it's ok to return false
+//
+// return false
diff --git a/graphql2/resolvers/pager_operation.go b/graphql2/resolvers/pager_operation.go
new file mode 100644
index 00000000..fe4eebc2
--- /dev/null
+++ b/graphql2/resolvers/pager_operation.go
@@ -0,0 +1,225 @@
+// This file was automatically generated by genny.
+// Any changes will be lost if this file is regenerated.
+// see https://github.com/cheekybits/genny
+
+package resolvers
+
+import (
+ "fmt"
+
+ "github.com/MichaelMure/git-bug/bug"
+)
+
+type BugOperationEdger func(value bug.Operation, offset int) Edge
+
+func BugOperationPaginate(source []bug.Operation, edger BugOperationEdger, input ConnectionInput) ([]OperationEdge, PageInfo, error) {
+ var result []OperationEdge
+ var pageInfo PageInfo
+
+ offset := 0
+
+ if input.After != nil {
+ for i, value := range source {
+ edge := edger(value, i)
+ if edge.GetCursor() == *input.After {
+ // remove all previous element including the "after" one
+ source = source[i+1:]
+ offset = i + 1
+ break
+ }
+ }
+ }
+
+ if input.Before != nil {
+ for i, value := range source {
+ edge := edger(value, i+offset)
+
+ if edge.GetCursor() == *input.Before {
+ // remove all after element including the "before" one
+ break
+ }
+
+ result = append(result, edge.(OperationEdge))
+ }
+ } else {
+ result = make([]OperationEdge, len(source))
+
+ for i, value := range source {
+ result[i] = edger(value, i+offset).(OperationEdge)
+ }
+ }
+
+ if input.First != nil {
+ if *input.First < 0 {
+ return nil, PageInfo{}, fmt.Errorf("first less than zero")
+ }
+
+ if len(result) > *input.First {
+ // Slice result to be of length first by removing edges from the end
+ result = result[:*input.First]
+ pageInfo.HasNextPage = true
+ }
+ }
+
+ if input.Last != nil {
+ if *input.Last < 0 {
+ return nil, PageInfo{}, fmt.Errorf("last less than zero")
+ }
+
+ if len(result) > *input.Last {
+ // Slice result to be of length last by removing edges from the start
+ result = result[len(result)-*input.Last:]
+ pageInfo.HasPreviousPage = true
+ }
+ }
+
+ return result, pageInfo, nil
+}
+
+// Apply the before/after cursor params to the source and return an array of edges
+//func ApplyCursorToEdges(source []interface{}, edger Edger, input ConnectionInput) []Edge {
+// var result []Edge
+//
+// if input.After != nil {
+// for i, value := range source {
+// edge := edger(value)
+// if edge.Cursor() == *input.After {
+// // remove all previous element including the "after" one
+// source = source[i+1:]
+// break
+// }
+// }
+// }
+//
+// if input.Before != nil {
+// for _, value := range source {
+// edge := edger(value)
+//
+// if edge.Cursor() == *input.Before {
+// // remove all after element including the "before" one
+// break
+// }
+//
+// result = append(result, edge)
+// }
+// } else {
+// result = make([]Edge, len(source))
+//
+// for i, value := range source {
+// result[i] = edger(value)
+// }
+// }
+//
+// return result
+//}
+
+// Apply the first/last cursor params to the edges
+//func EdgesToReturn(edges []Edge, input ConnectionInput) ([]Edge, PageInfo, error) {
+// hasPreviousPage := false
+// hasNextPage := false
+//
+// if input.First != nil {
+// if *input.First < 0 {
+// return nil, nil, fmt.Errorf("first less than zero")
+// }
+//
+// if len(edges) > *input.First {
+// // Slice result to be of length first by removing edges from the end
+// edges = edges[:*input.First]
+// hasNextPage = true
+// }
+// }
+//
+// if input.Last != nil {
+// if *input.Last < 0 {
+// return nil, nil, fmt.Errorf("last less than zero")
+// }
+//
+// if len(edges) > *input.Last {
+// // Slice result to be of length last by removing edges from the start
+// edges = edges[len(edges)-*input.Last:]
+// hasPreviousPage = true
+// }
+// }
+//
+// pageInfo := PageInfo{
+// HasNextPage: hasNextPage,
+// HasPreviousPage: hasPreviousPage,
+// }
+//
+// return edges, pageInfo, nil
+//}
+
+//func EdgesToReturn(allEdges []Edge, before *cursor, after *cursor, first *int, last *int) ([]Edge, error) {
+// result := ApplyCursorToEdges(allEdges, before, after)
+//
+// if first != nil {
+// if *first < 0 {
+// return nil, fmt.Errorf("first less than zero")
+// }
+//
+// if len(result) > *first {
+// // Slice result to be of length first by removing edges from the end
+// result = result[:*first]
+// }
+// }
+//
+// if last != nil {
+// if *last < 0 {
+// return nil, fmt.Errorf("last less than zero")
+// }
+//
+// if len(result) > *last {
+// // Slice result to be of length last by removing edges from the start
+// result = result[len(result)-*last:]
+// }
+// }
+//
+// return result, nil
+//}
+
+//func ApplyCursorToEdges(allEdges []Edge, before *cursor, after *cursor) []Edge {
+// result := allEdges
+//
+// if after != nil {
+// for i, edge := range result {
+// if edge.Cursor() == *after {
+// // remove all previous element including the "after" one
+// result = result[i+1:]
+// break
+// }
+// }
+// }
+//
+// if before != nil {
+// for i, edge := range result {
+// if edge.Cursor() == *before {
+// // remove all after element including the "before" one
+// result = result[:i]
+// }
+// }
+// }
+//
+// return result
+//}
+
+//func HasPreviousPage(allEdges []Edge, before *cursor, after *cursor, last *int) bool {
+// if last != nil {
+// edges := ApplyCursorToEdges(allEdges, before, after)
+// return len(edges) > *last
+// }
+//
+// // TODO: handle "after", but according to the spec it's ok to return false
+//
+// return false
+//}
+//
+//func HasNextPage(allEdges []Edge, before *cursor, after *cursor, first *int) bool {
+// if first != nil {
+// edges := ApplyCursorToEdges(allEdges, before, after)
+// return len(edges) > *first
+// }
+//
+// // TODO: handle "before", but according to the spec it's ok to return false
+//
+// return false
diff --git a/graphql2/resolvers/pagers.go b/graphql2/resolvers/pagers.go
new file mode 100644
index 00000000..378dcdbf
--- /dev/null
+++ b/graphql2/resolvers/pagers.go
@@ -0,0 +1,51 @@
+//go:generate genny -in=pagers_template.go -out=pager_bug.go gen "NodeType=bug.Snapshot EdgeType=BugEdge"
+//go:generate genny -in=pagers_template.go -out=pager_operation.go gen "NodeType=bug.Operation EdgeType=OperationEdge"
+//go:generate genny -in=pagers_template.go -out=pager_comment.go gen "NodeType=bug.Comment EdgeType=CommentEdge"
+
+package resolvers
+
+import (
+ "encoding/base64"
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+const cursorPrefix = "cursor:"
+
+type Edge interface {
+ GetCursor() string
+}
+
+// Creates the cursor string from an offset
+func offsetToCursor(offset int) string {
+ str := fmt.Sprintf("%v%v", cursorPrefix, offset)
+ return base64.StdEncoding.EncodeToString([]byte(str))
+}
+
+// Re-derives the offset from the cursor string.
+func cursorToOffset(cursor string) (int, error) {
+ str := ""
+ b, err := base64.StdEncoding.DecodeString(cursor)
+ if err == nil {
+ str = string(b)
+ }
+ str = strings.Replace(str, cursorPrefix, "", -1)
+ offset, err := strconv.Atoi(str)
+ if err != nil {
+ return 0, fmt.Errorf("Invalid cursor")
+ }
+ return offset, nil
+}
+
+func (e OperationEdge) GetCursor() string {
+ return e.Cursor
+}
+
+func (e BugEdge) GetCursor() string {
+ return e.Cursor
+}
+
+func (e CommentEdge) GetCursor() string {
+ return e.Cursor
+}
diff --git a/graphql2/resolvers/pagers_template.go b/graphql2/resolvers/pagers_template.go
new file mode 100644
index 00000000..0ca7de75
--- /dev/null
+++ b/graphql2/resolvers/pagers_template.go
@@ -0,0 +1,224 @@
+package resolvers
+
+import (
+ "fmt"
+ "github.com/cheekybits/genny/generic"
+)
+
+type NodeType generic.Type
+type EdgeType generic.Type
+
+type NodeTypeEdger func(value NodeType, offset int) Edge
+
+func NodeTypePaginate(source []NodeType, edger NodeTypeEdger, input ConnectionInput) ([]EdgeType, PageInfo, error) {
+ var result []EdgeType
+ var pageInfo PageInfo
+
+ offset := 0
+
+ if input.After != nil {
+ for i, value := range source {
+ edge := edger(value, i)
+ if edge.GetCursor() == *input.After {
+ // remove all previous element including the "after" one
+ source = source[i+1:]
+ offset = i + 1
+ break
+ }
+ }
+ }
+
+ if input.Before != nil {
+ for i, value := range source {
+ edge := edger(value, i+offset)
+
+ if edge.GetCursor() == *input.Before {
+ // remove all after element including the "before" one
+ break
+ }
+
+ result = append(result, edge.(EdgeType))
+ }
+ } else {
+ result = make([]EdgeType, len(source))
+
+ for i, value := range source {
+ result[i] = edger(value, i+offset).(EdgeType)
+ }
+ }
+
+ if input.First != nil {
+ if *input.First < 0 {
+ return nil, PageInfo{}, fmt.Errorf("first less than zero")
+ }
+
+ if len(result) > *input.First {
+ // Slice result to be of length first by removing edges from the end
+ result = result[:*input.First]
+ pageInfo.HasNextPage = true
+ }
+ }
+
+ if input.Last != nil {
+ if *input.Last < 0 {
+ return nil, PageInfo{}, fmt.Errorf("last less than zero")
+ }
+
+ if len(result) > *input.Last {
+ // Slice result to be of length last by removing edges from the start
+ result = result[len(result)-*input.Last:]
+ pageInfo.HasPreviousPage = true
+ }
+ }
+
+ return result, pageInfo, nil
+}
+
+// Apply the before/after cursor params to the source and return an array of edges
+//func ApplyCursorToEdges(source []interface{}, edger Edger, input ConnectionInput) []Edge {
+// var result []Edge
+//
+// if input.After != nil {
+// for i, value := range source {
+// edge := edger(value)
+// if edge.Cursor() == *input.After {
+// // remove all previous element including the "after" one
+// source = source[i+1:]
+// break
+// }
+// }
+// }
+//
+// if input.Before != nil {
+// for _, value := range source {
+// edge := edger(value)
+//
+// if edge.Cursor() == *input.Before {
+// // remove all after element including the "before" one
+// break
+// }
+//
+// result = append(result, edge)
+// }
+// } else {
+// result = make([]Edge, len(source))
+//
+// for i, value := range source {
+// result[i] = edger(value)
+// }
+// }
+//
+// return result
+//}
+
+// Apply the first/last cursor params to the edges
+//func EdgesToReturn(edges []Edge, input ConnectionInput) ([]Edge, PageInfo, error) {
+// hasPreviousPage := false
+// hasNextPage := false
+//
+// if input.First != nil {
+// if *input.First < 0 {
+// return nil, nil, fmt.Errorf("first less than zero")
+// }
+//
+// if len(edges) > *input.First {
+// // Slice result to be of length first by removing edges from the end
+// edges = edges[:*input.First]
+// hasNextPage = true
+// }
+// }
+//
+// if input.Last != nil {
+// if *input.Last < 0 {
+// return nil, nil, fmt.Errorf("last less than zero")
+// }
+//
+// if len(edges) > *input.Last {
+// // Slice result to be of length last by removing edges from the start
+// edges = edges[len(edges)-*input.Last:]
+// hasPreviousPage = true
+// }
+// }
+//
+// pageInfo := PageInfo{
+// HasNextPage: hasNextPage,
+// HasPreviousPage: hasPreviousPage,
+// }
+//
+// return edges, pageInfo, nil
+//}
+
+//func EdgesToReturn(allEdges []Edge, before *cursor, after *cursor, first *int, last *int) ([]Edge, error) {
+// result := ApplyCursorToEdges(allEdges, before, after)
+//
+// if first != nil {
+// if *first < 0 {
+// return nil, fmt.Errorf("first less than zero")
+// }
+//
+// if len(result) > *first {
+// // Slice result to be of length first by removing edges from the end
+// result = result[:*first]
+// }
+// }
+//
+// if last != nil {
+// if *last < 0 {
+// return nil, fmt.Errorf("last less than zero")
+// }
+//
+// if len(result) > *last {
+// // Slice result to be of length last by removing edges from the start
+// result = result[len(result)-*last:]
+// }
+// }
+//
+// return result, nil
+//}
+
+//func ApplyCursorToEdges(allEdges []Edge, before *cursor, after *cursor) []Edge {
+// result := allEdges
+//
+// if after != nil {
+// for i, edge := range result {
+// if edge.Cursor() == *after {
+// // remove all previous element including the "after" one
+// result = result[i+1:]
+// break
+// }
+// }
+// }
+//
+// if before != nil {
+// for i, edge := range result {
+// if edge.Cursor() == *before {
+// // remove all after element including the "before" one
+// result = result[:i]
+// }
+// }
+// }
+//
+// return result
+//}
+
+//func HasPreviousPage(allEdges []Edge, before *cursor, after *cursor, last *int) bool {
+// if last != nil {
+// edges := ApplyCursorToEdges(allEdges, before, after)
+// return len(edges) > *last
+// }
+//
+// // TODO: handle "after", but according to the spec it's ok to return false
+//
+// return false
+//}
+//
+//func HasNextPage(allEdges []Edge, before *cursor, after *cursor, first *int) bool {
+// if first != nil {
+// edges := ApplyCursorToEdges(allEdges, before, after)
+// return len(edges) > *first
+// }
+//
+// // TODO: handle "before", but according to the spec it's ok to return false
+//
+// return false
+//}
diff --git a/graphql2/resolvers/query.go b/graphql2/resolvers/query.go
new file mode 100644
index 00000000..cceca334
--- /dev/null
+++ b/graphql2/resolvers/query.go
@@ -0,0 +1,36 @@
+package resolvers
+
+import (
+ "context"
+ "github.com/MichaelMure/git-bug/cache"
+)
+
+type rootQueryResolver struct {
+ cache cache.Cacher
+}
+
+func (r rootQueryResolver) DefaultRepository(ctx context.Context) (*repoResolver, error) {
+ repo, err := r.cache.DefaultRepo()
+
+ if err != nil {
+ return nil, err
+ }
+
+ return &repoResolver{
+ cache: r.cache,
+ repo: repo,
+ }, nil
+}
+
+func (r rootQueryResolver) Repository(ctx context.Context, id string) (*repoResolver, error) {
+ repo, err := r.cache.ResolveRepo(id)
+
+ if err != nil {
+ return nil, err
+ }
+
+ return &repoResolver{
+ cache: r.cache,
+ repo: repo,
+ }, nil
+}
diff --git a/graphql2/resolvers/repo.go b/graphql2/resolvers/repo.go
new file mode 100644
index 00000000..14019b65
--- /dev/null
+++ b/graphql2/resolvers/repo.go
@@ -0,0 +1,26 @@
+package resolvers
+
+import (
+ "context"
+ "github.com/MichaelMure/git-bug/bug"
+ "github.com/MichaelMure/git-bug/cache"
+)
+
+type repoResolver struct {
+ cache cache.Cacher
+ repo cache.RepoCacher
+}
+
+func (repoResolver) AllBugs(ctx context.Context, obj *repoResolver, input ConnectionInput) (BugConnection, error) {
+ panic("implement me")
+}
+
+func (repoResolver) Bug(ctx context.Context, obj *repoResolver, prefix string) (*bug.Snapshot, error) {
+ b, err := obj.repo.ResolveBugPrefix(prefix)
+
+ if err != nil {
+ return nil, err
+ }
+
+ return b.Snapshot(), nil
+}
diff --git a/graphql2/resolvers/root.go b/graphql2/resolvers/root.go
new file mode 100644
index 00000000..e5f83060
--- /dev/null
+++ b/graphql2/resolvers/root.go
@@ -0,0 +1,53 @@
+package resolvers
+
+import (
+ "github.com/MichaelMure/git-bug/cache"
+)
+
+type RootResolver struct {
+ cache.RootCache
+}
+
+func NewRootResolver() *RootResolver {
+ return &RootResolver{
+ RootCache: cache.NewCache(),
+ }
+}
+
+func (r RootResolver) Query() QueryResolver {
+ return &rootQueryResolver{
+ cache: &r.RootCache,
+ }
+}
+
+func (RootResolver) AddCommentOperation() AddCommentOperationResolver {
+ return &addCommentOperationResolver{}
+}
+
+func (r RootResolver) Bug() BugResolver {
+ return &bugResolver{
+ cache: &r.RootCache,
+ }
+}
+
+func (RootResolver) CreateOperation() CreateOperationResolver {
+ return &createOperationResolver{}
+}
+
+func (RootResolver) LabelChangeOperation() LabelChangeOperationResolver {
+ return &labelChangeOperation{}
+}
+
+func (r RootResolver) Repository() RepositoryResolver {
+ return &repoResolver{
+ cache: &r.RootCache,
+ }
+}
+
+func (RootResolver) SetStatusOperation() SetStatusOperationResolver {
+ return &setStatusOperationResolver{}
+}
+
+func (RootResolver) SetTitleOperation() SetTitleOperationResolver {
+ return &setTitleOperationResolver{}
+}
diff --git a/graphql2/schema.graphql b/graphql2/schema.graphql
index 3c24c746..47716488 100644
--- a/graphql2/schema.graphql
+++ b/graphql2/schema.graphql
@@ -1,7 +1,3 @@
-schema {
- query: RootQuery
-}
-
scalar Time
scalar Label
@@ -14,10 +10,24 @@ type PageInfo {
hasPreviousPage: Boolean!
# When paginating backwards, the cursor to continue.
- startCursor: String
+# startCursor: String
# When paginating forwards, the cursor to continue.
- endCursor: String
+# endCursor: String
+}
+
+input ConnectionInput {
+ # Returns the elements in the list that come after the specified cursor.
+ after: String
+
+ # Returns the elements in the list that come before the specified cursor.
+ before: String
+
+ # Returns the first _n_ elements from the list.
+ first: Int
+
+ # Returns the last _n_ elements from the list.
+ last: Int
}
# Represents an person in a git object.
@@ -31,8 +41,7 @@ type Person {
type CommentConnection {
- edges: [CommentEdge]
- nodes: [Comment]
+ edges: [CommentEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
@@ -42,23 +51,6 @@ type CommentEdge {
node: Comment!
}
-interface Commentable {
- # A list of comments associated with the object.
- comments(
- # Returns the elements in the list that come after the specified cursor.
- after: String
-
- # Returns the elements in the list that come before the specified cursor.
- before: String
-
- # Returns the first _n_ elements from the list.
- first: Int
-
- # Returns the last _n_ elements from the list.
- last: Int
- ): CommentConnection!
-}
-
# Represents a comment on a bug.
type Comment implements Authored {
# The author of this comment.
@@ -80,15 +72,14 @@ interface Authored {
}
type OperationConnection {
- edges: [OperationEdge]!
- nodes: [OperationUnion]!
+ edges: [OperationEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type OperationEdge {
cursor: String!
- node: OperationUnion
+ node: OperationUnion!
}
# An operation applied to a bug.
@@ -133,8 +124,8 @@ type LabelChangeOperation implements Operation, Authored {
author: Person!
date: Time!
- added: [Label]!
- removed: [Label]!
+ added: [Label!]!
+ removed: [Label!]!
}
union OperationUnion =
@@ -144,14 +135,11 @@ union OperationUnion =
| SetStatusOperation
| LabelChangeOperation
-# The connection type for Label.
+# The connection type for Bug.
type BugConnection {
# A list of edges.
edges: [BugEdge]!
- # A list of nodes.
- nodes: [Bug]!
-
# Information to aid in pagination.
pageInfo: PageInfo!
@@ -165,69 +153,29 @@ type BugEdge {
cursor: String!
# The item at the end of the edge.
- node: Bug
+ node: Bug!
}
-type Bug implements Authored, Commentable {
+type Bug {
id: String!
humanId: String!
title: String!
status: Status!
# A list of labels associated with the repository.
- labels: [Label]!
+ labels: [Label!]!
- comments(
- # Returns the elements in the list that come after the specified cursor.
- after: String
+ comments(input: ConnectionInput!): CommentConnection!
- # Returns the elements in the list that come before the specified cursor.
- before: String
-
- # Returns the first _n_ elements from the list.
- first: Int
-
- # Returns the last _n_ elements from the list.
- last: Int
-
- # If provided, searches comments by name and description.
- query: String
- ): CommentConnection!
-
- operations(
- # Returns the elements in the list that come after the specified cursor.
- after: String
-
- # Returns the elements in the list that come before the specified cursor.
- before: String
-
- # Returns the first _n_ elements from the list.
- first: Int
-
- # Returns the last _n_ elements from the list.
- last: Int
-
- # If provided, searches operations by name and description.
- query: String
- ): OperationConnection!
+ operations(input: ConnectionInput!): OperationConnection!
}
-type RootQuery {
- allBugs(
- # Returns the elements in the list that come after the specified cursor.
- after: String
-
- # Returns the elements in the list that come before the specified cursor.
- before: String
-
- # Returns the first _n_ elements from the list.
- first: Int
-
- # Returns the last _n_ elements from the list.
- last: Int
+type Repository {
+ allBugs(input: ConnectionInput!): BugConnection!
+ bug(prefix: String!): Bug
+}
- # If provided, searches labels by name and description.
- query: String
- ): BugConnection!
- bug(id: String!): Bug
+type Query {
+ defaultRepository: Repository
+ repository(id: String!): Repository
}
diff --git a/vendor/github.com/cheekybits/genny/LICENSE b/vendor/github.com/cheekybits/genny/LICENSE
new file mode 100644
index 00000000..519d7f22
--- /dev/null
+++ b/vendor/github.com/cheekybits/genny/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 cheekybits
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/vendor/github.com/cheekybits/genny/generic/doc.go b/vendor/github.com/cheekybits/genny/generic/doc.go
new file mode 100644
index 00000000..3bd6c869
--- /dev/null
+++ b/vendor/github.com/cheekybits/genny/generic/doc.go
@@ -0,0 +1,2 @@
+// Package generic contains the generic marker types.
+package generic
diff --git a/vendor/github.com/cheekybits/genny/generic/generic.go b/vendor/github.com/cheekybits/genny/generic/generic.go
new file mode 100644
index 00000000..04a2306c
--- /dev/null
+++ b/vendor/github.com/cheekybits/genny/generic/generic.go
@@ -0,0 +1,13 @@
+package generic
+
+// Type is the placeholder type that indicates a generic value.
+// When genny is executed, variables of this type will be replaced with
+// references to the specific types.
+// var GenericType generic.Type
+type Type interface{}
+
+// Number is the placehoder type that indiccates a generic numerical value.
+// When genny is executed, variables of this type will be replaced with
+// references to the specific types.
+// var GenericType generic.Number
+type Number float64
diff --git a/vendor/github.com/gorilla/websocket/.gitignore b/vendor/github.com/gorilla/websocket/.gitignore
new file mode 100644
index 00000000..ac710204
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/.gitignore
@@ -0,0 +1,25 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+
+.idea/
+*.iml \ No newline at end of file
diff --git a/vendor/github.com/gorilla/websocket/.travis.yml b/vendor/github.com/gorilla/websocket/.travis.yml
new file mode 100644
index 00000000..3d8d29cf
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/.travis.yml
@@ -0,0 +1,19 @@
+language: go
+sudo: false
+
+matrix:
+ include:
+ - go: 1.4
+ - go: 1.5
+ - go: 1.6
+ - go: 1.7
+ - go: 1.8
+ - go: tip
+ allow_failures:
+ - go: tip
+
+script:
+ - go get -t -v ./...
+ - diff -u <(echo -n) <(gofmt -d .)
+ - go vet $(go list ./... | grep -v /vendor/)
+ - go test -v -race ./...
diff --git a/vendor/github.com/gorilla/websocket/AUTHORS b/vendor/github.com/gorilla/websocket/AUTHORS
new file mode 100644
index 00000000..b003eca0
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/AUTHORS
@@ -0,0 +1,8 @@
+# This is the official list of Gorilla WebSocket authors for copyright
+# purposes.
+#
+# Please keep the list sorted.
+
+Gary Burd <gary@beagledreams.com>
+Joachim Bauch <mail@joachim-bauch.de>
+
diff --git a/vendor/github.com/gorilla/websocket/LICENSE b/vendor/github.com/gorilla/websocket/LICENSE
new file mode 100644
index 00000000..9171c972
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/gorilla/websocket/README.md b/vendor/github.com/gorilla/websocket/README.md
new file mode 100644
index 00000000..33c3d2be
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/README.md
@@ -0,0 +1,64 @@
+# Gorilla WebSocket
+
+Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
+[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol.
+
+[![Build Status](https://travis-ci.org/gorilla/websocket.svg?branch=master)](https://travis-ci.org/gorilla/websocket)
+[![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket)
+
+### Documentation
+
+* [API Reference](http://godoc.org/github.com/gorilla/websocket)
+* [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat)
+* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command)
+* [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo)
+* [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch)
+
+### Status
+
+The Gorilla WebSocket package provides a complete and tested implementation of
+the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. The
+package API is stable.
+
+### Installation
+
+ go get github.com/gorilla/websocket
+
+### Protocol Compliance
+
+The Gorilla WebSocket package passes the server tests in the [Autobahn Test
+Suite](http://autobahn.ws/testsuite) using the application in the [examples/autobahn
+subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn).
+
+### Gorilla WebSocket compared with other packages
+
+<table>
+<tr>
+<th></th>
+<th><a href="http://godoc.org/github.com/gorilla/websocket">github.com/gorilla</a></th>
+<th><a href="http://godoc.org/golang.org/x/net/websocket">golang.org/x/net</a></th>
+</tr>
+<tr>
+<tr><td colspan="3"><a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a> Features</td></tr>
+<tr><td>Passes <a href="http://autobahn.ws/testsuite/">Autobahn Test Suite</a></td><td><a href="https://github.com/gorilla/websocket/tree/master/examples/autobahn">Yes</a></td><td>No</td></tr>
+<tr><td>Receive <a href="https://tools.ietf.org/html/rfc6455#section-5.4">fragmented</a> message<td>Yes</td><td><a href="https://code.google.com/p/go/issues/detail?id=7632">No</a>, see note 1</td></tr>
+<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.1">close</a> message</td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td><a href="https://code.google.com/p/go/issues/detail?id=4588">No</a></td></tr>
+<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.2">pings</a> and receive <a href="https://tools.ietf.org/html/rfc6455#section-5.5.3">pongs</a></td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td>No</td></tr>
+<tr><td>Get the <a href="https://tools.ietf.org/html/rfc6455#section-5.6">type</a> of a received data message</td><td>Yes</td><td>Yes, see note 2</td></tr>
+<tr><td colspan="3">Other Features</tr></td>
+<tr><td><a href="https://tools.ietf.org/html/rfc7692">Compression Extensions</a></td><td>Experimental</td><td>No</td></tr>
+<tr><td>Read message using io.Reader</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextReader">Yes</a></td><td>No, see note 3</td></tr>
+<tr><td>Write message using io.WriteCloser</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextWriter">Yes</a></td><td>No, see note 3</td></tr>
+</table>
+
+Notes:
+
+1. Large messages are fragmented in [Chrome's new WebSocket implementation](http://www.ietf.org/mail-archive/web/hybi/current/msg10503.html).
+2. The application can get the type of a received data message by implementing
+ a [Codec marshal](http://godoc.org/golang.org/x/net/websocket#Codec.Marshal)
+ function.
+3. The go.net io.Reader and io.Writer operate across WebSocket frame boundaries.
+ Read returns when the input buffer is full or a frame boundary is
+ encountered. Each call to Write sends a single frame message. The Gorilla
+ io.Reader and io.WriteCloser operate on a single WebSocket message.
+
diff --git a/vendor/github.com/gorilla/websocket/client.go b/vendor/github.com/gorilla/websocket/client.go
new file mode 100644
index 00000000..43a87c75
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/client.go
@@ -0,0 +1,392 @@
+// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package websocket
+
+import (
+ "bufio"
+ "bytes"
+ "crypto/tls"
+ "encoding/base64"
+ "errors"
+ "io"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "net/url"
+ "strings"
+ "time"
+)
+
+// ErrBadHandshake is returned when the server response to opening handshake is
+// invalid.
+var ErrBadHandshake = errors.New("websocket: bad handshake")
+
+var errInvalidCompression = errors.New("websocket: invalid compression negotiation")
+
+// NewClient creates a new client connection using the given net connection.
+// The URL u specifies the host and request URI. Use requestHeader to specify
+// the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies
+// (Cookie). Use the response.Header to get the selected subprotocol
+// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
+//
+// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
+// non-nil *http.Response so that callers can handle redirects, authentication,
+// etc.
+//
+// Deprecated: Use Dialer instead.
+func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) {
+ d := Dialer{
+ ReadBufferSize: readBufSize,
+ WriteBufferSize: writeBufSize,
+ NetDial: func(net, addr string) (net.Conn, error) {
+ return netConn, nil
+ },
+ }
+ return d.Dial(u.String(), requestHeader)
+}
+
+// A Dialer contains options for connecting to WebSocket server.
+type Dialer struct {
+ // NetDial specifies the dial function for creating TCP connections. If
+ // NetDial is nil, net.Dial is used.
+ NetDial func(network, addr string) (net.Conn, error)
+
+ // Proxy specifies a function to return a proxy for a given
+ // Request. If the function returns a non-nil error, the
+ // request is aborted with the provided error.
+ // If Proxy is nil or returns a nil *URL, no proxy is used.
+ Proxy func(*http.Request) (*url.URL, error)
+
+ // TLSClientConfig specifies the TLS configuration to use with tls.Client.
+ // If nil, the default configuration is used.
+ TLSClientConfig *tls.Config
+
+ // HandshakeTimeout specifies the duration for the handshake to complete.
+ HandshakeTimeout time.Duration
+
+ // ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
+ // size is zero, then a useful default size is used. The I/O buffer sizes
+ // do not limit the size of the messages that can be sent or received.
+ ReadBufferSize, WriteBufferSize int
+
+ // Subprotocols specifies the client's requested subprotocols.
+ Subprotocols []string
+
+ // EnableCompression specifies if the client should attempt to negotiate
+ // per message compression (RFC 7692). Setting this value to true does not
+ // guarantee that compression will be supported. Currently only "no context
+ // takeover" modes are supported.
+ EnableCompression bool
+
+ // Jar specifies the cookie jar.
+ // If Jar is nil, cookies are not sent in requests and ignored
+ // in responses.
+ Jar http.CookieJar
+}
+
+var errMalformedURL = errors.New("malformed ws or wss URL")
+
+// parseURL parses the URL.
+//
+// This function is a replacement for the standard library url.Parse function.
+// In Go 1.4 and earlier, url.Parse loses information from the path.
+func parseURL(s string) (*url.URL, error) {
+ // From the RFC:
+ //
+ // ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
+ // wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
+ var u url.URL
+ switch {
+ case strings.HasPrefix(s, "ws://"):
+ u.Scheme = "ws"
+ s = s[len("ws://"):]
+ case strings.HasPrefix(s, "wss://"):
+ u.Scheme = "wss"
+ s = s[len("wss://"):]
+ default:
+ return nil, errMalformedURL
+ }
+
+ if i := strings.Index(s, "?"); i >= 0 {
+ u.RawQuery = s[i+1:]
+ s = s[:i]
+ }
+
+ if i := strings.Index(s, "/"); i >= 0 {
+ u.Opaque = s[i:]
+ s = s[:i]
+ } else {
+ u.Opaque = "/"
+ }
+
+ u.Host = s
+
+ if strings.Contains(u.Host, "@") {
+ // Don't bother parsing user information because user information is
+ // not allowed in websocket URIs.
+ return nil, errMalformedURL
+ }
+
+ return &u, nil
+}
+
+func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
+ hostPort = u.Host
+ hostNoPort = u.Host
+ if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") {
+ hostNoPort = hostNoPort[:i]
+ } else {
+ switch u.Scheme {
+ case "wss":
+ hostPort += ":443"
+ case "https":
+ hostPort += ":443"
+ default:
+ hostPort += ":80"
+ }
+ }
+ return hostPort, hostNoPort
+}
+
+// DefaultDialer is a dialer with all fields set to the default zero values.
+var DefaultDialer = &Dialer{
+ Proxy: http.ProxyFromEnvironment,
+}
+
+// Dial creates a new client connection. Use requestHeader to specify the
+// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie).
+// Use the response.Header to get the selected subprotocol
+// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
+//
+// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
+// non-nil *http.Response so that callers can handle redirects, authentication,
+// etcetera. The response body may not contain the entire response and does not
+// need to be closed by the application.
+func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
+
+ if d == nil {
+ d = &Dialer{
+ Proxy: http.ProxyFromEnvironment,
+ }
+ }
+
+ challengeKey, err := generateChallengeKey()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ u, err := parseURL(urlStr)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ switch u.Scheme {
+ case "ws":
+ u.Scheme = "http"
+ case "wss":
+ u.Scheme = "https"
+ default:
+ return nil, nil, errMalformedURL
+ }
+
+ if u.User != nil {
+ // User name and password are not allowed in websocket URIs.
+ return nil, nil, errMalformedURL
+ }
+
+ req := &http.Request{
+ Method: "GET",
+ URL: u,
+ Proto: "HTTP/1.1",
+ ProtoMajor: 1,
+ ProtoMinor: 1,
+ Header: make(http.Header),
+ Host: u.Host,
+ }
+
+ // Set the cookies present in the cookie jar of the dialer
+ if d.Jar != nil {
+ for _, cookie := range d.Jar.Cookies(u) {
+ req.AddCookie(cookie)
+ }
+ }
+
+ // Set the request headers using the capitalization for names and values in
+ // RFC examples. Although the capitalization shouldn't matter, there are
+ // servers that depend on it. The Header.Set method is not used because the
+ // method canonicalizes the header names.
+ req.Header["Upgrade"] = []string{"websocket"}
+ req.Header["Connection"] = []string{"Upgrade"}
+ req.Header["Sec-WebSocket-Key"] = []string{challengeKey}
+ req.Header["Sec-WebSocket-Version"] = []string{"13"}
+ if len(d.Subprotocols) > 0 {
+ req.Header["Sec-WebSocket-Protocol"] = []string{strings.Join(d.Subprotocols, ", ")}
+ }
+ for k, vs := range requestHeader {
+ switch {
+ case k == "Host":
+ if len(vs) > 0 {
+ req.Host = vs[0]
+ }
+ case k == "Upgrade" ||
+ k == "Connection" ||
+ k == "Sec-Websocket-Key" ||
+ k == "Sec-Websocket-Version" ||
+ k == "Sec-Websocket-Extensions" ||
+ (k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
+ return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
+ default:
+ req.Header[k] = vs
+ }
+ }
+
+ if d.EnableCompression {
+ req.Header.Set("Sec-Websocket-Extensions", "permessage-deflate; server_no_context_takeover; client_no_context_takeover")
+ }
+
+ hostPort, hostNoPort := hostPortNoPort(u)
+
+ var proxyURL *url.URL
+ // Check wether the proxy method has been configured
+ if d.Proxy != nil {
+ proxyURL, err = d.Proxy(req)
+ }
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var targetHostPort string
+ if proxyURL != nil {
+ targetHostPort, _ = hostPortNoPort(proxyURL)
+ } else {
+ targetHostPort = hostPort
+ }
+
+ var deadline time.Time
+ if d.HandshakeTimeout != 0 {
+ deadline = time.Now().Add(d.HandshakeTimeout)
+ }
+
+ netDial := d.NetDial
+ if netDial == nil {
+ netDialer := &net.Dialer{Deadline: deadline}
+ netDial = netDialer.Dial
+ }
+
+ netConn, err := netDial("tcp", targetHostPort)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ defer func() {
+ if netConn != nil {
+ netConn.Close()
+ }
+ }()
+
+ if err := netConn.SetDeadline(deadline); err != nil {
+ return nil, nil, err
+ }
+
+ if proxyURL != nil {
+ connectHeader := make(http.Header)
+ if user := proxyURL.User; user != nil {
+ proxyUser := user.Username()
+ if proxyPassword, passwordSet := user.Password(); passwordSet {
+ credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword))
+ connectHeader.Set("Proxy-Authorization", "Basic "+credential)
+ }
+ }
+ connectReq := &http.Request{
+ Method: "CONNECT",
+ URL: &url.URL{Opaque: hostPort},
+ Host: hostPort,
+ Header: connectHeader,
+ }
+
+ connectReq.Write(netConn)
+
+ // Read response.
+ // Okay to use and discard buffered reader here, because
+ // TLS server will not speak until spoken to.
+ br := bufio.NewReader(netConn)
+ resp, err := http.ReadResponse(br, connectReq)
+ if err != nil {
+ return nil, nil, err
+ }
+ if resp.StatusCode != 200 {
+ f := strings.SplitN(resp.Status, " ", 2)
+ return nil, nil, errors.New(f[1])
+ }
+ }
+
+ if u.Scheme == "https" {
+ cfg := cloneTLSConfig(d.TLSClientConfig)
+ if cfg.ServerName == "" {
+ cfg.ServerName = hostNoPort
+ }
+ tlsConn := tls.Client(netConn, cfg)
+ netConn = tlsConn
+ if err := tlsConn.Handshake(); err != nil {
+ return nil, nil, err
+ }
+ if !cfg.InsecureSkipVerify {
+ if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
+ return nil, nil, err
+ }
+ }
+ }
+
+ conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize)
+
+ if err := req.Write(netConn); err != nil {
+ return nil, nil, err
+ }
+
+ resp, err := http.ReadResponse(conn.br, req)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if d.Jar != nil {
+ if rc := resp.Cookies(); len(rc) > 0 {
+ d.Jar.SetCookies(u, rc)
+ }
+ }
+
+ if resp.StatusCode != 101 ||
+ !strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") ||
+ !strings.EqualFold(resp.Header.Get("Connection"), "upgrade") ||
+ resp.Header.Get("Sec-Websocket-Accept") != computeAcceptKey(challengeKey) {
+ // Before closing the network connection on return from this
+ // function, slurp up some of the response to aid application
+ // debugging.
+ buf := make([]byte, 1024)
+ n, _ := io.ReadFull(resp.Body, buf)
+ resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n]))
+ return nil, resp, ErrBadHandshake
+ }
+
+ for _, ext := range parseExtensions(resp.Header) {
+ if ext[""] != "permessage-deflate" {
+ continue
+ }
+ _, snct := ext["server_no_context_takeover"]
+ _, cnct := ext["client_no_context_takeover"]
+ if !snct || !cnct {
+ return nil, resp, errInvalidCompression
+ }
+ conn.newCompressionWriter = compressNoContextTakeover
+ conn.newDecompressionReader = decompressNoContextTakeover
+ break
+ }
+
+ resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
+ conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")
+
+ netConn.SetDeadline(time.Time{})
+ netConn = nil // to avoid close in defer.
+ return conn, resp, nil
+}
diff --git a/vendor/github.com/gorilla/websocket/client_clone.go b/vendor/github.com/gorilla/websocket/client_clone.go
new file mode 100644
index 00000000..4f0d9437
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/client_clone.go
@@ -0,0 +1,16 @@
+// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.8
+
+package websocket
+
+import "crypto/tls"
+
+func cloneTLSConfig(cfg *tls.Config) *tls.Config {
+ if cfg == nil {
+ return &tls.Config{}
+ }
+ return cfg.Clone()
+}
diff --git a/vendor/github.com/gorilla/websocket/client_clone_legacy.go b/vendor/github.com/gorilla/websocket/client_clone_legacy.go
new file mode 100644
index 00000000..babb007f
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/client_clone_legacy.go
@@ -0,0 +1,38 @@
+// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !go1.8
+
+package websocket
+
+import "crypto/tls"
+
+// cloneTLSConfig clones all public fields except the fields
+// SessionTicketsDisabled and SessionTicketKey. This avoids copying the
+// sync.Mutex in the sync.Once and makes it safe to call cloneTLSConfig on a
+// config in active use.
+func cloneTLSConfig(cfg *tls.Config) *tls.Config {
+ if cfg == nil {
+ return &tls.Config{}
+ }
+ return &tls.Config{
+ Rand: cfg.Rand,
+ Time: cfg.Time,
+ Certificates: cfg.Certificates,
+ NameToCertificate: cfg.NameToCertificate,
+ GetCertificate: cfg.GetCertificate,
+ RootCAs: cfg.RootCAs,
+ NextProtos: cfg.NextProtos,
+ ServerName: cfg.ServerName,
+ ClientAuth: cfg.ClientAuth,
+ ClientCAs: cfg.ClientCAs,
+ InsecureSkipVerify: cfg.InsecureSkipVerify,
+ CipherSuites: cfg.CipherSuites,
+ PreferServerCipherSuites: cfg.PreferServerCipherSuites,
+ ClientSessionCache: cfg.ClientSessionCache,
+ MinVersion: cfg.MinVersion,
+ MaxVersion: cfg.MaxVersion,
+ CurvePreferences: cfg.CurvePreferences,
+ }
+}
diff --git a/vendor/github.com/gorilla/websocket/compression.go b/vendor/github.com/gorilla/websocket/compression.go
new file mode 100644
index 00000000..813ffb1e
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/compression.go
@@ -0,0 +1,148 @@
+// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package websocket
+
+import (
+ "compress/flate"
+ "errors"
+ "io"
+ "strings"
+ "sync"
+)
+
+const (
+ minCompressionLevel = -2 // flate.HuffmanOnly not defined in Go < 1.6
+ maxCompressionLevel = flate.BestCompression
+ defaultCompressionLevel = 1
+)
+
+var (
+ flateWriterPools [maxCompressionLevel - minCompressionLevel + 1]sync.Pool
+ flateReaderPool = sync.Pool{New: func() interface{} {
+ return flate.NewReader(nil)
+ }}
+)
+
+func decompressNoContextTakeover(r io.Reader) io.ReadCloser {
+ const tail =
+ // Add four bytes as specified in RFC
+ "\x00\x00\xff\xff" +
+ // Add final block to squelch unexpected EOF error from flate reader.
+ "\x01\x00\x00\xff\xff"
+
+ fr, _ := flateReaderPool.Get().(io.ReadCloser)
+ fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil)
+ return &flateReadWrapper{fr}
+}
+
+func isValidCompressionLevel(level int) bool {
+ return minCompressionLevel <= level && level <= maxCompressionLevel
+}
+
+func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteCloser {
+ p := &flateWriterPools[level-minCompressionLevel]
+ tw := &truncWriter{w: w}
+ fw, _ := p.Get().(*flate.Writer)
+ if fw == nil {
+ fw, _ = flate.NewWriter(tw, level)
+ } else {
+ fw.Reset(tw)
+ }
+ return &flateWriteWrapper{fw: fw, tw: tw, p: p}
+}
+
+// truncWriter is an io.Writer that writes all but the last four bytes of the
+// stream to another io.Writer.
+type truncWriter struct {
+ w io.WriteCloser
+ n int
+ p [4]byte
+}
+
+func (w *truncWriter) Write(p []byte) (int, error) {
+ n := 0
+
+ // fill buffer first for simplicity.
+ if w.n < len(w.p) {
+ n = copy(w.p[w.n:], p)
+ p = p[n:]
+ w.n += n
+ if len(p) == 0 {
+ return n, nil
+ }
+ }
+
+ m := len(p)
+ if m > len(w.p) {
+ m = len(w.p)
+ }
+
+ if nn, err := w.w.Write(w.p[:m]); err != nil {
+ return n + nn, err
+ }
+
+ copy(w.p[:], w.p[m:])
+ copy(w.p[len(w.p)-m:], p[len(p)-m:])
+ nn, err := w.w.Write(p[:len(p)-m])
+ return n + nn, err
+}
+
+type flateWriteWrapper struct {
+ fw *flate.Writer
+ tw *truncWriter
+ p *sync.Pool
+}
+
+func (w *flateWriteWrapper) Write(p []byte) (int, error) {
+ if w.fw == nil {
+ return 0, errWriteClosed
+ }
+ return w.fw.Write(p)
+}
+
+func (w *flateWriteWrapper) Close() error {
+ if w.fw == nil {
+ return errWriteClosed
+ }
+ err1 := w.fw.Flush()
+ w.p.Put(w.fw)
+ w.fw = nil
+ if w.tw.p != [4]byte{0, 0, 0xff, 0xff} {
+ return errors.New("websocket: internal error, unexpected bytes at end of flate stream")
+ }
+ err2 := w.tw.w.Close()
+ if err1 != nil {
+ return err1
+ }
+ return err2
+}
+
+type flateReadWrapper struct {
+ fr io.ReadCloser
+}
+
+func (r *flateReadWrapper) Read(p []byte) (int, error) {
+ if r.fr == nil {
+ return 0, io.ErrClosedPipe
+ }
+ n, err := r.fr.Read(p)
+ if err == io.EOF {
+ // Preemptively place the reader back in the pool. This helps with
+ // scenarios where the application does not call NextReader() soon after
+ // this final read.
+ r.Close()
+ }
+ return n, err
+}
+
+func (r *flateReadWrapper) Close() error {
+ if r.fr == nil {
+ return io.ErrClosedPipe
+ }
+ err := r.fr.Close()
+ flateReaderPool.Put(r.fr)
+ r.fr = nil
+ return err
+}
diff --git a/vendor/github.com/gorilla/websocket/conn.go b/vendor/github.com/gorilla/websocket/conn.go
new file mode 100644
index 00000000..97e1dbac
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/conn.go
@@ -0,0 +1,1149 @@
+// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package websocket
+
+import (
+ "bufio"
+ "encoding/binary"
+ "errors"
+ "io"
+ "io/ioutil"
+ "math/rand"
+ "net"
+ "strconv"
+ "sync"
+ "time"
+ "unicode/utf8"
+)
+
+const (
+ // Frame header byte 0 bits from Section 5.2 of RFC 6455
+ finalBit = 1 << 7
+ rsv1Bit = 1 << 6
+ rsv2Bit = 1 << 5
+ rsv3Bit = 1 << 4
+
+ // Frame header byte 1 bits from Section 5.2 of RFC 6455
+ maskBit = 1 << 7
+
+ maxFrameHeaderSize = 2 + 8 + 4 // Fixed header + length + mask
+ maxControlFramePayloadSize = 125
+
+ writeWait = time.Second
+
+ defaultReadBufferSize = 4096
+ defaultWriteBufferSize = 4096
+
+ continuationFrame = 0
+ noFrame = -1
+)
+
+// Close codes defined in RFC 6455, section 11.7.
+const (
+ CloseNormalClosure = 1000
+ CloseGoingAway = 1001
+ CloseProtocolError = 1002
+ CloseUnsupportedData = 1003
+ CloseNoStatusReceived = 1005
+ CloseAbnormalClosure = 1006
+ CloseInvalidFramePayloadData = 1007
+ ClosePolicyViolation = 1008
+ CloseMessageTooBig = 1009
+ CloseMandatoryExtension = 1010
+ CloseInternalServerErr = 1011
+ CloseServiceRestart = 1012
+ CloseTryAgainLater = 1013
+ CloseTLSHandshake = 1015
+)
+
+// The message types are defined in RFC 6455, section 11.8.
+const (
+ // TextMessage denotes a text data message. The text message payload is
+ // interpreted as UTF-8 encoded text data.
+ TextMessage = 1
+
+ // BinaryMessage denotes a binary data message.
+ BinaryMessage = 2
+
+ // CloseMessage denotes a close control message. The optional message
+ // payload contains a numeric code and text. Use the FormatCloseMessage
+ // function to format a close message payload.
+ CloseMessage = 8
+
+ // PingMessage denotes a ping control message. The optional message payload
+ // is UTF-8 encoded text.
+ PingMessage = 9
+
+ // PongMessage denotes a ping control message. The optional message payload
+ // is UTF-8 encoded text.
+ PongMessage = 10
+)
+
+// ErrCloseSent is returned when the application writes a message to the
+// connection after sending a close message.
+var ErrCloseSent = errors.New("websocket: close sent")
+
+// ErrReadLimit is returned when reading a message that is larger than the
+// read limit set for the connection.
+var ErrReadLimit = errors.New("websocket: read limit exceeded")
+
+// netError satisfies the net Error interface.
+type netError struct {
+ msg string
+ temporary bool
+ timeout bool
+}
+
+func (e *netError) Error() string { return e.msg }
+func (e *netError) Temporary() bool { return e.temporary }
+func (e *netError) Timeout() bool { return e.timeout }
+
+// CloseError represents close frame.
+type CloseError struct {
+
+ // Code is defined in RFC 6455, section 11.7.
+ Code int
+
+ // Text is the optional text payload.
+ Text string
+}
+
+func (e *CloseError) Error() string {
+ s := []byte("websocket: close ")
+ s = strconv.AppendInt(s, int64(e.Code), 10)
+ switch e.Code {
+ case CloseNormalClosure:
+ s = append(s, " (normal)"...)
+ case CloseGoingAway:
+ s = append(s, " (going away)"...)
+ case CloseProtocolError:
+ s = append(s, " (protocol error)"...)
+ case CloseUnsupportedData:
+ s = append(s, " (unsupported data)"...)
+ case CloseNoStatusReceived:
+ s = append(s, " (no status)"...)
+ case CloseAbnormalClosure:
+ s = append(s, " (abnormal closure)"...)
+ case CloseInvalidFramePayloadData:
+ s = append(s, " (invalid payload data)"...)
+ case ClosePolicyViolation:
+ s = append(s, " (policy violation)"...)
+ case CloseMessageTooBig:
+ s = append(s, " (message too big)"...)
+ case CloseMandatoryExtension:
+ s = append(s, " (mandatory extension missing)"...)
+ case CloseInternalServerErr:
+ s = append(s, " (internal server error)"...)
+ case CloseTLSHandshake:
+ s = append(s, " (TLS handshake error)"...)
+ }
+ if e.Text != "" {
+ s = append(s, ": "...)
+ s = append(s, e.Text...)
+ }
+ return string(s)
+}
+
+// IsCloseError returns boolean indicating whether the error is a *CloseError
+// with one of the specified codes.
+func IsCloseError(err error, codes ...int) bool {
+ if e, ok := err.(*CloseError); ok {
+ for _, code := range codes {
+ if e.Code == code {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// IsUnexpectedCloseError returns boolean indicating whether the error is a
+// *CloseError with a code not in the list of expected codes.
+func IsUnexpectedCloseError(err error, expectedCodes ...int) bool {
+ if e, ok := err.(*CloseError); ok {
+ for _, code := range expectedCodes {
+ if e.Code == code {
+ return false
+ }
+ }
+ return true
+ }
+ return false
+}
+
+var (
+ errWriteTimeout = &netError{msg: "websocket: write timeout", timeout: true, temporary: true}
+ errUnexpectedEOF = &CloseError{Code: CloseAbnormalClosure, Text: io.ErrUnexpectedEOF.Error()}
+ errBadWriteOpCode = errors.New("websocket: bad write message type")
+ errWriteClosed = errors.New("websocket: write closed")
+ errInvalidControlFrame = errors.New("websocket: invalid control frame")
+)
+
+func newMaskKey() [4]byte {
+ n := rand.Uint32()
+ return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)}
+}
+
+func hideTempErr(err error) error {
+ if e, ok := err.(net.Error); ok && e.Temporary() {
+ err = &netError{msg: e.Error(), timeout: e.Timeout()}
+ }
+ return err
+}
+
+func isControl(frameType int) bool {
+ return frameType == CloseMessage || frameType == PingMessage || frameType == PongMessage
+}
+
+func isData(frameType int) bool {
+ return frameType == TextMessage || frameType == BinaryMessage
+}
+
+var validReceivedCloseCodes = map[int]bool{
+ // see http://www.iana.org/assignments/websocket/websocket.xhtml#close-code-number
+
+ CloseNormalClosure: true,
+ CloseGoingAway: true,
+ CloseProtocolError: true,
+ CloseUnsupportedData: true,
+ CloseNoStatusReceived: false,
+ CloseAbnormalClosure: false,
+ CloseInvalidFramePayloadData: true,
+ ClosePolicyViolation: true,
+ CloseMessageTooBig: true,
+ CloseMandatoryExtension: true,
+ CloseInternalServerErr: true,
+ CloseServiceRestart: true,
+ CloseTryAgainLater: true,
+ CloseTLSHandshake: false,
+}
+
+func isValidReceivedCloseCode(code int) bool {
+ return validReceivedCloseCodes[code] || (code >= 3000 && code <= 4999)
+}
+
+// The Conn type represents a WebSocket connection.
+type Conn struct {
+ conn net.Conn
+ isServer bool
+ subprotocol string
+
+ // Write fields
+ mu chan bool // used as mutex to protect write to conn
+ writeBuf []byte // frame is constructed in this buffer.
+ writeDeadline time.Time
+ writer io.WriteCloser // the current writer returned to the application
+ isWriting bool // for best-effort concurrent write detection
+
+ writeErrMu sync.Mutex
+ writeErr error
+
+ enableWriteCompression bool
+ compressionLevel int
+ newCompressionWriter func(io.WriteCloser, int) io.WriteCloser
+
+ // Read fields
+ reader io.ReadCloser // the current reader returned to the application
+ readErr error
+ br *bufio.Reader
+ readRemaining int64 // bytes remaining in current frame.
+ readFinal bool // true the current message has more frames.
+ readLength int64 // Message size.
+ readLimit int64 // Maximum message size.
+ readMaskPos int
+ readMaskKey [4]byte
+ handlePong func(string) error
+ handlePing func(string) error
+ handleClose func(int, string) error
+ readErrCount int
+ messageReader *messageReader // the current low-level reader
+
+ readDecompress bool // whether last read frame had RSV1 set
+ newDecompressionReader func(io.Reader) io.ReadCloser
+}
+
+func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int) *Conn {
+ return newConnBRW(conn, isServer, readBufferSize, writeBufferSize, nil)
+}
+
+type writeHook struct {
+ p []byte
+}
+
+func (wh *writeHook) Write(p []byte) (int, error) {
+ wh.p = p
+ return len(p), nil
+}
+
+func newConnBRW(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int, brw *bufio.ReadWriter) *Conn {
+ mu := make(chan bool, 1)
+ mu <- true
+
+ var br *bufio.Reader
+ if readBufferSize == 0 && brw != nil && brw.Reader != nil {
+ // Reuse the supplied bufio.Reader if the buffer has a useful size.
+ // This code assumes that peek on a reader returns
+ // bufio.Reader.buf[:0].
+ brw.Reader.Reset(conn)
+ if p, err := brw.Reader.Peek(0); err == nil && cap(p) >= 256 {
+ br = brw.Reader
+ }
+ }
+ if br == nil {
+ if readBufferSize == 0 {
+ readBufferSize = defaultReadBufferSize
+ }
+ if readBufferSize < maxControlFramePayloadSize {
+ readBufferSize = maxControlFramePayloadSize
+ }
+ br = bufio.NewReaderSize(conn, readBufferSize)
+ }
+
+ var writeBuf []byte
+ if writeBufferSize == 0 && brw != nil && brw.Writer != nil {
+ // Use the bufio.Writer's buffer if the buffer has a useful size. This
+ // code assumes that bufio.Writer.buf[:1] is passed to the
+ // bufio.Writer's underlying writer.
+ var wh writeHook
+ brw.Writer.Reset(&wh)
+ brw.Writer.WriteByte(0)
+ brw.Flush()
+ if cap(wh.p) >= maxFrameHeaderSize+256 {
+ writeBuf = wh.p[:cap(wh.p)]
+ }
+ }
+
+ if writeBuf == nil {
+ if writeBufferSize == 0 {
+ writeBufferSize = defaultWriteBufferSize
+ }
+ writeBuf = make([]byte, writeBufferSize+maxFrameHeaderSize)
+ }
+
+ c := &Conn{
+ isServer: isServer,
+ br: br,
+ conn: conn,
+ mu: mu,
+ readFinal: true,
+ writeBuf: writeBuf,
+ enableWriteCompression: true,
+ compressionLevel: defaultCompressionLevel,
+ }
+ c.SetCloseHandler(nil)
+ c.SetPingHandler(nil)
+ c.SetPongHandler(nil)
+ return c
+}
+
+// Subprotocol returns the negotiated protocol for the connection.
+func (c *Conn) Subprotocol() string {
+ return c.subprotocol
+}
+
+// Close closes the underlying network connection without sending or waiting for a close frame.
+func (c *Conn) Close() error {
+ return c.conn.Close()
+}
+
+// LocalAddr returns the local network address.
+func (c *Conn) LocalAddr() net.Addr {
+ return c.conn.LocalAddr()
+}
+
+// RemoteAddr returns the remote network address.
+func (c *Conn) RemoteAddr() net.Addr {
+ return c.conn.RemoteAddr()
+}
+
+// Write methods
+
+func (c *Conn) writeFatal(err error) error {
+ err = hideTempErr(err)
+ c.writeErrMu.Lock()
+ if c.writeErr == nil {
+ c.writeErr = err
+ }
+ c.writeErrMu.Unlock()
+ return err
+}
+
+func (c *Conn) write(frameType int, deadline time.Time, bufs ...[]byte) error {
+ <-c.mu
+ defer func() { c.mu <- true }()
+
+ c.writeErrMu.Lock()
+ err := c.writeErr
+ c.writeErrMu.Unlock()
+ if err != nil {
+ return err
+ }
+
+ c.conn.SetWriteDeadline(deadline)
+ for _, buf := range bufs {
+ if len(buf) > 0 {
+ _, err := c.conn.Write(buf)
+ if err != nil {
+ return c.writeFatal(err)
+ }
+ }
+ }
+
+ if frameType == CloseMessage {
+ c.writeFatal(ErrCloseSent)
+ }
+ return nil
+}
+
+// WriteControl writes a control message with the given deadline. The allowed
+// message types are CloseMessage, PingMessage and PongMessage.
+func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) error {
+ if !isControl(messageType) {
+ return errBadWriteOpCode
+ }
+ if len(data) > maxControlFramePayloadSize {
+ return errInvalidControlFrame
+ }
+
+ b0 := byte(messageType) | finalBit
+ b1 := byte(len(data))
+ if !c.isServer {
+ b1 |= maskBit
+ }
+
+ buf := make([]byte, 0, maxFrameHeaderSize+maxControlFramePayloadSize)
+ buf = append(buf, b0, b1)
+
+ if c.isServer {
+ buf = append(buf, data...)
+ } else {
+ key := newMaskKey()
+ buf = append(buf, key[:]...)
+ buf = append(buf, data...)
+ maskBytes(key, 0, buf[6:])
+ }
+
+ d := time.Hour * 1000
+ if !deadline.IsZero() {
+ d = deadline.Sub(time.Now())
+ if d < 0 {
+ return errWriteTimeout
+ }
+ }
+
+ timer := time.NewTimer(d)
+ select {
+ case <-c.mu:
+ timer.Stop()
+ case <-timer.C:
+ return errWriteTimeout
+ }
+ defer func() { c.mu <- true }()
+
+ c.writeErrMu.Lock()
+ err := c.writeErr
+ c.writeErrMu.Unlock()
+ if err != nil {
+ return err
+ }
+
+ c.conn.SetWriteDeadline(deadline)
+ _, err = c.conn.Write(buf)
+ if err != nil {
+ return c.writeFatal(err)
+ }
+ if messageType == CloseMessage {
+ c.writeFatal(ErrCloseSent)
+ }
+ return err
+}
+
+func (c *Conn) prepWrite(messageType int) error {
+ // Close previous writer if not already closed by the application. It's
+ // probably better to return an error in this situation, but we cannot
+ // change this without breaking existing applications.
+ if c.writer != nil {
+ c.writer.Close()
+ c.writer = nil
+ }
+
+ if !isControl(messageType) && !isData(messageType) {
+ return errBadWriteOpCode
+ }
+
+ c.writeErrMu.Lock()
+ err := c.writeErr
+ c.writeErrMu.Unlock()
+ return err
+}
+
+// NextWriter returns a writer for the next message to send. The writer's Close
+// method flushes the complete message to the network.
+//
+// There can be at most one open writer on a connection. NextWriter closes the
+// previous writer if the application has not already done so.
+func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
+ if err := c.prepWrite(messageType); err != nil {
+ return nil, err
+ }
+
+ mw := &messageWriter{
+ c: c,
+ frameType: messageType,
+ pos: maxFrameHeaderSize,
+ }
+ c.writer = mw
+ if c.newCompressionWriter != nil && c.enableWriteCompression && isData(messageType) {
+ w := c.newCompressionWriter(c.writer, c.compressionLevel)
+ mw.compress = true
+ c.writer = w
+ }
+ return c.writer, nil
+}
+
+type messageWriter struct {
+ c *Conn
+ compress bool // whether next call to flushFrame should set RSV1
+ pos int // end of data in writeBuf.
+ frameType int // type of the current frame.
+ err error
+}
+
+func (w *messageWriter) fatal(err error) error {
+ if w.err != nil {
+ w.err = err
+ w.c.writer = nil
+ }
+ return err
+}
+
+// flushFrame writes buffered data and extra as a frame to the network. The
+// final argument indicates that this is the last frame in the message.
+func (w *messageWriter) flushFrame(final bool, extra []byte) error {
+ c := w.c
+ length := w.pos - maxFrameHeaderSize + len(extra)
+
+ // Check for invalid control frames.
+ if isControl(w.frameType) &&
+ (!final || length > maxControlFramePayloadSize) {
+ return w.fatal(errInvalidControlFrame)
+ }
+
+ b0 := byte(w.frameType)
+ if final {
+ b0 |= finalBit
+ }
+ if w.compress {
+ b0 |= rsv1Bit
+ }
+ w.compress = false
+
+ b1 := byte(0)
+ if !c.isServer {
+ b1 |= maskBit
+ }
+
+ // Assume that the frame starts at beginning of c.writeBuf.
+ framePos := 0
+ if c.isServer {
+ // Adjust up if mask not included in the header.
+ framePos = 4
+ }
+
+ switch {
+ case length >= 65536:
+ c.writeBuf[framePos] = b0
+ c.writeBuf[framePos+1] = b1 | 127
+ binary.BigEndian.PutUint64(c.writeBuf[framePos+2:], uint64(length))
+ case length > 125:
+ framePos += 6
+ c.writeBuf[framePos] = b0
+ c.writeBuf[framePos+1] = b1 | 126
+ binary.BigEndian.PutUint16(c.writeBuf[framePos+2:], uint16(length))
+ default:
+ framePos += 8
+ c.writeBuf[framePos] = b0
+ c.writeBuf[framePos+1] = b1 | byte(length)
+ }
+
+ if !c.isServer {
+ key := newMaskKey()
+ copy(c.writeBuf[maxFrameHeaderSize-4:], key[:])
+ maskBytes(key, 0, c.writeBuf[maxFrameHeaderSize:w.pos])
+ if len(extra) > 0 {
+ return c.writeFatal(errors.New("websocket: internal error, extra used in client mode"))
+ }
+ }
+
+ // Write the buffers to the connection with best-effort detection of
+ // concurrent writes. See the concurrency section in the package
+ // documentation for more info.
+
+ if c.isWriting {
+ panic("concurrent write to websocket connection")
+ }
+ c.isWriting = true
+
+ err := c.write(w.frameType, c.writeDeadline, c.writeBuf[framePos:w.pos], extra)
+
+ if !c.isWriting {
+ panic("concurrent write to websocket connection")
+ }
+ c.isWriting = false
+
+ if err != nil {
+ return w.fatal(err)
+ }
+
+ if final {
+ c.writer = nil
+ return nil
+ }
+
+ // Setup for next frame.
+ w.pos = maxFrameHeaderSize
+ w.frameType = continuationFrame
+ return nil
+}
+
+func (w *messageWriter) ncopy(max int) (int, error) {
+ n := len(w.c.writeBuf) - w.pos
+ if n <= 0 {
+ if err := w.flushFrame(false, nil); err != nil {
+ return 0, err
+ }
+ n = len(w.c.writeBuf) - w.pos
+ }
+ if n > max {
+ n = max
+ }
+ return n, nil
+}
+
+func (w *messageWriter) Write(p []byte) (int, error) {
+ if w.err != nil {
+ return 0, w.err
+ }
+
+ if len(p) > 2*len(w.c.writeBuf) && w.c.isServer {
+ // Don't buffer large messages.
+ err := w.flushFrame(false, p)
+ if err != nil {
+ return 0, err
+ }
+ return len(p), nil
+ }
+
+ nn := len(p)
+ for len(p) > 0 {
+ n, err := w.ncopy(len(p))
+ if err != nil {
+ return 0, err
+ }
+ copy(w.c.writeBuf[w.pos:], p[:n])
+ w.pos += n
+ p = p[n:]
+ }
+ return nn, nil
+}
+
+func (w *messageWriter) WriteString(p string) (int, error) {
+ if w.err != nil {
+ return 0, w.err
+ }
+
+ nn := len(p)
+ for len(p) > 0 {
+ n, err := w.ncopy(len(p))
+ if err != nil {
+ return 0, err
+ }
+ copy(w.c.writeBuf[w.pos:], p[:n])
+ w.pos += n
+ p = p[n:]
+ }
+ return nn, nil
+}
+
+func (w *messageWriter) ReadFrom(r io.Reader) (nn int64, err error) {
+ if w.err != nil {
+ return 0, w.err
+ }
+ for {
+ if w.pos == len(w.c.writeBuf) {
+ err = w.flushFrame(false, nil)
+ if err != nil {
+ break
+ }
+ }
+ var n int
+ n, err = r.Read(w.c.writeBuf[w.pos:])
+ w.pos += n
+ nn += int64(n)
+ if err != nil {
+ if err == io.EOF {
+ err = nil
+ }
+ break
+ }
+ }
+ return nn, err
+}
+
+func (w *messageWriter) Close() error {
+ if w.err != nil {
+ return w.err
+ }
+ if err := w.flushFrame(true, nil); err != nil {
+ return err
+ }
+ w.err = errWriteClosed
+ return nil
+}
+
+// WritePreparedMessage writes prepared message into connection.
+func (c *Conn) WritePreparedMessage(pm *PreparedMessage) error {
+ frameType, frameData, err := pm.frame(prepareKey{
+ isServer: c.isServer,
+ compress: c.newCompressionWriter != nil && c.enableWriteCompression && isData(pm.messageType),
+ compressionLevel: c.compressionLevel,
+ })
+ if err != nil {
+ return err
+ }
+ if c.isWriting {
+ panic("concurrent write to websocket connection")
+ }
+ c.isWriting = true
+ err = c.write(frameType, c.writeDeadline, frameData, nil)
+ if !c.isWriting {
+ panic("concurrent write to websocket connection")
+ }
+ c.isWriting = false
+ return err
+}
+
+// WriteMessage is a helper method for getting a writer using NextWriter,
+// writing the message and closing the writer.
+func (c *Conn) WriteMessage(messageType int, data []byte) error {
+
+ if c.isServer && (c.newCompressionWriter == nil || !c.enableWriteCompression) {
+ // Fast path with no allocations and single frame.
+
+ if err := c.prepWrite(messageType); err != nil {
+ return err
+ }
+ mw := messageWriter{c: c, frameType: messageType, pos: maxFrameHeaderSize}
+ n := copy(c.writeBuf[mw.pos:], data)
+ mw.pos += n
+ data = data[n:]
+ return mw.flushFrame(true, data)
+ }
+
+ w, err := c.NextWriter(messageType)
+ if err != nil {
+ return err
+ }
+ if _, err = w.Write(data); err != nil {
+ return err
+ }
+ return w.Close()
+}
+
+// SetWriteDeadline sets the write deadline on the underlying network
+// connection. After a write has timed out, the websocket state is corrupt and
+// all future writes will return an error. A zero value for t means writes will
+// not time out.
+func (c *Conn) SetWriteDeadline(t time.Time) error {
+ c.writeDeadline = t
+ return nil
+}
+
+// Read methods
+
+func (c *Conn) advanceFrame() (int, error) {
+
+ // 1. Skip remainder of previous frame.
+
+ if c.readRemaining > 0 {
+ if _, err := io.CopyN(ioutil.Discard, c.br, c.readRemaining); err != nil {
+ return noFrame, err
+ }
+ }
+
+ // 2. Read and parse first two bytes of frame header.
+
+ p, err := c.read(2)
+ if err != nil {
+ return noFrame, err
+ }
+
+ final := p[0]&finalBit != 0
+ frameType := int(p[0] & 0xf)
+ mask := p[1]&maskBit != 0
+ c.readRemaining = int64(p[1] & 0x7f)
+
+ c.readDecompress = false
+ if c.newDecompressionReader != nil && (p[0]&rsv1Bit) != 0 {
+ c.readDecompress = true
+ p[0] &^= rsv1Bit
+ }
+
+ if rsv := p[0] & (rsv1Bit | rsv2Bit | rsv3Bit); rsv != 0 {
+ return noFrame, c.handleProtocolError("unexpected reserved bits 0x" + strconv.FormatInt(int64(rsv), 16))
+ }
+
+ switch frameType {
+ case CloseMessage, PingMessage, PongMessage:
+ if c.readRemaining > maxControlFramePayloadSize {
+ return noFrame, c.handleProtocolError("control frame length > 125")
+ }
+ if !final {
+ return noFrame, c.handleProtocolError("control frame not final")
+ }
+ case TextMessage, BinaryMessage:
+ if !c.readFinal {
+ return noFrame, c.handleProtocolError("message start before final message frame")
+ }
+ c.readFinal = final
+ case continuationFrame:
+ if c.readFinal {
+ return noFrame, c.handleProtocolError("continuation after final message frame")
+ }
+ c.readFinal = final
+ default:
+ return noFrame, c.handleProtocolError("unknown opcode " + strconv.Itoa(frameType))
+ }
+
+ // 3. Read and parse frame length.
+
+ switch c.readRemaining {
+ case 126:
+ p, err := c.read(2)
+ if err != nil {
+ return noFrame, err
+ }
+ c.readRemaining = int64(binary.BigEndian.Uint16(p))
+ case 127:
+ p, err := c.read(8)
+ if err != nil {
+ return noFrame, err
+ }
+ c.readRemaining = int64(binary.BigEndian.Uint64(p))
+ }
+
+ // 4. Handle frame masking.
+
+ if mask != c.isServer {
+ return noFrame, c.handleProtocolError("incorrect mask flag")
+ }
+
+ if mask {
+ c.readMaskPos = 0
+ p, err := c.read(len(c.readMaskKey))
+ if err != nil {
+ return noFrame, err
+ }
+ copy(c.readMaskKey[:], p)
+ }
+
+ // 5. For text and binary messages, enforce read limit and return.
+
+ if frameType == continuationFrame || frameType == TextMessage || frameType == BinaryMessage {
+
+ c.readLength += c.readRemaining
+ if c.readLimit > 0 && c.readLength > c.readLimit {
+ c.WriteControl(CloseMessage, FormatCloseMessage(CloseMessageTooBig, ""), time.Now().Add(writeWait))
+ return noFrame, ErrReadLimit
+ }
+
+ return frameType, nil
+ }
+
+ // 6. Read control frame payload.
+
+ var payload []byte
+ if c.readRemaining > 0 {
+ payload, err = c.read(int(c.readRemaining))
+ c.readRemaining = 0
+ if err != nil {
+ return noFrame, err
+ }
+ if c.isServer {
+ maskBytes(c.readMaskKey, 0, payload)
+ }
+ }
+
+ // 7. Process control frame payload.
+
+ switch frameType {
+ case PongMessage:
+ if err := c.handlePong(string(payload)); err != nil {
+ return noFrame, err
+ }
+ case PingMessage:
+ if err := c.handlePing(string(payload)); err != nil {
+ return noFrame, err
+ }
+ case CloseMessage:
+ closeCode := CloseNoStatusReceived
+ closeText := ""
+ if len(payload) >= 2 {
+ closeCode = int(binary.BigEndian.Uint16(payload))
+ if !isValidReceivedCloseCode(closeCode) {
+ return noFrame, c.handleProtocolError("invalid close code")
+ }
+ closeText = string(payload[2:])
+ if !utf8.ValidString(closeText) {
+ return noFrame, c.handleProtocolError("invalid utf8 payload in close frame")
+ }
+ }
+ if err := c.handleClose(closeCode, closeText); err != nil {
+ return noFrame, err
+ }
+ return noFrame, &CloseError{Code: closeCode, Text: closeText}
+ }
+
+ return frameType, nil
+}
+
+func (c *Conn) handleProtocolError(message string) error {
+ c.WriteControl(CloseMessage, FormatCloseMessage(CloseProtocolError, message), time.Now().Add(writeWait))
+ return errors.New("websocket: " + message)
+}
+
+// NextReader returns the next data message received from the peer. The
+// returned messageType is either TextMessage or BinaryMessage.
+//
+// There can be at most one open reader on a connection. NextReader discards
+// the previous message if the application has not already consumed it.
+//
+// Applications must break out of the application's read loop when this method
+// returns a non-nil error value. Errors returned from this method are
+// permanent. Once this method returns a non-nil error, all subsequent calls to
+// this method return the same error.
+func (c *Conn) NextReader() (messageType int, r io.Reader, err error) {
+ // Close previous reader, only relevant for decompression.
+ if c.reader != nil {
+ c.reader.Close()
+ c.reader = nil
+ }
+
+ c.messageReader = nil
+ c.readLength = 0
+
+ for c.readErr == nil {
+ frameType, err := c.advanceFrame()
+ if err != nil {
+ c.readErr = hideTempErr(err)
+ break
+ }
+ if frameType == TextMessage || frameType == BinaryMessage {
+ c.messageReader = &messageReader{c}
+ c.reader = c.messageReader
+ if c.readDecompress {
+ c.reader = c.newDecompressionReader(c.reader)
+ }
+ return frameType, c.reader, nil
+ }
+ }
+
+ // Applications that do handle the error returned from this method spin in
+ // tight loop on connection failure. To help application developers detect
+ // this error, panic on repeated reads to the failed connection.
+ c.readErrCount++
+ if c.readErrCount >= 1000 {
+ panic("repeated read on failed websocket connection")
+ }
+
+ return noFrame, nil, c.readErr
+}
+
+type messageReader struct{ c *Conn }
+
+func (r *messageReader) Read(b []byte) (int, error) {
+ c := r.c
+ if c.messageReader != r {
+ return 0, io.EOF
+ }
+
+ for c.readErr == nil {
+
+ if c.readRemaining > 0 {
+ if int64(len(b)) > c.readRemaining {
+ b = b[:c.readRemaining]
+ }
+ n, err := c.br.Read(b)
+ c.readErr = hideTempErr(err)
+ if c.isServer {
+ c.readMaskPos = maskBytes(c.readMaskKey, c.readMaskPos, b[:n])
+ }
+ c.readRemaining -= int64(n)
+ if c.readRemaining > 0 && c.readErr == io.EOF {
+ c.readErr = errUnexpectedEOF
+ }
+ return n, c.readErr
+ }
+
+ if c.readFinal {
+ c.messageReader = nil
+ return 0, io.EOF
+ }
+
+ frameType, err := c.advanceFrame()
+ switch {
+ case err != nil:
+ c.readErr = hideTempErr(err)
+ case frameType == TextMessage || frameType == BinaryMessage:
+ c.readErr = errors.New("websocket: internal error, unexpected text or binary in Reader")
+ }
+ }
+
+ err := c.readErr
+ if err == io.EOF && c.messageReader == r {
+ err = errUnexpectedEOF
+ }
+ return 0, err
+}
+
+func (r *messageReader) Close() error {
+ return nil
+}
+
+// ReadMessage is a helper method for getting a reader using NextReader and
+// reading from that reader to a buffer.
+func (c *Conn) ReadMessage() (messageType int, p []byte, err error) {
+ var r io.Reader
+ messageType, r, err = c.NextReader()
+ if err != nil {
+ return messageType, nil, err
+ }
+ p, err = ioutil.ReadAll(r)
+ return messageType, p, err
+}
+
+// SetReadDeadline sets the read deadline on the underlying network connection.
+// After a read has timed out, the websocket connection state is corrupt and
+// all future reads will return an error. A zero value for t means reads will
+// not time out.
+func (c *Conn) SetReadDeadline(t time.Time) error {
+ return c.conn.SetReadDeadline(t)
+}
+
+// SetReadLimit sets the maximum size for a message read from the peer. If a
+// message exceeds the limit, the connection sends a close frame to the peer
+// and returns ErrReadLimit to the application.
+func (c *Conn) SetReadLimit(limit int64) {
+ c.readLimit = limit
+}
+
+// CloseHandler returns the current close handler
+func (c *Conn) CloseHandler() func(code int, text string) error {
+ return c.handleClose
+}
+
+// SetCloseHandler sets the handler for close messages received from the peer.
+// The code argument to h is the received close code or CloseNoStatusReceived
+// if the close message is empty. The default close handler sends a close frame
+// back to the peer.
+//
+// The application must read the connection to process close messages as
+// described in the section on Control Frames above.
+//
+// The connection read methods return a CloseError when a close frame is
+// received. Most applications should handle close messages as part of their
+// normal error handling. Applications should only set a close handler when the
+// application must perform some action before sending a close frame back to
+// the peer.
+func (c *Conn) SetCloseHandler(h func(code int, text string) error) {
+ if h == nil {
+ h = func(code int, text string) error {
+ message := []byte{}
+ if code != CloseNoStatusReceived {
+ message = FormatCloseMessage(code, "")
+ }
+ c.WriteControl(CloseMessage, message, time.Now().Add(writeWait))
+ return nil
+ }
+ }
+ c.handleClose = h
+}
+
+// PingHandler returns the current ping handler
+func (c *Conn) PingHandler() func(appData string) error {
+ return c.handlePing
+}
+
+// SetPingHandler sets the handler for ping messages received from the peer.
+// The appData argument to h is the PING frame application data. The default
+// ping handler sends a pong to the peer.
+//
+// The application must read the connection to process ping messages as
+// described in the section on Control Frames above.
+func (c *Conn) SetPingHandler(h func(appData string) error) {
+ if h == nil {
+ h = func(message string) error {
+ err := c.WriteControl(PongMessage, []byte(message), time.Now().Add(writeWait))
+ if err == ErrCloseSent {
+ return nil
+ } else if e, ok := err.(net.Error); ok && e.Temporary() {
+ return nil
+ }
+ return err
+ }
+ }
+ c.handlePing = h
+}
+
+// PongHandler returns the current pong handler
+func (c *Conn) PongHandler() func(appData string) error {
+ return c.handlePong
+}
+
+// SetPongHandler sets the handler for pong messages received from the peer.
+// The appData argument to h is the PONG frame application data. The default
+// pong handler does nothing.
+//
+// The application must read the connection to process ping messages as
+// described in the section on Control Frames above.
+func (c *Conn) SetPongHandler(h func(appData string) error) {
+ if h == nil {
+ h = func(string) error { return nil }
+ }
+ c.handlePong = h
+}
+
+// UnderlyingConn returns the internal net.Conn. This can be used to further
+// modifications to connection specific flags.
+func (c *Conn) UnderlyingConn() net.Conn {
+ return c.conn
+}
+
+// EnableWriteCompression enables and disables write compression of
+// subsequent text and binary messages. This function is a noop if
+// compression was not negotiated with the peer.
+func (c *Conn) EnableWriteCompression(enable bool) {
+ c.enableWriteCompression = enable
+}
+
+// SetCompressionLevel sets the flate compression level for subsequent text and
+// binary messages. This function is a noop if compression was not negotiated
+// with the peer. See the compress/flate package for a description of
+// compression levels.
+func (c *Conn) SetCompressionLevel(level int) error {
+ if !isValidCompressionLevel(level) {
+ return errors.New("websocket: invalid compression level")
+ }
+ c.compressionLevel = level
+ return nil
+}
+
+// FormatCloseMessage formats closeCode and text as a WebSocket close message.
+func FormatCloseMessage(closeCode int, text string) []byte {
+ buf := make([]byte, 2+len(text))
+ binary.BigEndian.PutUint16(buf, uint16(closeCode))
+ copy(buf[2:], text)
+ return buf
+}
diff --git a/vendor/github.com/gorilla/websocket/conn_read.go b/vendor/github.com/gorilla/websocket/conn_read.go
new file mode 100644
index 00000000..1ea15059
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/conn_read.go
@@ -0,0 +1,18 @@
+// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.5
+
+package websocket
+
+import "io"
+
+func (c *Conn) read(n int) ([]byte, error) {
+ p, err := c.br.Peek(n)
+ if err == io.EOF {
+ err = errUnexpectedEOF
+ }
+ c.br.Discard(len(p))
+ return p, err
+}
diff --git a/vendor/github.com/gorilla/websocket/conn_read_legacy.go b/vendor/github.com/gorilla/websocket/conn_read_legacy.go
new file mode 100644
index 00000000..018541cf
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/conn_read_legacy.go
@@ -0,0 +1,21 @@
+// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !go1.5
+
+package websocket
+
+import "io"
+
+func (c *Conn) read(n int) ([]byte, error) {
+ p, err := c.br.Peek(n)
+ if err == io.EOF {
+ err = errUnexpectedEOF
+ }
+ if len(p) > 0 {
+ // advance over the bytes just read
+ io.ReadFull(c.br, p)
+ }
+ return p, err
+}
diff --git a/vendor/github.com/gorilla/websocket/doc.go b/vendor/github.com/gorilla/websocket/doc.go
new file mode 100644
index 00000000..e291a952
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/doc.go
@@ -0,0 +1,180 @@
+// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package websocket implements the WebSocket protocol defined in RFC 6455.
+//
+// Overview
+//
+// The Conn type represents a WebSocket connection. A server application uses
+// the Upgrade function from an Upgrader object with a HTTP request handler
+// to get a pointer to a Conn:
+//
+// var upgrader = websocket.Upgrader{
+// ReadBufferSize: 1024,
+// WriteBufferSize: 1024,
+// }
+//
+// func handler(w http.ResponseWriter, r *http.Request) {
+// conn, err := upgrader.Upgrade(w, r, nil)
+// if err != nil {
+// log.Println(err)
+// return
+// }
+// ... Use conn to send and receive messages.
+// }
+//
+// Call the connection's WriteMessage and ReadMessage methods to send and
+// receive messages as a slice of bytes. This snippet of code shows how to echo
+// messages using these methods:
+//
+// for {
+// messageType, p, err := conn.ReadMessage()
+// if err != nil {
+// return
+// }
+// if err = conn.WriteMessage(messageType, p); err != nil {
+// return err
+// }
+// }
+//
+// In above snippet of code, p is a []byte and messageType is an int with value
+// websocket.BinaryMessage or websocket.TextMessage.
+//
+// An application can also send and receive messages using the io.WriteCloser
+// and io.Reader interfaces. To send a message, call the connection NextWriter
+// method to get an io.WriteCloser, write the message to the writer and close
+// the writer when done. To receive a message, call the connection NextReader
+// method to get an io.Reader and read until io.EOF is returned. This snippet
+// shows how to echo messages using the NextWriter and NextReader methods:
+//
+// for {
+// messageType, r, err := conn.NextReader()
+// if err != nil {
+// return
+// }
+// w, err := conn.NextWriter(messageType)
+// if err != nil {
+// return err
+// }
+// if _, err := io.Copy(w, r); err != nil {
+// return err
+// }
+// if err := w.Close(); err != nil {
+// return err
+// }
+// }
+//
+// Data Messages
+//
+// The WebSocket protocol distinguishes between text and binary data messages.
+// Text messages are interpreted as UTF-8 encoded text. The interpretation of
+// binary messages is left to the application.
+//
+// This package uses the TextMessage and BinaryMessage integer constants to
+// identify the two data message types. The ReadMessage and NextReader methods
+// return the type of the received message. The messageType argument to the
+// WriteMessage and NextWriter methods specifies the type of a sent message.
+//
+// It is the application's responsibility to ensure that text messages are
+// valid UTF-8 encoded text.
+//
+// Control Messages
+//
+// The WebSocket protocol defines three types of control messages: close, ping
+// and pong. Call the connection WriteControl, WriteMessage or NextWriter
+// methods to send a control message to the peer.
+//
+// Connections handle received close messages by sending a close message to the
+// peer and returning a *CloseError from the the NextReader, ReadMessage or the
+// message Read method.
+//
+// Connections handle received ping and pong messages by invoking callback
+// functions set with SetPingHandler and SetPongHandler methods. The callback
+// functions are called from the NextReader, ReadMessage and the message Read
+// methods.
+//
+// The default ping handler sends a pong to the peer. The application's reading
+// goroutine can block for a short time while the handler writes the pong data
+// to the connection.
+//
+// The application must read the connection to process ping, pong and close
+// messages sent from the peer. If the application is not otherwise interested
+// in messages from the peer, then the application should start a goroutine to
+// read and discard messages from the peer. A simple example is:
+//
+// func readLoop(c *websocket.Conn) {
+// for {
+// if _, _, err := c.NextReader(); err != nil {
+// c.Close()
+// break
+// }
+// }
+// }
+//
+// Concurrency
+//
+// Connections support one concurrent reader and one concurrent writer.
+//
+// Applications are responsible for ensuring that no more than one goroutine
+// calls the write methods (NextWriter, SetWriteDeadline, WriteMessage,
+// WriteJSON, EnableWriteCompression, SetCompressionLevel) concurrently and
+// that no more than one goroutine calls the read methods (NextReader,
+// SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler)
+// concurrently.
+//
+// The Close and WriteControl methods can be called concurrently with all other
+// methods.
+//
+// Origin Considerations
+//
+// Web browsers allow Javascript applications to open a WebSocket connection to
+// any host. It's up to the server to enforce an origin policy using the Origin
+// request header sent by the browser.
+//
+// The Upgrader calls the function specified in the CheckOrigin field to check
+// the origin. If the CheckOrigin function returns false, then the Upgrade
+// method fails the WebSocket handshake with HTTP status 403.
+//
+// If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail
+// the handshake if the Origin request header is present and not equal to the
+// Host request header.
+//
+// An application can allow connections from any origin by specifying a
+// function that always returns true:
+//
+// var upgrader = websocket.Upgrader{
+// CheckOrigin: func(r *http.Request) bool { return true },
+// }
+//
+// The deprecated Upgrade function does not enforce an origin policy. It's the
+// application's responsibility to check the Origin header before calling
+// Upgrade.
+//
+// Compression EXPERIMENTAL
+//
+// Per message compression extensions (RFC 7692) are experimentally supported
+// by this package in a limited capacity. Setting the EnableCompression option
+// to true in Dialer or Upgrader will attempt to negotiate per message deflate
+// support.
+//
+// var upgrader = websocket.Upgrader{
+// EnableCompression: true,
+// }
+//
+// If compression was successfully negotiated with the connection's peer, any
+// message received in compressed form will be automatically decompressed.
+// All Read methods will return uncompressed bytes.
+//
+// Per message compression of messages written to a connection can be enabled
+// or disabled by calling the corresponding Conn method:
+//
+// conn.EnableWriteCompression(false)
+//
+// Currently this package does not support compression with "context takeover".
+// This means that messages must be compressed and decompressed in isolation,
+// without retaining sliding window or dictionary state across messages. For
+// more details refer to RFC 7692.
+//
+// Use of compression is experimental and may result in decreased performance.
+package websocket
diff --git a/vendor/github.com/gorilla/websocket/json.go b/vendor/github.com/gorilla/websocket/json.go
new file mode 100644
index 00000000..4f0e3687
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/json.go
@@ -0,0 +1,55 @@
+// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package websocket
+
+import (
+ "encoding/json"
+ "io"
+)
+
+// WriteJSON is deprecated, use c.WriteJSON instead.
+func WriteJSON(c *Conn, v interface{}) error {
+ return c.WriteJSON(v)
+}
+
+// WriteJSON writes the JSON encoding of v to the connection.
+//
+// See the documentation for encoding/json Marshal for details about the
+// conversion of Go values to JSON.
+func (c *Conn) WriteJSON(v interface{}) error {
+ w, err := c.NextWriter(TextMessage)
+ if err != nil {
+ return err
+ }
+ err1 := json.NewEncoder(w).Encode(v)
+ err2 := w.Close()
+ if err1 != nil {
+ return err1
+ }
+ return err2
+}
+
+// ReadJSON is deprecated, use c.ReadJSON instead.
+func ReadJSON(c *Conn, v interface{}) error {
+ return c.ReadJSON(v)
+}
+
+// ReadJSON reads the next JSON-encoded message from the connection and stores
+// it in the value pointed to by v.
+//
+// See the documentation for the encoding/json Unmarshal function for details
+// about the conversion of JSON to a Go value.
+func (c *Conn) ReadJSON(v interface{}) error {
+ _, r, err := c.NextReader()
+ if err != nil {
+ return err
+ }
+ err = json.NewDecoder(r).Decode(v)
+ if err == io.EOF {
+ // One value is expected in the message.
+ err = io.ErrUnexpectedEOF
+ }
+ return err
+}
diff --git a/vendor/github.com/gorilla/websocket/mask.go b/vendor/github.com/gorilla/websocket/mask.go
new file mode 100644
index 00000000..6a88bbc7
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/mask.go
@@ -0,0 +1,55 @@
+// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of
+// this source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+// +build !appengine
+
+package websocket
+
+import "unsafe"
+
+const wordSize = int(unsafe.Sizeof(uintptr(0)))
+
+func maskBytes(key [4]byte, pos int, b []byte) int {
+
+ // Mask one byte at a time for small buffers.
+ if len(b) < 2*wordSize {
+ for i := range b {
+ b[i] ^= key[pos&3]
+ pos++
+ }
+ return pos & 3
+ }
+
+ // Mask one byte at a time to word boundary.
+ if n := int(uintptr(unsafe.Pointer(&b[0]))) % wordSize; n != 0 {
+ n = wordSize - n
+ for i := range b[:n] {
+ b[i] ^= key[pos&3]
+ pos++
+ }
+ b = b[n:]
+ }
+
+ // Create aligned word size key.
+ var k [wordSize]byte
+ for i := range k {
+ k[i] = key[(pos+i)&3]
+ }
+ kw := *(*uintptr)(unsafe.Pointer(&k))
+
+ // Mask one word at a time.
+ n := (len(b) / wordSize) * wordSize
+ for i := 0; i < n; i += wordSize {
+ *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(i))) ^= kw
+ }
+
+ // Mask one byte at a time for remaining bytes.
+ b = b[n:]
+ for i := range b {
+ b[i] ^= key[pos&3]
+ pos++
+ }
+
+ return pos & 3
+}
diff --git a/vendor/github.com/gorilla/websocket/mask_safe.go b/vendor/github.com/gorilla/websocket/mask_safe.go
new file mode 100644
index 00000000..2aac060e
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/mask_safe.go
@@ -0,0 +1,15 @@
+// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of
+// this source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+// +build appengine
+
+package websocket
+
+func maskBytes(key [4]byte, pos int, b []byte) int {
+ for i := range b {
+ b[i] ^= key[pos&3]
+ pos++
+ }
+ return pos & 3
+}
diff --git a/vendor/github.com/gorilla/websocket/prepared.go b/vendor/github.com/gorilla/websocket/prepared.go
new file mode 100644
index 00000000..1efffbd1
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/prepared.go
@@ -0,0 +1,103 @@
+// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package websocket
+
+import (
+ "bytes"
+ "net"
+ "sync"
+ "time"
+)
+
+// PreparedMessage caches on the wire representations of a message payload.
+// Use PreparedMessage to efficiently send a message payload to multiple
+// connections. PreparedMessage is especially useful when compression is used
+// because the CPU and memory expensive compression operation can be executed
+// once for a given set of compression options.
+type PreparedMessage struct {
+ messageType int
+ data []byte
+ err error
+ mu sync.Mutex
+ frames map[prepareKey]*preparedFrame
+}
+
+// prepareKey defines a unique set of options to cache prepared frames in PreparedMessage.
+type prepareKey struct {
+ isServer bool
+ compress bool
+ compressionLevel int
+}
+
+// preparedFrame contains data in wire representation.
+type preparedFrame struct {
+ once sync.Once
+ data []byte
+}
+
+// NewPreparedMessage returns an initialized PreparedMessage. You can then send
+// it to connection using WritePreparedMessage method. Valid wire
+// representation will be calculated lazily only once for a set of current
+// connection options.
+func NewPreparedMessage(messageType int, data []byte) (*PreparedMessage, error) {
+ pm := &PreparedMessage{
+ messageType: messageType,
+ frames: make(map[prepareKey]*preparedFrame),
+ data: data,
+ }
+
+ // Prepare a plain server frame.
+ _, frameData, err := pm.frame(prepareKey{isServer: true, compress: false})
+ if err != nil {
+ return nil, err
+ }
+
+ // To protect against caller modifying the data argument, remember the data
+ // copied to the plain server frame.
+ pm.data = frameData[len(frameData)-len(data):]
+ return pm, nil
+}
+
+func (pm *PreparedMessage) frame(key prepareKey) (int, []byte, error) {
+ pm.mu.Lock()
+ frame, ok := pm.frames[key]
+ if !ok {
+ frame = &preparedFrame{}
+ pm.frames[key] = frame
+ }
+ pm.mu.Unlock()
+
+ var err error
+ frame.once.Do(func() {
+ // Prepare a frame using a 'fake' connection.
+ // TODO: Refactor code in conn.go to allow more direct construction of
+ // the frame.
+ mu := make(chan bool, 1)
+ mu <- true
+ var nc prepareConn
+ c := &Conn{
+ conn: &nc,
+ mu: mu,
+ isServer: key.isServer,
+ compressionLevel: key.compressionLevel,
+ enableWriteCompression: true,
+ writeBuf: make([]byte, defaultWriteBufferSize+maxFrameHeaderSize),
+ }
+ if key.compress {
+ c.newCompressionWriter = compressNoContextTakeover
+ }
+ err = c.WriteMessage(pm.messageType, pm.data)
+ frame.data = nc.buf.Bytes()
+ })
+ return pm.messageType, frame.data, err
+}
+
+type prepareConn struct {
+ buf bytes.Buffer
+ net.Conn
+}
+
+func (pc *prepareConn) Write(p []byte) (int, error) { return pc.buf.Write(p) }
+func (pc *prepareConn) SetWriteDeadline(t time.Time) error { return nil }
diff --git a/vendor/github.com/gorilla/websocket/server.go b/vendor/github.com/gorilla/websocket/server.go
new file mode 100644
index 00000000..3495e0f1
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/server.go
@@ -0,0 +1,291 @@
+// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package websocket
+
+import (
+ "bufio"
+ "errors"
+ "net"
+ "net/http"
+ "net/url"
+ "strings"
+ "time"
+)
+
+// HandshakeError describes an error with the handshake from the peer.
+type HandshakeError struct {
+ message string
+}
+
+func (e HandshakeError) Error() string { return e.message }
+
+// Upgrader specifies parameters for upgrading an HTTP connection to a
+// WebSocket connection.
+type Upgrader struct {
+ // HandshakeTimeout specifies the duration for the handshake to complete.
+ HandshakeTimeout time.Duration
+
+ // ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
+ // size is zero, then buffers allocated by the HTTP server are used. The
+ // I/O buffer sizes do not limit the size of the messages that can be sent
+ // or received.
+ ReadBufferSize, WriteBufferSize int
+
+ // Subprotocols specifies the server's supported protocols in order of
+ // preference. If this field is set, then the Upgrade method negotiates a
+ // subprotocol by selecting the first match in this list with a protocol
+ // requested by the client.
+ Subprotocols []string
+
+ // Error specifies the function for generating HTTP error responses. If Error
+ // is nil, then http.Error is used to generate the HTTP response.
+ Error func(w http.ResponseWriter, r *http.Request, status int, reason error)
+
+ // CheckOrigin returns true if the request Origin header is acceptable. If
+ // CheckOrigin is nil, the host in the Origin header must not be set or
+ // must match the host of the request.
+ CheckOrigin func(r *http.Request) bool
+
+ // EnableCompression specify if the server should attempt to negotiate per
+ // message compression (RFC 7692). Setting this value to true does not
+ // guarantee that compression will be supported. Currently only "no context
+ // takeover" modes are supported.
+ EnableCompression bool
+}
+
+func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status int, reason string) (*Conn, error) {
+ err := HandshakeError{reason}
+ if u.Error != nil {
+ u.Error(w, r, status, err)
+ } else {
+ w.Header().Set("Sec-Websocket-Version", "13")
+ http.Error(w, http.StatusText(status), status)
+ }
+ return nil, err
+}
+
+// checkSameOrigin returns true if the origin is not set or is equal to the request host.
+func checkSameOrigin(r *http.Request) bool {
+ origin := r.Header["Origin"]
+ if len(origin) == 0 {
+ return true
+ }
+ u, err := url.Parse(origin[0])
+ if err != nil {
+ return false
+ }
+ return u.Host == r.Host
+}
+
+func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string {
+ if u.Subprotocols != nil {
+ clientProtocols := Subprotocols(r)
+ for _, serverProtocol := range u.Subprotocols {
+ for _, clientProtocol := range clientProtocols {
+ if clientProtocol == serverProtocol {
+ return clientProtocol
+ }
+ }
+ }
+ } else if responseHeader != nil {
+ return responseHeader.Get("Sec-Websocket-Protocol")
+ }
+ return ""
+}
+
+// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
+//
+// The responseHeader is included in the response to the client's upgrade
+// request. Use the responseHeader to specify cookies (Set-Cookie) and the
+// application negotiated subprotocol (Sec-Websocket-Protocol).
+//
+// If the upgrade fails, then Upgrade replies to the client with an HTTP error
+// response.
+func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) {
+ if r.Method != "GET" {
+ return u.returnError(w, r, http.StatusMethodNotAllowed, "websocket: not a websocket handshake: request method is not GET")
+ }
+
+ if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok {
+ return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-Websocket-Extensions' headers are unsupported")
+ }
+
+ if !tokenListContainsValue(r.Header, "Connection", "upgrade") {
+ return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'upgrade' token not found in 'Connection' header")
+ }
+
+ if !tokenListContainsValue(r.Header, "Upgrade", "websocket") {
+ return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'websocket' token not found in 'Upgrade' header")
+ }
+
+ if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") {
+ return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header")
+ }
+
+ checkOrigin := u.CheckOrigin
+ if checkOrigin == nil {
+ checkOrigin = checkSameOrigin
+ }
+ if !checkOrigin(r) {
+ return u.returnError(w, r, http.StatusForbidden, "websocket: 'Origin' header value not allowed")
+ }
+
+ challengeKey := r.Header.Get("Sec-Websocket-Key")
+ if challengeKey == "" {
+ return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-Websocket-Key' header is missing or blank")
+ }
+
+ subprotocol := u.selectSubprotocol(r, responseHeader)
+
+ // Negotiate PMCE
+ var compress bool
+ if u.EnableCompression {
+ for _, ext := range parseExtensions(r.Header) {
+ if ext[""] != "permessage-deflate" {
+ continue
+ }
+ compress = true
+ break
+ }
+ }
+
+ var (
+ netConn net.Conn
+ err error
+ )
+
+ h, ok := w.(http.Hijacker)
+ if !ok {
+ return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker")
+ }
+ var brw *bufio.ReadWriter
+ netConn, brw, err = h.Hijack()
+ if err != nil {
+ return u.returnError(w, r, http.StatusInternalServerError, err.Error())
+ }
+
+ if brw.Reader.Buffered() > 0 {
+ netConn.Close()
+ return nil, errors.New("websocket: client sent data before handshake is complete")
+ }
+
+ c := newConnBRW(netConn, true, u.ReadBufferSize, u.WriteBufferSize, brw)
+ c.subprotocol = subprotocol
+
+ if compress {
+ c.newCompressionWriter = compressNoContextTakeover
+ c.newDecompressionReader = decompressNoContextTakeover
+ }
+
+ p := c.writeBuf[:0]
+ p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...)
+ p = append(p, computeAcceptKey(challengeKey)...)
+ p = append(p, "\r\n"...)
+ if c.subprotocol != "" {
+ p = append(p, "Sec-Websocket-Protocol: "...)
+ p = append(p, c.subprotocol...)
+ p = append(p, "\r\n"...)
+ }
+ if compress {
+ p = append(p, "Sec-Websocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...)
+ }
+ for k, vs := range responseHeader {
+ if k == "Sec-Websocket-Protocol" {
+ continue
+ }
+ for _, v := range vs {
+ p = append(p, k...)
+ p = append(p, ": "...)
+ for i := 0; i < len(v); i++ {
+ b := v[i]
+ if b <= 31 {
+ // prevent response splitting.
+ b = ' '
+ }
+ p = append(p, b)
+ }
+ p = append(p, "\r\n"...)
+ }
+ }
+ p = append(p, "\r\n"...)
+
+ // Clear deadlines set by HTTP server.
+ netConn.SetDeadline(time.Time{})
+
+ if u.HandshakeTimeout > 0 {
+ netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout))
+ }
+ if _, err = netConn.Write(p); err != nil {
+ netConn.Close()
+ return nil, err
+ }
+ if u.HandshakeTimeout > 0 {
+ netConn.SetWriteDeadline(time.Time{})
+ }
+
+ return c, nil
+}
+
+// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
+//
+// This function is deprecated, use websocket.Upgrader instead.
+//
+// The application is responsible for checking the request origin before
+// calling Upgrade. An example implementation of the same origin policy is:
+//
+// if req.Header.Get("Origin") != "http://"+req.Host {
+// http.Error(w, "Origin not allowed", 403)
+// return
+// }
+//
+// If the endpoint supports subprotocols, then the application is responsible
+// for negotiating the protocol used on the connection. Use the Subprotocols()
+// function to get the subprotocols requested by the client. Use the
+// Sec-Websocket-Protocol response header to specify the subprotocol selected
+// by the application.
+//
+// The responseHeader is included in the response to the client's upgrade
+// request. Use the responseHeader to specify cookies (Set-Cookie) and the
+// negotiated subprotocol (Sec-Websocket-Protocol).
+//
+// The connection buffers IO to the underlying network connection. The
+// readBufSize and writeBufSize parameters specify the size of the buffers to
+// use. Messages can be larger than the buffers.
+//
+// If the request is not a valid WebSocket handshake, then Upgrade returns an
+// error of type HandshakeError. Applications should handle this error by
+// replying to the client with an HTTP error response.
+func Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header, readBufSize, writeBufSize int) (*Conn, error) {
+ u := Upgrader{ReadBufferSize: readBufSize, WriteBufferSize: writeBufSize}
+ u.Error = func(w http.ResponseWriter, r *http.Request, status int, reason error) {
+ // don't return errors to maintain backwards compatibility
+ }
+ u.CheckOrigin = func(r *http.Request) bool {
+ // allow all connections by default
+ return true
+ }
+ return u.Upgrade(w, r, responseHeader)
+}
+
+// Subprotocols returns the subprotocols requested by the client in the
+// Sec-Websocket-Protocol header.
+func Subprotocols(r *http.Request) []string {
+ h := strings.TrimSpace(r.Header.Get("Sec-Websocket-Protocol"))
+ if h == "" {
+ return nil
+ }
+ protocols := strings.Split(h, ",")
+ for i := range protocols {
+ protocols[i] = strings.TrimSpace(protocols[i])
+ }
+ return protocols
+}
+
+// IsWebSocketUpgrade returns true if the client requested upgrade to the
+// WebSocket protocol.
+func IsWebSocketUpgrade(r *http.Request) bool {
+ return tokenListContainsValue(r.Header, "Connection", "upgrade") &&
+ tokenListContainsValue(r.Header, "Upgrade", "websocket")
+}
diff --git a/vendor/github.com/gorilla/websocket/util.go b/vendor/github.com/gorilla/websocket/util.go
new file mode 100644
index 00000000..9a4908df
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/util.go
@@ -0,0 +1,214 @@
+// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package websocket
+
+import (
+ "crypto/rand"
+ "crypto/sha1"
+ "encoding/base64"
+ "io"
+ "net/http"
+ "strings"
+)
+
+var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
+
+func computeAcceptKey(challengeKey string) string {
+ h := sha1.New()
+ h.Write([]byte(challengeKey))
+ h.Write(keyGUID)
+ return base64.StdEncoding.EncodeToString(h.Sum(nil))
+}
+
+func generateChallengeKey() (string, error) {
+ p := make([]byte, 16)
+ if _, err := io.ReadFull(rand.Reader, p); err != nil {
+ return "", err
+ }
+ return base64.StdEncoding.EncodeToString(p), nil
+}
+
+// Octet types from RFC 2616.
+var octetTypes [256]byte
+
+const (
+ isTokenOctet = 1 << iota
+ isSpaceOctet
+)
+
+func init() {
+ // From RFC 2616
+ //
+ // OCTET = <any 8-bit sequence of data>
+ // CHAR = <any US-ASCII character (octets 0 - 127)>
+ // CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
+ // CR = <US-ASCII CR, carriage return (13)>
+ // LF = <US-ASCII LF, linefeed (10)>
+ // SP = <US-ASCII SP, space (32)>
+ // HT = <US-ASCII HT, horizontal-tab (9)>
+ // <"> = <US-ASCII double-quote mark (34)>
+ // CRLF = CR LF
+ // LWS = [CRLF] 1*( SP | HT )
+ // TEXT = <any OCTET except CTLs, but including LWS>
+ // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
+ // | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
+ // token = 1*<any CHAR except CTLs or separators>
+ // qdtext = <any TEXT except <">>
+
+ for c := 0; c < 256; c++ {
+ var t byte
+ isCtl := c <= 31 || c == 127
+ isChar := 0 <= c && c <= 127
+ isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0
+ if strings.IndexRune(" \t\r\n", rune(c)) >= 0 {
+ t |= isSpaceOctet
+ }
+ if isChar && !isCtl && !isSeparator {
+ t |= isTokenOctet
+ }
+ octetTypes[c] = t
+ }
+}
+
+func skipSpace(s string) (rest string) {
+ i := 0
+ for ; i < len(s); i++ {
+ if octetTypes[s[i]]&isSpaceOctet == 0 {
+ break
+ }
+ }
+ return s[i:]
+}
+
+func nextToken(s string) (token, rest string) {
+ i := 0
+ for ; i < len(s); i++ {
+ if octetTypes[s[i]]&isTokenOctet == 0 {
+ break
+ }
+ }
+ return s[:i], s[i:]
+}
+
+func nextTokenOrQuoted(s string) (value string, rest string) {
+ if !strings.HasPrefix(s, "\"") {
+ return nextToken(s)
+ }
+ s = s[1:]
+ for i := 0; i < len(s); i++ {
+ switch s[i] {
+ case '"':
+ return s[:i], s[i+1:]
+ case '\\':
+ p := make([]byte, len(s)-1)
+ j := copy(p, s[:i])
+ escape := true
+ for i = i + 1; i < len(s); i++ {
+ b := s[i]
+ switch {
+ case escape:
+ escape = false
+ p[j] = b
+ j += 1
+ case b == '\\':
+ escape = true
+ case b == '"':
+ return string(p[:j]), s[i+1:]
+ default:
+ p[j] = b
+ j += 1
+ }
+ }
+ return "", ""
+ }
+ }
+ return "", ""
+}
+
+// tokenListContainsValue returns true if the 1#token header with the given
+// name contains token.
+func tokenListContainsValue(header http.Header, name string, value string) bool {
+headers:
+ for _, s := range header[name] {
+ for {
+ var t string
+ t, s = nextToken(skipSpace(s))
+ if t == "" {
+ continue headers
+ }
+ s = skipSpace(s)
+ if s != "" && s[0] != ',' {
+ continue headers
+ }
+ if strings.EqualFold(t, value) {
+ return true
+ }
+ if s == "" {
+ continue headers
+ }
+ s = s[1:]
+ }
+ }
+ return false
+}
+
+// parseExtensiosn parses WebSocket extensions from a header.
+func parseExtensions(header http.Header) []map[string]string {
+
+ // From RFC 6455:
+ //
+ // Sec-WebSocket-Extensions = extension-list
+ // extension-list = 1#extension
+ // extension = extension-token *( ";" extension-param )
+ // extension-token = registered-token
+ // registered-token = token
+ // extension-param = token [ "=" (token | quoted-string) ]
+ // ;When using the quoted-string syntax variant, the value
+ // ;after quoted-string unescaping MUST conform to the
+ // ;'token' ABNF.
+
+ var result []map[string]string
+headers:
+ for _, s := range header["Sec-Websocket-Extensions"] {
+ for {
+ var t string
+ t, s = nextToken(skipSpace(s))
+ if t == "" {
+ continue headers
+ }
+ ext := map[string]string{"": t}
+ for {
+ s = skipSpace(s)
+ if !strings.HasPrefix(s, ";") {
+ break
+ }
+ var k string
+ k, s = nextToken(skipSpace(s[1:]))
+ if k == "" {
+ continue headers
+ }
+ s = skipSpace(s)
+ var v string
+ if strings.HasPrefix(s, "=") {
+ v, s = nextTokenOrQuoted(skipSpace(s[1:]))
+ s = skipSpace(s)
+ }
+ if s != "" && s[0] != ',' && s[0] != ';' {
+ continue headers
+ }
+ ext[k] = v
+ }
+ if s != "" && s[0] != ',' {
+ continue headers
+ }
+ result = append(result, ext)
+ if s == "" {
+ continue headers
+ }
+ s = s[1:]
+ }
+ }
+ return result
+}
diff --git a/vendor/github.com/vektah/gqlgen/LICENSE b/vendor/github.com/vektah/gqlgen/LICENSE
new file mode 100644
index 00000000..18e1b249
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2018 Adam Scarr
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/vektah/gqlgen/graphql/bool.go b/vendor/github.com/vektah/gqlgen/graphql/bool.go
new file mode 100644
index 00000000..7053bbca
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/graphql/bool.go
@@ -0,0 +1,30 @@
+package graphql
+
+import (
+ "fmt"
+ "io"
+ "strings"
+)
+
+func MarshalBoolean(b bool) Marshaler {
+ return WriterFunc(func(w io.Writer) {
+ if b {
+ w.Write(trueLit)
+ } else {
+ w.Write(falseLit)
+ }
+ })
+}
+
+func UnmarshalBoolean(v interface{}) (bool, error) {
+ switch v := v.(type) {
+ case string:
+ return "true" == strings.ToLower(v), nil
+ case int:
+ return v != 0, nil
+ case bool:
+ return v, nil
+ default:
+ return false, fmt.Errorf("%T is not a bool", v)
+ }
+}
diff --git a/vendor/github.com/vektah/gqlgen/graphql/context.go b/vendor/github.com/vektah/gqlgen/graphql/context.go
new file mode 100644
index 00000000..8f544100
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/graphql/context.go
@@ -0,0 +1,145 @@
+package graphql
+
+import (
+ "context"
+ "fmt"
+ "sync"
+
+ "github.com/vektah/gqlgen/neelance/query"
+)
+
+type Resolver func(ctx context.Context) (res interface{}, err error)
+type ResolverMiddleware func(ctx context.Context, next Resolver) (res interface{}, err error)
+type RequestMiddleware func(ctx context.Context, next func(ctx context.Context) []byte) []byte
+
+type RequestContext struct {
+ RawQuery string
+ Variables map[string]interface{}
+ Doc *query.Document
+ // ErrorPresenter will be used to generate the error
+ // message from errors given to Error().
+ ErrorPresenter ErrorPresenterFunc
+ Recover RecoverFunc
+ ResolverMiddleware ResolverMiddleware
+ RequestMiddleware RequestMiddleware
+
+ errorsMu sync.Mutex
+ Errors []*Error
+}
+
+func DefaultResolverMiddleware(ctx context.Context, next Resolver) (res interface{}, err error) {
+ return next(ctx)
+}
+
+func DefaultRequestMiddleware(ctx context.Context, next func(ctx context.Context) []byte) []byte {
+ return next(ctx)
+}
+
+func NewRequestContext(doc *query.Document, query string, variables map[string]interface{}) *RequestContext {
+ return &RequestContext{
+ Doc: doc,
+ RawQuery: query,
+ Variables: variables,
+ ResolverMiddleware: DefaultResolverMiddleware,
+ RequestMiddleware: DefaultRequestMiddleware,
+ Recover: DefaultRecover,
+ ErrorPresenter: DefaultErrorPresenter,
+ }
+}
+
+type key string
+
+const (
+ request key = "request_context"
+ resolver key = "resolver_context"
+)
+
+func GetRequestContext(ctx context.Context) *RequestContext {
+ val := ctx.Value(request)
+ if val == nil {
+ return nil
+ }
+
+ return val.(*RequestContext)
+}
+
+func WithRequestContext(ctx context.Context, rc *RequestContext) context.Context {
+ return context.WithValue(ctx, request, rc)
+}
+
+type ResolverContext struct {
+ // The name of the type this field belongs to
+ Object string
+ // These are the args after processing, they can be mutated in middleware to change what the resolver will get.
+ Args map[string]interface{}
+ // The raw field
+ Field CollectedField
+ // The path of fields to get to this resolver
+ Path []interface{}
+}
+
+func (r *ResolverContext) PushField(alias string) {
+ r.Path = append(r.Path, alias)
+}
+
+func (r *ResolverContext) PushIndex(index int) {
+ r.Path = append(r.Path, index)
+}
+
+func (r *ResolverContext) Pop() {
+ r.Path = r.Path[0 : len(r.Path)-1]
+}
+
+func GetResolverContext(ctx context.Context) *ResolverContext {
+ val := ctx.Value(resolver)
+ if val == nil {
+ return nil
+ }
+
+ return val.(*ResolverContext)
+}
+
+func WithResolverContext(ctx context.Context, rc *ResolverContext) context.Context {
+ parent := GetResolverContext(ctx)
+ rc.Path = nil
+ if parent != nil {
+ rc.Path = append(rc.Path, parent.Path...)
+ }
+ if rc.Field.Alias != "" {
+ rc.PushField(rc.Field.Alias)
+ }
+ return context.WithValue(ctx, resolver, rc)
+}
+
+// This is just a convenient wrapper method for CollectFields
+func CollectFieldsCtx(ctx context.Context, satisfies []string) []CollectedField {
+ reqctx := GetRequestContext(ctx)
+ resctx := GetResolverContext(ctx)
+ return CollectFields(reqctx.Doc, resctx.Field.Selections, satisfies, reqctx.Variables)
+}
+
+// Errorf sends an error string to the client, passing it through the formatter.
+func (c *RequestContext) Errorf(ctx context.Context, format string, args ...interface{}) {
+ c.errorsMu.Lock()
+ defer c.errorsMu.Unlock()
+
+ c.Errors = append(c.Errors, c.ErrorPresenter(ctx, fmt.Errorf(format, args...)))
+}
+
+// Error sends an error to the client, passing it through the formatter.
+func (c *RequestContext) Error(ctx context.Context, err error) {
+ c.errorsMu.Lock()
+ defer c.errorsMu.Unlock()
+
+ c.Errors = append(c.Errors, c.ErrorPresenter(ctx, err))
+}
+
+// AddError is a convenience method for adding an error to the current response
+func AddError(ctx context.Context, err error) {
+ GetRequestContext(ctx).Error(ctx, err)
+}
+
+// AddErrorf is a convenience method for adding an error to the current response
+func AddErrorf(ctx context.Context, format string, args ...interface{}) {
+ GetRequestContext(ctx).Errorf(ctx, format, args...)
+}
diff --git a/vendor/github.com/vektah/gqlgen/graphql/defer.go b/vendor/github.com/vektah/gqlgen/graphql/defer.go
new file mode 100644
index 00000000..79346a84
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/graphql/defer.go
@@ -0,0 +1,30 @@
+package graphql
+
+import (
+ "io"
+ "sync"
+)
+
+// Defer will begin executing the given function and immediately return a result that will block until the function completes
+func Defer(f func() Marshaler) Marshaler {
+ var deferred deferred
+ deferred.mu.Lock()
+
+ go func() {
+ deferred.result = f()
+ deferred.mu.Unlock()
+ }()
+
+ return &deferred
+}
+
+type deferred struct {
+ result Marshaler
+ mu sync.Mutex
+}
+
+func (d *deferred) MarshalGQL(w io.Writer) {
+ d.mu.Lock()
+ d.result.MarshalGQL(w)
+ d.mu.Unlock()
+}
diff --git a/vendor/github.com/vektah/gqlgen/graphql/error.go b/vendor/github.com/vektah/gqlgen/graphql/error.go
new file mode 100644
index 00000000..15e65fab
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/graphql/error.go
@@ -0,0 +1,46 @@
+package graphql
+
+import (
+ "context"
+)
+
+// Error is the standard graphql error type described in https://facebook.github.io/graphql/draft/#sec-Errors
+type Error struct {
+ Message string `json:"message"`
+ Path []interface{} `json:"path,omitempty"`
+ Locations []ErrorLocation `json:"locations,omitempty"`
+ Extensions map[string]interface{} `json:"extensions,omitempty"`
+}
+
+func (e *Error) Error() string {
+ return e.Message
+}
+
+type ErrorLocation struct {
+ Line int `json:"line,omitempty"`
+ Column int `json:"column,omitempty"`
+}
+
+type ErrorPresenterFunc func(context.Context, error) *Error
+
+type ExtendedError interface {
+ Extensions() map[string]interface{}
+}
+
+func DefaultErrorPresenter(ctx context.Context, err error) *Error {
+ if gqlerr, ok := err.(*Error); ok {
+ gqlerr.Path = GetResolverContext(ctx).Path
+ return gqlerr
+ }
+
+ var extensions map[string]interface{}
+ if ee, ok := err.(ExtendedError); ok {
+ extensions = ee.Extensions()
+ }
+
+ return &Error{
+ Message: err.Error(),
+ Path: GetResolverContext(ctx).Path,
+ Extensions: extensions,
+ }
+}
diff --git a/vendor/github.com/vektah/gqlgen/graphql/exec.go b/vendor/github.com/vektah/gqlgen/graphql/exec.go
new file mode 100644
index 00000000..2c034888
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/graphql/exec.go
@@ -0,0 +1,118 @@
+package graphql
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/vektah/gqlgen/neelance/common"
+ "github.com/vektah/gqlgen/neelance/query"
+ "github.com/vektah/gqlgen/neelance/schema"
+)
+
+type ExecutableSchema interface {
+ Schema() *schema.Schema
+
+ Query(ctx context.Context, op *query.Operation) *Response
+ Mutation(ctx context.Context, op *query.Operation) *Response
+ Subscription(ctx context.Context, op *query.Operation) func() *Response
+}
+
+func CollectFields(doc *query.Document, selSet []query.Selection, satisfies []string, variables map[string]interface{}) []CollectedField {
+ return collectFields(doc, selSet, satisfies, variables, map[string]bool{})
+}
+
+func collectFields(doc *query.Document, selSet []query.Selection, satisfies []string, variables map[string]interface{}, visited map[string]bool) []CollectedField {
+ var groupedFields []CollectedField
+
+ for _, sel := range selSet {
+ switch sel := sel.(type) {
+ case *query.Field:
+ f := getOrCreateField(&groupedFields, sel.Alias.Name, func() CollectedField {
+ f := CollectedField{
+ Alias: sel.Alias.Name,
+ Name: sel.Name.Name,
+ }
+ if len(sel.Arguments) > 0 {
+ f.Args = map[string]interface{}{}
+ for _, arg := range sel.Arguments {
+ if variable, ok := arg.Value.(*common.Variable); ok {
+ if val, ok := variables[variable.Name]; ok {
+ f.Args[arg.Name.Name] = val
+ }
+ } else {
+ f.Args[arg.Name.Name] = arg.Value.Value(variables)
+ }
+ }
+ }
+ return f
+ })
+
+ f.Selections = append(f.Selections, sel.Selections...)
+ case *query.InlineFragment:
+ if !instanceOf(sel.On.Ident.Name, satisfies) {
+ continue
+ }
+
+ for _, childField := range collectFields(doc, sel.Selections, satisfies, variables, visited) {
+ f := getOrCreateField(&groupedFields, childField.Name, func() CollectedField { return childField })
+ f.Selections = append(f.Selections, childField.Selections...)
+ }
+
+ case *query.FragmentSpread:
+ fragmentName := sel.Name.Name
+ if _, seen := visited[fragmentName]; seen {
+ continue
+ }
+ visited[fragmentName] = true
+
+ fragment := doc.Fragments.Get(fragmentName)
+ if fragment == nil {
+ // should never happen, validator has already run
+ panic(fmt.Errorf("missing fragment %s", fragmentName))
+ }
+
+ if !instanceOf(fragment.On.Ident.Name, satisfies) {
+ continue
+ }
+
+ for _, childField := range collectFields(doc, fragment.Selections, satisfies, variables, visited) {
+ f := getOrCreateField(&groupedFields, childField.Name, func() CollectedField { return childField })
+ f.Selections = append(f.Selections, childField.Selections...)
+ }
+
+ default:
+ panic(fmt.Errorf("unsupported %T", sel))
+ }
+ }
+
+ return groupedFields
+}
+
+type CollectedField struct {
+ Alias string
+ Name string
+ Args map[string]interface{}
+ Selections []query.Selection
+}
+
+func instanceOf(val string, satisfies []string) bool {
+ for _, s := range satisfies {
+ if val == s {
+ return true
+ }
+ }
+ return false
+}
+
+func getOrCreateField(c *[]CollectedField, name string, creator func() CollectedField) *CollectedField {
+ for i, cf := range *c {
+ if cf.Alias == name {
+ return &(*c)[i]
+ }
+ }
+
+ f := creator()
+
+ *c = append(*c, f)
+ return &(*c)[len(*c)-1]
+}
diff --git a/vendor/github.com/vektah/gqlgen/graphql/float.go b/vendor/github.com/vektah/gqlgen/graphql/float.go
new file mode 100644
index 00000000..c08b490a
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/graphql/float.go
@@ -0,0 +1,26 @@
+package graphql
+
+import (
+ "fmt"
+ "io"
+ "strconv"
+)
+
+func MarshalFloat(f float64) Marshaler {
+ return WriterFunc(func(w io.Writer) {
+ io.WriteString(w, fmt.Sprintf("%f", f))
+ })
+}
+
+func UnmarshalFloat(v interface{}) (float64, error) {
+ switch v := v.(type) {
+ case string:
+ return strconv.ParseFloat(v, 64)
+ case int:
+ return float64(v), nil
+ case float64:
+ return v, nil
+ default:
+ return 0, fmt.Errorf("%T is not an float", v)
+ }
+}
diff --git a/vendor/github.com/vektah/gqlgen/graphql/id.go b/vendor/github.com/vektah/gqlgen/graphql/id.go
new file mode 100644
index 00000000..7958670c
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/graphql/id.go
@@ -0,0 +1,33 @@
+package graphql
+
+import (
+ "fmt"
+ "io"
+ "strconv"
+)
+
+func MarshalID(s string) Marshaler {
+ return WriterFunc(func(w io.Writer) {
+ io.WriteString(w, strconv.Quote(s))
+ })
+}
+func UnmarshalID(v interface{}) (string, error) {
+ switch v := v.(type) {
+ case string:
+ return v, nil
+ case int:
+ return strconv.Itoa(v), nil
+ case float64:
+ return fmt.Sprintf("%f", v), nil
+ case bool:
+ if v {
+ return "true", nil
+ } else {
+ return "false", nil
+ }
+ case nil:
+ return "null", nil
+ default:
+ return "", fmt.Errorf("%T is not a string", v)
+ }
+}
diff --git a/vendor/github.com/vektah/gqlgen/graphql/int.go b/vendor/github.com/vektah/gqlgen/graphql/int.go
new file mode 100644
index 00000000..b63b4c2a
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/graphql/int.go
@@ -0,0 +1,26 @@
+package graphql
+
+import (
+ "fmt"
+ "io"
+ "strconv"
+)
+
+func MarshalInt(i int) Marshaler {
+ return WriterFunc(func(w io.Writer) {
+ io.WriteString(w, strconv.Itoa(i))
+ })
+}
+
+func UnmarshalInt(v interface{}) (int, error) {
+ switch v := v.(type) {
+ case string:
+ return strconv.Atoi(v)
+ case int:
+ return v, nil
+ case float64:
+ return int(v), nil
+ default:
+ return 0, fmt.Errorf("%T is not an int", v)
+ }
+}
diff --git a/vendor/github.com/vektah/gqlgen/graphql/jsonw.go b/vendor/github.com/vektah/gqlgen/graphql/jsonw.go
new file mode 100644
index 00000000..ef9e69c7
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/graphql/jsonw.go
@@ -0,0 +1,83 @@
+package graphql
+
+import (
+ "io"
+ "strconv"
+)
+
+var nullLit = []byte(`null`)
+var trueLit = []byte(`true`)
+var falseLit = []byte(`false`)
+var openBrace = []byte(`{`)
+var closeBrace = []byte(`}`)
+var openBracket = []byte(`[`)
+var closeBracket = []byte(`]`)
+var colon = []byte(`:`)
+var comma = []byte(`,`)
+
+var Null = lit(nullLit)
+var True = lit(trueLit)
+var False = lit(falseLit)
+
+type Marshaler interface {
+ MarshalGQL(w io.Writer)
+}
+
+type Unmarshaler interface {
+ UnmarshalGQL(v interface{}) error
+}
+
+type OrderedMap struct {
+ Keys []string
+ Values []Marshaler
+}
+
+type WriterFunc func(writer io.Writer)
+
+func (f WriterFunc) MarshalGQL(w io.Writer) {
+ f(w)
+}
+
+func NewOrderedMap(len int) *OrderedMap {
+ return &OrderedMap{
+ Keys: make([]string, len),
+ Values: make([]Marshaler, len),
+ }
+}
+
+func (m *OrderedMap) Add(key string, value Marshaler) {
+ m.Keys = append(m.Keys, key)
+ m.Values = append(m.Values, value)
+}
+
+func (m *OrderedMap) MarshalGQL(writer io.Writer) {
+ writer.Write(openBrace)
+ for i, key := range m.Keys {
+ if i != 0 {
+ writer.Write(comma)
+ }
+ io.WriteString(writer, strconv.Quote(key))
+ writer.Write(colon)
+ m.Values[i].MarshalGQL(writer)
+ }
+ writer.Write(closeBrace)
+}
+
+type Array []Marshaler
+
+func (a Array) MarshalGQL(writer io.Writer) {
+ writer.Write(openBracket)
+ for i, val := range a {
+ if i != 0 {
+ writer.Write(comma)
+ }
+ val.MarshalGQL(writer)
+ }
+ writer.Write(closeBracket)
+}
+
+func lit(b []byte) Marshaler {
+ return WriterFunc(func(w io.Writer) {
+ w.Write(b)
+ })
+}
diff --git a/vendor/github.com/vektah/gqlgen/graphql/map.go b/vendor/github.com/vektah/gqlgen/graphql/map.go
new file mode 100644
index 00000000..1e91d1d9
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/graphql/map.go
@@ -0,0 +1,24 @@
+package graphql
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+)
+
+func MarshalMap(val map[string]interface{}) Marshaler {
+ return WriterFunc(func(w io.Writer) {
+ err := json.NewEncoder(w).Encode(val)
+ if err != nil {
+ panic(err)
+ }
+ })
+}
+
+func UnmarshalMap(v interface{}) (map[string]interface{}, error) {
+ if m, ok := v.(map[string]interface{}); ok {
+ return m, nil
+ }
+
+ return nil, fmt.Errorf("%T is not a map", v)
+}
diff --git a/vendor/github.com/vektah/gqlgen/graphql/oneshot.go b/vendor/github.com/vektah/gqlgen/graphql/oneshot.go
new file mode 100644
index 00000000..dd31f5ba
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/graphql/oneshot.go
@@ -0,0 +1,14 @@
+package graphql
+
+func OneShot(resp *Response) func() *Response {
+ var oneshot bool
+
+ return func() *Response {
+ if oneshot {
+ return nil
+ }
+ oneshot = true
+
+ return resp
+ }
+}
diff --git a/vendor/github.com/vektah/gqlgen/graphql/recovery.go b/vendor/github.com/vektah/gqlgen/graphql/recovery.go
new file mode 100644
index 00000000..3aa032dc
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/graphql/recovery.go
@@ -0,0 +1,19 @@
+package graphql
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "os"
+ "runtime/debug"
+)
+
+type RecoverFunc func(ctx context.Context, err interface{}) (userMessage error)
+
+func DefaultRecover(ctx context.Context, err interface{}) error {
+ fmt.Fprintln(os.Stderr, err)
+ fmt.Fprintln(os.Stderr)
+ debug.PrintStack()
+
+ return errors.New("internal system error")
+}
diff --git a/vendor/github.com/vektah/gqlgen/graphql/response.go b/vendor/github.com/vektah/gqlgen/graphql/response.go
new file mode 100644
index 00000000..c0dc1c23
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/graphql/response.go
@@ -0,0 +1,18 @@
+package graphql
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+)
+
+type Response struct {
+ Data json.RawMessage `json:"data"`
+ Errors []*Error `json:"errors,omitempty"`
+}
+
+func ErrorResponse(ctx context.Context, messagef string, args ...interface{}) *Response {
+ return &Response{
+ Errors: []*Error{{Message: fmt.Sprintf(messagef, args...)}},
+ }
+}
diff --git a/vendor/github.com/vektah/gqlgen/graphql/string.go b/vendor/github.com/vektah/gqlgen/graphql/string.go
new file mode 100644
index 00000000..d5fb3294
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/graphql/string.go
@@ -0,0 +1,63 @@
+package graphql
+
+import (
+ "fmt"
+ "io"
+ "strconv"
+)
+
+const encodeHex = "0123456789ABCDEF"
+
+func MarshalString(s string) Marshaler {
+ return WriterFunc(func(w io.Writer) {
+ start := 0
+ io.WriteString(w, `"`)
+
+ for i, c := range s {
+ if c < 0x20 || c == '\\' || c == '"' {
+ io.WriteString(w, s[start:i])
+
+ switch c {
+ case '\t':
+ io.WriteString(w, `\t`)
+ case '\r':
+ io.WriteString(w, `\r`)
+ case '\n':
+ io.WriteString(w, `\n`)
+ case '\\':
+ io.WriteString(w, `\\`)
+ case '"':
+ io.WriteString(w, `\"`)
+ default:
+ io.WriteString(w, `\u00`)
+ w.Write([]byte{encodeHex[c>>4], encodeHex[c&0xf]})
+ }
+
+ start = i + 1
+ }
+ }
+
+ io.WriteString(w, s[start:])
+ io.WriteString(w, `"`)
+ })
+}
+func UnmarshalString(v interface{}) (string, error) {
+ switch v := v.(type) {
+ case string:
+ return v, nil
+ case int:
+ return strconv.Itoa(v), nil
+ case float64:
+ return fmt.Sprintf("%f", v), nil
+ case bool:
+ if v {
+ return "true", nil
+ } else {
+ return "false", nil
+ }
+ case nil:
+ return "null", nil
+ default:
+ return "", fmt.Errorf("%T is not a string", v)
+ }
+}
diff --git a/vendor/github.com/vektah/gqlgen/graphql/time.go b/vendor/github.com/vektah/gqlgen/graphql/time.go
new file mode 100644
index 00000000..4f448560
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/graphql/time.go
@@ -0,0 +1,21 @@
+package graphql
+
+import (
+ "errors"
+ "io"
+ "strconv"
+ "time"
+)
+
+func MarshalTime(t time.Time) Marshaler {
+ return WriterFunc(func(w io.Writer) {
+ io.WriteString(w, strconv.Quote(t.Format(time.RFC3339)))
+ })
+}
+
+func UnmarshalTime(v interface{}) (time.Time, error) {
+ if tmpStr, ok := v.(string); ok {
+ return time.Parse(time.RFC3339, tmpStr)
+ }
+ return time.Time{}, errors.New("time should be RFC3339 formatted string")
+}
diff --git a/vendor/github.com/vektah/gqlgen/handler/graphql.go b/vendor/github.com/vektah/gqlgen/handler/graphql.go
new file mode 100644
index 00000000..4a5c61f5
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/handler/graphql.go
@@ -0,0 +1,235 @@
+package handler
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/gorilla/websocket"
+ "github.com/vektah/gqlgen/graphql"
+ "github.com/vektah/gqlgen/neelance/errors"
+ "github.com/vektah/gqlgen/neelance/query"
+ "github.com/vektah/gqlgen/neelance/validation"
+)
+
+type params struct {
+ Query string `json:"query"`
+ OperationName string `json:"operationName"`
+ Variables map[string]interface{} `json:"variables"`
+}
+
+type Config struct {
+ upgrader websocket.Upgrader
+ recover graphql.RecoverFunc
+ errorPresenter graphql.ErrorPresenterFunc
+ resolverHook graphql.ResolverMiddleware
+ requestHook graphql.RequestMiddleware
+}
+
+func (c *Config) newRequestContext(doc *query.Document, query string, variables map[string]interface{}) *graphql.RequestContext {
+ reqCtx := graphql.NewRequestContext(doc, query, variables)
+ if hook := c.recover; hook != nil {
+ reqCtx.Recover = hook
+ }
+
+ if hook := c.errorPresenter; hook != nil {
+ reqCtx.ErrorPresenter = hook
+ }
+
+ if hook := c.resolverHook; hook != nil {
+ reqCtx.ResolverMiddleware = hook
+ }
+
+ if hook := c.requestHook; hook != nil {
+ reqCtx.RequestMiddleware = hook
+ }
+
+ return reqCtx
+}
+
+type Option func(cfg *Config)
+
+func WebsocketUpgrader(upgrader websocket.Upgrader) Option {
+ return func(cfg *Config) {
+ cfg.upgrader = upgrader
+ }
+}
+
+func RecoverFunc(recover graphql.RecoverFunc) Option {
+ return func(cfg *Config) {
+ cfg.recover = recover
+ }
+}
+
+// ErrorPresenter transforms errors found while resolving into errors that will be returned to the user. It provides
+// a good place to add any extra fields, like error.type, that might be desired by your frontend. Check the default
+// implementation in graphql.DefaultErrorPresenter for an example.
+func ErrorPresenter(f graphql.ErrorPresenterFunc) Option {
+ return func(cfg *Config) {
+ cfg.errorPresenter = f
+ }
+}
+
+// ResolverMiddleware allows you to define a function that will be called around every resolver,
+// useful for tracing and logging.
+// It will only be called for user defined resolvers, any direct binding to models is assumed
+// to cost nothing.
+func ResolverMiddleware(middleware graphql.ResolverMiddleware) Option {
+ return func(cfg *Config) {
+ if cfg.resolverHook == nil {
+ cfg.resolverHook = middleware
+ return
+ }
+
+ lastResolve := cfg.resolverHook
+ cfg.resolverHook = func(ctx context.Context, next graphql.Resolver) (res interface{}, err error) {
+ return lastResolve(ctx, func(ctx context.Context) (res interface{}, err error) {
+ return middleware(ctx, next)
+ })
+ }
+ }
+}
+
+// RequestMiddleware allows you to define a function that will be called around the root request,
+// after the query has been parsed. This is useful for logging and tracing
+func RequestMiddleware(middleware graphql.RequestMiddleware) Option {
+ return func(cfg *Config) {
+ if cfg.requestHook == nil {
+ cfg.requestHook = middleware
+ return
+ }
+
+ lastResolve := cfg.requestHook
+ cfg.requestHook = func(ctx context.Context, next func(ctx context.Context) []byte) []byte {
+ return lastResolve(ctx, func(ctx context.Context) []byte {
+ return middleware(ctx, next)
+ })
+ }
+ }
+}
+
+func GraphQL(exec graphql.ExecutableSchema, options ...Option) http.HandlerFunc {
+ cfg := Config{
+ upgrader: websocket.Upgrader{
+ ReadBufferSize: 1024,
+ WriteBufferSize: 1024,
+ },
+ }
+
+ for _, option := range options {
+ option(&cfg)
+ }
+
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == http.MethodOptions {
+ w.Header().Set("Allow", "OPTIONS, GET, POST")
+ w.WriteHeader(http.StatusOK)
+ return
+ }
+
+ if strings.Contains(r.Header.Get("Upgrade"), "websocket") {
+ connectWs(exec, w, r, &cfg)
+ return
+ }
+
+ var reqParams params
+ switch r.Method {
+ case http.MethodGet:
+ reqParams.Query = r.URL.Query().Get("query")
+ reqParams.OperationName = r.URL.Query().Get("operationName")
+
+ if variables := r.URL.Query().Get("variables"); variables != "" {
+ if err := json.Unmarshal([]byte(variables), &reqParams.Variables); err != nil {
+ sendErrorf(w, http.StatusBadRequest, "variables could not be decoded")
+ return
+ }
+ }
+ case http.MethodPost:
+ if err := json.NewDecoder(r.Body).Decode(&reqParams); err != nil {
+ sendErrorf(w, http.StatusBadRequest, "json body could not be decoded: "+err.Error())
+ return
+ }
+ default:
+ w.WriteHeader(http.StatusMethodNotAllowed)
+ return
+ }
+ w.Header().Set("Content-Type", "application/json")
+
+ doc, qErr := query.Parse(reqParams.Query)
+ if qErr != nil {
+ sendError(w, http.StatusUnprocessableEntity, qErr)
+ return
+ }
+
+ errs := validation.Validate(exec.Schema(), doc)
+ if len(errs) != 0 {
+ sendError(w, http.StatusUnprocessableEntity, errs...)
+ return
+ }
+
+ op, err := doc.GetOperation(reqParams.OperationName)
+ if err != nil {
+ sendErrorf(w, http.StatusUnprocessableEntity, err.Error())
+ return
+ }
+
+ reqCtx := cfg.newRequestContext(doc, reqParams.Query, reqParams.Variables)
+ ctx := graphql.WithRequestContext(r.Context(), reqCtx)
+
+ defer func() {
+ if err := recover(); err != nil {
+ userErr := reqCtx.Recover(ctx, err)
+ sendErrorf(w, http.StatusUnprocessableEntity, userErr.Error())
+ }
+ }()
+
+ switch op.Type {
+ case query.Query:
+ b, err := json.Marshal(exec.Query(ctx, op))
+ if err != nil {
+ panic(err)
+ }
+ w.Write(b)
+ case query.Mutation:
+ b, err := json.Marshal(exec.Mutation(ctx, op))
+ if err != nil {
+ panic(err)
+ }
+ w.Write(b)
+ default:
+ sendErrorf(w, http.StatusBadRequest, "unsupported operation type")
+ }
+ })
+}
+
+func sendError(w http.ResponseWriter, code int, errors ...*errors.QueryError) {
+ w.WriteHeader(code)
+ var errs []*graphql.Error
+ for _, err := range errors {
+ var locations []graphql.ErrorLocation
+ for _, l := range err.Locations {
+ fmt.Println(graphql.ErrorLocation(l))
+ locations = append(locations, graphql.ErrorLocation{
+ Line: l.Line,
+ Column: l.Column,
+ })
+ }
+
+ errs = append(errs, &graphql.Error{
+ Message: err.Message,
+ Path: err.Path,
+ Locations: locations,
+ })
+ }
+ b, err := json.Marshal(&graphql.Response{Errors: errs})
+ if err != nil {
+ panic(err)
+ }
+ w.Write(b)
+}
+
+func sendErrorf(w http.ResponseWriter, code int, format string, args ...interface{}) {
+ sendError(w, code, &errors.QueryError{Message: fmt.Sprintf(format, args...)})
+}
diff --git a/vendor/github.com/vektah/gqlgen/handler/playground.go b/vendor/github.com/vektah/gqlgen/handler/playground.go
new file mode 100644
index 00000000..44533590
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/handler/playground.go
@@ -0,0 +1,51 @@
+package handler
+
+import (
+ "html/template"
+ "net/http"
+)
+
+var page = template.Must(template.New("graphiql").Parse(`<!DOCTYPE html>
+<html>
+<head>
+ <meta charset=utf-8/>
+ <meta name="viewport" content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui">
+ <link rel="shortcut icon" href="https://graphcool-playground.netlify.com/favicon.png">
+ <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/graphql-playground-react@{{ .version }}/build/static/css/index.css"/>
+ <link rel="shortcut icon" href="//cdn.jsdelivr.net/npm/graphql-playground-react@{{ .version }}/build/favicon.png"/>
+ <script src="//cdn.jsdelivr.net/npm/graphql-playground-react@{{ .version }}/build/static/js/middleware.js"></script>
+ <title>{{.title}}</title>
+</head>
+<body>
+<style type="text/css">
+ html { font-family: "Open Sans", sans-serif; overflow: hidden; }
+ body { margin: 0; background: #172a3a; }
+</style>
+<div id="root"/>
+<script type="text/javascript">
+ window.addEventListener('load', function (event) {
+ const root = document.getElementById('root');
+ root.classList.add('playgroundIn');
+ const wsProto = location.protocol == 'https:' ? 'wss:' : 'ws:'
+ GraphQLPlayground.init(root, {
+ endpoint: location.protocol + '//' + location.host + '{{.endpoint}}',
+ subscriptionsEndpoint: wsProto + '//' + location.host + '{{.endpoint }}',
+ })
+ })
+</script>
+</body>
+</html>
+`))
+
+func Playground(title string, endpoint string) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ err := page.Execute(w, map[string]string{
+ "title": title,
+ "endpoint": endpoint,
+ "version": "1.4.3",
+ })
+ if err != nil {
+ panic(err)
+ }
+ }
+}
diff --git a/vendor/github.com/vektah/gqlgen/handler/stub.go b/vendor/github.com/vektah/gqlgen/handler/stub.go
new file mode 100644
index 00000000..46b27e46
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/handler/stub.go
@@ -0,0 +1,45 @@
+package handler
+
+import (
+ "context"
+ "time"
+
+ "github.com/vektah/gqlgen/graphql"
+ "github.com/vektah/gqlgen/neelance/query"
+ "github.com/vektah/gqlgen/neelance/schema"
+)
+
+type executableSchemaStub struct {
+}
+
+var _ graphql.ExecutableSchema = &executableSchemaStub{}
+
+func (e *executableSchemaStub) Schema() *schema.Schema {
+ return schema.MustParse(`
+ schema { query: Query }
+ type Query { me: User! }
+ type User { name: String! }
+ `)
+}
+
+func (e *executableSchemaStub) Query(ctx context.Context, op *query.Operation) *graphql.Response {
+ return &graphql.Response{Data: []byte(`{"name":"test"}`)}
+}
+
+func (e *executableSchemaStub) Mutation(ctx context.Context, op *query.Operation) *graphql.Response {
+ return graphql.ErrorResponse(ctx, "mutations are not supported")
+}
+
+func (e *executableSchemaStub) Subscription(ctx context.Context, op *query.Operation) func() *graphql.Response {
+ return func() *graphql.Response {
+ time.Sleep(50 * time.Millisecond)
+ select {
+ case <-ctx.Done():
+ return nil
+ default:
+ return &graphql.Response{
+ Data: []byte(`{"name":"test"}`),
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/vektah/gqlgen/handler/websocket.go b/vendor/github.com/vektah/gqlgen/handler/websocket.go
new file mode 100644
index 00000000..e80748ca
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/handler/websocket.go
@@ -0,0 +1,245 @@
+package handler
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/http"
+ "sync"
+
+ "github.com/gorilla/websocket"
+ "github.com/vektah/gqlgen/graphql"
+ "github.com/vektah/gqlgen/neelance/errors"
+ "github.com/vektah/gqlgen/neelance/query"
+ "github.com/vektah/gqlgen/neelance/validation"
+)
+
+const (
+ connectionInitMsg = "connection_init" // Client -> Server
+ connectionTerminateMsg = "connection_terminate" // Client -> Server
+ startMsg = "start" // Client -> Server
+ stopMsg = "stop" // Client -> Server
+ connectionAckMsg = "connection_ack" // Server -> Client
+ connectionErrorMsg = "connection_error" // Server -> Client
+ dataMsg = "data" // Server -> Client
+ errorMsg = "error" // Server -> Client
+ completeMsg = "complete" // Server -> Client
+ //connectionKeepAliveMsg = "ka" // Server -> Client TODO: keepalives
+)
+
+type operationMessage struct {
+ Payload json.RawMessage `json:"payload,omitempty"`
+ ID string `json:"id,omitempty"`
+ Type string `json:"type"`
+}
+
+type wsConnection struct {
+ ctx context.Context
+ conn *websocket.Conn
+ exec graphql.ExecutableSchema
+ active map[string]context.CancelFunc
+ mu sync.Mutex
+ cfg *Config
+}
+
+func connectWs(exec graphql.ExecutableSchema, w http.ResponseWriter, r *http.Request, cfg *Config) {
+ ws, err := cfg.upgrader.Upgrade(w, r, http.Header{
+ "Sec-Websocket-Protocol": []string{"graphql-ws"},
+ })
+ if err != nil {
+ log.Printf("unable to upgrade %T to websocket %s: ", w, err.Error())
+ sendErrorf(w, http.StatusBadRequest, "unable to upgrade")
+ return
+ }
+
+ conn := wsConnection{
+ active: map[string]context.CancelFunc{},
+ exec: exec,
+ conn: ws,
+ ctx: r.Context(),
+ cfg: cfg,
+ }
+
+ if !conn.init() {
+ return
+ }
+
+ conn.run()
+}
+
+func (c *wsConnection) init() bool {
+ message := c.readOp()
+ if message == nil {
+ c.close(websocket.CloseProtocolError, "decoding error")
+ return false
+ }
+
+ switch message.Type {
+ case connectionInitMsg:
+ c.write(&operationMessage{Type: connectionAckMsg})
+ case connectionTerminateMsg:
+ c.close(websocket.CloseNormalClosure, "terminated")
+ return false
+ default:
+ c.sendConnectionError("unexpected message %s", message.Type)
+ c.close(websocket.CloseProtocolError, "unexpected message")
+ return false
+ }
+
+ return true
+}
+
+func (c *wsConnection) write(msg *operationMessage) {
+ c.mu.Lock()
+ c.conn.WriteJSON(msg)
+ c.mu.Unlock()
+}
+
+func (c *wsConnection) run() {
+ for {
+ message := c.readOp()
+ if message == nil {
+ return
+ }
+
+ switch message.Type {
+ case startMsg:
+ if !c.subscribe(message) {
+ return
+ }
+ case stopMsg:
+ c.mu.Lock()
+ closer := c.active[message.ID]
+ c.mu.Unlock()
+ if closer == nil {
+ c.sendError(message.ID, errors.Errorf("%s is not running, cannot stop", message.ID))
+ continue
+ }
+
+ closer()
+ case connectionTerminateMsg:
+ c.close(websocket.CloseNormalClosure, "terminated")
+ return
+ default:
+ c.sendConnectionError("unexpected message %s", message.Type)
+ c.close(websocket.CloseProtocolError, "unexpected message")
+ return
+ }
+ }
+}
+
+func (c *wsConnection) subscribe(message *operationMessage) bool {
+ var reqParams params
+ if err := json.Unmarshal(message.Payload, &reqParams); err != nil {
+ c.sendConnectionError("invalid json")
+ return false
+ }
+
+ doc, qErr := query.Parse(reqParams.Query)
+ if qErr != nil {
+ c.sendError(message.ID, qErr)
+ return true
+ }
+
+ errs := validation.Validate(c.exec.Schema(), doc)
+ if len(errs) != 0 {
+ c.sendError(message.ID, errs...)
+ return true
+ }
+
+ op, err := doc.GetOperation(reqParams.OperationName)
+ if err != nil {
+ c.sendError(message.ID, errors.Errorf("%s", err.Error()))
+ return true
+ }
+
+ reqCtx := c.cfg.newRequestContext(doc, reqParams.Query, reqParams.Variables)
+ ctx := graphql.WithRequestContext(c.ctx, reqCtx)
+
+ if op.Type != query.Subscription {
+ var result *graphql.Response
+ if op.Type == query.Query {
+ result = c.exec.Query(ctx, op)
+ } else {
+ result = c.exec.Mutation(ctx, op)
+ }
+
+ c.sendData(message.ID, result)
+ c.write(&operationMessage{ID: message.ID, Type: completeMsg})
+ return true
+ }
+
+ ctx, cancel := context.WithCancel(ctx)
+ c.mu.Lock()
+ c.active[message.ID] = cancel
+ c.mu.Unlock()
+ go func() {
+ defer func() {
+ if r := recover(); r != nil {
+ userErr := reqCtx.Recover(ctx, r)
+ c.sendError(message.ID, &errors.QueryError{Message: userErr.Error()})
+ }
+ }()
+ next := c.exec.Subscription(ctx, op)
+ for result := next(); result != nil; result = next() {
+ c.sendData(message.ID, result)
+ }
+
+ c.write(&operationMessage{ID: message.ID, Type: completeMsg})
+
+ c.mu.Lock()
+ delete(c.active, message.ID)
+ c.mu.Unlock()
+ cancel()
+ }()
+
+ return true
+}
+
+func (c *wsConnection) sendData(id string, response *graphql.Response) {
+ b, err := json.Marshal(response)
+ if err != nil {
+ c.sendError(id, errors.Errorf("unable to encode json response: %s", err.Error()))
+ return
+ }
+
+ c.write(&operationMessage{Type: dataMsg, ID: id, Payload: b})
+}
+
+func (c *wsConnection) sendError(id string, errors ...*errors.QueryError) {
+ var errs []error
+ for _, err := range errors {
+ errs = append(errs, err)
+ }
+ b, err := json.Marshal(errs)
+ if err != nil {
+ panic(err)
+ }
+ c.write(&operationMessage{Type: errorMsg, ID: id, Payload: b})
+}
+
+func (c *wsConnection) sendConnectionError(format string, args ...interface{}) {
+ b, err := json.Marshal(&graphql.Error{Message: fmt.Sprintf(format, args...)})
+ if err != nil {
+ panic(err)
+ }
+
+ c.write(&operationMessage{Type: connectionErrorMsg, Payload: b})
+}
+
+func (c *wsConnection) readOp() *operationMessage {
+ message := operationMessage{}
+ if err := c.conn.ReadJSON(&message); err != nil {
+ c.sendConnectionError("invalid json")
+ return nil
+ }
+ return &message
+}
+
+func (c *wsConnection) close(closeCode int, message string) {
+ c.mu.Lock()
+ _ = c.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(closeCode, message))
+ c.mu.Unlock()
+ _ = c.conn.Close()
+}
diff --git a/vendor/github.com/vektah/gqlgen/neelance/LICENSE b/vendor/github.com/vektah/gqlgen/neelance/LICENSE
new file mode 100644
index 00000000..3907ceca
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/neelance/LICENSE
@@ -0,0 +1,24 @@
+Copyright (c) 2016 Richard Musiol. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/vektah/gqlgen/neelance/common/directive.go b/vendor/github.com/vektah/gqlgen/neelance/common/directive.go
new file mode 100644
index 00000000..62dca47f
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/neelance/common/directive.go
@@ -0,0 +1,32 @@
+package common
+
+type Directive struct {
+ Name Ident
+ Args ArgumentList
+}
+
+func ParseDirectives(l *Lexer) DirectiveList {
+ var directives DirectiveList
+ for l.Peek() == '@' {
+ l.ConsumeToken('@')
+ d := &Directive{}
+ d.Name = l.ConsumeIdentWithLoc()
+ d.Name.Loc.Column--
+ if l.Peek() == '(' {
+ d.Args = ParseArguments(l)
+ }
+ directives = append(directives, d)
+ }
+ return directives
+}
+
+type DirectiveList []*Directive
+
+func (l DirectiveList) Get(name string) *Directive {
+ for _, d := range l {
+ if d.Name.Name == name {
+ return d
+ }
+ }
+ return nil
+}
diff --git a/vendor/github.com/vektah/gqlgen/neelance/common/lexer.go b/vendor/github.com/vektah/gqlgen/neelance/common/lexer.go
new file mode 100644
index 00000000..fdc1e622
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/neelance/common/lexer.go
@@ -0,0 +1,122 @@
+package common
+
+import (
+ "fmt"
+ "text/scanner"
+
+ "github.com/vektah/gqlgen/neelance/errors"
+)
+
+type syntaxError string
+
+type Lexer struct {
+ sc *scanner.Scanner
+ next rune
+ descComment string
+}
+
+type Ident struct {
+ Name string
+ Loc errors.Location
+}
+
+func New(sc *scanner.Scanner) *Lexer {
+ l := &Lexer{sc: sc}
+ l.Consume()
+ return l
+}
+
+func (l *Lexer) CatchSyntaxError(f func()) (errRes *errors.QueryError) {
+ defer func() {
+ if err := recover(); err != nil {
+ if err, ok := err.(syntaxError); ok {
+ errRes = errors.Errorf("syntax error: %s", err)
+ errRes.Locations = []errors.Location{l.Location()}
+ return
+ }
+ panic(err)
+ }
+ }()
+
+ f()
+ return
+}
+
+func (l *Lexer) Peek() rune {
+ return l.next
+}
+
+func (l *Lexer) Consume() {
+ l.descComment = ""
+ for {
+ l.next = l.sc.Scan()
+ if l.next == ',' {
+ continue
+ }
+ if l.next == '#' {
+ if l.sc.Peek() == ' ' {
+ l.sc.Next()
+ }
+ if l.descComment != "" {
+ l.descComment += "\n"
+ }
+ for {
+ next := l.sc.Next()
+ if next == '\n' || next == scanner.EOF {
+ break
+ }
+ l.descComment += string(next)
+ }
+ continue
+ }
+ break
+ }
+}
+
+func (l *Lexer) ConsumeIdent() string {
+ name := l.sc.TokenText()
+ l.ConsumeToken(scanner.Ident)
+ return name
+}
+
+func (l *Lexer) ConsumeIdentWithLoc() Ident {
+ loc := l.Location()
+ name := l.sc.TokenText()
+ l.ConsumeToken(scanner.Ident)
+ return Ident{name, loc}
+}
+
+func (l *Lexer) ConsumeKeyword(keyword string) {
+ if l.next != scanner.Ident || l.sc.TokenText() != keyword {
+ l.SyntaxError(fmt.Sprintf("unexpected %q, expecting %q", l.sc.TokenText(), keyword))
+ }
+ l.Consume()
+}
+
+func (l *Lexer) ConsumeLiteral() *BasicLit {
+ lit := &BasicLit{Type: l.next, Text: l.sc.TokenText()}
+ l.Consume()
+ return lit
+}
+
+func (l *Lexer) ConsumeToken(expected rune) {
+ if l.next != expected {
+ l.SyntaxError(fmt.Sprintf("unexpected %q, expecting %s", l.sc.TokenText(), scanner.TokenString(expected)))
+ }
+ l.Consume()
+}
+
+func (l *Lexer) DescComment() string {
+ return l.descComment
+}
+
+func (l *Lexer) SyntaxError(message string) {
+ panic(syntaxError(message))
+}
+
+func (l *Lexer) Location() errors.Location {
+ return errors.Location{
+ Line: l.sc.Line,
+ Column: l.sc.Column,
+ }
+}
diff --git a/vendor/github.com/vektah/gqlgen/neelance/common/literals.go b/vendor/github.com/vektah/gqlgen/neelance/common/literals.go
new file mode 100644
index 00000000..55619ba0
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/neelance/common/literals.go
@@ -0,0 +1,206 @@
+package common
+
+import (
+ "strconv"
+ "strings"
+ "text/scanner"
+
+ "github.com/vektah/gqlgen/neelance/errors"
+)
+
+type Literal interface {
+ Value(vars map[string]interface{}) interface{}
+ String() string
+ Location() errors.Location
+}
+
+type BasicLit struct {
+ Type rune
+ Text string
+ Loc errors.Location
+}
+
+func (lit *BasicLit) Value(vars map[string]interface{}) interface{} {
+ switch lit.Type {
+ case scanner.Int:
+ value, err := strconv.ParseInt(lit.Text, 10, 64)
+ if err != nil {
+ panic(err)
+ }
+ return int(value)
+
+ case scanner.Float:
+ value, err := strconv.ParseFloat(lit.Text, 64)
+ if err != nil {
+ panic(err)
+ }
+ return value
+
+ case scanner.String:
+ value, err := strconv.Unquote(lit.Text)
+ if err != nil {
+ panic(err)
+ }
+ return value
+
+ case scanner.Ident:
+ switch lit.Text {
+ case "true":
+ return true
+ case "false":
+ return false
+ default:
+ return lit.Text
+ }
+
+ default:
+ panic("invalid literal")
+ }
+}
+
+func (lit *BasicLit) String() string {
+ return lit.Text
+}
+
+func (lit *BasicLit) Location() errors.Location {
+ return lit.Loc
+}
+
+type ListLit struct {
+ Entries []Literal
+ Loc errors.Location
+}
+
+func (lit *ListLit) Value(vars map[string]interface{}) interface{} {
+ entries := make([]interface{}, len(lit.Entries))
+ for i, entry := range lit.Entries {
+ entries[i] = entry.Value(vars)
+ }
+ return entries
+}
+
+func (lit *ListLit) String() string {
+ entries := make([]string, len(lit.Entries))
+ for i, entry := range lit.Entries {
+ entries[i] = entry.String()
+ }
+ return "[" + strings.Join(entries, ", ") + "]"
+}
+
+func (lit *ListLit) Location() errors.Location {
+ return lit.Loc
+}
+
+type ObjectLit struct {
+ Fields []*ObjectLitField
+ Loc errors.Location
+}
+
+type ObjectLitField struct {
+ Name Ident
+ Value Literal
+}
+
+func (lit *ObjectLit) Value(vars map[string]interface{}) interface{} {
+ fields := make(map[string]interface{}, len(lit.Fields))
+ for _, f := range lit.Fields {
+ fields[f.Name.Name] = f.Value.Value(vars)
+ }
+ return fields
+}
+
+func (lit *ObjectLit) String() string {
+ entries := make([]string, 0, len(lit.Fields))
+ for _, f := range lit.Fields {
+ entries = append(entries, f.Name.Name+": "+f.Value.String())
+ }
+ return "{" + strings.Join(entries, ", ") + "}"
+}
+
+func (lit *ObjectLit) Location() errors.Location {
+ return lit.Loc
+}
+
+type NullLit struct {
+ Loc errors.Location
+}
+
+func (lit *NullLit) Value(vars map[string]interface{}) interface{} {
+ return nil
+}
+
+func (lit *NullLit) String() string {
+ return "null"
+}
+
+func (lit *NullLit) Location() errors.Location {
+ return lit.Loc
+}
+
+type Variable struct {
+ Name string
+ Loc errors.Location
+}
+
+func (v Variable) Value(vars map[string]interface{}) interface{} {
+ return vars[v.Name]
+}
+
+func (v Variable) String() string {
+ return "$" + v.Name
+}
+
+func (v *Variable) Location() errors.Location {
+ return v.Loc
+}
+
+func ParseLiteral(l *Lexer, constOnly bool) Literal {
+ loc := l.Location()
+ switch l.Peek() {
+ case '$':
+ if constOnly {
+ l.SyntaxError("variable not allowed")
+ panic("unreachable")
+ }
+ l.ConsumeToken('$')
+ return &Variable{l.ConsumeIdent(), loc}
+
+ case scanner.Int, scanner.Float, scanner.String, scanner.Ident:
+ lit := l.ConsumeLiteral()
+ if lit.Type == scanner.Ident && lit.Text == "null" {
+ return &NullLit{loc}
+ }
+ lit.Loc = loc
+ return lit
+ case '-':
+ l.ConsumeToken('-')
+ lit := l.ConsumeLiteral()
+ lit.Text = "-" + lit.Text
+ lit.Loc = loc
+ return lit
+ case '[':
+ l.ConsumeToken('[')
+ var list []Literal
+ for l.Peek() != ']' {
+ list = append(list, ParseLiteral(l, constOnly))
+ }
+ l.ConsumeToken(']')
+ return &ListLit{list, loc}
+
+ case '{':
+ l.ConsumeToken('{')
+ var fields []*ObjectLitField
+ for l.Peek() != '}' {
+ name := l.ConsumeIdentWithLoc()
+ l.ConsumeToken(':')
+ value := ParseLiteral(l, constOnly)
+ fields = append(fields, &ObjectLitField{name, value})
+ }
+ l.ConsumeToken('}')
+ return &ObjectLit{fields, loc}
+
+ default:
+ l.SyntaxError("invalid value")
+ panic("unreachable")
+ }
+}
diff --git a/vendor/github.com/vektah/gqlgen/neelance/common/types.go b/vendor/github.com/vektah/gqlgen/neelance/common/types.go
new file mode 100644
index 00000000..0bbf24ef
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/neelance/common/types.go
@@ -0,0 +1,80 @@
+package common
+
+import (
+ "github.com/vektah/gqlgen/neelance/errors"
+)
+
+type Type interface {
+ Kind() string
+ String() string
+}
+
+type List struct {
+ OfType Type
+}
+
+type NonNull struct {
+ OfType Type
+}
+
+type TypeName struct {
+ Ident
+}
+
+func (*List) Kind() string { return "LIST" }
+func (*NonNull) Kind() string { return "NON_NULL" }
+func (*TypeName) Kind() string { panic("TypeName needs to be resolved to actual type") }
+
+func (t *List) String() string { return "[" + t.OfType.String() + "]" }
+func (t *NonNull) String() string { return t.OfType.String() + "!" }
+func (*TypeName) String() string { panic("TypeName needs to be resolved to actual type") }
+
+func ParseType(l *Lexer) Type {
+ t := parseNullType(l)
+ if l.Peek() == '!' {
+ l.ConsumeToken('!')
+ return &NonNull{OfType: t}
+ }
+ return t
+}
+
+func parseNullType(l *Lexer) Type {
+ if l.Peek() == '[' {
+ l.ConsumeToken('[')
+ ofType := ParseType(l)
+ l.ConsumeToken(']')
+ return &List{OfType: ofType}
+ }
+
+ return &TypeName{Ident: l.ConsumeIdentWithLoc()}
+}
+
+type Resolver func(name string) Type
+
+func ResolveType(t Type, resolver Resolver) (Type, *errors.QueryError) {
+ switch t := t.(type) {
+ case *List:
+ ofType, err := ResolveType(t.OfType, resolver)
+ if err != nil {
+ return nil, err
+ }
+ return &List{OfType: ofType}, nil
+ case *NonNull:
+ ofType, err := ResolveType(t.OfType, resolver)
+ if err != nil {
+ return nil, err
+ }
+ return &NonNull{OfType: ofType}, nil
+ case *TypeName:
+ refT := resolver(t.Name)
+ if refT == nil {
+ err := errors.Errorf("Unknown type %q.", t.Name)
+ err.Rule = "KnownTypeNames"
+ err.Locations = []errors.Location{t.Loc}
+ return nil, err
+ }
+ return refT, nil
+ default:
+ return t, nil
+ }
+}
diff --git a/vendor/github.com/vektah/gqlgen/neelance/common/values.go b/vendor/github.com/vektah/gqlgen/neelance/common/values.go
new file mode 100644
index 00000000..09338da8
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/neelance/common/values.go
@@ -0,0 +1,77 @@
+package common
+
+import (
+ "github.com/vektah/gqlgen/neelance/errors"
+)
+
+type InputValue struct {
+ Name Ident
+ Type Type
+ Default Literal
+ Desc string
+ Loc errors.Location
+ TypeLoc errors.Location
+}
+
+type InputValueList []*InputValue
+
+func (l InputValueList) Get(name string) *InputValue {
+ for _, v := range l {
+ if v.Name.Name == name {
+ return v
+ }
+ }
+ return nil
+}
+
+func ParseInputValue(l *Lexer) *InputValue {
+ p := &InputValue{}
+ p.Loc = l.Location()
+ p.Desc = l.DescComment()
+ p.Name = l.ConsumeIdentWithLoc()
+ l.ConsumeToken(':')
+ p.TypeLoc = l.Location()
+ p.Type = ParseType(l)
+ if l.Peek() == '=' {
+ l.ConsumeToken('=')
+ p.Default = ParseLiteral(l, true)
+ }
+ return p
+}
+
+type Argument struct {
+ Name Ident
+ Value Literal
+}
+
+type ArgumentList []Argument
+
+func (l ArgumentList) Get(name string) (Literal, bool) {
+ for _, arg := range l {
+ if arg.Name.Name == name {
+ return arg.Value, true
+ }
+ }
+ return nil, false
+}
+
+func (l ArgumentList) MustGet(name string) Literal {
+ value, ok := l.Get(name)
+ if !ok {
+ panic("argument not found")
+ }
+ return value
+}
+
+func ParseArguments(l *Lexer) ArgumentList {
+ var args ArgumentList
+ l.ConsumeToken('(')
+ for l.Peek() != ')' {
+ name := l.ConsumeIdentWithLoc()
+ l.ConsumeToken(':')
+ value := ParseLiteral(l, false)
+ args = append(args, Argument{Name: name, Value: value})
+ }
+ l.ConsumeToken(')')
+ return args
+}
diff --git a/vendor/github.com/vektah/gqlgen/neelance/errors/errors.go b/vendor/github.com/vektah/gqlgen/neelance/errors/errors.go
new file mode 100644
index 00000000..fdfa6202
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/neelance/errors/errors.go
@@ -0,0 +1,41 @@
+package errors
+
+import (
+ "fmt"
+)
+
+type QueryError struct {
+ Message string `json:"message"`
+ Locations []Location `json:"locations,omitempty"`
+ Path []interface{} `json:"path,omitempty"`
+ Rule string `json:"-"`
+ ResolverError error `json:"-"`
+}
+
+type Location struct {
+ Line int `json:"line"`
+ Column int `json:"column"`
+}
+
+func (a Location) Before(b Location) bool {
+ return a.Line < b.Line || (a.Line == b.Line && a.Column < b.Column)
+}
+
+func Errorf(format string, a ...interface{}) *QueryError {
+ return &QueryError{
+ Message: fmt.Sprintf(format, a...),
+ }
+}
+
+func (err *QueryError) Error() string {
+ if err == nil {
+ return "<nil>"
+ }
+ str := fmt.Sprintf("graphql: %s", err.Message)
+ for _, loc := range err.Locations {
+ str += fmt.Sprintf(" (line %d, column %d)", loc.Line, loc.Column)
+ }
+ return str
+}
+
+var _ error = &QueryError{}
diff --git a/vendor/github.com/vektah/gqlgen/neelance/introspection/introspection.go b/vendor/github.com/vektah/gqlgen/neelance/introspection/introspection.go
new file mode 100644
index 00000000..5e354c9a
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/neelance/introspection/introspection.go
@@ -0,0 +1,313 @@
+package introspection
+
+import (
+ "sort"
+
+ "github.com/vektah/gqlgen/neelance/common"
+ "github.com/vektah/gqlgen/neelance/schema"
+)
+
+type Schema struct {
+ schema *schema.Schema
+}
+
+// WrapSchema is only used internally.
+func WrapSchema(schema *schema.Schema) *Schema {
+ return &Schema{schema}
+}
+
+func (r *Schema) Types() []Type {
+ var names []string
+ for name := range r.schema.Types {
+ names = append(names, name)
+ }
+ sort.Strings(names)
+
+ l := make([]Type, len(names))
+ for i, name := range names {
+ l[i] = Type{r.schema.Types[name]}
+ }
+ return l
+}
+
+func (r *Schema) Directives() []Directive {
+ var names []string
+ for name := range r.schema.Directives {
+ names = append(names, name)
+ }
+ sort.Strings(names)
+
+ l := make([]Directive, len(names))
+ for i, name := range names {
+ l[i] = Directive{r.schema.Directives[name]}
+ }
+ return l
+}
+
+func (r *Schema) QueryType() Type {
+ t, ok := r.schema.EntryPoints["query"]
+ if !ok {
+ return Type{}
+ }
+ return Type{t}
+}
+
+func (r *Schema) MutationType() *Type {
+ t, ok := r.schema.EntryPoints["mutation"]
+ if !ok {
+ return nil
+ }
+ return &Type{t}
+}
+
+func (r *Schema) SubscriptionType() *Type {
+ t, ok := r.schema.EntryPoints["subscription"]
+ if !ok {
+ return nil
+ }
+ return &Type{t}
+}
+
+type Type struct {
+ typ common.Type
+}
+
+// WrapType is only used internally.
+func WrapType(typ common.Type) *Type {
+ return &Type{typ}
+}
+
+func (r *Type) Kind() string {
+ return r.typ.Kind()
+}
+
+func (r *Type) Name() *string {
+ if named, ok := r.typ.(schema.NamedType); ok {
+ name := named.TypeName()
+ return &name
+ }
+ return nil
+}
+
+func (r *Type) Description() *string {
+ if named, ok := r.typ.(schema.NamedType); ok {
+ desc := named.Description()
+ if desc == "" {
+ return nil
+ }
+ return &desc
+ }
+ return nil
+}
+
+func (r *Type) Fields(includeDeprecated bool) []Field {
+ var fields schema.FieldList
+ switch t := r.typ.(type) {
+ case *schema.Object:
+ fields = t.Fields
+ case *schema.Interface:
+ fields = t.Fields
+ default:
+ return nil
+ }
+
+ var l []Field
+ for _, f := range fields {
+ if d := f.Directives.Get("deprecated"); d == nil || includeDeprecated {
+ l = append(l, Field{f})
+ }
+ }
+ return l
+}
+
+func (r *Type) Interfaces() []Type {
+ t, ok := r.typ.(*schema.Object)
+ if !ok {
+ return nil
+ }
+
+ l := make([]Type, len(t.Interfaces))
+ for i, intf := range t.Interfaces {
+ l[i] = Type{intf}
+ }
+ return l
+}
+
+func (r *Type) PossibleTypes() []Type {
+ var possibleTypes []*schema.Object
+ switch t := r.typ.(type) {
+ case *schema.Interface:
+ possibleTypes = t.PossibleTypes
+ case *schema.Union:
+ possibleTypes = t.PossibleTypes
+ default:
+ return nil
+ }
+
+ l := make([]Type, len(possibleTypes))
+ for i, intf := range possibleTypes {
+ l[i] = Type{intf}
+ }
+ return l
+}
+
+func (r *Type) EnumValues(includeDeprecated bool) []EnumValue {
+ t, ok := r.typ.(*schema.Enum)
+ if !ok {
+ return nil
+ }
+
+ var l []EnumValue
+ for _, v := range t.Values {
+ if d := v.Directives.Get("deprecated"); d == nil || includeDeprecated {
+ l = append(l, EnumValue{v})
+ }
+ }
+ return l
+}
+
+func (r *Type) InputFields() []InputValue {
+ t, ok := r.typ.(*schema.InputObject)
+ if !ok {
+ return nil
+ }
+
+ l := make([]InputValue, len(t.Values))
+ for i, v := range t.Values {
+ l[i] = InputValue{v}
+ }
+ return l
+}
+
+func (r *Type) OfType() *Type {
+ switch t := r.typ.(type) {
+ case *common.List:
+ return &Type{t.OfType}
+ case *common.NonNull:
+ return &Type{t.OfType}
+ default:
+ return nil
+ }
+}
+
+type Field struct {
+ field *schema.Field
+}
+
+func (r *Field) Name() string {
+ return r.field.Name
+}
+
+func (r *Field) Description() *string {
+ if r.field.Desc == "" {
+ return nil
+ }
+ return &r.field.Desc
+}
+
+func (r *Field) Args() []InputValue {
+ l := make([]InputValue, len(r.field.Args))
+ for i, v := range r.field.Args {
+ l[i] = InputValue{v}
+ }
+ return l
+}
+
+func (r *Field) Type() Type {
+ return Type{r.field.Type}
+}
+
+func (r *Field) IsDeprecated() bool {
+ return r.field.Directives.Get("deprecated") != nil
+}
+
+func (r *Field) DeprecationReason() *string {
+ d := r.field.Directives.Get("deprecated")
+ if d == nil {
+ return nil
+ }
+ reason := d.Args.MustGet("reason").Value(nil).(string)
+ return &reason
+}
+
+type InputValue struct {
+ value *common.InputValue
+}
+
+func (r *InputValue) Name() string {
+ return r.value.Name.Name
+}
+
+func (r *InputValue) Description() *string {
+ if r.value.Desc == "" {
+ return nil
+ }
+ return &r.value.Desc
+}
+
+func (r *InputValue) Type() Type {
+ return Type{r.value.Type}
+}
+
+func (r *InputValue) DefaultValue() *string {
+ if r.value.Default == nil {
+ return nil
+ }
+ s := r.value.Default.String()
+ return &s
+}
+
+type EnumValue struct {
+ value *schema.EnumValue
+}
+
+func (r *EnumValue) Name() string {
+ return r.value.Name
+}
+
+func (r *EnumValue) Description() *string {
+ if r.value.Desc == "" {
+ return nil
+ }
+ return &r.value.Desc
+}
+
+func (r *EnumValue) IsDeprecated() bool {
+ return r.value.Directives.Get("deprecated") != nil
+}
+
+func (r *EnumValue) DeprecationReason() *string {
+ d := r.value.Directives.Get("deprecated")
+ if d == nil {
+ return nil
+ }
+ reason := d.Args.MustGet("reason").Value(nil).(string)
+ return &reason
+}
+
+type Directive struct {
+ directive *schema.DirectiveDecl
+}
+
+func (r *Directive) Name() string {
+ return r.directive.Name
+}
+
+func (r *Directive) Description() *string {
+ if r.directive.Desc == "" {
+ return nil
+ }
+ return &r.directive.Desc
+}
+
+func (r *Directive) Locations() []string {
+ return r.directive.Locs
+}
+
+func (r *Directive) Args() []InputValue {
+ l := make([]InputValue, len(r.directive.Args))
+ for i, v := range r.directive.Args {
+ l[i] = InputValue{v}
+ }
+ return l
+}
diff --git a/vendor/github.com/vektah/gqlgen/neelance/introspection/query.go b/vendor/github.com/vektah/gqlgen/neelance/introspection/query.go
new file mode 100644
index 00000000..b1e4fbc6
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/neelance/introspection/query.go
@@ -0,0 +1,104 @@
+package introspection
+
+// Query is the query generated by graphiql to determine type information
+const Query = `
+query IntrospectionQuery {
+ __schema {
+ queryType {
+ name
+ }
+ mutationType {
+ name
+ }
+ subscriptionType {
+ name
+ }
+ types {
+ ...FullType
+ }
+ directives {
+ name
+ description
+ locations
+ args {
+ ...InputValue
+ }
+ }
+ }
+}
+
+fragment FullType on __Type {
+ kind
+ name
+ description
+ fields(includeDeprecated: true) {
+ name
+ description
+ args {
+ ...InputValue
+ }
+ type {
+ ...TypeRef
+ }
+ isDeprecated
+ deprecationReason
+ }
+ inputFields {
+ ...InputValue
+ }
+ interfaces {
+ ...TypeRef
+ }
+ enumValues(includeDeprecated: true) {
+ name
+ description
+ isDeprecated
+ deprecationReason
+ }
+ possibleTypes {
+ ...TypeRef
+ }
+}
+
+fragment InputValue on __InputValue {
+ name
+ description
+ type {
+ ...TypeRef
+ }
+ defaultValue
+}
+
+fragment TypeRef on __Type {
+ kind
+ name
+ ofType {
+ kind
+ name
+ ofType {
+ kind
+ name
+ ofType {
+ kind
+ name
+ ofType {
+ kind
+ name
+ ofType {
+ kind
+ name
+ ofType {
+ kind
+ name
+ ofType {
+ kind
+ name
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+`
diff --git a/vendor/github.com/vektah/gqlgen/neelance/query/query.go b/vendor/github.com/vektah/gqlgen/neelance/query/query.go
new file mode 100644
index 00000000..b6f35354
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/neelance/query/query.go
@@ -0,0 +1,261 @@
+package query
+
+import (
+ "fmt"
+ "strings"
+ "text/scanner"
+
+ "github.com/vektah/gqlgen/neelance/common"
+ "github.com/vektah/gqlgen/neelance/errors"
+)
+
+type Document struct {
+ Operations OperationList
+ Fragments FragmentList
+}
+
+type OperationList []*Operation
+
+func (l OperationList) Get(name string) *Operation {
+ for _, f := range l {
+ if f.Name.Name == name {
+ return f
+ }
+ }
+ return nil
+}
+
+type FragmentList []*FragmentDecl
+
+func (l FragmentList) Get(name string) *FragmentDecl {
+ for _, f := range l {
+ if f.Name.Name == name {
+ return f
+ }
+ }
+ return nil
+}
+
+type Operation struct {
+ Type OperationType
+ Name common.Ident
+ Vars common.InputValueList
+ Selections []Selection
+ Directives common.DirectiveList
+ Loc errors.Location
+}
+
+type OperationType string
+
+const (
+ Query OperationType = "QUERY"
+ Mutation = "MUTATION"
+ Subscription = "SUBSCRIPTION"
+)
+
+type Fragment struct {
+ On common.TypeName
+ Selections []Selection
+}
+
+type FragmentDecl struct {
+ Fragment
+ Name common.Ident
+ Directives common.DirectiveList
+ Loc errors.Location
+}
+
+type Selection interface {
+ isSelection()
+}
+
+type Field struct {
+ Alias common.Ident
+ Name common.Ident
+ Arguments common.ArgumentList
+ Directives common.DirectiveList
+ Selections []Selection
+ SelectionSetLoc errors.Location
+}
+
+type InlineFragment struct {
+ Fragment
+ Directives common.DirectiveList
+ Loc errors.Location
+}
+
+type FragmentSpread struct {
+ Name common.Ident
+ Directives common.DirectiveList
+ Loc errors.Location
+}
+
+func (Field) isSelection() {}
+func (InlineFragment) isSelection() {}
+func (FragmentSpread) isSelection() {}
+
+func Parse(queryString string) (*Document, *errors.QueryError) {
+ sc := &scanner.Scanner{
+ Mode: scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats | scanner.ScanStrings,
+ }
+ sc.Init(strings.NewReader(queryString))
+
+ l := common.New(sc)
+ var doc *Document
+ err := l.CatchSyntaxError(func() {
+ doc = parseDocument(l)
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return doc, nil
+}
+
+func parseDocument(l *common.Lexer) *Document {
+ d := &Document{}
+ for l.Peek() != scanner.EOF {
+ if l.Peek() == '{' {
+ op := &Operation{Type: Query, Loc: l.Location()}
+ op.Selections = parseSelectionSet(l)
+ d.Operations = append(d.Operations, op)
+ continue
+ }
+
+ loc := l.Location()
+ switch x := l.ConsumeIdent(); x {
+ case "query":
+ op := parseOperation(l, Query)
+ op.Loc = loc
+ d.Operations = append(d.Operations, op)
+
+ case "mutation":
+ d.Operations = append(d.Operations, parseOperation(l, Mutation))
+
+ case "subscription":
+ d.Operations = append(d.Operations, parseOperation(l, Subscription))
+
+ case "fragment":
+ frag := parseFragment(l)
+ frag.Loc = loc
+ d.Fragments = append(d.Fragments, frag)
+
+ default:
+ l.SyntaxError(fmt.Sprintf(`unexpected %q, expecting "fragment"`, x))
+ }
+ }
+ return d
+}
+
+func parseOperation(l *common.Lexer, opType OperationType) *Operation {
+ op := &Operation{Type: opType}
+ op.Name.Loc = l.Location()
+ if l.Peek() == scanner.Ident {
+ op.Name = l.ConsumeIdentWithLoc()
+ }
+ op.Directives = common.ParseDirectives(l)
+ if l.Peek() == '(' {
+ l.ConsumeToken('(')
+ for l.Peek() != ')' {
+ loc := l.Location()
+ l.ConsumeToken('$')
+ iv := common.ParseInputValue(l)
+ iv.Loc = loc
+ op.Vars = append(op.Vars, iv)
+ }
+ l.ConsumeToken(')')
+ }
+ op.Selections = parseSelectionSet(l)
+ return op
+}
+
+func parseFragment(l *common.Lexer) *FragmentDecl {
+ f := &FragmentDecl{}
+ f.Name = l.ConsumeIdentWithLoc()
+ l.ConsumeKeyword("on")
+ f.On = common.TypeName{Ident: l.ConsumeIdentWithLoc()}
+ f.Directives = common.ParseDirectives(l)
+ f.Selections = parseSelectionSet(l)
+ return f
+}
+
+func parseSelectionSet(l *common.Lexer) []Selection {
+ var sels []Selection
+ l.ConsumeToken('{')
+ for l.Peek() != '}' {
+ sels = append(sels, parseSelection(l))
+ }
+ l.ConsumeToken('}')
+ return sels
+}
+
+func parseSelection(l *common.Lexer) Selection {
+ if l.Peek() == '.' {
+ return parseSpread(l)
+ }
+ return parseField(l)
+}
+
+func parseField(l *common.Lexer) *Field {
+ f := &Field{}
+ f.Alias = l.ConsumeIdentWithLoc()
+ f.Name = f.Alias
+ if l.Peek() == ':' {
+ l.ConsumeToken(':')
+ f.Name = l.ConsumeIdentWithLoc()
+ }
+ if l.Peek() == '(' {
+ f.Arguments = common.ParseArguments(l)
+ }
+ f.Directives = common.ParseDirectives(l)
+ if l.Peek() == '{' {
+ f.SelectionSetLoc = l.Location()
+ f.Selections = parseSelectionSet(l)
+ }
+ return f
+}
+
+func parseSpread(l *common.Lexer) Selection {
+ loc := l.Location()
+ l.ConsumeToken('.')
+ l.ConsumeToken('.')
+ l.ConsumeToken('.')
+
+ f := &InlineFragment{Loc: loc}
+ if l.Peek() == scanner.Ident {
+ ident := l.ConsumeIdentWithLoc()
+ if ident.Name != "on" {
+ fs := &FragmentSpread{
+ Name: ident,
+ Loc: loc,
+ }
+ fs.Directives = common.ParseDirectives(l)
+ return fs
+ }
+ f.On = common.TypeName{Ident: l.ConsumeIdentWithLoc()}
+ }
+ f.Directives = common.ParseDirectives(l)
+ f.Selections = parseSelectionSet(l)
+ return f
+}
+
+func (d *Document) GetOperation(operationName string) (*Operation, error) {
+ if len(d.Operations) == 0 {
+ return nil, fmt.Errorf("no operations in query document")
+ }
+
+ if operationName == "" {
+ if len(d.Operations) > 1 {
+ return nil, fmt.Errorf("more than one operation in query document and no operation name given")
+ }
+ for _, op := range d.Operations {
+ return op, nil // return the one and only operation
+ }
+ }
+
+ op := d.Operations.Get(operationName)
+ if op == nil {
+ return nil, fmt.Errorf("no operation with name %q", operationName)
+ }
+ return op, nil
+}
diff --git a/vendor/github.com/vektah/gqlgen/neelance/schema/meta.go b/vendor/github.com/vektah/gqlgen/neelance/schema/meta.go
new file mode 100644
index 00000000..efdcaa2c
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/neelance/schema/meta.go
@@ -0,0 +1,193 @@
+package schema
+
+var Meta *Schema
+
+func init() {
+ Meta = &Schema{} // bootstrap
+ Meta = New()
+ if err := Meta.Parse(metaSrc); err != nil {
+ panic(err)
+ }
+}
+
+var metaSrc = `
+ # The ` + "`" + `Int` + "`" + ` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.
+ scalar Int
+
+ # The ` + "`" + `Float` + "`" + ` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).
+ scalar Float
+
+ # The ` + "`" + `String` + "`" + ` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.
+ scalar String
+
+ # The ` + "`" + `Boolean` + "`" + ` scalar type represents ` + "`" + `true` + "`" + ` or ` + "`" + `false` + "`" + `.
+ scalar Boolean
+
+ # 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 ` + "`" + `Map` + "`" + ` scalar type is a simple json object
+ scalar Map
+
+ # Directs the executor to include this field or fragment only when the ` + "`" + `if` + "`" + ` argument is true.
+ directive @include(
+ # Included when true.
+ if: Boolean!
+ ) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
+
+ # Directs the executor to skip this field or fragment when the ` + "`" + `if` + "`" + ` argument is true.
+ directive @skip(
+ # Skipped when true.
+ if: Boolean!
+ ) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
+
+ # Marks an element of a GraphQL schema as no longer supported.
+ directive @deprecated(
+ # Explains why this element was deprecated, usually also including a suggestion
+ # for how to access supported similar data. Formatted in
+ # [Markdown](https://daringfireball.net/projects/markdown/).
+ reason: String = "No longer supported"
+ ) on FIELD_DEFINITION | ENUM_VALUE
+
+ # A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.
+ #
+ # In some cases, you need to provide options to alter GraphQL's execution behavior
+ # in ways field arguments will not suffice, such as conditionally including or
+ # skipping a field. Directives provide this by describing additional information
+ # to the executor.
+ type __Directive {
+ name: String!
+ description: String
+ locations: [__DirectiveLocation!]!
+ args: [__InputValue!]!
+ }
+
+ # A Directive can be adjacent to many parts of the GraphQL language, a
+ # __DirectiveLocation describes one such possible adjacencies.
+ enum __DirectiveLocation {
+ # Location adjacent to a query operation.
+ QUERY
+ # Location adjacent to a mutation operation.
+ MUTATION
+ # Location adjacent to a subscription operation.
+ SUBSCRIPTION
+ # Location adjacent to a field.
+ FIELD
+ # Location adjacent to a fragment definition.
+ FRAGMENT_DEFINITION
+ # Location adjacent to a fragment spread.
+ FRAGMENT_SPREAD
+ # Location adjacent to an inline fragment.
+ INLINE_FRAGMENT
+ # Location adjacent to a schema definition.
+ SCHEMA
+ # Location adjacent to a scalar definition.
+ SCALAR
+ # Location adjacent to an object type definition.
+ OBJECT
+ # Location adjacent to a field definition.
+ FIELD_DEFINITION
+ # Location adjacent to an argument definition.
+ ARGUMENT_DEFINITION
+ # Location adjacent to an interface definition.
+ INTERFACE
+ # Location adjacent to a union definition.
+ UNION
+ # Location adjacent to an enum definition.
+ ENUM
+ # Location adjacent to an enum value definition.
+ ENUM_VALUE
+ # Location adjacent to an input object type definition.
+ INPUT_OBJECT
+ # Location adjacent to an input object field definition.
+ INPUT_FIELD_DEFINITION
+ }
+
+ # One possible value for a given Enum. Enum values are unique values, not a
+ # placeholder for a string or numeric value. However an Enum value is returned in
+ # a JSON response as a string.
+ type __EnumValue {
+ name: String!
+ description: String
+ isDeprecated: Boolean!
+ deprecationReason: String
+ }
+
+ # Object and Interface types are described by a list of Fields, each of which has
+ # a name, potentially a list of arguments, and a return type.
+ type __Field {
+ name: String!
+ description: String
+ args: [__InputValue!]!
+ type: __Type!
+ isDeprecated: Boolean!
+ deprecationReason: String
+ }
+
+ # Arguments provided to Fields or Directives and the input fields of an
+ # InputObject are represented as Input Values which describe their type and
+ # optionally a default value.
+ type __InputValue {
+ name: String!
+ description: String
+ type: __Type!
+ # A GraphQL-formatted string representing the default value for this input value.
+ defaultValue: String
+ }
+
+ # A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all
+ # available types and directives on the server, as well as the entry points for
+ # query, mutation, and subscription operations.
+ type __Schema {
+ # A list of all types supported by this server.
+ types: [__Type!]!
+ # The type that query operations will be rooted at.
+ queryType: __Type!
+ # If this server supports mutation, the type that mutation operations will be rooted at.
+ mutationType: __Type
+ # If this server support subscription, the type that subscription operations will be rooted at.
+ subscriptionType: __Type
+ # A list of all directives supported by this server.
+ directives: [__Directive!]!
+ }
+
+ # The fundamental unit of any GraphQL Schema is the type. There are many kinds of
+ # types in GraphQL as represented by the ` + "`" + `__TypeKind` + "`" + ` enum.
+ #
+ # Depending on the kind of a type, certain fields describe information about that
+ # type. Scalar types provide no information beyond a name and description, while
+ # Enum types provide their values. Object and Interface types provide the fields
+ # they describe. Abstract types, Union and Interface, provide the Object types
+ # possible at runtime. List and NonNull types compose other types.
+ type __Type {
+ kind: __TypeKind!
+ name: String
+ description: String
+ fields(includeDeprecated: Boolean = false): [__Field!]
+ interfaces: [__Type!]
+ possibleTypes: [__Type!]
+ enumValues(includeDeprecated: Boolean = false): [__EnumValue!]
+ inputFields: [__InputValue!]
+ ofType: __Type
+ }
+
+ # An enum describing what kind of type a given ` + "`" + `__Type` + "`" + ` is.
+ enum __TypeKind {
+ # Indicates this type is a scalar.
+ SCALAR
+ # Indicates this type is an object. ` + "`" + `fields` + "`" + ` and ` + "`" + `interfaces` + "`" + ` are valid fields.
+ OBJECT
+ # Indicates this type is an interface. ` + "`" + `fields` + "`" + ` and ` + "`" + `possibleTypes` + "`" + ` are valid fields.
+ INTERFACE
+ # Indicates this type is a union. ` + "`" + `possibleTypes` + "`" + ` is a valid field.
+ UNION
+ # Indicates this type is an enum. ` + "`" + `enumValues` + "`" + ` is a valid field.
+ ENUM
+ # Indicates this type is an input object. ` + "`" + `inputFields` + "`" + ` is a valid field.
+ INPUT_OBJECT
+ # Indicates this type is a list. ` + "`" + `ofType` + "`" + ` is a valid field.
+ LIST
+ # Indicates this type is a non-null. ` + "`" + `ofType` + "`" + ` is a valid field.
+ NON_NULL
+ }
+`
diff --git a/vendor/github.com/vektah/gqlgen/neelance/schema/schema.go b/vendor/github.com/vektah/gqlgen/neelance/schema/schema.go
new file mode 100644
index 00000000..0b1317a5
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/neelance/schema/schema.go
@@ -0,0 +1,489 @@
+package schema
+
+import (
+ "fmt"
+ "strings"
+ "text/scanner"
+
+ "github.com/vektah/gqlgen/neelance/common"
+ "github.com/vektah/gqlgen/neelance/errors"
+)
+
+type Schema struct {
+ EntryPoints map[string]NamedType
+ Types map[string]NamedType
+ Directives map[string]*DirectiveDecl
+
+ entryPointNames map[string]string
+ objects []*Object
+ unions []*Union
+ enums []*Enum
+}
+
+var defaultEntrypoints = map[string]string{
+ "query": "Query",
+ "mutation": "Mutation",
+ "subscription": "Subscription",
+}
+
+func (s *Schema) Resolve(name string) common.Type {
+ return s.Types[name]
+}
+
+type NamedType interface {
+ common.Type
+ TypeName() string
+ Description() string
+}
+
+type Scalar struct {
+ Name string
+ Desc string
+}
+
+type Object struct {
+ Name string
+ Interfaces []*Interface
+ Fields FieldList
+ Desc string
+
+ interfaceNames []string
+}
+
+type Interface struct {
+ Name string
+ PossibleTypes []*Object
+ Fields FieldList
+ Desc string
+}
+
+type Union struct {
+ Name string
+ PossibleTypes []*Object
+ Desc string
+
+ typeNames []string
+}
+
+type Enum struct {
+ Name string
+ Values []*EnumValue
+ Desc string
+}
+
+type EnumValue struct {
+ Name string
+ Directives common.DirectiveList
+ Desc string
+}
+
+type InputObject struct {
+ Name string
+ Desc string
+ Values common.InputValueList
+}
+
+type FieldList []*Field
+
+func (l FieldList) Get(name string) *Field {
+ for _, f := range l {
+ if f.Name == name {
+ return f
+ }
+ }
+ return nil
+}
+
+func (l FieldList) Names() []string {
+ names := make([]string, len(l))
+ for i, f := range l {
+ names[i] = f.Name
+ }
+ return names
+}
+
+type DirectiveDecl struct {
+ Name string
+ Desc string
+ Locs []string
+ Args common.InputValueList
+}
+
+func (*Scalar) Kind() string { return "SCALAR" }
+func (*Object) Kind() string { return "OBJECT" }
+func (*Interface) Kind() string { return "INTERFACE" }
+func (*Union) Kind() string { return "UNION" }
+func (*Enum) Kind() string { return "ENUM" }
+func (*InputObject) Kind() string { return "INPUT_OBJECT" }
+
+func (t *Scalar) String() string { return t.Name }
+func (t *Object) String() string { return t.Name }
+func (t *Interface) String() string { return t.Name }
+func (t *Union) String() string { return t.Name }
+func (t *Enum) String() string { return t.Name }
+func (t *InputObject) String() string { return t.Name }
+
+func (t *Scalar) TypeName() string { return t.Name }
+func (t *Object) TypeName() string { return t.Name }
+func (t *Interface) TypeName() string { return t.Name }
+func (t *Union) TypeName() string { return t.Name }
+func (t *Enum) TypeName() string { return t.Name }
+func (t *InputObject) TypeName() string { return t.Name }
+
+func (t *Scalar) Description() string { return t.Desc }
+func (t *Object) Description() string { return t.Desc }
+func (t *Interface) Description() string { return t.Desc }
+func (t *Union) Description() string { return t.Desc }
+func (t *Enum) Description() string { return t.Desc }
+func (t *InputObject) Description() string { return t.Desc }
+
+type Field struct {
+ Name string
+ Args common.InputValueList
+ Type common.Type
+ Directives common.DirectiveList
+ Desc string
+}
+
+func MustParse(str string) *Schema {
+ s := New()
+ err := s.Parse(str)
+ if err != nil {
+ panic(err)
+ }
+ return s
+}
+
+func New() *Schema {
+ s := &Schema{
+ entryPointNames: make(map[string]string),
+ Types: make(map[string]NamedType),
+ Directives: make(map[string]*DirectiveDecl),
+ }
+ for n, t := range Meta.Types {
+ s.Types[n] = t
+ }
+ for n, d := range Meta.Directives {
+ s.Directives[n] = d
+ }
+ return s
+}
+
+func (s *Schema) Parse(schemaString string) error {
+ sc := &scanner.Scanner{
+ Mode: scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats | scanner.ScanStrings,
+ }
+ sc.Init(strings.NewReader(schemaString))
+
+ l := common.New(sc)
+ err := l.CatchSyntaxError(func() {
+ parseSchema(s, l)
+ })
+ if err != nil {
+ return err
+ }
+
+ for _, t := range s.Types {
+ if err := resolveNamedType(s, t); err != nil {
+ return err
+ }
+ }
+ for _, d := range s.Directives {
+ for _, arg := range d.Args {
+ t, err := common.ResolveType(arg.Type, s.Resolve)
+ if err != nil {
+ return err
+ }
+ arg.Type = t
+ }
+ }
+
+ s.EntryPoints = make(map[string]NamedType)
+ for key, name := range s.entryPointNames {
+ t, ok := s.Types[name]
+ if !ok {
+ if !ok {
+ return errors.Errorf("type %q not found", name)
+ }
+ }
+ s.EntryPoints[key] = t
+ }
+
+ for entrypointName, typeName := range defaultEntrypoints {
+ if _, ok := s.EntryPoints[entrypointName]; ok {
+ continue
+ }
+
+ if _, ok := s.Types[typeName]; !ok {
+ continue
+ }
+
+ s.EntryPoints[entrypointName] = s.Types[typeName]
+ }
+
+ for _, obj := range s.objects {
+ obj.Interfaces = make([]*Interface, len(obj.interfaceNames))
+ for i, intfName := range obj.interfaceNames {
+ t, ok := s.Types[intfName]
+ if !ok {
+ return errors.Errorf("interface %q not found", intfName)
+ }
+ intf, ok := t.(*Interface)
+ if !ok {
+ return errors.Errorf("type %q is not an interface", intfName)
+ }
+ obj.Interfaces[i] = intf
+ intf.PossibleTypes = append(intf.PossibleTypes, obj)
+ }
+ }
+
+ for _, union := range s.unions {
+ union.PossibleTypes = make([]*Object, len(union.typeNames))
+ for i, name := range union.typeNames {
+ t, ok := s.Types[name]
+ if !ok {
+ return errors.Errorf("object type %q not found", name)
+ }
+ obj, ok := t.(*Object)
+ if !ok {
+ return errors.Errorf("type %q is not an object", name)
+ }
+ union.PossibleTypes[i] = obj
+ }
+ }
+
+ for _, enum := range s.enums {
+ for _, value := range enum.Values {
+ if err := resolveDirectives(s, value.Directives); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}
+
+func resolveNamedType(s *Schema, t NamedType) error {
+ switch t := t.(type) {
+ case *Object:
+ for _, f := range t.Fields {
+ if err := resolveField(s, f); err != nil {
+ return err
+ }
+ }
+ case *Interface:
+ for _, f := range t.Fields {
+ if err := resolveField(s, f); err != nil {
+ return err
+ }
+ }
+ case *InputObject:
+ if err := resolveInputObject(s, t.Values); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func resolveField(s *Schema, f *Field) error {
+ t, err := common.ResolveType(f.Type, s.Resolve)
+ if err != nil {
+ return err
+ }
+ f.Type = t
+ if err := resolveDirectives(s, f.Directives); err != nil {
+ return err
+ }
+ return resolveInputObject(s, f.Args)
+}
+
+func resolveDirectives(s *Schema, directives common.DirectiveList) error {
+ for _, d := range directives {
+ dirName := d.Name.Name
+ dd, ok := s.Directives[dirName]
+ if !ok {
+ return errors.Errorf("directive %q not found", dirName)
+ }
+ for _, arg := range d.Args {
+ if dd.Args.Get(arg.Name.Name) == nil {
+ return errors.Errorf("invalid argument %q for directive %q", arg.Name.Name, dirName)
+ }
+ }
+ for _, arg := range dd.Args {
+ if _, ok := d.Args.Get(arg.Name.Name); !ok {
+ d.Args = append(d.Args, common.Argument{Name: arg.Name, Value: arg.Default})
+ }
+ }
+ }
+ return nil
+}
+
+func resolveInputObject(s *Schema, values common.InputValueList) error {
+ for _, v := range values {
+ t, err := common.ResolveType(v.Type, s.Resolve)
+ if err != nil {
+ return err
+ }
+ v.Type = t
+ }
+ return nil
+}
+
+func parseSchema(s *Schema, l *common.Lexer) {
+ for l.Peek() != scanner.EOF {
+ desc := l.DescComment()
+ switch x := l.ConsumeIdent(); x {
+ case "schema":
+ l.ConsumeToken('{')
+ for l.Peek() != '}' {
+ name := l.ConsumeIdent()
+ l.ConsumeToken(':')
+ typ := l.ConsumeIdent()
+ s.entryPointNames[name] = typ
+ }
+ l.ConsumeToken('}')
+ case "type":
+ obj := parseObjectDecl(l)
+ obj.Desc = desc
+ s.Types[obj.Name] = obj
+ s.objects = append(s.objects, obj)
+ case "interface":
+ intf := parseInterfaceDecl(l)
+ intf.Desc = desc
+ s.Types[intf.Name] = intf
+ case "union":
+ union := parseUnionDecl(l)
+ union.Desc = desc
+ s.Types[union.Name] = union
+ s.unions = append(s.unions, union)
+ case "enum":
+ enum := parseEnumDecl(l)
+ enum.Desc = desc
+ s.Types[enum.Name] = enum
+ s.enums = append(s.enums, enum)
+ case "input":
+ input := parseInputDecl(l)
+ input.Desc = desc
+ s.Types[input.Name] = input
+ case "scalar":
+ name := l.ConsumeIdent()
+ s.Types[name] = &Scalar{Name: name, Desc: desc}
+ case "directive":
+ directive := parseDirectiveDecl(l)
+ directive.Desc = desc
+ s.Directives[directive.Name] = directive
+ default:
+ l.SyntaxError(fmt.Sprintf(`unexpected %q, expecting "schema", "type", "enum", "interface", "union", "input", "scalar" or "directive"`, x))
+ }
+ }
+}
+
+func parseObjectDecl(l *common.Lexer) *Object {
+ o := &Object{}
+ o.Name = l.ConsumeIdent()
+ if l.Peek() == scanner.Ident {
+ l.ConsumeKeyword("implements")
+ for {
+ o.interfaceNames = append(o.interfaceNames, l.ConsumeIdent())
+ if l.Peek() == '{' {
+ break
+ }
+ }
+ }
+ l.ConsumeToken('{')
+ o.Fields = parseFields(l)
+ l.ConsumeToken('}')
+ return o
+}
+
+func parseInterfaceDecl(l *common.Lexer) *Interface {
+ i := &Interface{}
+ i.Name = l.ConsumeIdent()
+ l.ConsumeToken('{')
+ i.Fields = parseFields(l)
+ l.ConsumeToken('}')
+ return i
+}
+
+func parseUnionDecl(l *common.Lexer) *Union {
+ union := &Union{}
+ union.Name = l.ConsumeIdent()
+ l.ConsumeToken('=')
+ union.typeNames = []string{l.ConsumeIdent()}
+ for l.Peek() == '|' {
+ l.ConsumeToken('|')
+ union.typeNames = append(union.typeNames, l.ConsumeIdent())
+ }
+ return union
+}
+
+func parseInputDecl(l *common.Lexer) *InputObject {
+ i := &InputObject{}
+ i.Name = l.ConsumeIdent()
+ l.ConsumeToken('{')
+ for l.Peek() != '}' {
+ i.Values = append(i.Values, common.ParseInputValue(l))
+ }
+ l.ConsumeToken('}')
+ return i
+}
+
+func parseEnumDecl(l *common.Lexer) *Enum {
+ enum := &Enum{}
+ enum.Name = l.ConsumeIdent()
+ l.ConsumeToken('{')
+ for l.Peek() != '}' {
+ v := &EnumValue{}
+ v.Desc = l.DescComment()
+ v.Name = l.ConsumeIdent()
+ v.Directives = common.ParseDirectives(l)
+ enum.Values = append(enum.Values, v)
+ }
+ l.ConsumeToken('}')
+ return enum
+}
+
+func parseDirectiveDecl(l *common.Lexer) *DirectiveDecl {
+ d := &DirectiveDecl{}
+ l.ConsumeToken('@')
+ d.Name = l.ConsumeIdent()
+ if l.Peek() == '(' {
+ l.ConsumeToken('(')
+ for l.Peek() != ')' {
+ v := common.ParseInputValue(l)
+ d.Args = append(d.Args, v)
+ }
+ l.ConsumeToken(')')
+ }
+ l.ConsumeKeyword("on")
+ for {
+ loc := l.ConsumeIdent()
+ d.Locs = append(d.Locs, loc)
+ if l.Peek() != '|' {
+ break
+ }
+ l.ConsumeToken('|')
+ }
+ return d
+}
+
+func parseFields(l *common.Lexer) FieldList {
+ var fields FieldList
+ for l.Peek() != '}' {
+ f := &Field{}
+ f.Desc = l.DescComment()
+ f.Name = l.ConsumeIdent()
+ if l.Peek() == '(' {
+ l.ConsumeToken('(')
+ for l.Peek() != ')' {
+ f.Args = append(f.Args, common.ParseInputValue(l))
+ }
+ l.ConsumeToken(')')
+ }
+ l.ConsumeToken(':')
+ f.Type = common.ParseType(l)
+ f.Directives = common.ParseDirectives(l)
+ fields = append(fields, f)
+ }
+ return fields
+}
diff --git a/vendor/github.com/vektah/gqlgen/neelance/tests/testdata/LICENSE b/vendor/github.com/vektah/gqlgen/neelance/tests/testdata/LICENSE
new file mode 100644
index 00000000..fce4519e
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/neelance/tests/testdata/LICENSE
@@ -0,0 +1,33 @@
+The files in this testdata directory are derived from the graphql-js project:
+https://github.com/graphql/graphql-js
+
+BSD License
+
+For GraphQL software
+
+Copyright (c) 2015, Facebook, Inc. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name Facebook nor the names of its contributors may be used to
+ endorse or promote products derived from this software without specific
+ prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
diff --git a/vendor/github.com/vektah/gqlgen/neelance/validation/suggestion.go b/vendor/github.com/vektah/gqlgen/neelance/validation/suggestion.go
new file mode 100644
index 00000000..9702b5f5
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/neelance/validation/suggestion.go
@@ -0,0 +1,71 @@
+package validation
+
+import (
+ "fmt"
+ "sort"
+ "strconv"
+ "strings"
+)
+
+func makeSuggestion(prefix string, options []string, input string) string {
+ var selected []string
+ distances := make(map[string]int)
+ for _, opt := range options {
+ distance := levenshteinDistance(input, opt)
+ threshold := max(len(input)/2, max(len(opt)/2, 1))
+ if distance < threshold {
+ selected = append(selected, opt)
+ distances[opt] = distance
+ }
+ }
+
+ if len(selected) == 0 {
+ return ""
+ }
+ sort.Slice(selected, func(i, j int) bool {
+ return distances[selected[i]] < distances[selected[j]]
+ })
+
+ parts := make([]string, len(selected))
+ for i, opt := range selected {
+ parts[i] = strconv.Quote(opt)
+ }
+ if len(parts) > 1 {
+ parts[len(parts)-1] = "or " + parts[len(parts)-1]
+ }
+ return fmt.Sprintf(" %s %s?", prefix, strings.Join(parts, ", "))
+}
+
+func levenshteinDistance(s1, s2 string) int {
+ column := make([]int, len(s1)+1)
+ for y := range s1 {
+ column[y+1] = y + 1
+ }
+ for x, rx := range s2 {
+ column[0] = x + 1
+ lastdiag := x
+ for y, ry := range s1 {
+ olddiag := column[y+1]
+ if rx != ry {
+ lastdiag++
+ }
+ column[y+1] = min(column[y+1]+1, min(column[y]+1, lastdiag))
+ lastdiag = olddiag
+ }
+ }
+ return column[len(s1)]
+}
+
+func min(a, b int) int {
+ if a < b {
+ return a
+ }
+ return b
+}
+
+func max(a, b int) int {
+ if a > b {
+ return a
+ }
+ return b
+}
diff --git a/vendor/github.com/vektah/gqlgen/neelance/validation/validation.go b/vendor/github.com/vektah/gqlgen/neelance/validation/validation.go
new file mode 100644
index 00000000..28124310
--- /dev/null
+++ b/vendor/github.com/vektah/gqlgen/neelance/validation/validation.go
@@ -0,0 +1,861 @@
+package validation
+
+import (
+ "fmt"
+ "math"
+ "reflect"
+ "strconv"
+ "strings"
+ "text/scanner"
+
+ "github.com/vektah/gqlgen/neelance/common"
+ "github.com/vektah/gqlgen/neelance/errors"
+ "github.com/vektah/gqlgen/neelance/query"
+ "github.com/vektah/gqlgen/neelance/schema"
+)
+
+type varSet map[*common.InputValue]struct{}
+
+type selectionPair struct{ a, b query.Selection }
+
+type fieldInfo struct {
+ sf *schema.Field
+ parent schema.NamedType
+}
+
+type context struct {
+ schema *schema.Schema
+ doc *query.Document
+ errs []*errors.QueryError
+ opErrs map[*query.Operation][]*errors.QueryError
+ usedVars map[*query.Operation]varSet
+ fieldMap map[*query.Field]fieldInfo
+ overlapValidated map[selectionPair]struct{}
+}
+
+func (c *context) addErr(loc errors.Location, rule string, format string, a ...interface{}) {
+ c.addErrMultiLoc([]errors.Location{loc}, rule, format, a...)
+}
+
+func (c *context) addErrMultiLoc(locs []errors.Location, rule string, format string, a ...interface{}) {
+ c.errs = append(c.errs, &errors.QueryError{
+ Message: fmt.Sprintf(format, a...),
+ Locations: locs,
+ Rule: rule,
+ })
+}
+
+type opContext struct {
+ *context
+ ops []*query.Operation
+}
+
+func Validate(s *schema.Schema, doc *query.Document) []*errors.QueryError {
+ c := &context{
+ schema: s,
+ doc: doc,
+ opErrs: make(map[*query.Operation][]*errors.QueryError),
+ usedVars: make(map[*query.Operation]varSet),
+ fieldMap: make(map[*query.Field]fieldInfo),
+ overlapValidated: make(map[selectionPair]struct{}),
+ }
+
+ opNames := make(nameSet)
+ fragUsedBy := make(map[*query.FragmentDecl][]*query.Operation)
+ for _, op := range doc.Operations {
+ c.usedVars[op] = make(varSet)
+ opc := &opContext{c, []*query.Operation{op}}
+
+ if op.Name.Name == "" && len(doc.Operations) != 1 {
+ c.addErr(op.Loc, "LoneAnonymousOperation", "This anonymous operation must be the only defined operation.")
+ }
+ if op.Name.Name != "" {
+ validateName(c, opNames, op.Name, "UniqueOperationNames", "operation")
+ }
+
+ validateDirectives(opc, string(op.Type), op.Directives)
+
+ varNames := make(nameSet)
+ for _, v := range op.Vars {
+ validateName(c, varNames, v.Name, "UniqueVariableNames", "variable")
+
+ t := resolveType(c, v.Type)
+ if !canBeInput(t) {
+ c.addErr(v.TypeLoc, "VariablesAreInputTypes", "Variable %q cannot be non-input type %q.", "$"+v.Name.Name, t)
+ }
+
+ if v.Default != nil {
+ validateLiteral(opc, v.Default)
+
+ if t != nil {
+ if nn, ok := t.(*common.NonNull); ok {
+ c.addErr(v.Default.Location(), "DefaultValuesOfCorrectType", "Variable %q of type %q is required and will not use the default value. Perhaps you meant to use type %q.", "$"+v.Name.Name, t, nn.OfType)
+ }
+
+ if ok, reason := validateValueType(opc, v.Default, t); !ok {
+ c.addErr(v.Default.Location(), "DefaultValuesOfCorrectType", "Variable %q of type %q has invalid default value %s.\n%s", "$"+v.Name.Name, t, v.Default, reason)
+ }
+ }
+ }
+ }
+
+ var entryPoint schema.NamedType
+ switch op.Type {
+ case query.Query:
+ entryPoint = s.EntryPoints["query"]
+ case query.Mutation:
+ entryPoint = s.EntryPoints["mutation"]
+ case query.Subscription:
+ entryPoint = s.EntryPoints["subscription"]
+ default:
+ panic("unreachable")
+ }
+
+ validateSelectionSet(opc, op.Selections, entryPoint)
+
+ fragUsed := make(map[*query.FragmentDecl]struct{})
+ markUsedFragments(c, op.Selections, fragUsed)
+ for frag := range fragUsed {
+ fragUsedBy[frag] = append(fragUsedBy[frag], op)
+ }
+ }
+
+ fragNames := make(nameSet)
+ fragVisited := make(map[*query.FragmentDecl]struct{})
+ for _, frag := range doc.Fragments {
+ opc := &opContext{c, fragUsedBy[frag]}
+
+ validateName(c, fragNames, frag.Name, "UniqueFragmentNames", "fragment")
+ validateDirectives(opc, "FRAGMENT_DEFINITION", frag.Directives)
+
+ t := unwrapType(resolveType(c, &frag.On))
+ // continue even if t is nil
+ if t != nil && !canBeFragment(t) {
+ c.addErr(frag.On.Loc, "FragmentsOnCompositeTypes", "Fragment %q cannot condition on non composite type %q.", frag.Name.Name, t)
+ continue
+ }
+
+ validateSelectionSet(opc, frag.Selections, t)
+
+ if _, ok := fragVisited[frag]; !ok {
+ detectFragmentCycle(c, frag.Selections, fragVisited, nil, map[string]int{frag.Name.Name: 0})
+ }
+ }
+
+ for _, frag := range doc.Fragments {
+ if len(fragUsedBy[frag]) == 0 {
+ c.addErr(frag.Loc, "NoUnusedFragments", "Fragment %q is never used.", frag.Name.Name)
+ }
+ }
+
+ for _, op := range doc.Operations {
+ c.errs = append(c.errs, c.opErrs[op]...)
+
+ opUsedVars := c.usedVars[op]
+ for _, v := range op.Vars {
+ if _, ok := opUsedVars[v]; !ok {
+ opSuffix := ""
+ if op.Name.Name != "" {
+ opSuffix = fmt.Sprintf(" in operation %q", op.Name.Name)
+ }
+ c.addErr(v.Loc, "NoUnusedVariables", "Variable %q is never used%s.", "$"+v.Name.Name, opSuffix)
+ }
+ }
+ }
+
+ return c.errs
+}
+
+func validateSelectionSet(c *opContext, sels []query.Selection, t schema.NamedType) {
+ for _, sel := range sels {
+ validateSelection(c, sel, t)
+ }
+
+ for i, a := range sels {
+ for _, b := range sels[i+1:] {
+ c.validateOverlap(a, b, nil, nil)
+ }
+ }
+}
+
+func validateSelection(c *opContext, sel query.Selection, t schema.NamedType) {
+ switch sel := sel.(type) {
+ case *query.Field:
+ validateDirectives(c, "FIELD", sel.Directives)
+
+ fieldName := sel.Name.Name
+ var f *schema.Field
+ switch fieldName {
+ case "__typename":
+ f = &schema.Field{
+ Name: "__typename",
+ Type: c.schema.Types["String"],
+ }
+ case "__schema":
+ f = &schema.Field{
+ Name: "__schema",
+ Type: c.schema.Types["__Schema"],
+ }
+ case "__type":
+ f = &schema.Field{
+ Name: "__type",
+ Args: common.InputValueList{
+ &common.InputValue{
+ Name: common.Ident{Name: "name"},
+ Type: &common.NonNull{OfType: c.schema.Types["String"]},
+ },
+ },
+ Type: c.schema.Types["__Type"],
+ }
+ default:
+ f = fields(t).Get(fieldName)
+ if f == nil && t != nil {
+ suggestion := makeSuggestion("Did you mean", fields(t).Names(), fieldName)
+ c.addErr(sel.Alias.Loc, "FieldsOnCorrectType", "Cannot query field %q on type %q.%s", fieldName, t, suggestion)
+ }
+ }
+ c.fieldMap[sel] = fieldInfo{sf: f, parent: t}
+
+ validateArgumentLiterals(c, sel.Arguments)
+ if f != nil {
+ validateArgumentTypes(c, sel.Arguments, f.Args, sel.Alias.Loc,
+ func() string { return fmt.Sprintf("field %q of type %q", fieldName, t) },
+ func() string { return fmt.Sprintf("Field %q", fieldName) },
+ )
+ }
+
+ var ft common.Type
+ if f != nil {
+ ft = f.Type
+ sf := hasSubfields(ft)
+ if sf && sel.Selections == nil {
+ c.addErr(sel.Alias.Loc, "ScalarLeafs", "Field %q of type %q must have a selection of subfields. Did you mean \"%s { ... }\"?", fieldName, ft, fieldName)
+ }
+ if !sf && sel.Selections != nil {
+ c.addErr(sel.SelectionSetLoc, "ScalarLeafs", "Field %q must not have a selection since type %q has no subfields.", fieldName, ft)
+ }
+ }
+ if sel.Selections != nil {
+ validateSelectionSet(c, sel.Selections, unwrapType(ft))
+ }
+
+ case *query.InlineFragment:
+ validateDirectives(c, "INLINE_FRAGMENT", sel.Directives)
+ if sel.On.Name != "" {
+ fragTyp := unwrapType(resolveType(c.context, &sel.On))
+ if fragTyp != nil && !compatible(t, fragTyp) {
+ c.addErr(sel.Loc, "PossibleFragmentSpreads", "Fragment cannot be spread here as objects of type %q can never be of type %q.", t, fragTyp)
+ }
+ t = fragTyp
+ // continue even if t is nil
+ }
+ if t != nil && !canBeFragment(t) {
+ c.addErr(sel.On.Loc, "FragmentsOnCompositeTypes", "Fragment cannot condition on non composite type %q.", t)
+ return
+ }
+ validateSelectionSet(c, sel.Selections, unwrapType(t))
+
+ case *query.FragmentSpread:
+ validateDirectives(c, "FRAGMENT_SPREAD", sel.Directives)
+ frag := c.doc.Fragments.Get(sel.Name.Name)
+ if frag == nil {
+ c.addErr(sel.Name.Loc, "KnownFragmentNames", "Unknown fragment %q.", sel.Name.Name)
+ return
+ }
+ fragTyp := c.schema.Types[frag.On.Name]
+ if !compatible(t, fragTyp) {
+ c.addErr(sel.Loc, "PossibleFragmentSpreads", "Fragment %q cannot be spread here as objects of type %q can never be of type %q.", frag.Name.Name, t, fragTyp)
+ }
+
+ default:
+ panic("unreachable")
+ }
+}
+
+func compatible(a, b common.Type) bool {
+ for _, pta := range possibleTypes(a) {
+ for _, ptb := range possibleTypes(b) {
+ if pta == ptb {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+func possibleTypes(t common.Type) []*schema.Object {
+ switch t := t.(type) {
+ case *schema.Object:
+ return []*schema.Object{t}
+ case *schema.Interface:
+ return t.PossibleTypes
+ case *schema.Union:
+ return t.PossibleTypes
+ default:
+ return nil
+ }
+}
+
+func markUsedFragments(c *context, sels []query.Selection, fragUsed map[*query.FragmentDecl]struct{}) {
+ for _, sel := range sels {
+ switch sel := sel.(type) {
+ case *query.Field:
+ if sel.Selections != nil {
+ markUsedFragments(c, sel.Selections, fragUsed)
+ }
+
+ case *query.InlineFragment:
+ markUsedFragments(c, sel.Selections, fragUsed)
+
+ case *query.FragmentSpread:
+ frag := c.doc.Fragments.Get(sel.Name.Name)
+ if frag == nil {
+ return
+ }
+
+ if _, ok := fragUsed[frag]; ok {
+ return
+ }
+ fragUsed[frag] = struct{}{}
+ markUsedFragments(c, frag.Selections, fragUsed)
+
+ default:
+ panic("unreachable")
+ }
+ }
+}
+
+func detectFragmentCycle(c *context, sels []query.Selection, fragVisited map[*query.FragmentDecl]struct{}, spreadPath []*query.FragmentSpread, spreadPathIndex map[string]int) {
+ for _, sel := range sels {
+ detectFragmentCycleSel(c, sel, fragVisited, spreadPath, spreadPathIndex)
+ }
+}
+
+func detectFragmentCycleSel(c *context, sel query.Selection, fragVisited map[*query.FragmentDecl]struct{}, spreadPath []*query.FragmentSpread, spreadPathIndex map[string]int) {
+ switch sel := sel.(type) {
+ case *query.Field:
+ if sel.Selections != nil {
+ detectFragmentCycle(c, sel.Selections, fragVisited, spreadPath, spreadPathIndex)
+ }
+
+ case *query.InlineFragment:
+ detectFragmentCycle(c, sel.Selections, fragVisited, spreadPath, spreadPathIndex)
+
+ case *query.FragmentSpread:
+ frag := c.doc.Fragments.Get(sel.Name.Name)
+ if frag == nil {
+ return
+ }
+
+ spreadPath = append(spreadPath, sel)
+ if i, ok := spreadPathIndex[frag.Name.Name]; ok {
+ cyclePath := spreadPath[i:]
+ via := ""
+ if len(cyclePath) > 1 {
+ names := make([]string, len(cyclePath)-1)
+ for i, frag := range cyclePath[:len(cyclePath)-1] {
+ names[i] = frag.Name.Name
+ }
+ via = " via " + strings.Join(names, ", ")
+ }
+
+ locs := make([]errors.Location, len(cyclePath))
+ for i, frag := range cyclePath {
+ locs[i] = frag.Loc
+ }
+ c.addErrMultiLoc(locs, "NoFragmentCycles", "Cannot spread fragment %q within itself%s.", frag.Name.Name, via)
+ return
+ }
+
+ if _, ok := fragVisited[frag]; ok {
+ return
+ }
+ fragVisited[frag] = struct{}{}
+
+ spreadPathIndex[frag.Name.Name] = len(spreadPath)
+ detectFragmentCycle(c, frag.Selections, fragVisited, spreadPath, spreadPathIndex)
+ delete(spreadPathIndex, frag.Name.Name)
+
+ default:
+ panic("unreachable")
+ }
+}
+
+func (c *context) validateOverlap(a, b query.Selection, reasons *[]string, locs *[]errors.Location) {
+ if a == b {
+ return
+ }
+
+ if _, ok := c.overlapValidated[selectionPair{a, b}]; ok {
+ return
+ }
+ c.overlapValidated[selectionPair{a, b}] = struct{}{}
+ c.overlapValidated[selectionPair{b, a}] = struct{}{}
+
+ switch a := a.(type) {
+ case *query.Field:
+ switch b := b.(type) {
+ case *query.Field:
+ if b.Alias.Loc.Before(a.Alias.Loc) {
+ a, b = b, a
+ }
+ if reasons2, locs2 := c.validateFieldOverlap(a, b); len(reasons2) != 0 {
+ locs2 = append(locs2, a.Alias.Loc, b.Alias.Loc)
+ if reasons == nil {
+ c.addErrMultiLoc(locs2, "OverlappingFieldsCanBeMerged", "Fields %q conflict because %s. Use different aliases on the fields to fetch both if this was intentional.", a.Alias.Name, strings.Join(reasons2, " and "))
+ return
+ }
+ for _, r := range reasons2 {
+ *reasons = append(*reasons, fmt.Sprintf("subfields %q conflict because %s", a.Alias.Name, r))
+ }
+ *locs = append(*locs, locs2...)
+ }
+
+ case *query.InlineFragment:
+ for _, sel := range b.Selections {
+ c.validateOverlap(a, sel, reasons, locs)
+ }
+
+ case *query.FragmentSpread:
+ if frag := c.doc.Fragments.Get(b.Name.Name); frag != nil {
+ for _, sel := range frag.Selections {
+ c.validateOverlap(a, sel, reasons, locs)
+ }
+ }
+
+ default:
+ panic("unreachable")
+ }
+
+ case *query.InlineFragment:
+ for _, sel := range a.Selections {
+ c.validateOverlap(sel, b, reasons, locs)
+ }
+
+ case *query.FragmentSpread:
+ if frag := c.doc.Fragments.Get(a.Name.Name); frag != nil {
+ for _, sel := range frag.Selections {
+ c.validateOverlap(sel, b, reasons, locs)
+ }
+ }
+
+ default:
+ panic("unreachable")
+ }
+}
+
+func (c *context) validateFieldOverlap(a, b *query.Field) ([]string, []errors.Location) {
+ if a.Alias.Name != b.Alias.Name {
+ return nil, nil
+ }
+
+ if asf := c.fieldMap[a].sf; asf != nil {
+ if bsf := c.fieldMap[b].sf; bsf != nil {
+ if !typesCompatible(asf.Type, bsf.Type) {
+ return []string{fmt.Sprintf("they return conflicting types %s and %s", asf.Type, bsf.Type)}, nil
+ }
+ }
+ }
+
+ at := c.fieldMap[a].parent
+ bt := c.fieldMap[b].parent
+ if at == nil || bt == nil || at == bt {
+ if a.Name.Name != b.Name.Name {
+ return []string{fmt.Sprintf("%s and %s are different fields", a.Name.Name, b.Name.Name)}, nil
+ }
+
+ if argumentsConflict(a.Arguments, b.Arguments) {
+ return []string{"they have differing arguments"}, nil
+ }
+ }
+
+ var reasons []string
+ var locs []errors.Location
+ for _, a2 := range a.Selections {
+ for _, b2 := range b.Selections {
+ c.validateOverlap(a2, b2, &reasons, &locs)
+ }
+ }
+ return reasons, locs
+}
+
+func argumentsConflict(a, b common.ArgumentList) bool {
+ if len(a) != len(b) {
+ return true
+ }
+ for _, argA := range a {
+ valB, ok := b.Get(argA.Name.Name)
+ if !ok || !reflect.DeepEqual(argA.Value.Value(nil), valB.Value(nil)) {
+ return true
+ }
+ }
+ return false
+}
+
+func fields(t common.Type) schema.FieldList {
+ switch t := t.(type) {
+ case *schema.Object:
+ return t.Fields
+ case *schema.Interface:
+ return t.Fields
+ default:
+ return nil
+ }
+}
+
+func unwrapType(t common.Type) schema.NamedType {
+ if t == nil {
+ return nil
+ }
+ for {
+ switch t2 := t.(type) {
+ case schema.NamedType:
+ return t2
+ case *common.List:
+ t = t2.OfType
+ case *common.NonNull:
+ t = t2.OfType
+ default:
+ panic("unreachable")
+ }
+ }
+}
+
+func resolveType(c *context, t common.Type) common.Type {
+ t2, err := common.ResolveType(t, c.schema.Resolve)
+ if err != nil {
+ c.errs = append(c.errs, err)
+ }
+ return t2
+}
+
+func validateDirectives(c *opContext, loc string, directives common.DirectiveList) {
+ directiveNames := make(nameSet)
+ for _, d := range directives {
+ dirName := d.Name.Name
+ validateNameCustomMsg(c.context, directiveNames, d.Name, "UniqueDirectivesPerLocation", func() string {
+ return fmt.Sprintf("The directive %q can only be used once at this location.", dirName)
+ })
+
+ validateArgumentLiterals(c, d.Args)
+
+ dd, ok := c.schema.Directives[dirName]
+ if !ok {
+ c.addErr(d.Name.Loc, "KnownDirectives", "Unknown directive %q.", dirName)
+ continue
+ }
+
+ locOK := false
+ for _, allowedLoc := range dd.Locs {
+ if loc == allowedLoc {
+ locOK = true
+ break
+ }
+ }
+ if !locOK {
+ c.addErr(d.Name.Loc, "KnownDirectives", "Directive %q may not be used on %s.", dirName, loc)
+ }
+
+ validateArgumentTypes(c, d.Args, dd.Args, d.Name.Loc,
+ func() string { return fmt.Sprintf("directive %q", "@"+dirName) },
+ func() string { return fmt.Sprintf("Directive %q", "@"+dirName) },
+ )
+ }
+}
+
+type nameSet map[string]errors.Location
+
+func validateName(c *context, set nameSet, name common.Ident, rule string, kind string) {
+ validateNameCustomMsg(c, set, name, rule, func() string {
+ return fmt.Sprintf("There can be only one %s named %q.", kind, name.Name)
+ })
+}
+
+func validateNameCustomMsg(c *context, set nameSet, name common.Ident, rule string, msg func() string) {
+ if loc, ok := set[name.Name]; ok {
+ c.addErrMultiLoc([]errors.Location{loc, name.Loc}, rule, msg())
+ return
+ }
+ set[name.Name] = name.Loc
+}
+
+func validateArgumentTypes(c *opContext, args common.ArgumentList, argDecls common.InputValueList, loc errors.Location, owner1, owner2 func() string) {
+ for _, selArg := range args {
+ arg := argDecls.Get(selArg.Name.Name)
+ if arg == nil {
+ c.addErr(selArg.Name.Loc, "KnownArgumentNames", "Unknown argument %q on %s.", selArg.Name.Name, owner1())
+ continue
+ }
+ value := selArg.Value
+ if ok, reason := validateValueType(c, value, arg.Type); !ok {
+ c.addErr(value.Location(), "ArgumentsOfCorrectType", "Argument %q has invalid value %s.\n%s", arg.Name.Name, value, reason)
+ }
+ }
+ for _, decl := range argDecls {
+ if _, ok := decl.Type.(*common.NonNull); ok {
+ if _, ok := args.Get(decl.Name.Name); !ok {
+ c.addErr(loc, "ProvidedNonNullArguments", "%s argument %q of type %q is required but not provided.", owner2(), decl.Name.Name, decl.Type)
+ }
+ }
+ }
+}
+
+func validateArgumentLiterals(c *opContext, args common.ArgumentList) {
+ argNames := make(nameSet)
+ for _, arg := range args {
+ validateName(c.context, argNames, arg.Name, "UniqueArgumentNames", "argument")
+ validateLiteral(c, arg.Value)
+ }
+}
+
+func validateLiteral(c *opContext, l common.Literal) {
+ switch l := l.(type) {
+ case *common.ObjectLit:
+ fieldNames := make(nameSet)
+ for _, f := range l.Fields {
+ validateName(c.context, fieldNames, f.Name, "UniqueInputFieldNames", "input field")
+ validateLiteral(c, f.Value)
+ }
+ case *common.ListLit:
+ for _, entry := range l.Entries {
+ validateLiteral(c, entry)
+ }
+ case *common.Variable:
+ for _, op := range c.ops {
+ v := op.Vars.Get(l.Name)
+ if v == nil {
+ byOp := ""
+ if op.Name.Name != "" {
+ byOp = fmt.Sprintf(" by operation %q", op.Name.Name)
+ }
+ c.opErrs[op] = append(c.opErrs[op], &errors.QueryError{
+ Message: fmt.Sprintf("Variable %q is not defined%s.", "$"+l.Name, byOp),
+ Locations: []errors.Location{l.Loc, op.Loc},
+ Rule: "NoUndefinedVariables",
+ })
+ continue
+ }
+ c.usedVars[op][v] = struct{}{}
+ }
+ }
+}
+
+func validateValueType(c *opContext, v common.Literal, t common.Type) (bool, string) {
+ if v, ok := v.(*common.Variable); ok {
+ for _, op := range c.ops {
+ if v2 := op.Vars.Get(v.Name); v2 != nil {
+ t2, err := common.ResolveType(v2.Type, c.schema.Resolve)
+ if _, ok := t2.(*common.NonNull); !ok && v2.Default != nil {
+ t2 = &common.NonNull{OfType: t2}
+ }
+ if err == nil && !typeCanBeUsedAs(t2, t) {
+ c.addErrMultiLoc([]errors.Location{v2.Loc, v.Loc}, "VariablesInAllowedPosition", "Variable %q of type %q used in position expecting type %q.", "$"+v.Name, t2, t)
+ }
+ }
+ }
+ return true, ""
+ }
+
+ if nn, ok := t.(*common.NonNull); ok {
+ if isNull(v) {
+ return false, fmt.Sprintf("Expected %q, found null.", t)
+ }
+ t = nn.OfType
+ }
+ if isNull(v) {
+ return true, ""
+ }
+
+ switch t := t.(type) {
+ case *schema.Scalar, *schema.Enum:
+ if lit, ok := v.(*common.BasicLit); ok {
+ if validateBasicLit(lit, t) {
+ return true, ""
+ }
+ } else {
+ // custom complex scalars will be validated when unmarshaling
+ return true, ""
+ }
+
+ case *common.List:
+ list, ok := v.(*common.ListLit)
+ if !ok {
+ return validateValueType(c, v, t.OfType) // single value instead of list
+ }
+ for i, entry := range list.Entries {
+ if ok, reason := validateValueType(c, entry, t.OfType); !ok {
+ return false, fmt.Sprintf("In element #%d: %s", i, reason)
+ }
+ }
+ return true, ""
+
+ case *schema.InputObject:
+ v, ok := v.(*common.ObjectLit)
+ if !ok {
+ return false, fmt.Sprintf("Expected %q, found not an object.", t)
+ }
+ for _, f := range v.Fields {
+ name := f.Name.Name
+ iv := t.Values.Get(name)
+ if iv == nil {
+ return false, fmt.Sprintf("In field %q: Unknown field.", name)
+ }
+ if ok, reason := validateValueType(c, f.Value, iv.Type); !ok {
+ return false, fmt.Sprintf("In field %q: %s", name, reason)
+ }
+ }
+ for _, iv := range t.Values {
+ found := false
+ for _, f := range v.Fields {
+ if f.Name.Name == iv.Name.Name {
+ found = true
+ break
+ }
+ }
+ if !found {
+ if _, ok := iv.Type.(*common.NonNull); ok && iv.Default == nil {
+ return false, fmt.Sprintf("In field %q: Expected %q, found null.", iv.Name.Name, iv.Type)
+ }
+ }
+ }
+ return true, ""
+ }
+
+ return false, fmt.Sprintf("Expected type %q, found %s.", t, v)
+}
+
+func validateBasicLit(v *common.BasicLit, t common.Type) bool {
+ switch t := t.(type) {
+ case *schema.Scalar:
+ switch t.Name {
+ case "Int":
+ if v.Type != scanner.Int {
+ return false
+ }
+ f, err := strconv.ParseFloat(v.Text, 64)
+ if err != nil {
+ panic(err)
+ }
+ return f >= math.MinInt32 && f <= math.MaxInt32
+ case "Float":
+ return v.Type == scanner.Int || v.Type == scanner.Float
+ case "String":
+ return v.Type == scanner.String
+ case "Boolean":
+ return v.Type == scanner.Ident && (v.Text == "true" || v.Text == "false")
+ case "ID":
+ return v.Type == scanner.Int || v.Type == scanner.String
+ default:
+ //TODO: Type-check against expected type by Unmarshaling
+ return true
+ }
+
+ case *schema.Enum:
+ if v.Type != scanner.Ident {
+ return false
+ }
+ for _, option := range t.Values {
+ if option.Name == v.Text {
+ return true
+ }
+ }
+ return false
+ }
+
+ return false
+}
+
+func canBeFragment(t common.Type) bool {
+ switch t.(type) {
+ case *schema.Object, *schema.Interface, *schema.Union:
+ return true
+ default:
+ return false
+ }
+}
+
+func canBeInput(t common.Type) bool {
+ switch t := t.(type) {
+ case *schema.InputObject, *schema.Scalar, *schema.Enum:
+ return true
+ case *common.List:
+ return canBeInput(t.OfType)
+ case *common.NonNull:
+ return canBeInput(t.OfType)
+ default:
+ return false
+ }
+}
+
+func hasSubfields(t common.Type) bool {
+ switch t := t.(type) {
+ case *schema.Object, *schema.Interface, *schema.Union:
+ return true
+ case *common.List:
+ return hasSubfields(t.OfType)
+ case *common.NonNull:
+ return hasSubfields(t.OfType)
+ default:
+ return false
+ }
+}
+
+func isLeaf(t common.Type) bool {
+ switch t.(type) {
+ case *schema.Scalar, *schema.Enum:
+ return true
+ default:
+ return false
+ }
+}
+
+func isNull(lit interface{}) bool {
+ _, ok := lit.(*common.NullLit)
+ return ok
+}
+
+func typesCompatible(a, b common.Type) bool {
+ al, aIsList := a.(*common.List)
+ bl, bIsList := b.(*common.List)
+ if aIsList || bIsList {
+ return aIsList && bIsList && typesCompatible(al.OfType, bl.OfType)
+ }
+
+ ann, aIsNN := a.(*common.NonNull)
+ bnn, bIsNN := b.(*common.NonNull)
+ if aIsNN || bIsNN {
+ return aIsNN && bIsNN && typesCompatible(ann.OfType, bnn.OfType)
+ }
+
+ if isLeaf(a) || isLeaf(b) {
+ return a == b
+ }
+
+ return true
+}
+
+func typeCanBeUsedAs(t, as common.Type) bool {
+ nnT, okT := t.(*common.NonNull)
+ if okT {
+ t = nnT.OfType
+ }
+
+ nnAs, okAs := as.(*common.NonNull)
+ if okAs {
+ as = nnAs.OfType
+ if !okT {
+ return false // nullable can not be used as non-null
+ }
+ }
+
+ if t == as {
+ return true
+ }
+
+ if lT, ok := t.(*common.List); ok {
+ if lAs, ok := as.(*common.List); ok {
+ return typeCanBeUsedAs(lT.OfType, lAs.OfType)
+ }
+ }
+ return false
+}