aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/99designs/gqlgen/handler
diff options
context:
space:
mode:
authorMichael Muré <batolettre@gmail.com>2018-09-14 12:40:31 +0200
committerMichael Muré <batolettre@gmail.com>2018-09-14 12:41:59 +0200
commitb478cd1bcb4756b20f7f4b15fcf81f23e1a60a02 (patch)
tree8ce232dcab3dd00708f8ba66c334472457e5980d /vendor/github.com/99designs/gqlgen/handler
parenta3fc9abb921f5ce7084d6ab7473442d0b72b1d78 (diff)
downloadgit-bug-b478cd1bcb4756b20f7f4b15fcf81f23e1a60a02.tar.gz
graphql: update gqlgen to 0.5.1
fix #6
Diffstat (limited to 'vendor/github.com/99designs/gqlgen/handler')
-rw-r--r--vendor/github.com/99designs/gqlgen/handler/graphql.go283
-rw-r--r--vendor/github.com/99designs/gqlgen/handler/playground.go54
-rw-r--r--vendor/github.com/99designs/gqlgen/handler/stub.go51
-rw-r--r--vendor/github.com/99designs/gqlgen/handler/websocket.go252
4 files changed, 640 insertions, 0 deletions
diff --git a/vendor/github.com/99designs/gqlgen/handler/graphql.go b/vendor/github.com/99designs/gqlgen/handler/graphql.go
new file mode 100644
index 00000000..9d222826
--- /dev/null
+++ b/vendor/github.com/99designs/gqlgen/handler/graphql.go
@@ -0,0 +1,283 @@
+package handler
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "strings"
+
+ "github.com/99designs/gqlgen/complexity"
+ "github.com/99designs/gqlgen/graphql"
+ "github.com/gorilla/websocket"
+ "github.com/hashicorp/golang-lru"
+ "github.com/vektah/gqlparser"
+ "github.com/vektah/gqlparser/ast"
+ "github.com/vektah/gqlparser/gqlerror"
+ "github.com/vektah/gqlparser/validator"
+)
+
+type params struct {
+ Query string `json:"query"`
+ OperationName string `json:"operationName"`
+ Variables map[string]interface{} `json:"variables"`
+}
+
+type Config struct {
+ cacheSize int
+ upgrader websocket.Upgrader
+ recover graphql.RecoverFunc
+ errorPresenter graphql.ErrorPresenterFunc
+ resolverHook graphql.FieldMiddleware
+ requestHook graphql.RequestMiddleware
+ complexityLimit int
+}
+
+func (c *Config) newRequestContext(doc *ast.QueryDocument, 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
+ }
+}
+
+// ComplexityLimit sets a maximum query complexity that is allowed to be executed.
+// If a query is submitted that exceeds the limit, a 422 status code will be returned.
+func ComplexityLimit(limit int) Option {
+ return func(cfg *Config) {
+ cfg.complexityLimit = limit
+ }
+}
+
+// ResolverMiddleware allows you to define a function that will be called around every resolver,
+// useful for tracing and logging.
+func ResolverMiddleware(middleware graphql.FieldMiddleware) 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)
+ })
+ }
+ }
+}
+
+// CacheSize sets the maximum size of the query cache.
+// If size is less than or equal to 0, the cache is disabled.
+func CacheSize(size int) Option {
+ return func(cfg *Config) {
+ cfg.cacheSize = size
+ }
+}
+
+const DefaultCacheSize = 1000
+
+func GraphQL(exec graphql.ExecutableSchema, options ...Option) http.HandlerFunc {
+ cfg := Config{
+ cacheSize: DefaultCacheSize,
+ upgrader: websocket.Upgrader{
+ ReadBufferSize: 1024,
+ WriteBufferSize: 1024,
+ },
+ }
+
+ for _, option := range options {
+ option(&cfg)
+ }
+
+ var cache *lru.Cache
+ if cfg.cacheSize > 0 {
+ var err error
+ cache, err = lru.New(DefaultCacheSize)
+ if err != nil {
+ // An error is only returned for non-positive cache size
+ // and we already checked for that.
+ panic("unexpected error creating cache: " + err.Error())
+ }
+ }
+
+ 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 := jsonDecode(strings.NewReader(variables), &reqParams.Variables); err != nil {
+ sendErrorf(w, http.StatusBadRequest, "variables could not be decoded")
+ return
+ }
+ }
+ case http.MethodPost:
+ if err := jsonDecode(r.Body, &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")
+
+ var doc *ast.QueryDocument
+ if cache != nil {
+ val, ok := cache.Get(reqParams.Query)
+ if ok {
+ doc = val.(*ast.QueryDocument)
+ }
+ }
+ if doc == nil {
+ var qErr gqlerror.List
+ doc, qErr = gqlparser.LoadQuery(exec.Schema(), reqParams.Query)
+ if len(qErr) > 0 {
+ sendError(w, http.StatusUnprocessableEntity, qErr...)
+ return
+ }
+ if cache != nil {
+ cache.Add(reqParams.Query, doc)
+ }
+ }
+
+ op := doc.Operations.ForName(reqParams.OperationName)
+ if op == nil {
+ sendErrorf(w, http.StatusUnprocessableEntity, "operation %s not found", reqParams.OperationName)
+ return
+ }
+
+ if op.Operation != ast.Query && r.Method == http.MethodGet {
+ sendErrorf(w, http.StatusUnprocessableEntity, "GET requests only allow query operations")
+ return
+ }
+
+ vars, err := validator.VariableValues(exec.Schema(), op, reqParams.Variables)
+ if err != nil {
+ sendError(w, http.StatusUnprocessableEntity, err)
+ return
+ }
+ reqCtx := cfg.newRequestContext(doc, reqParams.Query, vars)
+ ctx := graphql.WithRequestContext(r.Context(), reqCtx)
+
+ defer func() {
+ if err := recover(); err != nil {
+ userErr := reqCtx.Recover(ctx, err)
+ sendErrorf(w, http.StatusUnprocessableEntity, userErr.Error())
+ }
+ }()
+
+ if cfg.complexityLimit > 0 {
+ queryComplexity := complexity.Calculate(exec, op, vars)
+ if queryComplexity > cfg.complexityLimit {
+ sendErrorf(w, http.StatusUnprocessableEntity, "query has complexity %d, which exceeds the limit of %d", queryComplexity, cfg.complexityLimit)
+ return
+ }
+ }
+
+ switch op.Operation {
+ case ast.Query:
+ b, err := json.Marshal(exec.Query(ctx, op))
+ if err != nil {
+ panic(err)
+ }
+ w.Write(b)
+ case ast.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 jsonDecode(r io.Reader, val interface{}) error {
+ dec := json.NewDecoder(r)
+ dec.UseNumber()
+ return dec.Decode(val)
+}
+
+func sendError(w http.ResponseWriter, code int, errors ...*gqlerror.Error) {
+ w.WriteHeader(code)
+ b, err := json.Marshal(&graphql.Response{Errors: errors})
+ if err != nil {
+ panic(err)
+ }
+ w.Write(b)
+}
+
+func sendErrorf(w http.ResponseWriter, code int, format string, args ...interface{}) {
+ sendError(w, code, &gqlerror.Error{Message: fmt.Sprintf(format, args...)})
+}
diff --git a/vendor/github.com/99designs/gqlgen/handler/playground.go b/vendor/github.com/99designs/gqlgen/handler/playground.go
new file mode 100644
index 00000000..d0ada8ca
--- /dev/null
+++ b/vendor/github.com/99designs/gqlgen/handler/playground.go
@@ -0,0 +1,54 @@
+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 }}',
+ settings: {
+ 'request.credentials': 'same-origin'
+ }
+ })
+ })
+</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.6.2",
+ })
+ if err != nil {
+ panic(err)
+ }
+ }
+}
diff --git a/vendor/github.com/99designs/gqlgen/handler/stub.go b/vendor/github.com/99designs/gqlgen/handler/stub.go
new file mode 100644
index 00000000..d237e188
--- /dev/null
+++ b/vendor/github.com/99designs/gqlgen/handler/stub.go
@@ -0,0 +1,51 @@
+package handler
+
+import (
+ "context"
+
+ "github.com/99designs/gqlgen/graphql"
+ "github.com/vektah/gqlparser"
+ "github.com/vektah/gqlparser/ast"
+)
+
+type executableSchemaStub struct {
+ NextResp chan struct{}
+}
+
+var _ graphql.ExecutableSchema = &executableSchemaStub{}
+
+func (e *executableSchemaStub) Schema() *ast.Schema {
+ return gqlparser.MustLoadSchema(&ast.Source{Input: `
+ schema { query: Query }
+ type Query {
+ me: User!
+ user(id: Int): User!
+ }
+ type User { name: String! }
+ `})
+}
+
+func (e *executableSchemaStub) Complexity(typeName, field string, childComplexity int, args map[string]interface{}) (int, bool) {
+ return 0, false
+}
+
+func (e *executableSchemaStub) Query(ctx context.Context, op *ast.OperationDefinition) *graphql.Response {
+ return &graphql.Response{Data: []byte(`{"name":"test"}`)}
+}
+
+func (e *executableSchemaStub) Mutation(ctx context.Context, op *ast.OperationDefinition) *graphql.Response {
+ return graphql.ErrorResponse(ctx, "mutations are not supported")
+}
+
+func (e *executableSchemaStub) Subscription(ctx context.Context, op *ast.OperationDefinition) func() *graphql.Response {
+ return func() *graphql.Response {
+ select {
+ case <-ctx.Done():
+ return nil
+ case <-e.NextResp:
+ return &graphql.Response{
+ Data: []byte(`{"name":"test"}`),
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/99designs/gqlgen/handler/websocket.go b/vendor/github.com/99designs/gqlgen/handler/websocket.go
new file mode 100644
index 00000000..2be1e87f
--- /dev/null
+++ b/vendor/github.com/99designs/gqlgen/handler/websocket.go
@@ -0,0 +1,252 @@
+package handler
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/http"
+ "sync"
+
+ "github.com/99designs/gqlgen/graphql"
+ "github.com/gorilla/websocket"
+ "github.com/vektah/gqlparser"
+ "github.com/vektah/gqlparser/ast"
+ "github.com/vektah/gqlparser/gqlerror"
+ "github.com/vektah/gqlparser/validator"
+)
+
+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, gqlerror.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 := jsonDecode(bytes.NewReader(message.Payload), &reqParams); err != nil {
+ c.sendConnectionError("invalid json")
+ return false
+ }
+
+ doc, qErr := gqlparser.LoadQuery(c.exec.Schema(), reqParams.Query)
+ if qErr != nil {
+ c.sendError(message.ID, qErr...)
+ return true
+ }
+
+ op := doc.Operations.ForName(reqParams.OperationName)
+ if op == nil {
+ c.sendError(message.ID, gqlerror.Errorf("operation %s not found", reqParams.OperationName))
+ return true
+ }
+
+ vars, err := validator.VariableValues(c.exec.Schema(), op, reqParams.Variables)
+ if err != nil {
+ c.sendError(message.ID, err)
+ return true
+ }
+ reqCtx := c.cfg.newRequestContext(doc, reqParams.Query, vars)
+ ctx := graphql.WithRequestContext(c.ctx, reqCtx)
+
+ if op.Operation != ast.Subscription {
+ var result *graphql.Response
+ if op.Operation == ast.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, &gqlerror.Error{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, gqlerror.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 ...*gqlerror.Error) {
+ 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(&gqlerror.Error{Message: fmt.Sprintf(format, args...)})
+ if err != nil {
+ panic(err)
+ }
+
+ c.write(&operationMessage{Type: connectionErrorMsg, Payload: b})
+}
+
+func (c *wsConnection) readOp() *operationMessage {
+ _, r, err := c.conn.NextReader()
+ if err != nil {
+ c.sendConnectionError("invalid json")
+ return nil
+ }
+ message := operationMessage{}
+ if err := jsonDecode(r, &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()
+}