From 6363518c85cbd8247a5f6507b8a1dd3903cfb71d Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Sun, 29 Jul 2018 18:11:33 +0200 Subject: relay connection working with gqlgen --- vendor/github.com/vektah/gqlgen/LICENSE | 19 + vendor/github.com/vektah/gqlgen/graphql/bool.go | 30 + vendor/github.com/vektah/gqlgen/graphql/context.go | 145 ++++ vendor/github.com/vektah/gqlgen/graphql/defer.go | 30 + vendor/github.com/vektah/gqlgen/graphql/error.go | 46 ++ vendor/github.com/vektah/gqlgen/graphql/exec.go | 118 +++ vendor/github.com/vektah/gqlgen/graphql/float.go | 26 + vendor/github.com/vektah/gqlgen/graphql/id.go | 33 + vendor/github.com/vektah/gqlgen/graphql/int.go | 26 + vendor/github.com/vektah/gqlgen/graphql/jsonw.go | 83 ++ vendor/github.com/vektah/gqlgen/graphql/map.go | 24 + vendor/github.com/vektah/gqlgen/graphql/oneshot.go | 14 + .../github.com/vektah/gqlgen/graphql/recovery.go | 19 + .../github.com/vektah/gqlgen/graphql/response.go | 18 + vendor/github.com/vektah/gqlgen/graphql/string.go | 63 ++ vendor/github.com/vektah/gqlgen/graphql/time.go | 21 + vendor/github.com/vektah/gqlgen/handler/graphql.go | 235 ++++++ .../github.com/vektah/gqlgen/handler/playground.go | 51 ++ vendor/github.com/vektah/gqlgen/handler/stub.go | 45 ++ .../github.com/vektah/gqlgen/handler/websocket.go | 245 ++++++ vendor/github.com/vektah/gqlgen/neelance/LICENSE | 24 + .../vektah/gqlgen/neelance/common/directive.go | 32 + .../vektah/gqlgen/neelance/common/lexer.go | 122 +++ .../vektah/gqlgen/neelance/common/literals.go | 206 +++++ .../vektah/gqlgen/neelance/common/types.go | 80 ++ .../vektah/gqlgen/neelance/common/values.go | 77 ++ .../vektah/gqlgen/neelance/errors/errors.go | 41 + .../gqlgen/neelance/introspection/introspection.go | 313 ++++++++ .../vektah/gqlgen/neelance/introspection/query.go | 104 +++ .../vektah/gqlgen/neelance/query/query.go | 261 +++++++ .../vektah/gqlgen/neelance/schema/meta.go | 193 +++++ .../vektah/gqlgen/neelance/schema/schema.go | 489 ++++++++++++ .../vektah/gqlgen/neelance/tests/testdata/LICENSE | 33 + .../gqlgen/neelance/validation/suggestion.go | 71 ++ .../gqlgen/neelance/validation/validation.go | 861 +++++++++++++++++++++ 35 files changed, 4198 insertions(+) create mode 100644 vendor/github.com/vektah/gqlgen/LICENSE create mode 100644 vendor/github.com/vektah/gqlgen/graphql/bool.go create mode 100644 vendor/github.com/vektah/gqlgen/graphql/context.go create mode 100644 vendor/github.com/vektah/gqlgen/graphql/defer.go create mode 100644 vendor/github.com/vektah/gqlgen/graphql/error.go create mode 100644 vendor/github.com/vektah/gqlgen/graphql/exec.go create mode 100644 vendor/github.com/vektah/gqlgen/graphql/float.go create mode 100644 vendor/github.com/vektah/gqlgen/graphql/id.go create mode 100644 vendor/github.com/vektah/gqlgen/graphql/int.go create mode 100644 vendor/github.com/vektah/gqlgen/graphql/jsonw.go create mode 100644 vendor/github.com/vektah/gqlgen/graphql/map.go create mode 100644 vendor/github.com/vektah/gqlgen/graphql/oneshot.go create mode 100644 vendor/github.com/vektah/gqlgen/graphql/recovery.go create mode 100644 vendor/github.com/vektah/gqlgen/graphql/response.go create mode 100644 vendor/github.com/vektah/gqlgen/graphql/string.go create mode 100644 vendor/github.com/vektah/gqlgen/graphql/time.go create mode 100644 vendor/github.com/vektah/gqlgen/handler/graphql.go create mode 100644 vendor/github.com/vektah/gqlgen/handler/playground.go create mode 100644 vendor/github.com/vektah/gqlgen/handler/stub.go create mode 100644 vendor/github.com/vektah/gqlgen/handler/websocket.go create mode 100644 vendor/github.com/vektah/gqlgen/neelance/LICENSE create mode 100644 vendor/github.com/vektah/gqlgen/neelance/common/directive.go create mode 100644 vendor/github.com/vektah/gqlgen/neelance/common/lexer.go create mode 100644 vendor/github.com/vektah/gqlgen/neelance/common/literals.go create mode 100644 vendor/github.com/vektah/gqlgen/neelance/common/types.go create mode 100644 vendor/github.com/vektah/gqlgen/neelance/common/values.go create mode 100644 vendor/github.com/vektah/gqlgen/neelance/errors/errors.go create mode 100644 vendor/github.com/vektah/gqlgen/neelance/introspection/introspection.go create mode 100644 vendor/github.com/vektah/gqlgen/neelance/introspection/query.go create mode 100644 vendor/github.com/vektah/gqlgen/neelance/query/query.go create mode 100644 vendor/github.com/vektah/gqlgen/neelance/schema/meta.go create mode 100644 vendor/github.com/vektah/gqlgen/neelance/schema/schema.go create mode 100644 vendor/github.com/vektah/gqlgen/neelance/tests/testdata/LICENSE create mode 100644 vendor/github.com/vektah/gqlgen/neelance/validation/suggestion.go create mode 100644 vendor/github.com/vektah/gqlgen/neelance/validation/validation.go (limited to 'vendor/github.com/vektah') 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(` + + + + + + + + + {{.title}} + + + +
+ + + +`)) + +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 "" + } + 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 +} -- cgit