4
0
mirror of https://github.com/cwinfo/matterbridge.git synced 2025-07-03 04:57:44 +00:00

Update dependencies (#1784)

This commit is contained in:
Wim
2022-04-01 00:23:19 +02:00
committed by GitHub
parent 4ab72acec6
commit c6716e030c
255 changed files with 69606 additions and 58489 deletions

View File

@ -0,0 +1,5 @@
/.idea
/.vscode
/internal/validation/testdata/graphql-js
/internal/validation/testdata/node_modules
/vendor

View File

@ -0,0 +1,35 @@
run:
timeout: 5m
linters-settings:
gofmt:
simplify: true
govet:
check-shadowing: true
enable-all: true
disable:
- fieldalignment
- deepequalerrors # remove later
linters:
disable-all: true
enable:
- deadcode
- gofmt
- gosimple
- govet
- ineffassign
- exportloopref
- structcheck
- staticcheck
- unconvert
- unused
- varcheck
- misspell
- goimports
issues:
exclude-rules:
- linters:
- unused
path: "graphql_test.go"

View File

@ -0,0 +1,10 @@
CHANGELOG
[v1.1.0](https://github.com/graph-gophers/graphql-go/releases/tag/v1.1.0) Release v1.1.0
* [FEATURE] Add types package #437
* [FEATURE] Expose `packer.Unmarshaler` as `decode.Unmarshaler` to the public #450
* [FEATURE] Add location fields to type definitions #454
* [FEATURE] `errors.Errorf` preserves original error similar to `fmt.Errorf` #456
* [BUGFIX] Fix duplicated __typename in response (fixes #369) #443
[v1.0.0](https://github.com/graph-gophers/graphql-go/releases/tag/v1.0.0) Initial release

View File

@ -0,0 +1,13 @@
## Contributing
- With issues:
- Use the search tool before opening a new issue.
- Please provide source code and commit sha if you found a bug.
- Review existing issues and provide feedback or react to them.
- With pull requests:
- Open your pull request against `master`
- Your pull request should have no more than two commits, if not you should squash them.
- It should pass all tests in the available continuous integrations systems such as TravisCI.
- You should add/modify tests to cover your proposed code changes.
- If your pull request contains a new feature, please document it on the README.

24
vendor/github.com/graph-gophers/graphql-go/LICENSE generated vendored Normal file
View File

@ -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.

169
vendor/github.com/graph-gophers/graphql-go/README.md generated vendored Normal file
View File

@ -0,0 +1,169 @@
# graphql-go [![Sourcegraph](https://sourcegraph.com/github.com/graph-gophers/graphql-go/-/badge.svg)](https://sourcegraph.com/github.com/graph-gophers/graphql-go?badge) [![Build Status](https://graph-gophers.semaphoreci.com/badges/graphql-go/branches/master.svg?style=shields)](https://graph-gophers.semaphoreci.com/projects/graphql-go) [![GoDoc](https://godoc.org/github.com/graph-gophers/graphql-go?status.svg)](https://godoc.org/github.com/graph-gophers/graphql-go)
<p align="center"><img src="docs/img/logo.png" width="300"></p>
The goal of this project is to provide full support of the [GraphQL draft specification](https://facebook.github.io/graphql/draft) with a set of idiomatic, easy to use Go packages.
While still under heavy development (`internal` APIs are almost certainly subject to change), this library is
safe for production use.
## Features
- minimal API
- support for `context.Context`
- support for the `OpenTracing` standard
- schema type-checking against resolvers
- resolvers are matched to the schema based on method sets (can resolve a GraphQL schema with a Go interface or Go struct).
- handles panics in resolvers
- parallel execution of resolvers
- subscriptions
- [sample WS transport](https://github.com/graph-gophers/graphql-transport-ws)
## Roadmap
We're trying out the GitHub Project feature to manage `graphql-go`'s [development roadmap](https://github.com/graph-gophers/graphql-go/projects/1).
Feedback is welcome and appreciated.
## (Some) Documentation
### Basic Sample
```go
package main
import (
"log"
"net/http"
graphql "github.com/graph-gophers/graphql-go"
"github.com/graph-gophers/graphql-go/relay"
)
type query struct{}
func (_ *query) Hello() string { return "Hello, world!" }
func main() {
s := `
type Query {
hello: String!
}
`
schema := graphql.MustParseSchema(s, &query{})
http.Handle("/query", &relay.Handler{Schema: schema})
log.Fatal(http.ListenAndServe(":8080", nil))
}
```
To test:
```sh
curl -XPOST -d '{"query": "{ hello }"}' localhost:8080/query
```
### Resolvers
A resolver must have one method or field for each field of the GraphQL type it resolves. The method or field name has to be [exported](https://golang.org/ref/spec#Exported_identifiers) and match the schema's field's name in a non-case-sensitive way.
You can use struct fields as resolvers by using `SchemaOpt: UseFieldResolvers()`. For example,
```
opts := []graphql.SchemaOpt{graphql.UseFieldResolvers()}
schema := graphql.MustParseSchema(s, &query{}, opts...)
```
When using `UseFieldResolvers` schema option, a struct field will be used *only* when:
- there is no method for a struct field
- a struct field does not implement an interface method
- a struct field does not have arguments
The method has up to two arguments:
- Optional `context.Context` argument.
- Mandatory `*struct { ... }` argument if the corresponding GraphQL field has arguments. The names of the struct fields have to be [exported](https://golang.org/ref/spec#Exported_identifiers) and have to match the names of the GraphQL arguments in a non-case-sensitive way.
The method has up to two results:
- The GraphQL field's value as determined by the resolver.
- Optional `error` result.
Example for a simple resolver method:
```go
func (r *helloWorldResolver) Hello() string {
return "Hello world!"
}
```
The following signature is also allowed:
```go
func (r *helloWorldResolver) Hello(ctx context.Context) (string, error) {
return "Hello world!", nil
}
```
### Schema Options
- `UseStringDescriptions()` enables the usage of double quoted and triple quoted. When this is not enabled, comments are parsed as descriptions instead.
- `UseFieldResolvers()` specifies whether to use struct field resolvers.
- `MaxDepth(n int)` specifies the maximum field nesting depth in a query. The default is 0 which disables max depth checking.
- `MaxParallelism(n int)` specifies the maximum number of resolvers per request allowed to run in parallel. The default is 10.
- `Tracer(tracer trace.Tracer)` is used to trace queries and fields. It defaults to `trace.OpenTracingTracer`.
- `ValidationTracer(tracer trace.ValidationTracer)` is used to trace validation errors. It defaults to `trace.NoopValidationTracer`.
- `Logger(logger log.Logger)` is used to log panics during query execution. It defaults to `exec.DefaultLogger`.
- `PanicHandler(panicHandler errors.PanicHandler)` is used to transform panics into errors during query execution. It defaults to `errors.DefaultPanicHandler`.
- `DisableIntrospection()` disables introspection queries.
### Custom Errors
Errors returned by resolvers can include custom extensions by implementing the `ResolverError` interface:
```go
type ResolverError interface {
error
Extensions() map[string]interface{}
}
```
Example of a simple custom error:
```go
type droidNotFoundError struct {
Code string `json:"code"`
Message string `json:"message"`
}
func (e droidNotFoundError) Error() string {
return fmt.Sprintf("error [%s]: %s", e.Code, e.Message)
}
func (e droidNotFoundError) Extensions() map[string]interface{} {
return map[string]interface{}{
"code": e.Code,
"message": e.Message,
}
}
```
Which could produce a GraphQL error such as:
```go
{
"errors": [
{
"message": "error [NotFound]: This is not the droid you are looking for",
"path": [
"droid"
],
"extensions": {
"code": "NotFound",
"message": "This is not the droid you are looking for"
}
}
],
"data": null
}
```
### [Examples](https://github.com/graph-gophers/graphql-go/wiki/Examples)
### [Companies that use this library](https://github.com/graph-gophers/graphql-go/wiki/Users)

View File

@ -0,0 +1,13 @@
package decode
// Unmarshaler defines the api of Go types mapped to custom GraphQL scalar types
type Unmarshaler interface {
// ImplementsGraphQLType maps the implementing custom Go type
// to the GraphQL scalar type in the schema.
ImplementsGraphQLType(name string) bool
// UnmarshalGraphQL is the custom unmarshaler for the implementing type
//
// This function will be called whenever you use the
// custom GraphQL scalar type as an input
UnmarshalGraphQL(input interface{}) error
}

View File

@ -0,0 +1,59 @@
package errors
import (
"fmt"
)
type QueryError struct {
Err error `json:"-"` // Err holds underlying if available
Message string `json:"message"`
Locations []Location `json:"locations,omitempty"`
Path []interface{} `json:"path,omitempty"`
Rule string `json:"-"`
ResolverError error `json:"-"`
Extensions map[string]interface{} `json:"extensions,omitempty"`
}
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 {
// similar to fmt.Errorf, Errorf will wrap the last argument if it is an instance of error
var err error
if n := len(a); n > 0 {
if v, ok := a[n-1].(error); ok {
err = v
}
}
return &QueryError{
Err: err,
Message: fmt.Sprintf(format, a...),
}
}
func (err *QueryError) Error() string {
if err == nil {
return "<nil>"
}
str := fmt.Sprintf("graphql: %s", err.Message)
for _, loc := range err.Locations {
str += fmt.Sprintf(" (line %d, column %d)", loc.Line, loc.Column)
}
return str
}
func (err *QueryError) Unwrap() error {
if err == nil {
return nil
}
return err.Err
}
var _ error = &QueryError{}

View File

@ -0,0 +1,18 @@
package errors
import (
"context"
)
// PanicHandler is the interface used to create custom panic errors that occur during query execution
type PanicHandler interface {
MakePanicError(ctx context.Context, value interface{}) *QueryError
}
// DefaultPanicHandler is the default PanicHandler
type DefaultPanicHandler struct{}
// MakePanicError creates a new QueryError from a panic that occurred during execution
func (h *DefaultPanicHandler) MakePanicError(ctx context.Context, value interface{}) *QueryError {
return Errorf("panic occurred: %v", value)
}

339
vendor/github.com/graph-gophers/graphql-go/graphql.go generated vendored Normal file
View File

@ -0,0 +1,339 @@
package graphql
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/graph-gophers/graphql-go/errors"
"github.com/graph-gophers/graphql-go/internal/common"
"github.com/graph-gophers/graphql-go/internal/exec"
"github.com/graph-gophers/graphql-go/internal/exec/resolvable"
"github.com/graph-gophers/graphql-go/internal/exec/selected"
"github.com/graph-gophers/graphql-go/internal/query"
"github.com/graph-gophers/graphql-go/internal/schema"
"github.com/graph-gophers/graphql-go/internal/validation"
"github.com/graph-gophers/graphql-go/introspection"
"github.com/graph-gophers/graphql-go/log"
"github.com/graph-gophers/graphql-go/trace"
"github.com/graph-gophers/graphql-go/types"
)
// ParseSchema parses a GraphQL schema and attaches the given root resolver. It returns an error if
// the Go type signature of the resolvers does not match the schema. If nil is passed as the
// resolver, then the schema can not be executed, but it may be inspected (e.g. with ToJSON).
func ParseSchema(schemaString string, resolver interface{}, opts ...SchemaOpt) (*Schema, error) {
s := &Schema{
schema: schema.New(),
maxParallelism: 10,
tracer: trace.OpenTracingTracer{},
logger: &log.DefaultLogger{},
panicHandler: &errors.DefaultPanicHandler{},
}
for _, opt := range opts {
opt(s)
}
if s.validationTracer == nil {
if tracer, ok := s.tracer.(trace.ValidationTracerContext); ok {
s.validationTracer = tracer
} else {
s.validationTracer = &validationBridgingTracer{tracer: trace.NoopValidationTracer{}}
}
}
if err := schema.Parse(s.schema, schemaString, s.useStringDescriptions); err != nil {
return nil, err
}
if err := s.validateSchema(); err != nil {
return nil, err
}
r, err := resolvable.ApplyResolver(s.schema, resolver)
if err != nil {
return nil, err
}
s.res = r
return s, nil
}
// MustParseSchema calls ParseSchema and panics on error.
func MustParseSchema(schemaString string, resolver interface{}, opts ...SchemaOpt) *Schema {
s, err := ParseSchema(schemaString, resolver, opts...)
if err != nil {
panic(err)
}
return s
}
// Schema represents a GraphQL schema with an optional resolver.
type Schema struct {
schema *types.Schema
res *resolvable.Schema
maxDepth int
maxParallelism int
tracer trace.Tracer
validationTracer trace.ValidationTracerContext
logger log.Logger
panicHandler errors.PanicHandler
useStringDescriptions bool
disableIntrospection bool
subscribeResolverTimeout time.Duration
}
func (s *Schema) ASTSchema() *types.Schema {
return s.schema
}
// SchemaOpt is an option to pass to ParseSchema or MustParseSchema.
type SchemaOpt func(*Schema)
// UseStringDescriptions enables the usage of double quoted and triple quoted
// strings as descriptions as per the June 2018 spec
// https://facebook.github.io/graphql/June2018/. When this is not enabled,
// comments are parsed as descriptions instead.
func UseStringDescriptions() SchemaOpt {
return func(s *Schema) {
s.useStringDescriptions = true
}
}
// UseFieldResolvers specifies whether to use struct field resolvers
func UseFieldResolvers() SchemaOpt {
return func(s *Schema) {
s.schema.UseFieldResolvers = true
}
}
// MaxDepth specifies the maximum field nesting depth in a query. The default is 0 which disables max depth checking.
func MaxDepth(n int) SchemaOpt {
return func(s *Schema) {
s.maxDepth = n
}
}
// MaxParallelism specifies the maximum number of resolvers per request allowed to run in parallel. The default is 10.
func MaxParallelism(n int) SchemaOpt {
return func(s *Schema) {
s.maxParallelism = n
}
}
// Tracer is used to trace queries and fields. It defaults to trace.OpenTracingTracer.
func Tracer(tracer trace.Tracer) SchemaOpt {
return func(s *Schema) {
s.tracer = tracer
}
}
// ValidationTracer is used to trace validation errors. It defaults to trace.NoopValidationTracer.
// Deprecated: context is needed to support tracing correctly. Use a Tracer which implements trace.ValidationTracerContext.
func ValidationTracer(tracer trace.ValidationTracer) SchemaOpt { //nolint:staticcheck
return func(s *Schema) {
s.validationTracer = &validationBridgingTracer{tracer: tracer}
}
}
// Logger is used to log panics during query execution. It defaults to exec.DefaultLogger.
func Logger(logger log.Logger) SchemaOpt {
return func(s *Schema) {
s.logger = logger
}
}
// PanicHandler is used to customize the panic errors during query execution.
// It defaults to errors.DefaultPanicHandler.
func PanicHandler(panicHandler errors.PanicHandler) SchemaOpt {
return func(s *Schema) {
s.panicHandler = panicHandler
}
}
// DisableIntrospection disables introspection queries.
func DisableIntrospection() SchemaOpt {
return func(s *Schema) {
s.disableIntrospection = true
}
}
// SubscribeResolverTimeout is an option to control the amount of time
// we allow for a single subscribe message resolver to complete it's job
// before it times out and returns an error to the subscriber.
func SubscribeResolverTimeout(timeout time.Duration) SchemaOpt {
return func(s *Schema) {
s.subscribeResolverTimeout = timeout
}
}
// Response represents a typical response of a GraphQL server. It may be encoded to JSON directly or
// it may be further processed to a custom response type, for example to include custom error data.
// Errors are intentionally serialized first based on the advice in https://github.com/facebook/graphql/commit/7b40390d48680b15cb93e02d46ac5eb249689876#diff-757cea6edf0288677a9eea4cfc801d87R107
type Response struct {
Errors []*errors.QueryError `json:"errors,omitempty"`
Data json.RawMessage `json:"data,omitempty"`
Extensions map[string]interface{} `json:"extensions,omitempty"`
}
// Validate validates the given query with the schema.
func (s *Schema) Validate(queryString string) []*errors.QueryError {
return s.ValidateWithVariables(queryString, nil)
}
// ValidateWithVariables validates the given query with the schema and the input variables.
func (s *Schema) ValidateWithVariables(queryString string, variables map[string]interface{}) []*errors.QueryError {
doc, qErr := query.Parse(queryString)
if qErr != nil {
return []*errors.QueryError{qErr}
}
return validation.Validate(s.schema, doc, variables, s.maxDepth)
}
// Exec executes the given query with the schema's resolver. It panics if the schema was created
// without a resolver. If the context get cancelled, no further resolvers will be called and a
// the context error will be returned as soon as possible (not immediately).
func (s *Schema) Exec(ctx context.Context, queryString string, operationName string, variables map[string]interface{}) *Response {
if !s.res.Resolver.IsValid() {
panic("schema created without resolver, can not exec")
}
return s.exec(ctx, queryString, operationName, variables, s.res)
}
func (s *Schema) exec(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, res *resolvable.Schema) *Response {
doc, qErr := query.Parse(queryString)
if qErr != nil {
return &Response{Errors: []*errors.QueryError{qErr}}
}
validationFinish := s.validationTracer.TraceValidation(ctx)
errs := validation.Validate(s.schema, doc, variables, s.maxDepth)
validationFinish(errs)
if len(errs) != 0 {
return &Response{Errors: errs}
}
op, err := getOperation(doc, operationName)
if err != nil {
return &Response{Errors: []*errors.QueryError{errors.Errorf("%s", err)}}
}
// If the optional "operationName" POST parameter is not provided then
// use the query's operation name for improved tracing.
if operationName == "" {
operationName = op.Name.Name
}
// Subscriptions are not valid in Exec. Use schema.Subscribe() instead.
if op.Type == query.Subscription {
return &Response{Errors: []*errors.QueryError{{Message: "graphql-ws protocol header is missing"}}}
}
if op.Type == query.Mutation {
if _, ok := s.schema.EntryPoints["mutation"]; !ok {
return &Response{Errors: []*errors.QueryError{{Message: "no mutations are offered by the schema"}}}
}
}
// Fill in variables with the defaults from the operation
if variables == nil {
variables = make(map[string]interface{}, len(op.Vars))
}
for _, v := range op.Vars {
if _, ok := variables[v.Name.Name]; !ok && v.Default != nil {
variables[v.Name.Name] = v.Default.Deserialize(nil)
}
}
r := &exec.Request{
Request: selected.Request{
Doc: doc,
Vars: variables,
Schema: s.schema,
DisableIntrospection: s.disableIntrospection,
},
Limiter: make(chan struct{}, s.maxParallelism),
Tracer: s.tracer,
Logger: s.logger,
PanicHandler: s.panicHandler,
}
varTypes := make(map[string]*introspection.Type)
for _, v := range op.Vars {
t, err := common.ResolveType(v.Type, s.schema.Resolve)
if err != nil {
return &Response{Errors: []*errors.QueryError{err}}
}
varTypes[v.Name.Name] = introspection.WrapType(t)
}
traceCtx, finish := s.tracer.TraceQuery(ctx, queryString, operationName, variables, varTypes)
data, errs := r.Execute(traceCtx, res, op)
finish(errs)
return &Response{
Data: data,
Errors: errs,
}
}
func (s *Schema) validateSchema() error {
// https://graphql.github.io/graphql-spec/June2018/#sec-Root-Operation-Types
// > The query root operation type must be provided and must be an Object type.
if err := validateRootOp(s.schema, "query", true); err != nil {
return err
}
// > The mutation root operation type is optional; if it is not provided, the service does not support mutations.
// > If it is provided, it must be an Object type.
if err := validateRootOp(s.schema, "mutation", false); err != nil {
return err
}
// > Similarly, the subscription root operation type is also optional; if it is not provided, the service does not
// > support subscriptions. If it is provided, it must be an Object type.
if err := validateRootOp(s.schema, "subscription", false); err != nil {
return err
}
return nil
}
type validationBridgingTracer struct {
tracer trace.ValidationTracer //nolint:staticcheck
}
func (t *validationBridgingTracer) TraceValidation(context.Context) trace.TraceValidationFinishFunc {
return t.tracer.TraceValidation()
}
func validateRootOp(s *types.Schema, name string, mandatory bool) error {
t, ok := s.EntryPoints[name]
if !ok {
if mandatory {
return fmt.Errorf("root operation %q must be defined", name)
}
return nil
}
if t.Kind() != "OBJECT" {
return fmt.Errorf("root operation %q must be an OBJECT", name)
}
return nil
}
func getOperation(document *types.ExecutableDefinition, operationName string) (*types.OperationDefinition, error) {
if len(document.Operations) == 0 {
return nil, fmt.Errorf("no operations in query document")
}
if operationName == "" {
if len(document.Operations) > 1 {
return nil, fmt.Errorf("more than one operation in query document and no operation name given")
}
for _, op := range document.Operations {
return op, nil // return the one and only operation
}
}
op := document.Operations.Get(operationName)
if op == nil {
return nil, fmt.Errorf("no operation with name %q", operationName)
}
return op, nil
}

30
vendor/github.com/graph-gophers/graphql-go/id.go generated vendored Normal file
View File

@ -0,0 +1,30 @@
package graphql
import (
"fmt"
"strconv"
)
// ID represents GraphQL's "ID" scalar type. A custom type may be used instead.
type ID string
func (ID) ImplementsGraphQLType(name string) bool {
return name == "ID"
}
func (id *ID) UnmarshalGraphQL(input interface{}) error {
var err error
switch input := input.(type) {
case string:
*id = ID(input)
case int32:
*id = ID(strconv.Itoa(int(input)))
default:
err = fmt.Errorf("wrong type for ID: %T", input)
}
return err
}
func (id ID) MarshalJSON() ([]byte, error) {
return strconv.AppendQuote(nil, string(id)), nil
}

View File

@ -0,0 +1,103 @@
// MIT License
//
// Copyright (c) 2019 GraphQL Contributors
//
// 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.
//
// This implementation has been adapted from the graphql-js reference implementation
// https://github.com/graphql/graphql-js/blob/5eb7c4ded7ceb83ac742149cbe0dae07a8af9a30/src/language/blockString.js
// which is released under the MIT License above.
package common
import (
"strings"
)
// Produces the value of a block string from its parsed raw value, similar to
// CoffeeScript's block string, Python's docstring trim or Ruby's strip_heredoc.
//
// This implements the GraphQL spec's BlockStringValue() static algorithm.
func blockString(raw string) string {
lines := strings.Split(raw, "\n")
// Remove common indentation from all lines except the first (which has none)
ind := blockStringIndentation(lines)
if ind > 0 {
for i := 1; i < len(lines); i++ {
l := lines[i]
if len(l) < ind {
lines[i] = ""
continue
}
lines[i] = l[ind:]
}
}
// Remove leading and trailing blank lines
trimStart := 0
for i := 0; i < len(lines) && isBlank(lines[i]); i++ {
trimStart++
}
lines = lines[trimStart:]
trimEnd := 0
for i := len(lines) - 1; i > 0 && isBlank(lines[i]); i-- {
trimEnd++
}
lines = lines[:len(lines)-trimEnd]
return strings.Join(lines, "\n")
}
func blockStringIndentation(lines []string) int {
var commonIndent *int
for i := 1; i < len(lines); i++ {
l := lines[i]
indent := leadingWhitespace(l)
if indent == len(l) {
// don't consider blank/empty lines
continue
}
if indent == 0 {
return 0
}
if commonIndent == nil || indent < *commonIndent {
commonIndent = &indent
}
}
if commonIndent == nil {
return 0
}
return *commonIndent
}
func isBlank(s string) bool {
return len(s) == 0 || leadingWhitespace(s) == len(s)
}
func leadingWhitespace(s string) int {
i := 0
for _, r := range s {
if r != '\t' && r != ' ' {
break
}
i++
}
return i
}

View File

@ -0,0 +1,18 @@
package common
import "github.com/graph-gophers/graphql-go/types"
func ParseDirectives(l *Lexer) types.DirectiveList {
var directives types.DirectiveList
for l.Peek() == '@' {
l.ConsumeToken('@')
d := &types.Directive{}
d.Name = l.ConsumeIdentWithLoc()
d.Name.Loc.Column--
if l.Peek() == '(' {
d.Arguments = ParseArgumentList(l)
}
directives = append(directives, d)
}
return directives
}

View File

@ -0,0 +1,229 @@
package common
import (
"bytes"
"fmt"
"strconv"
"strings"
"text/scanner"
"github.com/graph-gophers/graphql-go/errors"
"github.com/graph-gophers/graphql-go/types"
)
type syntaxError string
type Lexer struct {
sc *scanner.Scanner
next rune
comment bytes.Buffer
useStringDescriptions bool
}
type Ident struct {
Name string
Loc errors.Location
}
func NewLexer(s string, useStringDescriptions bool) *Lexer {
sc := &scanner.Scanner{
Mode: scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats | scanner.ScanStrings,
}
sc.Init(strings.NewReader(s))
l := Lexer{sc: sc, useStringDescriptions: useStringDescriptions}
l.sc.Error = l.CatchScannerError
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
}
// ConsumeWhitespace consumes whitespace and tokens equivalent to whitespace (e.g. commas and comments).
//
// Consumed comment characters will build the description for the next type or field encountered.
// The description is available from `DescComment()`, and will be reset every time `ConsumeWhitespace()` is
// executed unless l.useStringDescriptions is set.
func (l *Lexer) ConsumeWhitespace() {
l.comment.Reset()
for {
l.next = l.sc.Scan()
if l.next == ',' {
// Similar to white space and line terminators, commas (',') are used to improve the
// legibility of source text and separate lexical tokens but are otherwise syntactically and
// semantically insignificant within GraphQL documents.
//
// http://facebook.github.io/graphql/draft/#sec-Insignificant-Commas
continue
}
if l.next == '#' {
// GraphQL source documents may contain single-line comments, starting with the '#' marker.
//
// A comment can contain any Unicode code point except `LineTerminator` so a comment always
// consists of all code points starting with the '#' character up to but not including the
// line terminator.
l.consumeComment()
continue
}
break
}
}
// consumeDescription optionally consumes a description based on the June 2018 graphql spec if any are present.
//
// Single quote strings are also single line. Triple quote strings can be multi-line. Triple quote strings
// whitespace trimmed on both ends.
// If a description is found, consume any following comments as well
//
// http://facebook.github.io/graphql/June2018/#sec-Descriptions
func (l *Lexer) consumeDescription() string {
// If the next token is not a string, we don't consume it
if l.next != scanner.String {
return ""
}
// Triple quote string is an empty "string" followed by an open quote due to the way the parser treats strings as one token
var desc string
if l.sc.Peek() == '"' {
desc = l.consumeTripleQuoteComment()
} else {
desc = l.consumeStringComment()
}
l.ConsumeWhitespace()
return desc
}
func (l *Lexer) ConsumeIdent() string {
name := l.sc.TokenText()
l.ConsumeToken(scanner.Ident)
return name
}
func (l *Lexer) ConsumeIdentWithLoc() types.Ident {
loc := l.Location()
name := l.sc.TokenText()
l.ConsumeToken(scanner.Ident)
return types.Ident{Name: name, Loc: 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.ConsumeWhitespace()
}
func (l *Lexer) ConsumeLiteral() *types.PrimitiveValue {
lit := &types.PrimitiveValue{Type: l.next, Text: l.sc.TokenText()}
l.ConsumeWhitespace()
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.ConsumeWhitespace()
}
func (l *Lexer) DescComment() string {
comment := l.comment.String()
desc := l.consumeDescription()
if l.useStringDescriptions {
return desc
}
return comment
}
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,
}
}
func (l *Lexer) consumeTripleQuoteComment() string {
l.next = l.sc.Next()
if l.next != '"' {
panic("consumeTripleQuoteComment used in wrong context: no third quote?")
}
var buf bytes.Buffer
var numQuotes int
for {
l.next = l.sc.Next()
if l.next == '"' {
numQuotes++
} else {
numQuotes = 0
}
buf.WriteRune(l.next)
if numQuotes == 3 || l.next == scanner.EOF {
break
}
}
val := buf.String()
val = val[:len(val)-numQuotes]
return blockString(val)
}
func (l *Lexer) consumeStringComment() string {
val, err := strconv.Unquote(l.sc.TokenText())
if err != nil {
panic(err)
}
return val
}
// consumeComment consumes all characters from `#` to the first encountered line terminator.
// The characters are appended to `l.comment`.
func (l *Lexer) consumeComment() {
if l.next != '#' {
panic("consumeComment used in wrong context")
}
// TODO: count and trim whitespace so we can dedent any following lines.
if l.sc.Peek() == ' ' {
l.sc.Next()
}
if l.comment.Len() > 0 {
l.comment.WriteRune('\n')
}
for {
next := l.sc.Next()
if next == '\r' || next == '\n' || next == scanner.EOF {
break
}
l.comment.WriteRune(next)
}
}
func (l *Lexer) CatchScannerError(s *scanner.Scanner, msg string) {
l.SyntaxError(msg)
}

View File

@ -0,0 +1,58 @@
package common
import (
"text/scanner"
"github.com/graph-gophers/graphql-go/types"
)
func ParseLiteral(l *Lexer, constOnly bool) types.Value {
loc := l.Location()
switch l.Peek() {
case '$':
if constOnly {
l.SyntaxError("variable not allowed")
panic("unreachable")
}
l.ConsumeToken('$')
return &types.Variable{Name: l.ConsumeIdent(), Loc: loc}
case scanner.Int, scanner.Float, scanner.String, scanner.Ident:
lit := l.ConsumeLiteral()
if lit.Type == scanner.Ident && lit.Text == "null" {
return &types.NullValue{Loc: 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 []types.Value
for l.Peek() != ']' {
list = append(list, ParseLiteral(l, constOnly))
}
l.ConsumeToken(']')
return &types.ListValue{Values: list, Loc: loc}
case '{':
l.ConsumeToken('{')
var fields []*types.ObjectField
for l.Peek() != '}' {
name := l.ConsumeIdentWithLoc()
l.ConsumeToken(':')
value := ParseLiteral(l, constOnly)
fields = append(fields, &types.ObjectField{Name: name, Value: value})
}
l.ConsumeToken('}')
return &types.ObjectValue{Fields: fields, Loc: loc}
default:
l.SyntaxError("invalid value")
panic("unreachable")
}
}

View File

@ -0,0 +1,67 @@
package common
import (
"github.com/graph-gophers/graphql-go/errors"
"github.com/graph-gophers/graphql-go/types"
)
func ParseType(l *Lexer) types.Type {
t := parseNullType(l)
if l.Peek() == '!' {
l.ConsumeToken('!')
return &types.NonNull{OfType: t}
}
return t
}
func parseNullType(l *Lexer) types.Type {
if l.Peek() == '[' {
l.ConsumeToken('[')
ofType := ParseType(l)
l.ConsumeToken(']')
return &types.List{OfType: ofType}
}
return &types.TypeName{Ident: l.ConsumeIdentWithLoc()}
}
type Resolver func(name string) types.Type
// ResolveType attempts to resolve a type's name against a resolving function.
// This function is used when one needs to check if a TypeName exists in the resolver (typically a Schema).
//
// In the example below, ResolveType would be used to check if the resolving function
// returns a valid type for Dimension:
//
// type Profile {
// picture(dimensions: Dimension): Url
// }
//
// ResolveType recursively unwraps List and NonNull types until a NamedType is reached.
func ResolveType(t types.Type, resolver Resolver) (types.Type, *errors.QueryError) {
switch t := t.(type) {
case *types.List:
ofType, err := ResolveType(t.OfType, resolver)
if err != nil {
return nil, err
}
return &types.List{OfType: ofType}, nil
case *types.NonNull:
ofType, err := ResolveType(t.OfType, resolver)
if err != nil {
return nil, err
}
return &types.NonNull{OfType: ofType}, nil
case *types.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
}
}

View File

@ -0,0 +1,37 @@
package common
import (
"github.com/graph-gophers/graphql-go/types"
)
func ParseInputValue(l *Lexer) *types.InputValueDefinition {
p := &types.InputValueDefinition{}
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)
}
p.Directives = ParseDirectives(l)
return p
}
func ParseArgumentList(l *Lexer) types.ArgumentList {
var args types.ArgumentList
l.ConsumeToken('(')
for l.Peek() != ')' {
name := l.ConsumeIdentWithLoc()
l.ConsumeToken(':')
value := ParseLiteral(l, false)
args = append(args, &types.Argument{
Name: name,
Value: value,
})
}
l.ConsumeToken(')')
return args
}

View File

@ -0,0 +1,381 @@
package exec
import (
"bytes"
"context"
"encoding/json"
"fmt"
"reflect"
"sync"
"time"
"github.com/graph-gophers/graphql-go/errors"
"github.com/graph-gophers/graphql-go/internal/exec/resolvable"
"github.com/graph-gophers/graphql-go/internal/exec/selected"
"github.com/graph-gophers/graphql-go/internal/query"
"github.com/graph-gophers/graphql-go/log"
"github.com/graph-gophers/graphql-go/trace"
"github.com/graph-gophers/graphql-go/types"
)
type Request struct {
selected.Request
Limiter chan struct{}
Tracer trace.Tracer
Logger log.Logger
PanicHandler errors.PanicHandler
SubscribeResolverTimeout time.Duration
}
func (r *Request) handlePanic(ctx context.Context) {
if value := recover(); value != nil {
r.Logger.LogPanic(ctx, value)
r.AddError(r.PanicHandler.MakePanicError(ctx, value))
}
}
type extensionser interface {
Extensions() map[string]interface{}
}
func (r *Request) Execute(ctx context.Context, s *resolvable.Schema, op *types.OperationDefinition) ([]byte, []*errors.QueryError) {
var out bytes.Buffer
func() {
defer r.handlePanic(ctx)
sels := selected.ApplyOperation(&r.Request, s, op)
r.execSelections(ctx, sels, nil, s, s.Resolver, &out, op.Type == query.Mutation)
}()
if err := ctx.Err(); err != nil {
return nil, []*errors.QueryError{errors.Errorf("%s", err)}
}
return out.Bytes(), r.Errs
}
type fieldToExec struct {
field *selected.SchemaField
sels []selected.Selection
resolver reflect.Value
out *bytes.Buffer
}
func resolvedToNull(b *bytes.Buffer) bool {
return bytes.Equal(b.Bytes(), []byte("null"))
}
func (r *Request) execSelections(ctx context.Context, sels []selected.Selection, path *pathSegment, s *resolvable.Schema, resolver reflect.Value, out *bytes.Buffer, serially bool) {
async := !serially && selected.HasAsyncSel(sels)
var fields []*fieldToExec
collectFieldsToResolve(sels, s, resolver, &fields, make(map[string]*fieldToExec))
if async {
var wg sync.WaitGroup
wg.Add(len(fields))
for _, f := range fields {
go func(f *fieldToExec) {
defer wg.Done()
defer r.handlePanic(ctx)
f.out = new(bytes.Buffer)
execFieldSelection(ctx, r, s, f, &pathSegment{path, f.field.Alias}, true)
}(f)
}
wg.Wait()
} else {
for _, f := range fields {
f.out = new(bytes.Buffer)
execFieldSelection(ctx, r, s, f, &pathSegment{path, f.field.Alias}, true)
}
}
out.WriteByte('{')
for i, f := range fields {
// If a non-nullable child resolved to null, an error was added to the
// "errors" list in the response, so this field resolves to null.
// If this field is non-nullable, the error is propagated to its parent.
if _, ok := f.field.Type.(*types.NonNull); ok && resolvedToNull(f.out) {
out.Reset()
out.Write([]byte("null"))
return
}
if i > 0 {
out.WriteByte(',')
}
out.WriteByte('"')
out.WriteString(f.field.Alias)
out.WriteByte('"')
out.WriteByte(':')
out.Write(f.out.Bytes())
}
out.WriteByte('}')
}
func collectFieldsToResolve(sels []selected.Selection, s *resolvable.Schema, resolver reflect.Value, fields *[]*fieldToExec, fieldByAlias map[string]*fieldToExec) {
for _, sel := range sels {
switch sel := sel.(type) {
case *selected.SchemaField:
field, ok := fieldByAlias[sel.Alias]
if !ok { // validation already checked for conflict (TODO)
field = &fieldToExec{field: sel, resolver: resolver}
fieldByAlias[sel.Alias] = field
*fields = append(*fields, field)
}
field.sels = append(field.sels, sel.Sels...)
case *selected.TypenameField:
_, ok := fieldByAlias[sel.Alias]
if !ok {
res := reflect.ValueOf(typeOf(sel, resolver))
f := s.FieldTypename
f.TypeName = res.String()
sf := &selected.SchemaField{
Field: f,
Alias: sel.Alias,
FixedResult: res,
}
field := &fieldToExec{field: sf, resolver: resolver}
*fields = append(*fields, field)
fieldByAlias[sel.Alias] = field
}
case *selected.TypeAssertion:
out := resolver.Method(sel.MethodIndex).Call(nil)
if !out[1].Bool() {
continue
}
collectFieldsToResolve(sel.Sels, s, out[0], fields, fieldByAlias)
default:
panic("unreachable")
}
}
}
func typeOf(tf *selected.TypenameField, resolver reflect.Value) string {
if len(tf.TypeAssertions) == 0 {
return tf.Name
}
for name, a := range tf.TypeAssertions {
out := resolver.Method(a.MethodIndex).Call(nil)
if out[1].Bool() {
return name
}
}
return ""
}
func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f *fieldToExec, path *pathSegment, applyLimiter bool) {
if applyLimiter {
r.Limiter <- struct{}{}
}
var result reflect.Value
var err *errors.QueryError
traceCtx, finish := r.Tracer.TraceField(ctx, f.field.TraceLabel, f.field.TypeName, f.field.Name, !f.field.Async, f.field.Args)
defer func() {
finish(err)
}()
err = func() (err *errors.QueryError) {
defer func() {
if panicValue := recover(); panicValue != nil {
r.Logger.LogPanic(ctx, panicValue)
err = r.PanicHandler.MakePanicError(ctx, panicValue)
err.Path = path.toSlice()
}
}()
if f.field.FixedResult.IsValid() {
result = f.field.FixedResult
return nil
}
if err := traceCtx.Err(); err != nil {
return errors.Errorf("%s", err) // don't execute any more resolvers if context got cancelled
}
res := f.resolver
if f.field.UseMethodResolver() {
var in []reflect.Value
if f.field.HasContext {
in = append(in, reflect.ValueOf(traceCtx))
}
if f.field.ArgsPacker != nil {
in = append(in, f.field.PackedArgs)
}
callOut := res.Method(f.field.MethodIndex).Call(in)
result = callOut[0]
if f.field.HasError && !callOut[1].IsNil() {
resolverErr := callOut[1].Interface().(error)
err := errors.Errorf("%s", resolverErr)
err.Path = path.toSlice()
err.ResolverError = resolverErr
if ex, ok := callOut[1].Interface().(extensionser); ok {
err.Extensions = ex.Extensions()
}
return err
}
} else {
// TODO extract out unwrapping ptr logic to a common place
if res.Kind() == reflect.Ptr {
res = res.Elem()
}
result = res.FieldByIndex(f.field.FieldIndex)
}
return nil
}()
if applyLimiter {
<-r.Limiter
}
if err != nil {
// If an error occurred while resolving a field, it should be treated as though the field
// returned null, and an error must be added to the "errors" list in the response.
r.AddError(err)
f.out.WriteString("null")
return
}
r.execSelectionSet(traceCtx, f.sels, f.field.Type, path, s, result, f.out)
}
func (r *Request) execSelectionSet(ctx context.Context, sels []selected.Selection, typ types.Type, path *pathSegment, s *resolvable.Schema, resolver reflect.Value, out *bytes.Buffer) {
t, nonNull := unwrapNonNull(typ)
// a reflect.Value of a nil interface will show up as an Invalid value
if resolver.Kind() == reflect.Invalid || ((resolver.Kind() == reflect.Ptr || resolver.Kind() == reflect.Interface) && resolver.IsNil()) {
// If a field of a non-null type resolves to null (either because the
// function to resolve the field returned null or because an error occurred),
// add an error to the "errors" list in the response.
if nonNull {
err := errors.Errorf("graphql: got nil for non-null %q", t)
err.Path = path.toSlice()
r.AddError(err)
}
out.WriteString("null")
return
}
switch t.(type) {
case *types.ObjectTypeDefinition, *types.InterfaceTypeDefinition, *types.Union:
r.execSelections(ctx, sels, path, s, resolver, out, false)
return
}
// Any pointers or interfaces at this point should be non-nil, so we can get the actual value of them
// for serialization
if resolver.Kind() == reflect.Ptr || resolver.Kind() == reflect.Interface {
resolver = resolver.Elem()
}
switch t := t.(type) {
case *types.List:
r.execList(ctx, sels, t, path, s, resolver, out)
case *types.ScalarTypeDefinition:
v := resolver.Interface()
data, err := json.Marshal(v)
if err != nil {
panic(errors.Errorf("could not marshal %v: %s", v, err))
}
out.Write(data)
case *types.EnumTypeDefinition:
var stringer fmt.Stringer = resolver
if s, ok := resolver.Interface().(fmt.Stringer); ok {
stringer = s
}
name := stringer.String()
var valid bool
for _, v := range t.EnumValuesDefinition {
if v.EnumValue == name {
valid = true
break
}
}
if !valid {
err := errors.Errorf("Invalid value %s.\nExpected type %s, found %s.", name, t.Name, name)
err.Path = path.toSlice()
r.AddError(err)
out.WriteString("null")
return
}
out.WriteByte('"')
out.WriteString(name)
out.WriteByte('"')
default:
panic("unreachable")
}
}
func (r *Request) execList(ctx context.Context, sels []selected.Selection, typ *types.List, path *pathSegment, s *resolvable.Schema, resolver reflect.Value, out *bytes.Buffer) {
l := resolver.Len()
entryouts := make([]bytes.Buffer, l)
if selected.HasAsyncSel(sels) {
// Limit the number of concurrent goroutines spawned as it can lead to large
// memory spikes for large lists.
concurrency := cap(r.Limiter)
sem := make(chan struct{}, concurrency)
for i := 0; i < l; i++ {
sem <- struct{}{}
go func(i int) {
defer func() { <-sem }()
defer r.handlePanic(ctx)
r.execSelectionSet(ctx, sels, typ.OfType, &pathSegment{path, i}, s, resolver.Index(i), &entryouts[i])
}(i)
}
for i := 0; i < concurrency; i++ {
sem <- struct{}{}
}
} else {
for i := 0; i < l; i++ {
r.execSelectionSet(ctx, sels, typ.OfType, &pathSegment{path, i}, s, resolver.Index(i), &entryouts[i])
}
}
_, listOfNonNull := typ.OfType.(*types.NonNull)
out.WriteByte('[')
for i, entryout := range entryouts {
// If the list wraps a non-null type and one of the list elements
// resolves to null, then the entire list resolves to null.
if listOfNonNull && resolvedToNull(&entryout) {
out.Reset()
out.WriteString("null")
return
}
if i > 0 {
out.WriteByte(',')
}
out.Write(entryout.Bytes())
}
out.WriteByte(']')
}
func unwrapNonNull(t types.Type) (types.Type, bool) {
if nn, ok := t.(*types.NonNull); ok {
return nn.OfType, true
}
return t, false
}
type pathSegment struct {
parent *pathSegment
value interface{}
}
func (p *pathSegment) toSlice() []interface{} {
if p == nil {
return nil
}
return append(p.parent.toSlice(), p.value)
}

View File

@ -0,0 +1,390 @@
package packer
import (
"fmt"
"math"
"reflect"
"strings"
"github.com/graph-gophers/graphql-go/decode"
"github.com/graph-gophers/graphql-go/errors"
"github.com/graph-gophers/graphql-go/types"
)
type packer interface {
Pack(value interface{}) (reflect.Value, error)
}
type Builder struct {
packerMap map[typePair]*packerMapEntry
structPackers []*StructPacker
}
type typePair struct {
graphQLType types.Type
resolverType reflect.Type
}
type packerMapEntry struct {
packer packer
targets []*packer
}
func NewBuilder() *Builder {
return &Builder{
packerMap: make(map[typePair]*packerMapEntry),
}
}
func (b *Builder) Finish() error {
for _, entry := range b.packerMap {
for _, target := range entry.targets {
*target = entry.packer
}
}
for _, p := range b.structPackers {
p.defaultStruct = reflect.New(p.structType).Elem()
for _, f := range p.fields {
if defaultVal := f.field.Default; defaultVal != nil {
v, err := f.fieldPacker.Pack(defaultVal.Deserialize(nil))
if err != nil {
return err
}
p.defaultStruct.FieldByIndex(f.fieldIndex).Set(v)
}
}
}
return nil
}
func (b *Builder) assignPacker(target *packer, schemaType types.Type, reflectType reflect.Type) error {
k := typePair{schemaType, reflectType}
ref, ok := b.packerMap[k]
if !ok {
ref = &packerMapEntry{}
b.packerMap[k] = ref
var err error
ref.packer, err = b.makePacker(schemaType, reflectType)
if err != nil {
return err
}
}
ref.targets = append(ref.targets, target)
return nil
}
func (b *Builder) makePacker(schemaType types.Type, reflectType reflect.Type) (packer, error) {
t, nonNull := unwrapNonNull(schemaType)
if !nonNull {
if reflectType.Kind() == reflect.Ptr {
elemType := reflectType.Elem()
addPtr := true
if _, ok := t.(*types.InputObject); ok {
elemType = reflectType // keep pointer for input objects
addPtr = false
}
elem, err := b.makeNonNullPacker(t, elemType)
if err != nil {
return nil, err
}
return &nullPacker{
elemPacker: elem,
valueType: reflectType,
addPtr: addPtr,
}, nil
} else if isNullable(reflectType) {
elemType := reflectType
addPtr := false
elem, err := b.makeNonNullPacker(t, elemType)
if err != nil {
return nil, err
}
return &nullPacker{
elemPacker: elem,
valueType: reflectType,
addPtr: addPtr,
}, nil
} else {
return nil, fmt.Errorf("%s is not a pointer or a nullable type", reflectType)
}
}
return b.makeNonNullPacker(t, reflectType)
}
func (b *Builder) makeNonNullPacker(schemaType types.Type, reflectType reflect.Type) (packer, error) {
if u, ok := reflect.New(reflectType).Interface().(decode.Unmarshaler); ok {
if !u.ImplementsGraphQLType(schemaType.String()) {
return nil, fmt.Errorf("can not unmarshal %s into %s", schemaType, reflectType)
}
return &unmarshalerPacker{
ValueType: reflectType,
}, nil
}
switch t := schemaType.(type) {
case *types.ScalarTypeDefinition:
return &ValuePacker{
ValueType: reflectType,
}, nil
case *types.EnumTypeDefinition:
if reflectType.Kind() != reflect.String {
return nil, fmt.Errorf("wrong type, expected %s", reflect.String)
}
return &ValuePacker{
ValueType: reflectType,
}, nil
case *types.InputObject:
e, err := b.MakeStructPacker(t.Values, reflectType)
if err != nil {
return nil, err
}
return e, nil
case *types.List:
if reflectType.Kind() != reflect.Slice {
return nil, fmt.Errorf("expected slice, got %s", reflectType)
}
p := &listPacker{
sliceType: reflectType,
}
if err := b.assignPacker(&p.elem, t.OfType, reflectType.Elem()); err != nil {
return nil, err
}
return p, nil
case *types.ObjectTypeDefinition, *types.InterfaceTypeDefinition, *types.Union:
return nil, fmt.Errorf("type of kind %s can not be used as input", t.Kind())
default:
panic("unreachable")
}
}
func (b *Builder) MakeStructPacker(values []*types.InputValueDefinition, typ reflect.Type) (*StructPacker, error) {
structType := typ
usePtr := false
if typ.Kind() == reflect.Ptr {
structType = typ.Elem()
usePtr = true
}
if structType.Kind() != reflect.Struct {
return nil, fmt.Errorf("expected struct or pointer to struct, got %s (hint: missing `args struct { ... }` wrapper for field arguments?)", typ)
}
var fields []*structPackerField
for _, v := range values {
fe := &structPackerField{field: v}
fx := func(n string) bool {
return strings.EqualFold(stripUnderscore(n), stripUnderscore(v.Name.Name))
}
sf, ok := structType.FieldByNameFunc(fx)
if !ok {
return nil, fmt.Errorf("%s does not define field %q (hint: missing `args struct { ... }` wrapper for field arguments, or missing field on input struct)", typ, v.Name.Name)
}
if sf.PkgPath != "" {
return nil, fmt.Errorf("field %q must be exported", sf.Name)
}
fe.fieldIndex = sf.Index
ft := v.Type
if v.Default != nil {
ft, _ = unwrapNonNull(ft)
ft = &types.NonNull{OfType: ft}
}
if err := b.assignPacker(&fe.fieldPacker, ft, sf.Type); err != nil {
return nil, fmt.Errorf("field %q: %s", sf.Name, err)
}
fields = append(fields, fe)
}
p := &StructPacker{
structType: structType,
usePtr: usePtr,
fields: fields,
}
b.structPackers = append(b.structPackers, p)
return p, nil
}
type StructPacker struct {
structType reflect.Type
usePtr bool
defaultStruct reflect.Value
fields []*structPackerField
}
type structPackerField struct {
field *types.InputValueDefinition
fieldIndex []int
fieldPacker packer
}
func (p *StructPacker) Pack(value interface{}) (reflect.Value, error) {
if value == nil {
return reflect.Value{}, errors.Errorf("got null for non-null")
}
values := value.(map[string]interface{})
v := reflect.New(p.structType)
v.Elem().Set(p.defaultStruct)
for _, f := range p.fields {
if value, ok := values[f.field.Name.Name]; ok {
packed, err := f.fieldPacker.Pack(value)
if err != nil {
return reflect.Value{}, err
}
v.Elem().FieldByIndex(f.fieldIndex).Set(packed)
}
}
if !p.usePtr {
return v.Elem(), nil
}
return v, nil
}
type listPacker struct {
sliceType reflect.Type
elem packer
}
func (e *listPacker) Pack(value interface{}) (reflect.Value, error) {
list, ok := value.([]interface{})
if !ok {
list = []interface{}{value}
}
v := reflect.MakeSlice(e.sliceType, len(list), len(list))
for i := range list {
packed, err := e.elem.Pack(list[i])
if err != nil {
return reflect.Value{}, err
}
v.Index(i).Set(packed)
}
return v, nil
}
type nullPacker struct {
elemPacker packer
valueType reflect.Type
addPtr bool
}
func (p *nullPacker) Pack(value interface{}) (reflect.Value, error) {
if value == nil && !isNullable(p.valueType) {
return reflect.Zero(p.valueType), nil
}
v, err := p.elemPacker.Pack(value)
if err != nil {
return reflect.Value{}, err
}
if p.addPtr {
ptr := reflect.New(p.valueType.Elem())
ptr.Elem().Set(v)
return ptr, nil
}
return v, nil
}
type ValuePacker struct {
ValueType reflect.Type
}
func (p *ValuePacker) Pack(value interface{}) (reflect.Value, error) {
if value == nil {
return reflect.Value{}, errors.Errorf("got null for non-null")
}
coerced, err := unmarshalInput(p.ValueType, value)
if err != nil {
return reflect.Value{}, fmt.Errorf("could not unmarshal %#v (%T) into %s: %s", value, value, p.ValueType, err)
}
return reflect.ValueOf(coerced), nil
}
type unmarshalerPacker struct {
ValueType reflect.Type
}
func (p *unmarshalerPacker) Pack(value interface{}) (reflect.Value, error) {
if value == nil && !isNullable(p.ValueType) {
return reflect.Value{}, errors.Errorf("got null for non-null")
}
v := reflect.New(p.ValueType)
if err := v.Interface().(decode.Unmarshaler).UnmarshalGraphQL(value); err != nil {
return reflect.Value{}, err
}
return v.Elem(), nil
}
func unmarshalInput(typ reflect.Type, input interface{}) (interface{}, error) {
if reflect.TypeOf(input) == typ {
return input, nil
}
switch typ.Kind() {
case reflect.Int32:
switch input := input.(type) {
case int:
if input < math.MinInt32 || input > math.MaxInt32 {
return nil, fmt.Errorf("not a 32-bit integer")
}
return int32(input), nil
case float64:
coerced := int32(input)
if input < math.MinInt32 || input > math.MaxInt32 || float64(coerced) != input {
return nil, fmt.Errorf("not a 32-bit integer")
}
return coerced, nil
}
case reflect.Float64:
switch input := input.(type) {
case int32:
return float64(input), nil
case int:
return float64(input), nil
}
case reflect.String:
if reflect.TypeOf(input).ConvertibleTo(typ) {
return reflect.ValueOf(input).Convert(typ).Interface(), nil
}
}
return nil, fmt.Errorf("incompatible type")
}
func unwrapNonNull(t types.Type) (types.Type, bool) {
if nn, ok := t.(*types.NonNull); ok {
return nn.OfType, true
}
return t, false
}
func stripUnderscore(s string) string {
return strings.Replace(s, "_", "", -1)
}
// NullUnmarshaller is an unmarshaller that can handle a nil input
type NullUnmarshaller interface {
decode.Unmarshaler
Nullable()
}
func isNullable(t reflect.Type) bool {
_, ok := reflect.New(t).Interface().(NullUnmarshaller)
return ok
}

View File

@ -0,0 +1,70 @@
package resolvable
import (
"reflect"
"github.com/graph-gophers/graphql-go/introspection"
"github.com/graph-gophers/graphql-go/types"
)
// Meta defines the details of the metadata schema for introspection.
type Meta struct {
FieldSchema Field
FieldType Field
FieldTypename Field
Schema *Object
Type *Object
}
func newMeta(s *types.Schema) *Meta {
var err error
b := newBuilder(s)
metaSchema := s.Types["__Schema"].(*types.ObjectTypeDefinition)
so, err := b.makeObjectExec(metaSchema.Name, metaSchema.Fields, nil, false, reflect.TypeOf(&introspection.Schema{}))
if err != nil {
panic(err)
}
metaType := s.Types["__Type"].(*types.ObjectTypeDefinition)
t, err := b.makeObjectExec(metaType.Name, metaType.Fields, nil, false, reflect.TypeOf(&introspection.Type{}))
if err != nil {
panic(err)
}
if err := b.finish(); err != nil {
panic(err)
}
fieldTypename := Field{
FieldDefinition: types.FieldDefinition{
Name: "__typename",
Type: &types.NonNull{OfType: s.Types["String"]},
},
TraceLabel: "GraphQL field: __typename",
}
fieldSchema := Field{
FieldDefinition: types.FieldDefinition{
Name: "__schema",
Type: s.Types["__Schema"],
},
TraceLabel: "GraphQL field: __schema",
}
fieldType := Field{
FieldDefinition: types.FieldDefinition{
Name: "__type",
Type: s.Types["__Type"],
},
TraceLabel: "GraphQL field: __type",
}
return &Meta{
FieldSchema: fieldSchema,
FieldTypename: fieldTypename,
FieldType: fieldType,
Schema: so,
Type: t,
}
}

View File

@ -0,0 +1,453 @@
package resolvable
import (
"context"
"fmt"
"reflect"
"strings"
"github.com/graph-gophers/graphql-go/decode"
"github.com/graph-gophers/graphql-go/internal/exec/packer"
"github.com/graph-gophers/graphql-go/types"
)
type Schema struct {
*Meta
types.Schema
Query Resolvable
Mutation Resolvable
Subscription Resolvable
Resolver reflect.Value
}
type Resolvable interface {
isResolvable()
}
type Object struct {
Name string
Fields map[string]*Field
TypeAssertions map[string]*TypeAssertion
}
type Field struct {
types.FieldDefinition
TypeName string
MethodIndex int
FieldIndex []int
HasContext bool
HasError bool
ArgsPacker *packer.StructPacker
ValueExec Resolvable
TraceLabel string
}
func (f *Field) UseMethodResolver() bool {
return len(f.FieldIndex) == 0
}
type TypeAssertion struct {
MethodIndex int
TypeExec Resolvable
}
type List struct {
Elem Resolvable
}
type Scalar struct{}
func (*Object) isResolvable() {}
func (*List) isResolvable() {}
func (*Scalar) isResolvable() {}
func ApplyResolver(s *types.Schema, resolver interface{}) (*Schema, error) {
if resolver == nil {
return &Schema{Meta: newMeta(s), Schema: *s}, nil
}
b := newBuilder(s)
var query, mutation, subscription Resolvable
if t, ok := s.EntryPoints["query"]; ok {
if err := b.assignExec(&query, t, reflect.TypeOf(resolver)); err != nil {
return nil, err
}
}
if t, ok := s.EntryPoints["mutation"]; ok {
if err := b.assignExec(&mutation, t, reflect.TypeOf(resolver)); err != nil {
return nil, err
}
}
if t, ok := s.EntryPoints["subscription"]; ok {
if err := b.assignExec(&subscription, t, reflect.TypeOf(resolver)); err != nil {
return nil, err
}
}
if err := b.finish(); err != nil {
return nil, err
}
return &Schema{
Meta: newMeta(s),
Schema: *s,
Resolver: reflect.ValueOf(resolver),
Query: query,
Mutation: mutation,
Subscription: subscription,
}, nil
}
type execBuilder struct {
schema *types.Schema
resMap map[typePair]*resMapEntry
packerBuilder *packer.Builder
}
type typePair struct {
graphQLType types.Type
resolverType reflect.Type
}
type resMapEntry struct {
exec Resolvable
targets []*Resolvable
}
func newBuilder(s *types.Schema) *execBuilder {
return &execBuilder{
schema: s,
resMap: make(map[typePair]*resMapEntry),
packerBuilder: packer.NewBuilder(),
}
}
func (b *execBuilder) finish() error {
for _, entry := range b.resMap {
for _, target := range entry.targets {
*target = entry.exec
}
}
return b.packerBuilder.Finish()
}
func (b *execBuilder) assignExec(target *Resolvable, t types.Type, resolverType reflect.Type) error {
k := typePair{t, resolverType}
ref, ok := b.resMap[k]
if !ok {
ref = &resMapEntry{}
b.resMap[k] = ref
var err error
ref.exec, err = b.makeExec(t, resolverType)
if err != nil {
return err
}
}
ref.targets = append(ref.targets, target)
return nil
}
func (b *execBuilder) makeExec(t types.Type, resolverType reflect.Type) (Resolvable, error) {
var nonNull bool
t, nonNull = unwrapNonNull(t)
switch t := t.(type) {
case *types.ObjectTypeDefinition:
return b.makeObjectExec(t.Name, t.Fields, nil, nonNull, resolverType)
case *types.InterfaceTypeDefinition:
return b.makeObjectExec(t.Name, t.Fields, t.PossibleTypes, nonNull, resolverType)
case *types.Union:
return b.makeObjectExec(t.Name, nil, t.UnionMemberTypes, nonNull, resolverType)
}
if !nonNull {
if resolverType.Kind() != reflect.Ptr {
return nil, fmt.Errorf("%s is not a pointer", resolverType)
}
resolverType = resolverType.Elem()
}
switch t := t.(type) {
case *types.ScalarTypeDefinition:
return makeScalarExec(t, resolverType)
case *types.EnumTypeDefinition:
return &Scalar{}, nil
case *types.List:
if resolverType.Kind() != reflect.Slice {
return nil, fmt.Errorf("%s is not a slice", resolverType)
}
e := &List{}
if err := b.assignExec(&e.Elem, t.OfType, resolverType.Elem()); err != nil {
return nil, err
}
return e, nil
default:
panic("invalid type: " + t.String())
}
}
func makeScalarExec(t *types.ScalarTypeDefinition, resolverType reflect.Type) (Resolvable, error) {
implementsType := false
switch r := reflect.New(resolverType).Interface().(type) {
case *int32:
implementsType = t.Name == "Int"
case *float64:
implementsType = t.Name == "Float"
case *string:
implementsType = t.Name == "String"
case *bool:
implementsType = t.Name == "Boolean"
case decode.Unmarshaler:
implementsType = r.ImplementsGraphQLType(t.Name)
}
if !implementsType {
return nil, fmt.Errorf("can not use %s as %s", resolverType, t.Name)
}
return &Scalar{}, nil
}
func (b *execBuilder) makeObjectExec(typeName string, fields types.FieldsDefinition, possibleTypes []*types.ObjectTypeDefinition,
nonNull bool, resolverType reflect.Type) (*Object, error) {
if !nonNull {
if resolverType.Kind() != reflect.Ptr && resolverType.Kind() != reflect.Interface {
return nil, fmt.Errorf("%s is not a pointer or interface", resolverType)
}
}
methodHasReceiver := resolverType.Kind() != reflect.Interface
Fields := make(map[string]*Field)
rt := unwrapPtr(resolverType)
fieldsCount := fieldCount(rt, map[string]int{})
for _, f := range fields {
var fieldIndex []int
methodIndex := findMethod(resolverType, f.Name)
if b.schema.UseFieldResolvers && methodIndex == -1 {
if fieldsCount[strings.ToLower(stripUnderscore(f.Name))] > 1 {
return nil, fmt.Errorf("%s does not resolve %q: ambiguous field %q", resolverType, typeName, f.Name)
}
fieldIndex = findField(rt, f.Name, []int{})
}
if methodIndex == -1 && len(fieldIndex) == 0 {
hint := ""
if findMethod(reflect.PtrTo(resolverType), f.Name) != -1 {
hint = " (hint: the method exists on the pointer type)"
}
return nil, fmt.Errorf("%s does not resolve %q: missing method for field %q%s", resolverType, typeName, f.Name, hint)
}
var m reflect.Method
var sf reflect.StructField
if methodIndex != -1 {
m = resolverType.Method(methodIndex)
} else {
sf = rt.FieldByIndex(fieldIndex)
}
fe, err := b.makeFieldExec(typeName, f, m, sf, methodIndex, fieldIndex, methodHasReceiver)
if err != nil {
var resolverName string
if methodIndex != -1 {
resolverName = m.Name
} else {
resolverName = sf.Name
}
return nil, fmt.Errorf("%s\n\tused by (%s).%s", err, resolverType, resolverName)
}
Fields[f.Name] = fe
}
// Check type assertions when
// 1) using method resolvers
// 2) Or resolver is not an interface type
typeAssertions := make(map[string]*TypeAssertion)
if !b.schema.UseFieldResolvers || resolverType.Kind() != reflect.Interface {
for _, impl := range possibleTypes {
methodIndex := findMethod(resolverType, "To"+impl.Name)
if methodIndex == -1 {
return nil, fmt.Errorf("%s does not resolve %q: missing method %q to convert to %q", resolverType, typeName, "To"+impl.Name, impl.Name)
}
if resolverType.Method(methodIndex).Type.NumOut() != 2 {
return nil, fmt.Errorf("%s does not resolve %q: method %q should return a value and a bool indicating success", resolverType, typeName, "To"+impl.Name)
}
a := &TypeAssertion{
MethodIndex: methodIndex,
}
if err := b.assignExec(&a.TypeExec, impl, resolverType.Method(methodIndex).Type.Out(0)); err != nil {
return nil, err
}
typeAssertions[impl.Name] = a
}
}
return &Object{
Name: typeName,
Fields: Fields,
TypeAssertions: typeAssertions,
}, nil
}
var contextType = reflect.TypeOf((*context.Context)(nil)).Elem()
var errorType = reflect.TypeOf((*error)(nil)).Elem()
func (b *execBuilder) makeFieldExec(typeName string, f *types.FieldDefinition, m reflect.Method, sf reflect.StructField,
methodIndex int, fieldIndex []int, methodHasReceiver bool) (*Field, error) {
var argsPacker *packer.StructPacker
var hasError bool
var hasContext bool
// Validate resolver method only when there is one
if methodIndex != -1 {
in := make([]reflect.Type, m.Type.NumIn())
for i := range in {
in[i] = m.Type.In(i)
}
if methodHasReceiver {
in = in[1:] // first parameter is receiver
}
hasContext = len(in) > 0 && in[0] == contextType
if hasContext {
in = in[1:]
}
if len(f.Arguments) > 0 {
if len(in) == 0 {
return nil, fmt.Errorf("must have parameter for field arguments")
}
var err error
argsPacker, err = b.packerBuilder.MakeStructPacker(f.Arguments, in[0])
if err != nil {
return nil, err
}
in = in[1:]
}
if len(in) > 0 {
return nil, fmt.Errorf("too many parameters")
}
maxNumOfReturns := 2
if m.Type.NumOut() < maxNumOfReturns-1 {
return nil, fmt.Errorf("too few return values")
}
if m.Type.NumOut() > maxNumOfReturns {
return nil, fmt.Errorf("too many return values")
}
hasError = m.Type.NumOut() == maxNumOfReturns
if hasError {
if m.Type.Out(maxNumOfReturns-1) != errorType {
return nil, fmt.Errorf(`must have "error" as its last return value`)
}
}
}
fe := &Field{
FieldDefinition: *f,
TypeName: typeName,
MethodIndex: methodIndex,
FieldIndex: fieldIndex,
HasContext: hasContext,
ArgsPacker: argsPacker,
HasError: hasError,
TraceLabel: fmt.Sprintf("GraphQL field: %s.%s", typeName, f.Name),
}
var out reflect.Type
if methodIndex != -1 {
out = m.Type.Out(0)
sub, ok := b.schema.EntryPoints["subscription"]
if ok && typeName == sub.TypeName() && out.Kind() == reflect.Chan {
out = m.Type.Out(0).Elem()
}
} else {
out = sf.Type
}
if err := b.assignExec(&fe.ValueExec, f.Type, out); err != nil {
return nil, err
}
return fe, nil
}
func findMethod(t reflect.Type, name string) int {
for i := 0; i < t.NumMethod(); i++ {
if strings.EqualFold(stripUnderscore(name), stripUnderscore(t.Method(i).Name)) {
return i
}
}
return -1
}
func findField(t reflect.Type, name string, index []int) []int {
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if field.Type.Kind() == reflect.Struct && field.Anonymous {
newIndex := findField(field.Type, name, []int{i})
if len(newIndex) > 1 {
return append(index, newIndex...)
}
}
if strings.EqualFold(stripUnderscore(name), stripUnderscore(field.Name)) {
return append(index, i)
}
}
return index
}
// fieldCount helps resolve ambiguity when more than one embedded struct contains fields with the same name.
func fieldCount(t reflect.Type, count map[string]int) map[string]int {
if t.Kind() != reflect.Struct {
return nil
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fieldName := strings.ToLower(stripUnderscore(field.Name))
if field.Type.Kind() == reflect.Struct && field.Anonymous {
count = fieldCount(field.Type, count)
} else {
if _, ok := count[fieldName]; !ok {
count[fieldName] = 0
}
count[fieldName]++
}
}
return count
}
func unwrapNonNull(t types.Type) (types.Type, bool) {
if nn, ok := t.(*types.NonNull); ok {
return nn.OfType, true
}
return t, false
}
func stripUnderscore(s string) string {
return strings.Replace(s, "_", "", -1)
}
func unwrapPtr(t reflect.Type) reflect.Type {
if t.Kind() == reflect.Ptr {
return t.Elem()
}
return t
}

View File

@ -0,0 +1,269 @@
package selected
import (
"fmt"
"reflect"
"sync"
"github.com/graph-gophers/graphql-go/errors"
"github.com/graph-gophers/graphql-go/internal/exec/packer"
"github.com/graph-gophers/graphql-go/internal/exec/resolvable"
"github.com/graph-gophers/graphql-go/internal/query"
"github.com/graph-gophers/graphql-go/introspection"
"github.com/graph-gophers/graphql-go/types"
)
type Request struct {
Schema *types.Schema
Doc *types.ExecutableDefinition
Vars map[string]interface{}
Mu sync.Mutex
Errs []*errors.QueryError
DisableIntrospection bool
}
func (r *Request) AddError(err *errors.QueryError) {
r.Mu.Lock()
r.Errs = append(r.Errs, err)
r.Mu.Unlock()
}
func ApplyOperation(r *Request, s *resolvable.Schema, op *types.OperationDefinition) []Selection {
var obj *resolvable.Object
switch op.Type {
case query.Query:
obj = s.Query.(*resolvable.Object)
case query.Mutation:
obj = s.Mutation.(*resolvable.Object)
case query.Subscription:
obj = s.Subscription.(*resolvable.Object)
}
return applySelectionSet(r, s, obj, op.Selections)
}
type Selection interface {
isSelection()
}
type SchemaField struct {
resolvable.Field
Alias string
Args map[string]interface{}
PackedArgs reflect.Value
Sels []Selection
Async bool
FixedResult reflect.Value
}
type TypeAssertion struct {
resolvable.TypeAssertion
Sels []Selection
}
type TypenameField struct {
resolvable.Object
Alias string
}
func (*SchemaField) isSelection() {}
func (*TypeAssertion) isSelection() {}
func (*TypenameField) isSelection() {}
func applySelectionSet(r *Request, s *resolvable.Schema, e *resolvable.Object, sels []types.Selection) (flattenedSels []Selection) {
for _, sel := range sels {
switch sel := sel.(type) {
case *types.Field:
field := sel
if skipByDirective(r, field.Directives) {
continue
}
switch field.Name.Name {
case "__typename":
// __typename is available even though r.DisableIntrospection == true
// because it is necessary when using union types and interfaces: https://graphql.org/learn/schema/#union-types
flattenedSels = append(flattenedSels, &TypenameField{
Object: *e,
Alias: field.Alias.Name,
})
case "__schema":
if !r.DisableIntrospection {
flattenedSels = append(flattenedSels, &SchemaField{
Field: s.Meta.FieldSchema,
Alias: field.Alias.Name,
Sels: applySelectionSet(r, s, s.Meta.Schema, field.SelectionSet),
Async: true,
FixedResult: reflect.ValueOf(introspection.WrapSchema(r.Schema)),
})
}
case "__type":
if !r.DisableIntrospection {
p := packer.ValuePacker{ValueType: reflect.TypeOf("")}
v, err := p.Pack(field.Arguments.MustGet("name").Deserialize(r.Vars))
if err != nil {
r.AddError(errors.Errorf("%s", err))
return nil
}
t, ok := r.Schema.Types[v.String()]
if !ok {
return nil
}
flattenedSels = append(flattenedSels, &SchemaField{
Field: s.Meta.FieldType,
Alias: field.Alias.Name,
Sels: applySelectionSet(r, s, s.Meta.Type, field.SelectionSet),
Async: true,
FixedResult: reflect.ValueOf(introspection.WrapType(t)),
})
}
default:
fe := e.Fields[field.Name.Name]
var args map[string]interface{}
var packedArgs reflect.Value
if fe.ArgsPacker != nil {
args = make(map[string]interface{})
for _, arg := range field.Arguments {
args[arg.Name.Name] = arg.Value.Deserialize(r.Vars)
}
var err error
packedArgs, err = fe.ArgsPacker.Pack(args)
if err != nil {
r.AddError(errors.Errorf("%s", err))
return
}
}
fieldSels := applyField(r, s, fe.ValueExec, field.SelectionSet)
flattenedSels = append(flattenedSels, &SchemaField{
Field: *fe,
Alias: field.Alias.Name,
Args: args,
PackedArgs: packedArgs,
Sels: fieldSels,
Async: fe.HasContext || fe.ArgsPacker != nil || fe.HasError || HasAsyncSel(fieldSels),
})
}
case *types.InlineFragment:
frag := sel
if skipByDirective(r, frag.Directives) {
continue
}
flattenedSels = append(flattenedSels, applyFragment(r, s, e, &frag.Fragment)...)
case *types.FragmentSpread:
spread := sel
if skipByDirective(r, spread.Directives) {
continue
}
flattenedSels = append(flattenedSels, applyFragment(r, s, e, &r.Doc.Fragments.Get(spread.Name.Name).Fragment)...)
default:
panic("invalid type")
}
}
return
}
func applyFragment(r *Request, s *resolvable.Schema, e *resolvable.Object, frag *types.Fragment) []Selection {
if frag.On.Name != e.Name {
t := r.Schema.Resolve(frag.On.Name)
face, ok := t.(*types.InterfaceTypeDefinition)
if !ok && frag.On.Name != "" {
a, ok2 := e.TypeAssertions[frag.On.Name]
if !ok2 {
panic(fmt.Errorf("%q does not implement %q", frag.On, e.Name)) // TODO proper error handling
}
return []Selection{&TypeAssertion{
TypeAssertion: *a,
Sels: applySelectionSet(r, s, a.TypeExec.(*resolvable.Object), frag.Selections),
}}
}
if ok && len(face.PossibleTypes) > 0 {
sels := []Selection{}
for _, t := range face.PossibleTypes {
if t.Name == e.Name {
return applySelectionSet(r, s, e, frag.Selections)
}
if a, ok := e.TypeAssertions[t.Name]; ok {
sels = append(sels, &TypeAssertion{
TypeAssertion: *a,
Sels: applySelectionSet(r, s, a.TypeExec.(*resolvable.Object), frag.Selections),
})
}
}
if len(sels) == 0 {
panic(fmt.Errorf("%q does not implement %q", e.Name, frag.On)) // TODO proper error handling
}
return sels
}
}
return applySelectionSet(r, s, e, frag.Selections)
}
func applyField(r *Request, s *resolvable.Schema, e resolvable.Resolvable, sels []types.Selection) []Selection {
switch e := e.(type) {
case *resolvable.Object:
return applySelectionSet(r, s, e, sels)
case *resolvable.List:
return applyField(r, s, e.Elem, sels)
case *resolvable.Scalar:
return nil
default:
panic("unreachable")
}
}
func skipByDirective(r *Request, directives types.DirectiveList) bool {
if d := directives.Get("skip"); d != nil {
p := packer.ValuePacker{ValueType: reflect.TypeOf(false)}
v, err := p.Pack(d.Arguments.MustGet("if").Deserialize(r.Vars))
if err != nil {
r.AddError(errors.Errorf("%s", err))
}
if err == nil && v.Bool() {
return true
}
}
if d := directives.Get("include"); d != nil {
p := packer.ValuePacker{ValueType: reflect.TypeOf(false)}
v, err := p.Pack(d.Arguments.MustGet("if").Deserialize(r.Vars))
if err != nil {
r.AddError(errors.Errorf("%s", err))
}
if err == nil && !v.Bool() {
return true
}
}
return false
}
func HasAsyncSel(sels []Selection) bool {
for _, sel := range sels {
switch sel := sel.(type) {
case *SchemaField:
if sel.Async {
return true
}
case *TypeAssertion:
if HasAsyncSel(sel.Sels) {
return true
}
case *TypenameField:
// sync
default:
panic("unreachable")
}
}
return false
}

View File

@ -0,0 +1,179 @@
package exec
import (
"bytes"
"context"
"encoding/json"
"fmt"
"reflect"
"time"
"github.com/graph-gophers/graphql-go/errors"
"github.com/graph-gophers/graphql-go/internal/exec/resolvable"
"github.com/graph-gophers/graphql-go/internal/exec/selected"
"github.com/graph-gophers/graphql-go/types"
)
type Response struct {
Data json.RawMessage
Errors []*errors.QueryError
}
func (r *Request) Subscribe(ctx context.Context, s *resolvable.Schema, op *types.OperationDefinition) <-chan *Response {
var result reflect.Value
var f *fieldToExec
var err *errors.QueryError
func() {
defer r.handlePanic(ctx)
sels := selected.ApplyOperation(&r.Request, s, op)
var fields []*fieldToExec
collectFieldsToResolve(sels, s, s.Resolver, &fields, make(map[string]*fieldToExec))
// TODO: move this check into validation.Validate
if len(fields) != 1 {
err = errors.Errorf("%s", "can subscribe to at most one subscription at a time")
return
}
f = fields[0]
var in []reflect.Value
if f.field.HasContext {
in = append(in, reflect.ValueOf(ctx))
}
if f.field.ArgsPacker != nil {
in = append(in, f.field.PackedArgs)
}
callOut := f.resolver.Method(f.field.MethodIndex).Call(in)
result = callOut[0]
if f.field.HasError && !callOut[1].IsNil() {
switch resolverErr := callOut[1].Interface().(type) {
case *errors.QueryError:
err = resolverErr
case error:
err = errors.Errorf("%s", resolverErr)
err.ResolverError = resolverErr
default:
panic(fmt.Errorf("can only deal with *QueryError and error types, got %T", resolverErr))
}
}
}()
// Handles the case where the locally executed func above panicked
if len(r.Request.Errs) > 0 {
return sendAndReturnClosed(&Response{Errors: r.Request.Errs})
}
if f == nil {
return sendAndReturnClosed(&Response{Errors: []*errors.QueryError{err}})
}
if err != nil {
if _, nonNullChild := f.field.Type.(*types.NonNull); nonNullChild {
return sendAndReturnClosed(&Response{Errors: []*errors.QueryError{err}})
}
return sendAndReturnClosed(&Response{Data: []byte(fmt.Sprintf(`{"%s":null}`, f.field.Alias)), Errors: []*errors.QueryError{err}})
}
if ctxErr := ctx.Err(); ctxErr != nil {
return sendAndReturnClosed(&Response{Errors: []*errors.QueryError{errors.Errorf("%s", ctxErr)}})
}
c := make(chan *Response)
// TODO: handle resolver nil channel better?
if result.IsZero() {
close(c)
return c
}
go func() {
for {
// Check subscription context
chosen, resp, ok := reflect.Select([]reflect.SelectCase{
{
Dir: reflect.SelectRecv,
Chan: reflect.ValueOf(ctx.Done()),
},
{
Dir: reflect.SelectRecv,
Chan: result,
},
})
switch chosen {
// subscription context done
case 0:
close(c)
return
// upstream received
case 1:
// upstream closed
if !ok {
close(c)
return
}
subR := &Request{
Request: selected.Request{
Doc: r.Request.Doc,
Vars: r.Request.Vars,
Schema: r.Request.Schema,
},
Limiter: r.Limiter,
Tracer: r.Tracer,
Logger: r.Logger,
}
var out bytes.Buffer
func() {
timeout := r.SubscribeResolverTimeout
if timeout == 0 {
timeout = time.Second
}
subCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
// resolve response
func() {
defer subR.handlePanic(subCtx)
var buf bytes.Buffer
subR.execSelectionSet(subCtx, f.sels, f.field.Type, &pathSegment{nil, f.field.Alias}, s, resp, &buf)
propagateChildError := false
if _, nonNullChild := f.field.Type.(*types.NonNull); nonNullChild && resolvedToNull(&buf) {
propagateChildError = true
}
if !propagateChildError {
out.WriteString(fmt.Sprintf(`{"%s":`, f.field.Alias))
out.Write(buf.Bytes())
out.WriteString(`}`)
}
}()
if err := subCtx.Err(); err != nil {
c <- &Response{Errors: []*errors.QueryError{errors.Errorf("%s", err)}}
return
}
// Send response within timeout
// TODO: maybe block until sent?
select {
case <-subCtx.Done():
case c <- &Response{Data: out.Bytes(), Errors: subR.Errs}:
}
}()
}
}
}()
return c
}
func sendAndReturnClosed(resp *Response) chan *Response {
c := make(chan *Response, 1)
c <- resp
close(c)
return c
}

View File

@ -0,0 +1,156 @@
package query
import (
"fmt"
"text/scanner"
"github.com/graph-gophers/graphql-go/errors"
"github.com/graph-gophers/graphql-go/internal/common"
"github.com/graph-gophers/graphql-go/types"
)
const (
Query types.OperationType = "QUERY"
Mutation types.OperationType = "MUTATION"
Subscription types.OperationType = "SUBSCRIPTION"
)
func Parse(queryString string) (*types.ExecutableDefinition, *errors.QueryError) {
l := common.NewLexer(queryString, false)
var execDef *types.ExecutableDefinition
err := l.CatchSyntaxError(func() { execDef = parseExecutableDefinition(l) })
if err != nil {
return nil, err
}
return execDef, nil
}
func parseExecutableDefinition(l *common.Lexer) *types.ExecutableDefinition {
ed := &types.ExecutableDefinition{}
l.ConsumeWhitespace()
for l.Peek() != scanner.EOF {
if l.Peek() == '{' {
op := &types.OperationDefinition{Type: Query, Loc: l.Location()}
op.Selections = parseSelectionSet(l)
ed.Operations = append(ed.Operations, op)
continue
}
loc := l.Location()
switch x := l.ConsumeIdent(); x {
case "query":
op := parseOperation(l, Query)
op.Loc = loc
ed.Operations = append(ed.Operations, op)
case "mutation":
ed.Operations = append(ed.Operations, parseOperation(l, Mutation))
case "subscription":
ed.Operations = append(ed.Operations, parseOperation(l, Subscription))
case "fragment":
frag := parseFragment(l)
frag.Loc = loc
ed.Fragments = append(ed.Fragments, frag)
default:
l.SyntaxError(fmt.Sprintf(`unexpected %q, expecting "fragment"`, x))
}
}
return ed
}
func parseOperation(l *common.Lexer, opType types.OperationType) *types.OperationDefinition {
op := &types.OperationDefinition{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) *types.FragmentDefinition {
f := &types.FragmentDefinition{}
f.Name = l.ConsumeIdentWithLoc()
l.ConsumeKeyword("on")
f.On = types.TypeName{Ident: l.ConsumeIdentWithLoc()}
f.Directives = common.ParseDirectives(l)
f.Selections = parseSelectionSet(l)
return f
}
func parseSelectionSet(l *common.Lexer) []types.Selection {
var sels []types.Selection
l.ConsumeToken('{')
for l.Peek() != '}' {
sels = append(sels, parseSelection(l))
}
l.ConsumeToken('}')
return sels
}
func parseSelection(l *common.Lexer) types.Selection {
if l.Peek() == '.' {
return parseSpread(l)
}
return parseFieldDef(l)
}
func parseFieldDef(l *common.Lexer) *types.Field {
f := &types.Field{}
f.Alias = l.ConsumeIdentWithLoc()
f.Name = f.Alias
if l.Peek() == ':' {
l.ConsumeToken(':')
f.Name = l.ConsumeIdentWithLoc()
}
if l.Peek() == '(' {
f.Arguments = common.ParseArgumentList(l)
}
f.Directives = common.ParseDirectives(l)
if l.Peek() == '{' {
f.SelectionSetLoc = l.Location()
f.SelectionSet = parseSelectionSet(l)
}
return f
}
func parseSpread(l *common.Lexer) types.Selection {
loc := l.Location()
l.ConsumeToken('.')
l.ConsumeToken('.')
l.ConsumeToken('.')
f := &types.InlineFragment{Loc: loc}
if l.Peek() == scanner.Ident {
ident := l.ConsumeIdentWithLoc()
if ident.Name != "on" {
fs := &types.FragmentSpread{
Name: ident,
Loc: loc,
}
fs.Directives = common.ParseDirectives(l)
return fs
}
f.On = types.TypeName{Ident: l.ConsumeIdentWithLoc()}
}
f.Directives = common.ParseDirectives(l)
f.Selections = parseSelectionSet(l)
return f
}

View File

@ -0,0 +1,203 @@
package schema
import (
"github.com/graph-gophers/graphql-go/types"
)
func init() {
_ = newMeta()
}
// newMeta initializes an instance of the meta Schema.
func newMeta() *types.Schema {
s := &types.Schema{
EntryPointNames: make(map[string]string),
Types: make(map[string]types.NamedType),
Directives: make(map[string]*types.DirectiveDefinition),
}
err := Parse(s, metaSrc, false)
if err != nil {
panic(err)
}
return s
}
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
# 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
}
`

View File

@ -0,0 +1,586 @@
package schema
import (
"fmt"
"text/scanner"
"github.com/graph-gophers/graphql-go/errors"
"github.com/graph-gophers/graphql-go/internal/common"
"github.com/graph-gophers/graphql-go/types"
)
// New initializes an instance of Schema.
func New() *types.Schema {
s := &types.Schema{
EntryPointNames: make(map[string]string),
Types: make(map[string]types.NamedType),
Directives: make(map[string]*types.DirectiveDefinition),
}
m := newMeta()
for n, t := range m.Types {
s.Types[n] = t
}
for n, d := range m.Directives {
s.Directives[n] = d
}
return s
}
func Parse(s *types.Schema, schemaString string, useStringDescriptions bool) error {
l := common.NewLexer(schemaString, useStringDescriptions)
err := l.CatchSyntaxError(func() { parseSchema(s, l) })
if err != nil {
return err
}
if err := mergeExtensions(s); 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.Arguments {
t, err := common.ResolveType(arg.Type, s.Resolve)
if err != nil {
return err
}
arg.Type = t
}
}
// https://graphql.github.io/graphql-spec/June2018/#sec-Root-Operation-Types
// > While any type can be the root operation type for a GraphQL operation, the type system definition language can
// > omit the schema definition when the query, mutation, and subscription root types are named Query, Mutation,
// > and Subscription respectively.
if len(s.EntryPointNames) == 0 {
if _, ok := s.Types["Query"]; ok {
s.EntryPointNames["query"] = "Query"
}
if _, ok := s.Types["Mutation"]; ok {
s.EntryPointNames["mutation"] = "Mutation"
}
if _, ok := s.Types["Subscription"]; ok {
s.EntryPointNames["subscription"] = "Subscription"
}
}
s.EntryPoints = make(map[string]types.NamedType)
for key, name := range s.EntryPointNames {
t, ok := s.Types[name]
if !ok {
return errors.Errorf("type %q not found", name)
}
s.EntryPoints[key] = t
}
// Interface types need validation: https://spec.graphql.org/draft/#sec-Interfaces.Interfaces-Implementing-Interfaces
for _, typeDef := range s.Types {
switch t := typeDef.(type) {
case *types.InterfaceTypeDefinition:
for i, implements := range t.Interfaces {
typ, ok := s.Types[implements.Name]
if !ok {
return errors.Errorf("interface %q not found", implements)
}
inteface, ok := typ.(*types.InterfaceTypeDefinition)
if !ok {
return errors.Errorf("type %q is not an interface", inteface)
}
for _, f := range inteface.Fields.Names() {
if t.Fields.Get(f) == nil {
return errors.Errorf("interface %q expects field %q but %q does not provide it", inteface.Name, f, t.Name)
}
}
t.Interfaces[i] = inteface
}
default:
continue
}
}
for _, obj := range s.Objects {
obj.Interfaces = make([]*types.InterfaceTypeDefinition, len(obj.InterfaceNames))
if err := resolveDirectives(s, obj.Directives, "OBJECT"); err != nil {
return err
}
for _, field := range obj.Fields {
if err := resolveDirectives(s, field.Directives, "FIELD_DEFINITION"); err != nil {
return err
}
}
for i, intfName := range obj.InterfaceNames {
t, ok := s.Types[intfName]
if !ok {
return errors.Errorf("interface %q not found", intfName)
}
intf, ok := t.(*types.InterfaceTypeDefinition)
if !ok {
return errors.Errorf("type %q is not an interface", intfName)
}
for _, f := range intf.Fields.Names() {
if obj.Fields.Get(f) == nil {
return errors.Errorf("interface %q expects field %q but %q does not provide it", intfName, f, obj.Name)
}
}
obj.Interfaces[i] = intf
intf.PossibleTypes = append(intf.PossibleTypes, obj)
}
}
for _, union := range s.Unions {
if err := resolveDirectives(s, union.Directives, "UNION"); err != nil {
return err
}
union.UnionMemberTypes = make([]*types.ObjectTypeDefinition, 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.(*types.ObjectTypeDefinition)
if !ok {
return errors.Errorf("type %q is not an object", name)
}
union.UnionMemberTypes[i] = obj
}
}
for _, enum := range s.Enums {
if err := resolveDirectives(s, enum.Directives, "ENUM"); err != nil {
return err
}
for _, value := range enum.EnumValuesDefinition {
if err := resolveDirectives(s, value.Directives, "ENUM_VALUE"); err != nil {
return err
}
}
}
return nil
}
func ParseSchema(schemaString string, useStringDescriptions bool) (*types.Schema, error) {
s := New()
err := Parse(s, schemaString, useStringDescriptions)
return s, err
}
func mergeExtensions(s *types.Schema) error {
for _, ext := range s.Extensions {
typ := s.Types[ext.Type.TypeName()]
if typ == nil {
return fmt.Errorf("trying to extend unknown type %q", ext.Type.TypeName())
}
if typ.Kind() != ext.Type.Kind() {
return fmt.Errorf("trying to extend type %q with type %q", typ.Kind(), ext.Type.Kind())
}
switch og := typ.(type) {
case *types.ObjectTypeDefinition:
e := ext.Type.(*types.ObjectTypeDefinition)
for _, field := range e.Fields {
if og.Fields.Get(field.Name) != nil {
return fmt.Errorf("extended field %q already exists", field.Name)
}
}
og.Fields = append(og.Fields, e.Fields...)
for _, en := range e.InterfaceNames {
for _, on := range og.InterfaceNames {
if on == en {
return fmt.Errorf("interface %q implemented in the extension is already implemented in %q", on, og.Name)
}
}
}
og.InterfaceNames = append(og.InterfaceNames, e.InterfaceNames...)
case *types.InputObject:
e := ext.Type.(*types.InputObject)
for _, field := range e.Values {
if og.Values.Get(field.Name.Name) != nil {
return fmt.Errorf("extended field %q already exists", field.Name)
}
}
og.Values = append(og.Values, e.Values...)
case *types.InterfaceTypeDefinition:
e := ext.Type.(*types.InterfaceTypeDefinition)
for _, field := range e.Fields {
if og.Fields.Get(field.Name) != nil {
return fmt.Errorf("extended field %s already exists", field.Name)
}
}
og.Fields = append(og.Fields, e.Fields...)
case *types.Union:
e := ext.Type.(*types.Union)
for _, en := range e.TypeNames {
for _, on := range og.TypeNames {
if on == en {
return fmt.Errorf("union type %q already declared in %q", on, og.Name)
}
}
}
og.TypeNames = append(og.TypeNames, e.TypeNames...)
case *types.EnumTypeDefinition:
e := ext.Type.(*types.EnumTypeDefinition)
for _, en := range e.EnumValuesDefinition {
for _, on := range og.EnumValuesDefinition {
if on.EnumValue == en.EnumValue {
return fmt.Errorf("enum value %q already declared in %q", on.EnumValue, og.Name)
}
}
}
og.EnumValuesDefinition = append(og.EnumValuesDefinition, e.EnumValuesDefinition...)
default:
return fmt.Errorf(`unexpected %q, expecting "schema", "type", "enum", "interface", "union" or "input"`, og.TypeName())
}
}
return nil
}
func resolveNamedType(s *types.Schema, t types.NamedType) error {
switch t := t.(type) {
case *types.ObjectTypeDefinition:
for _, f := range t.Fields {
if err := resolveField(s, f); err != nil {
return err
}
}
case *types.InterfaceTypeDefinition:
for _, f := range t.Fields {
if err := resolveField(s, f); err != nil {
return err
}
}
case *types.InputObject:
if err := resolveInputObject(s, t.Values); err != nil {
return err
}
}
return nil
}
func resolveField(s *types.Schema, f *types.FieldDefinition) error {
t, err := common.ResolveType(f.Type, s.Resolve)
if err != nil {
return err
}
f.Type = t
if err := resolveDirectives(s, f.Directives, "FIELD_DEFINITION"); err != nil {
return err
}
return resolveInputObject(s, f.Arguments)
}
func resolveDirectives(s *types.Schema, directives types.DirectiveList, loc string) error {
for _, d := range directives {
dirName := d.Name.Name
dd, ok := s.Directives[dirName]
if !ok {
return errors.Errorf("directive %q not found", dirName)
}
validLoc := false
for _, l := range dd.Locations {
if l == loc {
validLoc = true
break
}
}
if !validLoc {
return errors.Errorf("invalid location %q for directive %q (must be one of %v)", loc, dirName, dd.Locations)
}
for _, arg := range d.Arguments {
if dd.Arguments.Get(arg.Name.Name) == nil {
return errors.Errorf("invalid argument %q for directive %q", arg.Name.Name, dirName)
}
}
for _, arg := range dd.Arguments {
if _, ok := d.Arguments.Get(arg.Name.Name); !ok {
d.Arguments = append(d.Arguments, &types.Argument{Name: arg.Name, Value: arg.Default})
}
}
}
return nil
}
func resolveInputObject(s *types.Schema, values types.ArgumentsDefinition) 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 *types.Schema, l *common.Lexer) {
l.ConsumeWhitespace()
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 := parseObjectDef(l)
obj.Desc = desc
s.Types[obj.Name] = obj
s.Objects = append(s.Objects, obj)
case "interface":
iface := parseInterfaceDef(l)
iface.Desc = desc
s.Types[iface.Name] = iface
case "union":
union := parseUnionDef(l)
union.Desc = desc
s.Types[union.Name] = union
s.Unions = append(s.Unions, union)
case "enum":
enum := parseEnumDef(l)
enum.Desc = desc
s.Types[enum.Name] = enum
s.Enums = append(s.Enums, enum)
case "input":
input := parseInputDef(l)
input.Desc = desc
s.Types[input.Name] = input
case "scalar":
loc := l.Location()
name := l.ConsumeIdent()
directives := common.ParseDirectives(l)
s.Types[name] = &types.ScalarTypeDefinition{Name: name, Desc: desc, Directives: directives, Loc: loc}
case "directive":
directive := parseDirectiveDef(l)
directive.Desc = desc
s.Directives[directive.Name] = directive
case "extend":
parseExtension(s, l)
default:
// TODO: Add support for type extensions.
l.SyntaxError(fmt.Sprintf(`unexpected %q, expecting "schema", "type", "enum", "interface", "union", "input", "scalar" or "directive"`, x))
}
}
}
func parseObjectDef(l *common.Lexer) *types.ObjectTypeDefinition {
object := &types.ObjectTypeDefinition{Loc: l.Location(), Name: l.ConsumeIdent()}
for {
if l.Peek() == '{' {
break
}
if l.Peek() == '@' {
object.Directives = common.ParseDirectives(l)
continue
}
if l.Peek() == scanner.Ident {
l.ConsumeKeyword("implements")
for l.Peek() != '{' && l.Peek() != '@' {
if l.Peek() == '&' {
l.ConsumeToken('&')
}
object.InterfaceNames = append(object.InterfaceNames, l.ConsumeIdent())
}
continue
}
}
l.ConsumeToken('{')
object.Fields = parseFieldsDef(l)
l.ConsumeToken('}')
return object
}
func parseInterfaceDef(l *common.Lexer) *types.InterfaceTypeDefinition {
i := &types.InterfaceTypeDefinition{Loc: l.Location(), Name: l.ConsumeIdent()}
if l.Peek() == scanner.Ident {
l.ConsumeKeyword("implements")
i.Interfaces = append(i.Interfaces, &types.InterfaceTypeDefinition{Name: l.ConsumeIdent()})
for l.Peek() == '&' {
l.ConsumeToken('&')
i.Interfaces = append(i.Interfaces, &types.InterfaceTypeDefinition{Name: l.ConsumeIdent()})
}
}
i.Directives = common.ParseDirectives(l)
l.ConsumeToken('{')
i.Fields = parseFieldsDef(l)
l.ConsumeToken('}')
return i
}
func parseUnionDef(l *common.Lexer) *types.Union {
union := &types.Union{Loc: l.Location(), Name: l.ConsumeIdent()}
union.Directives = common.ParseDirectives(l)
l.ConsumeToken('=')
union.TypeNames = []string{l.ConsumeIdent()}
for l.Peek() == '|' {
l.ConsumeToken('|')
union.TypeNames = append(union.TypeNames, l.ConsumeIdent())
}
return union
}
func parseInputDef(l *common.Lexer) *types.InputObject {
i := &types.InputObject{}
i.Loc = l.Location()
i.Name = l.ConsumeIdent()
i.Directives = common.ParseDirectives(l)
l.ConsumeToken('{')
for l.Peek() != '}' {
i.Values = append(i.Values, common.ParseInputValue(l))
}
l.ConsumeToken('}')
return i
}
func parseEnumDef(l *common.Lexer) *types.EnumTypeDefinition {
enum := &types.EnumTypeDefinition{Loc: l.Location(), Name: l.ConsumeIdent()}
enum.Directives = common.ParseDirectives(l)
l.ConsumeToken('{')
for l.Peek() != '}' {
v := &types.EnumValueDefinition{
Desc: l.DescComment(),
Loc: l.Location(),
EnumValue: l.ConsumeIdent(),
Directives: common.ParseDirectives(l),
}
enum.EnumValuesDefinition = append(enum.EnumValuesDefinition, v)
}
l.ConsumeToken('}')
return enum
}
func parseDirectiveDef(l *common.Lexer) *types.DirectiveDefinition {
l.ConsumeToken('@')
loc := l.Location()
d := &types.DirectiveDefinition{Name: l.ConsumeIdent(), Loc: loc}
if l.Peek() == '(' {
l.ConsumeToken('(')
for l.Peek() != ')' {
v := common.ParseInputValue(l)
d.Arguments = append(d.Arguments, v)
}
l.ConsumeToken(')')
}
l.ConsumeKeyword("on")
for {
loc := l.ConsumeIdent()
d.Locations = append(d.Locations, loc)
if l.Peek() != '|' {
break
}
l.ConsumeToken('|')
}
return d
}
func parseExtension(s *types.Schema, l *common.Lexer) {
loc := l.Location()
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 := parseObjectDef(l)
s.Extensions = append(s.Extensions, &types.Extension{Type: obj, Loc: loc})
case "interface":
iface := parseInterfaceDef(l)
s.Extensions = append(s.Extensions, &types.Extension{Type: iface, Loc: loc})
case "union":
union := parseUnionDef(l)
s.Extensions = append(s.Extensions, &types.Extension{Type: union, Loc: loc})
case "enum":
enum := parseEnumDef(l)
s.Extensions = append(s.Extensions, &types.Extension{Type: enum, Loc: loc})
case "input":
input := parseInputDef(l)
s.Extensions = append(s.Extensions, &types.Extension{Type: input, Loc: loc})
default:
// TODO: Add ScalarTypeDefinition when adding directives
l.SyntaxError(fmt.Sprintf(`unexpected %q, expecting "schema", "type", "enum", "interface", "union" or "input"`, x))
}
}
func parseFieldsDef(l *common.Lexer) types.FieldsDefinition {
var fields types.FieldsDefinition
for l.Peek() != '}' {
f := &types.FieldDefinition{}
f.Desc = l.DescComment()
f.Loc = l.Location()
f.Name = l.ConsumeIdent()
if l.Peek() == '(' {
l.ConsumeToken('(')
for l.Peek() != ')' {
f.Arguments = append(f.Arguments, common.ParseInputValue(l))
}
l.ConsumeToken(')')
}
l.ConsumeToken(':')
f.Type = common.ParseType(l)
f.Directives = common.ParseDirectives(l)
fields = append(fields, f)
}
return fields
}

View File

@ -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
}

View File

@ -0,0 +1,980 @@
package validation
import (
"fmt"
"math"
"reflect"
"strconv"
"strings"
"text/scanner"
"github.com/graph-gophers/graphql-go/errors"
"github.com/graph-gophers/graphql-go/internal/common"
"github.com/graph-gophers/graphql-go/internal/query"
"github.com/graph-gophers/graphql-go/types"
)
type varSet map[*types.InputValueDefinition]struct{}
type selectionPair struct{ a, b types.Selection }
type nameSet map[string]errors.Location
type fieldInfo struct {
sf *types.FieldDefinition
parent types.NamedType
}
type context struct {
schema *types.Schema
doc *types.ExecutableDefinition
errs []*errors.QueryError
opErrs map[*types.OperationDefinition][]*errors.QueryError
usedVars map[*types.OperationDefinition]varSet
fieldMap map[*types.Field]fieldInfo
overlapValidated map[selectionPair]struct{}
maxDepth int
}
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 []*types.OperationDefinition
}
func newContext(s *types.Schema, doc *types.ExecutableDefinition, maxDepth int) *context {
return &context{
schema: s,
doc: doc,
opErrs: make(map[*types.OperationDefinition][]*errors.QueryError),
usedVars: make(map[*types.OperationDefinition]varSet),
fieldMap: make(map[*types.Field]fieldInfo),
overlapValidated: make(map[selectionPair]struct{}),
maxDepth: maxDepth,
}
}
func Validate(s *types.Schema, doc *types.ExecutableDefinition, variables map[string]interface{}, maxDepth int) []*errors.QueryError {
c := newContext(s, doc, maxDepth)
opNames := make(nameSet)
fragUsedBy := make(map[*types.FragmentDefinition][]*types.OperationDefinition)
for _, op := range doc.Operations {
c.usedVars[op] = make(varSet)
opc := &opContext{c, []*types.OperationDefinition{op}}
// Check if max depth is exceeded, if it's set. If max depth is exceeded,
// don't continue to validate the document and exit early.
if validateMaxDepth(opc, op.Selections, nil, 1) {
return c.errs
}
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)
}
validateValue(opc, v, variables[v.Name.Name], t)
if v.Default != nil {
validateLiteral(opc, v.Default)
if t != nil {
if nn, ok := t.(*types.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 types.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[*types.FragmentDefinition]struct{})
markUsedFragments(c, op.Selections, fragUsed)
for frag := range fragUsed {
fragUsedBy[frag] = append(fragUsedBy[frag], op)
}
}
fragNames := make(nameSet)
fragVisited := make(map[*types.FragmentDefinition]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 validateValue(c *opContext, v *types.InputValueDefinition, val interface{}, t types.Type) {
switch t := t.(type) {
case *types.NonNull:
if val == nil {
c.addErr(v.Loc, "VariablesOfCorrectType", "Variable \"%s\" has invalid value null.\nExpected type \"%s\", found null.", v.Name.Name, t)
return
}
validateValue(c, v, val, t.OfType)
case *types.List:
if val == nil {
return
}
vv, ok := val.([]interface{})
if !ok {
// Input coercion rules allow single items without wrapping array
validateValue(c, v, val, t.OfType)
return
}
for _, elem := range vv {
validateValue(c, v, elem, t.OfType)
}
case *types.EnumTypeDefinition:
if val == nil {
return
}
e, ok := val.(string)
if !ok {
c.addErr(v.Loc, "VariablesOfCorrectType", "Variable \"%s\" has invalid type %T.\nExpected type \"%s\", found %v.", v.Name.Name, val, t, val)
return
}
for _, option := range t.EnumValuesDefinition {
if option.EnumValue == e {
return
}
}
c.addErr(v.Loc, "VariablesOfCorrectType", "Variable \"%s\" has invalid value %s.\nExpected type \"%s\", found %s.", v.Name.Name, e, t, e)
case *types.InputObject:
if val == nil {
return
}
in, ok := val.(map[string]interface{})
if !ok {
c.addErr(v.Loc, "VariablesOfCorrectType", "Variable \"%s\" has invalid type %T.\nExpected type \"%s\", found %s.", v.Name.Name, val, t, val)
return
}
for _, f := range t.Values {
fieldVal := in[f.Name.Name]
validateValue(c, f, fieldVal, f.Type)
}
}
}
// validates the query doesn't go deeper than maxDepth (if set). Returns whether
// or not query validated max depth to avoid excessive recursion.
//
// The visited map is necessary to ensure that max depth validation does not get stuck in cyclical
// fragment spreads.
func validateMaxDepth(c *opContext, sels []types.Selection, visited map[*types.FragmentDefinition]struct{}, depth int) bool {
// maxDepth checking is turned off when maxDepth is 0
if c.maxDepth == 0 {
return false
}
exceededMaxDepth := false
if visited == nil {
visited = map[*types.FragmentDefinition]struct{}{}
}
for _, sel := range sels {
switch sel := sel.(type) {
case *types.Field:
if depth > c.maxDepth {
exceededMaxDepth = true
c.addErr(sel.Alias.Loc, "MaxDepthExceeded", "Field %q has depth %d that exceeds max depth %d", sel.Name.Name, depth, c.maxDepth)
continue
}
exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, sel.SelectionSet, visited, depth+1)
case *types.InlineFragment:
// Depth is not checked because inline fragments resolve to other fields which are checked.
// Depth is not incremented because inline fragments have the same depth as neighboring fields
exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, sel.Selections, visited, depth)
case *types.FragmentSpread:
// Depth is not checked because fragments resolve to other fields which are checked.
frag := c.doc.Fragments.Get(sel.Name.Name)
if frag == nil {
// In case of unknown fragment (invalid request), ignore max depth evaluation
c.addErr(sel.Loc, "MaxDepthEvaluationError", "Unknown fragment %q. Unable to evaluate depth.", sel.Name.Name)
continue
}
if _, ok := visited[frag]; ok {
// we've already seen this fragment, don't check depth again.
continue
}
visited[frag] = struct{}{}
// Depth is not incremented because fragments have the same depth as surrounding fields
exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, frag.Selections, visited, depth)
}
}
return exceededMaxDepth
}
func validateSelectionSet(c *opContext, sels []types.Selection, t types.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 types.Selection, t types.NamedType) {
switch sel := sel.(type) {
case *types.Field:
validateDirectives(c, "FIELD", sel.Directives)
fieldName := sel.Name.Name
var f *types.FieldDefinition
switch fieldName {
case "__typename":
f = &types.FieldDefinition{
Name: "__typename",
Type: c.schema.Types["String"],
}
case "__schema":
f = &types.FieldDefinition{
Name: "__schema",
Type: c.schema.Types["__Schema"],
}
case "__type":
f = &types.FieldDefinition{
Name: "__type",
Arguments: types.ArgumentsDefinition{
&types.InputValueDefinition{
Name: types.Ident{Name: "name"},
Type: &types.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.Arguments, 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 types.Type
if f != nil {
ft = f.Type
sf := hasSubfields(ft)
if sf && sel.SelectionSet == 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.SelectionSet != nil {
c.addErr(sel.SelectionSetLoc, "ScalarLeafs", "Field %q must not have a selection since type %q has no subfields.", fieldName, ft)
}
}
if sel.SelectionSet != nil {
validateSelectionSet(c, sel.SelectionSet, unwrapType(ft))
}
case *types.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 *types.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 types.Type) bool {
for _, pta := range possibleTypes(a) {
for _, ptb := range possibleTypes(b) {
if pta == ptb {
return true
}
}
}
return false
}
func possibleTypes(t types.Type) []*types.ObjectTypeDefinition {
switch t := t.(type) {
case *types.ObjectTypeDefinition:
return []*types.ObjectTypeDefinition{t}
case *types.InterfaceTypeDefinition:
return t.PossibleTypes
case *types.Union:
return t.UnionMemberTypes
default:
return nil
}
}
func markUsedFragments(c *context, sels []types.Selection, fragUsed map[*types.FragmentDefinition]struct{}) {
for _, sel := range sels {
switch sel := sel.(type) {
case *types.Field:
if sel.SelectionSet != nil {
markUsedFragments(c, sel.SelectionSet, fragUsed)
}
case *types.InlineFragment:
markUsedFragments(c, sel.Selections, fragUsed)
case *types.FragmentSpread:
frag := c.doc.Fragments.Get(sel.Name.Name)
if frag == nil {
return
}
if _, ok := fragUsed[frag]; ok {
continue
}
fragUsed[frag] = struct{}{}
markUsedFragments(c, frag.Selections, fragUsed)
default:
panic("unreachable")
}
}
}
func detectFragmentCycle(c *context, sels []types.Selection, fragVisited map[*types.FragmentDefinition]struct{}, spreadPath []*types.FragmentSpread, spreadPathIndex map[string]int) {
for _, sel := range sels {
detectFragmentCycleSel(c, sel, fragVisited, spreadPath, spreadPathIndex)
}
}
func detectFragmentCycleSel(c *context, sel types.Selection, fragVisited map[*types.FragmentDefinition]struct{}, spreadPath []*types.FragmentSpread, spreadPathIndex map[string]int) {
switch sel := sel.(type) {
case *types.Field:
if sel.SelectionSet != nil {
detectFragmentCycle(c, sel.SelectionSet, fragVisited, spreadPath, spreadPathIndex)
}
case *types.InlineFragment:
detectFragmentCycle(c, sel.Selections, fragVisited, spreadPath, spreadPathIndex)
case *types.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 types.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 *types.Field:
switch b := b.(type) {
case *types.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 *types.InlineFragment:
for _, sel := range b.Selections {
c.validateOverlap(a, sel, reasons, locs)
}
case *types.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 *types.InlineFragment:
for _, sel := range a.Selections {
c.validateOverlap(sel, b, reasons, locs)
}
case *types.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 *types.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.SelectionSet {
for _, b2 := range b.SelectionSet {
c.validateOverlap(a2, b2, &reasons, &locs)
}
}
return reasons, locs
}
func argumentsConflict(a, b types.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.Deserialize(nil), valB.Deserialize(nil)) {
return true
}
}
return false
}
func fields(t types.Type) types.FieldsDefinition {
switch t := t.(type) {
case *types.ObjectTypeDefinition:
return t.Fields
case *types.InterfaceTypeDefinition:
return t.Fields
default:
return nil
}
}
func unwrapType(t types.Type) types.NamedType {
if t == nil {
return nil
}
for {
switch t2 := t.(type) {
case types.NamedType:
return t2
case *types.List:
t = t2.OfType
case *types.NonNull:
t = t2.OfType
default:
panic("unreachable")
}
}
}
func resolveType(c *context, t types.Type) types.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 types.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.Arguments)
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.Locations {
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.Arguments, dd.Arguments, d.Name.Loc,
func() string { return fmt.Sprintf("directive %q", "@"+dirName) },
func() string { return fmt.Sprintf("Directive %q", "@"+dirName) },
)
}
}
func validateName(c *context, set nameSet, name types.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 types.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 types.ArgumentList, argDecls types.ArgumentsDefinition, 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.(*types.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 types.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 types.Value) {
switch l := l.(type) {
case *types.ObjectValue:
fieldNames := make(nameSet)
for _, f := range l.Fields {
validateName(c.context, fieldNames, f.Name, "UniqueInputFieldNames", "input field")
validateLiteral(c, f.Value)
}
case *types.ListValue:
for _, entry := range l.Values {
validateLiteral(c, entry)
}
case *types.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
}
validateValueType(c, l, resolveType(c.context, v.Type))
c.usedVars[op][v] = struct{}{}
}
}
}
func validateValueType(c *opContext, v types.Value, t types.Type) (bool, string) {
if v, ok := v.(*types.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.(*types.NonNull); !ok && v2.Default != nil {
t2 = &types.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.(*types.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 *types.ScalarTypeDefinition, *types.EnumTypeDefinition:
if lit, ok := v.(*types.PrimitiveValue); ok {
if validateBasicLit(lit, t) {
return true, ""
}
return false, fmt.Sprintf("Expected type %q, found %s.", t, v)
}
return true, ""
case *types.List:
list, ok := v.(*types.ListValue)
if !ok {
return validateValueType(c, v, t.OfType) // single value instead of list
}
for i, entry := range list.Values {
if ok, reason := validateValueType(c, entry, t.OfType); !ok {
return false, fmt.Sprintf("In element #%d: %s", i, reason)
}
}
return true, ""
case *types.InputObject:
v, ok := v.(*types.ObjectValue)
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.(*types.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 *types.PrimitiveValue, t types.Type) bool {
switch t := t.(type) {
case *types.ScalarTypeDefinition:
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 Unmarshalling
return true
}
case *types.EnumTypeDefinition:
if v.Type != scanner.Ident {
return false
}
for _, option := range t.EnumValuesDefinition {
if option.EnumValue == v.Text {
return true
}
}
return false
}
return false
}
func canBeFragment(t types.Type) bool {
switch t.(type) {
case *types.ObjectTypeDefinition, *types.InterfaceTypeDefinition, *types.Union:
return true
default:
return false
}
}
func canBeInput(t types.Type) bool {
switch t := t.(type) {
case *types.InputObject, *types.ScalarTypeDefinition, *types.EnumTypeDefinition:
return true
case *types.List:
return canBeInput(t.OfType)
case *types.NonNull:
return canBeInput(t.OfType)
default:
return false
}
}
func hasSubfields(t types.Type) bool {
switch t := t.(type) {
case *types.ObjectTypeDefinition, *types.InterfaceTypeDefinition, *types.Union:
return true
case *types.List:
return hasSubfields(t.OfType)
case *types.NonNull:
return hasSubfields(t.OfType)
default:
return false
}
}
func isLeaf(t types.Type) bool {
switch t.(type) {
case *types.ScalarTypeDefinition, *types.EnumTypeDefinition:
return true
default:
return false
}
}
func isNull(lit interface{}) bool {
_, ok := lit.(*types.NullValue)
return ok
}
func typesCompatible(a, b types.Type) bool {
al, aIsList := a.(*types.List)
bl, bIsList := b.(*types.List)
if aIsList || bIsList {
return aIsList && bIsList && typesCompatible(al.OfType, bl.OfType)
}
ann, aIsNN := a.(*types.NonNull)
bnn, bIsNN := b.(*types.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 types.Type) bool {
nnT, okT := t.(*types.NonNull)
if okT {
t = nnT.OfType
}
nnAs, okAs := as.(*types.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.(*types.List); ok {
if lAs, ok := as.(*types.List); ok {
return typeCanBeUsedAs(lT.OfType, lAs.OfType)
}
}
return false
}

View File

@ -0,0 +1,118 @@
package graphql
import (
"context"
"encoding/json"
"github.com/graph-gophers/graphql-go/internal/exec/resolvable"
"github.com/graph-gophers/graphql-go/introspection"
)
// Inspect allows inspection of the given schema.
func (s *Schema) Inspect() *introspection.Schema {
return introspection.WrapSchema(s.schema)
}
// ToJSON encodes the schema in a JSON format used by tools like Relay.
func (s *Schema) ToJSON() ([]byte, error) {
result := s.exec(context.Background(), introspectionQuery, "", nil, &resolvable.Schema{
Meta: s.res.Meta,
Query: &resolvable.Object{},
Schema: *s.schema,
})
if len(result.Errors) != 0 {
panic(result.Errors[0])
}
return json.MarshalIndent(result.Data, "", "\t")
}
var introspectionQuery = `
query {
__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
}
}
}
}
}
}
}
}
`

View File

@ -0,0 +1,312 @@
package introspection
import (
"sort"
"github.com/graph-gophers/graphql-go/types"
)
type Schema struct {
schema *types.Schema
}
// WrapSchema is only used internally.
func WrapSchema(schema *types.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 nil
}
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 types.Type
}
// WrapType is only used internally.
func WrapType(typ types.Type) *Type {
return &Type{typ}
}
func (r *Type) Kind() string {
return r.typ.Kind()
}
func (r *Type) Name() *string {
if named, ok := r.typ.(types.NamedType); ok {
name := named.TypeName()
return &name
}
return nil
}
func (r *Type) Description() *string {
if named, ok := r.typ.(types.NamedType); ok {
desc := named.Description()
if desc == "" {
return nil
}
return &desc
}
return nil
}
func (r *Type) Fields(args *struct{ IncludeDeprecated bool }) *[]*Field {
var fields types.FieldsDefinition
switch t := r.typ.(type) {
case *types.ObjectTypeDefinition:
fields = t.Fields
case *types.InterfaceTypeDefinition:
fields = t.Fields
default:
return nil
}
var l []*Field
for _, f := range fields {
if d := f.Directives.Get("deprecated"); d == nil || args.IncludeDeprecated {
l = append(l, &Field{field: f})
}
}
return &l
}
func (r *Type) Interfaces() *[]*Type {
t, ok := r.typ.(*types.ObjectTypeDefinition)
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 []*types.ObjectTypeDefinition
switch t := r.typ.(type) {
case *types.InterfaceTypeDefinition:
possibleTypes = t.PossibleTypes
case *types.Union:
possibleTypes = t.UnionMemberTypes
default:
return nil
}
l := make([]*Type, len(possibleTypes))
for i, intf := range possibleTypes {
l[i] = &Type{intf}
}
return &l
}
func (r *Type) EnumValues(args *struct{ IncludeDeprecated bool }) *[]*EnumValue {
t, ok := r.typ.(*types.EnumTypeDefinition)
if !ok {
return nil
}
var l []*EnumValue
for _, v := range t.EnumValuesDefinition {
if d := v.Directives.Get("deprecated"); d == nil || args.IncludeDeprecated {
l = append(l, &EnumValue{v})
}
}
return &l
}
func (r *Type) InputFields() *[]*InputValue {
t, ok := r.typ.(*types.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 *types.List:
return &Type{t.OfType}
case *types.NonNull:
return &Type{t.OfType}
default:
return nil
}
}
type Field struct {
field *types.FieldDefinition
}
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.Arguments))
for i, v := range r.field.Arguments {
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.Arguments.MustGet("reason").Deserialize(nil).(string)
return &reason
}
type InputValue struct {
value *types.InputValueDefinition
}
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 *types.EnumValueDefinition
}
func (r *EnumValue) Name() string {
return r.value.EnumValue
}
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.Arguments.MustGet("reason").Deserialize(nil).(string)
return &reason
}
type Directive struct {
directive *types.DirectiveDefinition
}
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.Locations
}
func (r *Directive) Args() []*InputValue {
l := make([]*InputValue, len(r.directive.Arguments))
for i, v := range r.directive.Arguments {
l[i] = &InputValue{v}
}
return l
}

23
vendor/github.com/graph-gophers/graphql-go/log/log.go generated vendored Normal file
View File

@ -0,0 +1,23 @@
package log
import (
"context"
"log"
"runtime"
)
// Logger is the interface used to log panics that occur during query execution. It is settable via graphql.ParseSchema
type Logger interface {
LogPanic(ctx context.Context, value interface{})
}
// DefaultLogger is the default logger used to log panics that occur during query execution
type DefaultLogger struct{}
// LogPanic is used to log recovered panic values that occur during query execution
func (l *DefaultLogger) LogPanic(ctx context.Context, value interface{}) {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
log.Printf("graphql: panic occurred: %v\n%s\ncontext: %v", value, buf, ctx)
}

View File

@ -0,0 +1,166 @@
package graphql
import (
"fmt"
"math"
)
// NullString is a string that can be null. Use it in input structs to
// differentiate a value explicitly set to null from an omitted value.
// When the value is defined (either null or a value) Set is true.
type NullString struct {
Value *string
Set bool
}
func (NullString) ImplementsGraphQLType(name string) bool {
return name == "String"
}
func (s *NullString) UnmarshalGraphQL(input interface{}) error {
s.Set = true
if input == nil {
return nil
}
switch v := input.(type) {
case string:
s.Value = &v
return nil
default:
return fmt.Errorf("wrong type for String: %T", v)
}
}
func (s *NullString) Nullable() {}
// NullBool is a string that can be null. Use it in input structs to
// differentiate a value explicitly set to null from an omitted value.
// When the value is defined (either null or a value) Set is true.
type NullBool struct {
Value *bool
Set bool
}
func (NullBool) ImplementsGraphQLType(name string) bool {
return name == "Boolean"
}
func (s *NullBool) UnmarshalGraphQL(input interface{}) error {
s.Set = true
if input == nil {
return nil
}
switch v := input.(type) {
case bool:
s.Value = &v
return nil
default:
return fmt.Errorf("wrong type for Boolean: %T", v)
}
}
func (s *NullBool) Nullable() {}
// NullInt is a string that can be null. Use it in input structs to
// differentiate a value explicitly set to null from an omitted value.
// When the value is defined (either null or a value) Set is true.
type NullInt struct {
Value *int32
Set bool
}
func (NullInt) ImplementsGraphQLType(name string) bool {
return name == "Int"
}
func (s *NullInt) UnmarshalGraphQL(input interface{}) error {
s.Set = true
if input == nil {
return nil
}
switch v := input.(type) {
case int32:
s.Value = &v
return nil
case float64:
coerced := int32(v)
if v < math.MinInt32 || v > math.MaxInt32 || float64(coerced) != v {
return fmt.Errorf("not a 32-bit integer")
}
s.Value = &coerced
return nil
default:
return fmt.Errorf("wrong type for Int: %T", v)
}
}
func (s *NullInt) Nullable() {}
// NullFloat is a string that can be null. Use it in input structs to
// differentiate a value explicitly set to null from an omitted value.
// When the value is defined (either null or a value) Set is true.
type NullFloat struct {
Value *float64
Set bool
}
func (NullFloat) ImplementsGraphQLType(name string) bool {
return name == "Float"
}
func (s *NullFloat) UnmarshalGraphQL(input interface{}) error {
s.Set = true
if input == nil {
return nil
}
switch v := input.(type) {
case float64:
s.Value = &v
return nil
case int32:
coerced := float64(v)
s.Value = &coerced
return nil
case int:
coerced := float64(v)
s.Value = &coerced
return nil
default:
return fmt.Errorf("wrong type for Float: %T", v)
}
}
func (s *NullFloat) Nullable() {}
// NullTime is a string that can be null. Use it in input structs to
// differentiate a value explicitly set to null from an omitted value.
// When the value is defined (either null or a value) Set is true.
type NullTime struct {
Value *Time
Set bool
}
func (NullTime) ImplementsGraphQLType(name string) bool {
return name == "Time"
}
func (s *NullTime) UnmarshalGraphQL(input interface{}) error {
s.Set = true
if input == nil {
return nil
}
s.Value = new(Time)
return s.Value.UnmarshalGraphQL(input)
}
func (s *NullTime) Nullable() {}

View File

@ -0,0 +1,96 @@
package graphql
import (
"context"
"errors"
qerrors "github.com/graph-gophers/graphql-go/errors"
"github.com/graph-gophers/graphql-go/internal/common"
"github.com/graph-gophers/graphql-go/internal/exec"
"github.com/graph-gophers/graphql-go/internal/exec/resolvable"
"github.com/graph-gophers/graphql-go/internal/exec/selected"
"github.com/graph-gophers/graphql-go/internal/query"
"github.com/graph-gophers/graphql-go/internal/validation"
"github.com/graph-gophers/graphql-go/introspection"
)
// Subscribe returns a response channel for the given subscription with the schema's
// resolver. It returns an error if the schema was created without a resolver.
// If the context gets cancelled, the response channel will be closed and no
// further resolvers will be called. The context error will be returned as soon
// as possible (not immediately).
func (s *Schema) Subscribe(ctx context.Context, queryString string, operationName string, variables map[string]interface{}) (<-chan interface{}, error) {
if !s.res.Resolver.IsValid() {
return nil, errors.New("schema created without resolver, can not subscribe")
}
if _, ok := s.schema.EntryPoints["subscription"]; !ok {
return nil, errors.New("no subscriptions are offered by the schema")
}
return s.subscribe(ctx, queryString, operationName, variables, s.res), nil
}
func (s *Schema) subscribe(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, res *resolvable.Schema) <-chan interface{} {
doc, qErr := query.Parse(queryString)
if qErr != nil {
return sendAndReturnClosed(&Response{Errors: []*qerrors.QueryError{qErr}})
}
validationFinish := s.validationTracer.TraceValidation(ctx)
errs := validation.Validate(s.schema, doc, variables, s.maxDepth)
validationFinish(errs)
if len(errs) != 0 {
return sendAndReturnClosed(&Response{Errors: errs})
}
op, err := getOperation(doc, operationName)
if err != nil {
return sendAndReturnClosed(&Response{Errors: []*qerrors.QueryError{qerrors.Errorf("%s", err)}})
}
r := &exec.Request{
Request: selected.Request{
Doc: doc,
Vars: variables,
Schema: s.schema,
},
Limiter: make(chan struct{}, s.maxParallelism),
Tracer: s.tracer,
Logger: s.logger,
PanicHandler: s.panicHandler,
SubscribeResolverTimeout: s.subscribeResolverTimeout,
}
varTypes := make(map[string]*introspection.Type)
for _, v := range op.Vars {
t, err := common.ResolveType(v.Type, s.schema.Resolve)
if err != nil {
return sendAndReturnClosed(&Response{Errors: []*qerrors.QueryError{err}})
}
varTypes[v.Name.Name] = introspection.WrapType(t)
}
if op.Type == query.Query || op.Type == query.Mutation {
data, errs := r.Execute(ctx, res, op)
return sendAndReturnClosed(&Response{Data: data, Errors: errs})
}
responses := r.Subscribe(ctx, res, op)
c := make(chan interface{})
go func() {
for resp := range responses {
c <- &Response{
Data: resp.Data,
Errors: resp.Errors,
}
}
close(c)
}()
return c
}
func sendAndReturnClosed(resp *Response) chan interface{} {
c := make(chan interface{}, 1)
c <- resp
close(c)
return c
}

64
vendor/github.com/graph-gophers/graphql-go/time.go generated vendored Normal file
View File

@ -0,0 +1,64 @@
package graphql
import (
"encoding/json"
"fmt"
"time"
)
// Time is a custom GraphQL type to represent an instant in time. It has to be added to a schema
// via "scalar Time" since it is not a predeclared GraphQL type like "ID".
type Time struct {
time.Time
}
// ImplementsGraphQLType maps this custom Go type
// to the graphql scalar type in the schema.
func (Time) ImplementsGraphQLType(name string) bool {
return name == "Time"
}
// UnmarshalGraphQL is a custom unmarshaler for Time
//
// This function will be called whenever you use the
// time scalar as an input
func (t *Time) UnmarshalGraphQL(input interface{}) error {
switch input := input.(type) {
case time.Time:
t.Time = input
return nil
case string:
var err error
t.Time, err = time.Parse(time.RFC3339, input)
return err
case []byte:
var err error
t.Time, err = time.Parse(time.RFC3339, string(input))
return err
case int32:
t.Time = time.Unix(int64(input), 0)
return nil
case int64:
if input >= 1e10 {
sec := input / 1e9
nsec := input - (sec * 1e9)
t.Time = time.Unix(sec, nsec)
} else {
t.Time = time.Unix(input, 0)
}
return nil
case float64:
t.Time = time.Unix(int64(input), 0)
return nil
default:
return fmt.Errorf("wrong type for Time: %T", input)
}
}
// MarshalJSON is a custom marshaler for Time
//
// This function will be called whenever you
// query for fields that use the Time type
func (t Time) MarshalJSON() ([]byte, error) {
return json.Marshal(t.Time)
}

View File

@ -0,0 +1,96 @@
package trace
import (
"context"
"fmt"
"github.com/graph-gophers/graphql-go/errors"
"github.com/graph-gophers/graphql-go/introspection"
opentracing "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
"github.com/opentracing/opentracing-go/log"
)
type TraceQueryFinishFunc func([]*errors.QueryError)
type TraceFieldFinishFunc func(*errors.QueryError)
type Tracer interface {
TraceQuery(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, varTypes map[string]*introspection.Type) (context.Context, TraceQueryFinishFunc)
TraceField(ctx context.Context, label, typeName, fieldName string, trivial bool, args map[string]interface{}) (context.Context, TraceFieldFinishFunc)
}
type OpenTracingTracer struct{}
func (OpenTracingTracer) TraceQuery(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, varTypes map[string]*introspection.Type) (context.Context, TraceQueryFinishFunc) {
span, spanCtx := opentracing.StartSpanFromContext(ctx, "GraphQL request")
span.SetTag("graphql.query", queryString)
if operationName != "" {
span.SetTag("graphql.operationName", operationName)
}
if len(variables) != 0 {
span.LogFields(log.Object("graphql.variables", variables))
}
return spanCtx, func(errs []*errors.QueryError) {
if len(errs) > 0 {
msg := errs[0].Error()
if len(errs) > 1 {
msg += fmt.Sprintf(" (and %d more errors)", len(errs)-1)
}
ext.Error.Set(span, true)
span.SetTag("graphql.error", msg)
}
span.Finish()
}
}
func (OpenTracingTracer) TraceField(ctx context.Context, label, typeName, fieldName string, trivial bool, args map[string]interface{}) (context.Context, TraceFieldFinishFunc) {
if trivial {
return ctx, noop
}
span, spanCtx := opentracing.StartSpanFromContext(ctx, label)
span.SetTag("graphql.type", typeName)
span.SetTag("graphql.field", fieldName)
for name, value := range args {
span.SetTag("graphql.args."+name, value)
}
return spanCtx, func(err *errors.QueryError) {
if err != nil {
ext.Error.Set(span, true)
span.SetTag("graphql.error", err.Error())
}
span.Finish()
}
}
func (OpenTracingTracer) TraceValidation(ctx context.Context) TraceValidationFinishFunc {
span, _ := opentracing.StartSpanFromContext(ctx, "Validate Query")
return func(errs []*errors.QueryError) {
if len(errs) > 0 {
msg := errs[0].Error()
if len(errs) > 1 {
msg += fmt.Sprintf(" (and %d more errors)", len(errs)-1)
}
ext.Error.Set(span, true)
span.SetTag("graphql.error", msg)
}
span.Finish()
}
}
func noop(*errors.QueryError) {}
type NoopTracer struct{}
func (NoopTracer) TraceQuery(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, varTypes map[string]*introspection.Type) (context.Context, TraceQueryFinishFunc) {
return ctx, func(errs []*errors.QueryError) {}
}
func (NoopTracer) TraceField(ctx context.Context, label, typeName, fieldName string, trivial bool, args map[string]interface{}) (context.Context, TraceFieldFinishFunc) {
return ctx, func(err *errors.QueryError) {}
}

View File

@ -0,0 +1,25 @@
package trace
import (
"context"
"github.com/graph-gophers/graphql-go/errors"
)
type TraceValidationFinishFunc = TraceQueryFinishFunc
// Deprecated: use ValidationTracerContext.
type ValidationTracer interface {
TraceValidation() TraceValidationFinishFunc
}
type ValidationTracerContext interface {
TraceValidation(ctx context.Context) TraceValidationFinishFunc
}
type NoopValidationTracer struct{}
// Deprecated: use a Tracer which implements ValidationTracerContext.
func (NoopValidationTracer) TraceValidation() TraceValidationFinishFunc {
return func(errs []*errors.QueryError) {}
}

View File

@ -0,0 +1,44 @@
package types
// Argument is a representation of the GraphQL Argument.
//
// https://spec.graphql.org/draft/#sec-Language.Arguments
type Argument struct {
Name Ident
Value Value
}
// ArgumentList is a collection of GraphQL Arguments.
type ArgumentList []*Argument
// Returns a Value in the ArgumentList by name.
func (l ArgumentList) Get(name string) (Value, bool) {
for _, arg := range l {
if arg.Name.Name == name {
return arg.Value, true
}
}
return nil, false
}
// MustGet returns a Value in the ArgumentList by name.
// MustGet will panic if the argument name is not found in the ArgumentList.
func (l ArgumentList) MustGet(name string) Value {
value, ok := l.Get(name)
if !ok {
panic("argument not found")
}
return value
}
type ArgumentsDefinition []*InputValueDefinition
// Get returns an InputValueDefinition in the ArgumentsDefinition by name or nil if not found.
func (a ArgumentsDefinition) Get(name string) *InputValueDefinition {
for _, inputValue := range a {
if inputValue.Name.Name == name {
return inputValue
}
}
return nil
}

View File

@ -0,0 +1,34 @@
package types
import "github.com/graph-gophers/graphql-go/errors"
// Directive is a representation of the GraphQL Directive.
//
// http://spec.graphql.org/draft/#sec-Language.Directives
type Directive struct {
Name Ident
Arguments ArgumentList
}
// DirectiveDefinition is a representation of the GraphQL DirectiveDefinition.
//
// http://spec.graphql.org/draft/#sec-Type-System.Directives
type DirectiveDefinition struct {
Name string
Desc string
Locations []string
Arguments ArgumentsDefinition
Loc errors.Location
}
type DirectiveList []*Directive
// Returns the Directive in the DirectiveList by name or nil if not found.
func (l DirectiveList) Get(name string) *Directive {
for _, d := range l {
if d.Name.Name == name {
return d
}
}
return nil
}

View File

@ -0,0 +1,9 @@
/*
Package types represents all types from the GraphQL specification in code.
The names of the Go types, whenever possible, match 1:1 with the names from
the specification.
*/
package types

View File

@ -0,0 +1,32 @@
package types
import "github.com/graph-gophers/graphql-go/errors"
// EnumTypeDefinition defines a set of possible enum values.
//
// Like scalar types, an EnumTypeDefinition also represents a leaf value in a GraphQL type system.
//
// http://spec.graphql.org/draft/#sec-Enums
type EnumTypeDefinition struct {
Name string
EnumValuesDefinition []*EnumValueDefinition
Desc string
Directives DirectiveList
Loc errors.Location
}
// EnumValueDefinition are unique values that may be serialized as a string: the name of the
// represented value.
//
// http://spec.graphql.org/draft/#EnumValueDefinition
type EnumValueDefinition struct {
EnumValue string
Directives DirectiveList
Desc string
Loc errors.Location
}
func (*EnumTypeDefinition) Kind() string { return "ENUM" }
func (t *EnumTypeDefinition) String() string { return t.Name }
func (t *EnumTypeDefinition) TypeName() string { return t.Name }
func (t *EnumTypeDefinition) Description() string { return t.Desc }

View File

@ -0,0 +1,13 @@
package types
import "github.com/graph-gophers/graphql-go/errors"
// Extension type defines a GraphQL type extension.
// Schemas, Objects, Inputs and Scalars can be extended.
//
// https://spec.graphql.org/draft/#sec-Type-System-Extensions
type Extension struct {
Type NamedType
Directives DirectiveList
Loc errors.Location
}

View File

@ -0,0 +1,39 @@
package types
import "github.com/graph-gophers/graphql-go/errors"
// FieldDefinition is a representation of a GraphQL FieldDefinition.
//
// http://spec.graphql.org/draft/#FieldDefinition
type FieldDefinition struct {
Name string
Arguments ArgumentsDefinition
Type Type
Directives DirectiveList
Desc string
Loc errors.Location
}
// FieldsDefinition is a list of an ObjectTypeDefinition's Fields.
//
// https://spec.graphql.org/draft/#FieldsDefinition
type FieldsDefinition []*FieldDefinition
// Get returns a FieldDefinition in a FieldsDefinition by name or nil if not found.
func (l FieldsDefinition) Get(name string) *FieldDefinition {
for _, f := range l {
if f.Name == name {
return f
}
}
return nil
}
// Names returns a slice of FieldDefinition names.
func (l FieldsDefinition) Names() []string {
names := make([]string, len(l))
for i, f := range l {
names[i] = f.Name
}
return names
}

View File

@ -0,0 +1,51 @@
package types
import "github.com/graph-gophers/graphql-go/errors"
type Fragment struct {
On TypeName
Selections SelectionSet
}
// InlineFragment is a representation of the GraphQL InlineFragment.
//
// http://spec.graphql.org/draft/#InlineFragment
type InlineFragment struct {
Fragment
Directives DirectiveList
Loc errors.Location
}
// FragmentDefinition is a representation of the GraphQL FragmentDefinition.
//
// http://spec.graphql.org/draft/#FragmentDefinition
type FragmentDefinition struct {
Fragment
Name Ident
Directives DirectiveList
Loc errors.Location
}
// FragmentSpread is a representation of the GraphQL FragmentSpread.
//
// http://spec.graphql.org/draft/#FragmentSpread
type FragmentSpread struct {
Name Ident
Directives DirectiveList
Loc errors.Location
}
type FragmentList []*FragmentDefinition
// Returns a FragmentDefinition by name or nil if not found.
func (l FragmentList) Get(name string) *FragmentDefinition {
for _, f := range l {
if f.Name.Name == name {
return f
}
}
return nil
}
func (InlineFragment) isSelection() {}
func (FragmentSpread) isSelection() {}

View File

@ -0,0 +1,47 @@
package types
import "github.com/graph-gophers/graphql-go/errors"
// InputValueDefinition is a representation of the GraphQL InputValueDefinition.
//
// http://spec.graphql.org/draft/#InputValueDefinition
type InputValueDefinition struct {
Name Ident
Type Type
Default Value
Desc string
Directives DirectiveList
Loc errors.Location
TypeLoc errors.Location
}
type InputValueDefinitionList []*InputValueDefinition
// Returns an InputValueDefinition by name or nil if not found.
func (l InputValueDefinitionList) Get(name string) *InputValueDefinition {
for _, v := range l {
if v.Name.Name == name {
return v
}
}
return nil
}
// InputObject types define a set of input fields; the input fields are either scalars, enums, or
// other input objects.
//
// This allows arguments to accept arbitrarily complex structs.
//
// http://spec.graphql.org/draft/#sec-Input-Objects
type InputObject struct {
Name string
Desc string
Values ArgumentsDefinition
Directives DirectiveList
Loc errors.Location
}
func (*InputObject) Kind() string { return "INPUT_OBJECT" }
func (t *InputObject) String() string { return t.Name }
func (t *InputObject) TypeName() string { return t.Name }
func (t *InputObject) Description() string { return t.Desc }

View File

@ -0,0 +1,25 @@
package types
import "github.com/graph-gophers/graphql-go/errors"
// InterfaceTypeDefinition recusrively defines list of named fields with their arguments via the
// implementation chain of interfaces.
//
// GraphQL objects can then implement these interfaces which requires that the object type will
// define all fields defined by those interfaces.
//
// http://spec.graphql.org/draft/#sec-Interfaces
type InterfaceTypeDefinition struct {
Name string
PossibleTypes []*ObjectTypeDefinition
Fields FieldsDefinition
Desc string
Directives DirectiveList
Loc errors.Location
Interfaces []*InterfaceTypeDefinition
}
func (*InterfaceTypeDefinition) Kind() string { return "INTERFACE" }
func (t *InterfaceTypeDefinition) String() string { return t.Name }
func (t *InterfaceTypeDefinition) TypeName() string { return t.Name }
func (t *InterfaceTypeDefinition) Description() string { return t.Desc }

View File

@ -0,0 +1,25 @@
package types
import "github.com/graph-gophers/graphql-go/errors"
// ObjectTypeDefinition represents a GraphQL ObjectTypeDefinition.
//
// type FooObject {
// foo: String
// }
//
// https://spec.graphql.org/draft/#sec-Objects
type ObjectTypeDefinition struct {
Name string
Interfaces []*InterfaceTypeDefinition
Fields FieldsDefinition
Desc string
Directives DirectiveList
InterfaceNames []string
Loc errors.Location
}
func (*ObjectTypeDefinition) Kind() string { return "OBJECT" }
func (t *ObjectTypeDefinition) String() string { return t.Name }
func (t *ObjectTypeDefinition) TypeName() string { return t.Name }
func (t *ObjectTypeDefinition) Description() string { return t.Desc }

View File

@ -0,0 +1,62 @@
package types
import "github.com/graph-gophers/graphql-go/errors"
// ExecutableDefinition represents a set of operations or fragments that can be executed
// against a schema.
//
// http://spec.graphql.org/draft/#ExecutableDefinition
type ExecutableDefinition struct {
Operations OperationList
Fragments FragmentList
}
// OperationDefinition represents a GraphQL Operation.
//
// https://spec.graphql.org/draft/#sec-Language.Operations
type OperationDefinition struct {
Type OperationType
Name Ident
Vars ArgumentsDefinition
Selections SelectionSet
Directives DirectiveList
Loc errors.Location
}
type OperationType string
// A Selection is a field requested in a GraphQL operation.
//
// http://spec.graphql.org/draft/#Selection
type Selection interface {
isSelection()
}
// A SelectionSet represents a collection of Selections
//
// http://spec.graphql.org/draft/#sec-Selection-Sets
type SelectionSet []Selection
// Field represents a field used in a query.
type Field struct {
Alias Ident
Name Ident
Arguments ArgumentList
Directives DirectiveList
SelectionSet SelectionSet
SelectionSetLoc errors.Location
}
func (Field) isSelection() {}
type OperationList []*OperationDefinition
// Get returns an OperationDefinition by name or nil if not found.
func (l OperationList) Get(name string) *OperationDefinition {
for _, f := range l {
if f.Name.Name == name {
return f
}
}
return nil
}

View File

@ -0,0 +1,22 @@
package types
import "github.com/graph-gophers/graphql-go/errors"
// ScalarTypeDefinition types represent primitive leaf values (e.g. a string or an integer) in a GraphQL type
// system.
//
// GraphQL responses take the form of a hierarchical tree; the leaves on these trees are GraphQL
// scalars.
//
// http://spec.graphql.org/draft/#sec-Scalars
type ScalarTypeDefinition struct {
Name string
Desc string
Directives DirectiveList
Loc errors.Location
}
func (*ScalarTypeDefinition) Kind() string { return "SCALAR" }
func (t *ScalarTypeDefinition) String() string { return t.Name }
func (t *ScalarTypeDefinition) TypeName() string { return t.Name }
func (t *ScalarTypeDefinition) Description() string { return t.Desc }

View File

@ -0,0 +1,42 @@
package types
// Schema represents a GraphQL service's collective type system capabilities.
// A schema is defined in terms of the types and directives it supports as well as the root
// operation types for each kind of operation: `query`, `mutation`, and `subscription`.
//
// For a more formal definition, read the relevant section in the specification:
//
// http://spec.graphql.org/draft/#sec-Schema
type Schema struct {
// EntryPoints determines the place in the type system where `query`, `mutation`, and
// `subscription` operations begin.
//
// http://spec.graphql.org/draft/#sec-Root-Operation-Types
//
EntryPoints map[string]NamedType
// Types are the fundamental unit of any GraphQL schema.
// There are six kinds of named types, and two wrapping types.
//
// http://spec.graphql.org/draft/#sec-Types
Types map[string]NamedType
// Directives are used to annotate various parts of a GraphQL document as an indicator that they
// should be evaluated differently by a validator, executor, or client tool such as a code
// generator.
//
// http://spec.graphql.org/#sec-Type-System.Directives
Directives map[string]*DirectiveDefinition
UseFieldResolvers bool
EntryPointNames map[string]string
Objects []*ObjectTypeDefinition
Unions []*Union
Enums []*EnumTypeDefinition
Extensions []*Extension
}
func (s *Schema) Resolve(name string) Type {
return s.Types[name]
}

View File

@ -0,0 +1,63 @@
package types
import (
"github.com/graph-gophers/graphql-go/errors"
)
// TypeName is a base building block for GraphQL type references.
type TypeName struct {
Ident
}
// NamedType represents a type with a name.
//
// http://spec.graphql.org/draft/#NamedType
type NamedType interface {
Type
TypeName() string
Description() string
}
type Ident struct {
Name string
Loc errors.Location
}
type Type interface {
// Kind returns one possible GraphQL type kind. A type kind must be
// valid as defined by the GraphQL spec.
//
// https://spec.graphql.org/draft/#sec-Type-Kinds
Kind() string
// String serializes a Type into a GraphQL specification format type.
//
// http://spec.graphql.org/draft/#sec-Serialization-Format
String() string
}
// List represents a GraphQL ListType.
//
// http://spec.graphql.org/draft/#ListType
type List struct {
// OfType represents the inner-type of a List type.
// For example, the List type `[Foo]` has an OfType of Foo.
OfType Type
}
// NonNull represents a GraphQL NonNullType.
//
// https://spec.graphql.org/draft/#NonNullType
type NonNull struct {
// OfType represents the inner-type of a NonNull type.
// For example, the NonNull type `Foo!` has an OfType of Foo.
OfType Type
}
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") }

View File

@ -0,0 +1,24 @@
package types
import "github.com/graph-gophers/graphql-go/errors"
// Union types represent objects that could be one of a list of GraphQL object types, but provides no
// guaranteed fields between those types.
//
// They also differ from interfaces in that object types declare what interfaces they implement, but
// are not aware of what unions contain them.
//
// http://spec.graphql.org/draft/#sec-Unions
type Union struct {
Name string
UnionMemberTypes []*ObjectTypeDefinition
Desc string
Directives DirectiveList
TypeNames []string
Loc errors.Location
}
func (*Union) Kind() string { return "UNION" }
func (t *Union) String() string { return t.Name }
func (t *Union) TypeName() string { return t.Name }
func (t *Union) Description() string { return t.Desc }

View File

@ -0,0 +1,141 @@
package types
import (
"strconv"
"strings"
"text/scanner"
"github.com/graph-gophers/graphql-go/errors"
)
// Value represents a literal input or literal default value in the GraphQL Specification.
//
// http://spec.graphql.org/draft/#sec-Input-Values
type Value interface {
// Deserialize transforms a GraphQL specification format literal into a Go type.
Deserialize(vars map[string]interface{}) interface{}
// String serializes a Value into a GraphQL specification format literal.
String() string
Location() errors.Location
}
// PrimitiveValue represents one of the following GraphQL scalars: Int, Float,
// String, or Boolean
type PrimitiveValue struct {
Type rune
Text string
Loc errors.Location
}
func (val *PrimitiveValue) Deserialize(vars map[string]interface{}) interface{} {
switch val.Type {
case scanner.Int:
value, err := strconv.ParseInt(val.Text, 10, 32)
if err != nil {
panic(err)
}
return int32(value)
case scanner.Float:
value, err := strconv.ParseFloat(val.Text, 64)
if err != nil {
panic(err)
}
return value
case scanner.String:
value, err := strconv.Unquote(val.Text)
if err != nil {
panic(err)
}
return value
case scanner.Ident:
switch val.Text {
case "true":
return true
case "false":
return false
default:
return val.Text
}
default:
panic("invalid literal value")
}
}
func (val *PrimitiveValue) String() string { return val.Text }
func (val *PrimitiveValue) Location() errors.Location { return val.Loc }
// ListValue represents a literal list Value in the GraphQL specification.
//
// http://spec.graphql.org/draft/#sec-List-Value
type ListValue struct {
Values []Value
Loc errors.Location
}
func (val *ListValue) Deserialize(vars map[string]interface{}) interface{} {
entries := make([]interface{}, len(val.Values))
for i, entry := range val.Values {
entries[i] = entry.Deserialize(vars)
}
return entries
}
func (val *ListValue) String() string {
entries := make([]string, len(val.Values))
for i, entry := range val.Values {
entries[i] = entry.String()
}
return "[" + strings.Join(entries, ", ") + "]"
}
func (val *ListValue) Location() errors.Location { return val.Loc }
// ObjectValue represents a literal object Value in the GraphQL specification.
//
// http://spec.graphql.org/draft/#sec-Object-Value
type ObjectValue struct {
Fields []*ObjectField
Loc errors.Location
}
// ObjectField represents field/value pairs in a literal ObjectValue.
type ObjectField struct {
Name Ident
Value Value
}
func (val *ObjectValue) Deserialize(vars map[string]interface{}) interface{} {
fields := make(map[string]interface{}, len(val.Fields))
for _, f := range val.Fields {
fields[f.Name.Name] = f.Value.Deserialize(vars)
}
return fields
}
func (val *ObjectValue) String() string {
entries := make([]string, 0, len(val.Fields))
for _, f := range val.Fields {
entries = append(entries, f.Name.Name+": "+f.Value.String())
}
return "{" + strings.Join(entries, ", ") + "}"
}
func (val *ObjectValue) Location() errors.Location {
return val.Loc
}
// NullValue represents a literal `null` Value in the GraphQL specification.
//
// http://spec.graphql.org/draft/#sec-Null-Value
type NullValue struct {
Loc errors.Location
}
func (val *NullValue) Deserialize(vars map[string]interface{}) interface{} { return nil }
func (val *NullValue) String() string { return "null" }
func (val *NullValue) Location() errors.Location { return val.Loc }

View File

@ -0,0 +1,15 @@
package types
import "github.com/graph-gophers/graphql-go/errors"
// Variable is used in GraphQL operations to parameterize an input value.
//
// http://spec.graphql.org/draft/#Variable
type Variable struct {
Name string
Loc errors.Location
}
func (v Variable) Deserialize(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 }