Repository: graphql-go/graphql Branch: master Commit: d5f8f3c54f31 Files: 137 Total size: 1.1 MB Directory structure: gitextract_a97o64la/ ├── .circleci/ │ └── config.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── abstract_test.go ├── benchutil/ │ ├── list_schema.go │ └── wide_schema.go ├── definition.go ├── definition_test.go ├── directives.go ├── directives_test.go ├── enum_type_test.go ├── examples/ │ ├── concurrent-resolvers/ │ │ └── main.go │ ├── context/ │ │ └── main.go │ ├── crud/ │ │ ├── Readme.md │ │ └── main.go │ ├── custom-scalar-type/ │ │ └── main.go │ ├── hello-world/ │ │ └── main.go │ ├── http/ │ │ ├── data.json │ │ └── main.go │ ├── http-post/ │ │ └── main.go │ ├── httpdynamic/ │ │ ├── README.md │ │ ├── data.json │ │ └── main.go │ ├── modify-context/ │ │ └── main.go │ ├── sql-nullstring/ │ │ ├── README.md │ │ └── main.go │ ├── star-wars/ │ │ └── main.go │ └── todo/ │ ├── README.md │ ├── main.go │ ├── schema/ │ │ └── schema.go │ └── static/ │ ├── assets/ │ │ ├── css/ │ │ │ └── style.css │ │ └── js/ │ │ └── app.js │ └── index.html ├── executor.go ├── executor_resolve_test.go ├── executor_schema_test.go ├── executor_test.go ├── extensions.go ├── extensions_test.go ├── go.mod ├── gqlerrors/ │ ├── error.go │ ├── formatted.go │ ├── located.go │ ├── sortutil.go │ └── syntax.go ├── graphql.go ├── graphql_bench_test.go ├── graphql_test.go ├── introspection.go ├── introspection_test.go ├── kitchen-sink.graphql ├── language/ │ ├── ast/ │ │ ├── arguments.go │ │ ├── definitions.go │ │ ├── directives.go │ │ ├── document.go │ │ ├── location.go │ │ ├── name.go │ │ ├── node.go │ │ ├── selections.go │ │ ├── type_definitions.go │ │ ├── types.go │ │ └── values.go │ ├── kinds/ │ │ └── kinds.go │ ├── lexer/ │ │ ├── lexer.go │ │ └── lexer_test.go │ ├── location/ │ │ └── location.go │ ├── parser/ │ │ ├── parser.go │ │ ├── parser_test.go │ │ └── schema_parser_test.go │ ├── printer/ │ │ ├── printer.go │ │ ├── printer_test.go │ │ └── schema_printer_test.go │ ├── source/ │ │ └── source.go │ ├── typeInfo/ │ │ └── type_info.go │ └── visitor/ │ ├── visitor.go │ └── visitor_test.go ├── lists_test.go ├── located.go ├── mutations_test.go ├── nonnull_test.go ├── quoted_or_list_internal_test.go ├── race_test.go ├── rules.go ├── rules_arguments_of_correct_type_test.go ├── rules_default_values_of_correct_type_test.go ├── rules_fields_on_correct_type_test.go ├── rules_fragments_on_composite_types_test.go ├── rules_known_argument_names_test.go ├── rules_known_directives_rule_test.go ├── rules_known_fragment_names_test.go ├── rules_known_type_names_test.go ├── rules_lone_anonymous_operation_rule_test.go ├── rules_no_fragment_cycles_test.go ├── rules_no_undefined_variables_test.go ├── rules_no_unused_fragments_test.go ├── rules_no_unused_variables_test.go ├── rules_overlapping_fields_can_be_merged.go ├── rules_overlapping_fields_can_be_merged_test.go ├── rules_possible_fragment_spreads_test.go ├── rules_provided_non_null_arguments_test.go ├── rules_scalar_leafs_test.go ├── rules_unique_argument_names_test.go ├── rules_unique_fragment_names_test.go ├── rules_unique_input_field_names_test.go ├── rules_unique_operation_names_test.go ├── rules_unique_variable_names_test.go ├── rules_variables_are_input_types_test.go ├── rules_variables_in_allowed_position_test.go ├── scalars.go ├── scalars_parse_test.go ├── scalars_serialization_test.go ├── scalars_test.go ├── schema-all-descriptions.graphql ├── schema-kitchen-sink.graphql ├── schema.go ├── subscription.go ├── subscription_test.go ├── suggested_list_internal_test.go ├── testutil/ │ ├── introspection_query.go │ ├── rules_test_harness.go │ ├── subscription.go │ ├── testutil.go │ └── testutil_test.go ├── type_comparators_internal_test.go ├── type_info.go ├── types.go ├── union_interface_test.go ├── util.go ├── util_test.go ├── validation_test.go ├── validator.go ├── validator_test.go ├── values.go ├── values_test.go └── variables_test.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .circleci/config.yml ================================================ test_with_go_modules: &test_with_go_modules steps: - checkout - run: go test ./... - run: go vet ./... test_without_go_modules: &test_without_go_modules working_directory: /go/src/github.com/graphql-go/graphql steps: - checkout - run: go get -v -t -d ./... - run: go test ./... - run: go vet ./... defaults: &defaults <<: *test_with_go_modules version: 2 jobs: golang:1.8.7: <<: *test_without_go_modules docker: - image: circleci/golang:1.8.7 golang:1.9.7: <<: *test_without_go_modules docker: - image: circleci/golang:1.9.7 golang:1.11: <<: *defaults docker: - image: circleci/golang:1.11 golang:latest: <<: *defaults docker: - image: circleci/golang:latest coveralls: docker: - image: circleci/golang:latest steps: - checkout - run: go get github.com/mattn/goveralls - run: go test -v -cover -race -coverprofile=coverage.out - run: /go/bin/goveralls -coverprofile=coverage.out -service=circle-ci -repotoken $COVERALLS_TOKEN workflows: version: 2 build: jobs: - golang:1.8.7 - golang:1.9.7 - golang:1.11 - golang:latest - coveralls ================================================ FILE: .gitignore ================================================ .DS_Store .idea ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to graphql This document is based on the [Node.js contribution guidelines](https://github.com/nodejs/node/blob/master/CONTRIBUTING.md) ## Chat room [![Join the chat at https://gitter.im/graphql-go/graphql](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/graphql-go/graphql?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Feel free to participate in the chat room for informal discussions and queries. Just drop by and say hi! ## Issue Contributions When opening new issues or commenting on existing issues on this repository please make sure discussions are related to concrete technical issues with the `graphql` implementation. ## Code Contributions The `graphql` project welcomes new contributors. This document will guide you through the contribution process. What do you want to contribute? - I want to otherwise correct or improve the docs or examples - I want to report a bug - I want to add some feature or functionality to an existing hardware platform - I want to add support for a new hardware platform Descriptions for each of these will eventually be provided below. ## General Guidelines * Reading up on [CodeReviewComments](https://github.com/golang/go/wiki/CodeReviewComments) would be a great start. * Submit a Github Pull Request to the appropriate branch and ideally discuss the changes with us in the [chat room](#chat-room). * We will look at the patch, test it out, and give you feedback. * Avoid doing minor whitespace changes, renaming, etc. along with merged content. These will be done by the maintainers from time to time but they can complicate merges and should be done separately. * Take care to maintain the existing coding style. * Always `golint` and `go fmt` your code. * Add unit tests for any new or changed functionality, especially for public APIs. * Run `go test` before submitting a PR. * For git help see [progit](http://git-scm.com/book) which is an awesome (and free) book on git ## Creating Pull Requests Because `graphql` makes use of self-referencing import paths, you will want to implement the local copy of your fork as a remote on your copy of the original `graphql` repo. Katrina Owen has [an excellent post on this workflow](https://splice.com/blog/contributing-open-source-git-repositories-go/). The basics are as follows: 1. Fork the project via the GitHub UI 2. `go get` the upstream repo and set it up as the `upstream` remote and your own repo as the `origin` remote: ```bash $ go get github.com/graphql-go/graphql $ cd $GOPATH/src/github.com/graphql-go/graphql $ git remote rename origin upstream $ git remote add origin git@github.com/YOUR_GITHUB_NAME/graphql ``` All import paths should now work fine assuming that you've got the proper branch checked out. ## Landing Pull Requests (This is for committers only. If you are unsure whether you are a committer, you are not.) 1. Set the contributor's fork as an upstream on your checkout ```git remote add contrib1 https://github.com/contrib1/graphql``` 2. Fetch the contributor's repo ```git fetch contrib1``` 3. Checkout a copy of the PR branch ```git checkout pr-1234 --track contrib1/branch-for-pr-1234``` 4. Review the PR as normal 5. Land when you're ready via the GitHub UI ## Developer's Certificate of Origin 1.0 By making a contribution to this project, I certify that: * (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or * (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or * (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. ## Code of Conduct This Code of Conduct is adapted from [Rust's wonderful CoC](http://www.rust-lang.org/conduct.html). * We are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, disability, ethnicity, religion, or similar personal characteristic. * Please avoid using overtly sexual nicknames or other nicknames that might detract from a friendly, safe and welcoming environment for all. * Please be kind and courteous. There's no need to be mean or rude. * Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer. * Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works. * We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behaviour. We interpret the term "harassment" as including the definition in the [Citizen Code of Conduct](http://citizencodeofconduct.org/); if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don't tolerate behavior that excludes people in socially marginalized groups. * Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one of the channel ops or any of the TC members immediately with a capture (log, photo, email) of the harassment if possible. Whether you're a regular contributor or a newcomer, we care about making this community a safe place for you and we've got your back. * Likewise any spamming, trolling, flaming, baiting or other attention-stealing behaviour is not welcome. * Avoid the use of personal pronouns in code comments or documentation. There is no need to address persons when explaining code (e.g. "When the developer") ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 Chris Ramón 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. ================================================ FILE: README.md ================================================ # graphql [![CircleCI](https://circleci.com/gh/graphql-go/graphql/tree/master.svg?style=svg)](https://circleci.com/gh/graphql-go/graphql/tree/master) [![Go Reference](https://pkg.go.dev/badge/github.com/graphql-go/graphql.svg)](https://pkg.go.dev/github.com/graphql-go/graphql) [![Coverage Status](https://coveralls.io/repos/github/graphql-go/graphql/badge.svg?branch=master)](https://coveralls.io/github/graphql-go/graphql?branch=master) [![Join the chat at https://gitter.im/graphql-go/graphql](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/graphql-go/graphql?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) An implementation of GraphQL in Go. Follows the official reference implementation [`graphql-js`](https://github.com/graphql/graphql-js). Supports: queries, mutations & subscriptions. ### Documentation godoc: https://pkg.go.dev/github.com/graphql-go/graphql ### Contribute Back Friendly reminder links are available in case you would like to contribute back into our commitment with Go and open-source. | Author | PayPal Link | |:-------------:|:-------------:| | [Hafiz Ismail](https://github.com/sogko) | Not available yet. | | [Chris Ramón](https://github.com/chris-ramon) | https://www.paypal.com/donate/?hosted_button_id=WHUQQYEMTRQBJ | ### Getting Started To install the library, run: ```bash go get github.com/graphql-go/graphql ``` The following is a simple example which defines a schema with a single `hello` string-type field and a `Resolve` method which returns the string `world`. A GraphQL query is performed against this schema with the resulting output printed in JSON format. ```go package main import ( "encoding/json" "fmt" "log" "github.com/graphql-go/graphql" ) func main() { // Schema fields := graphql.Fields{ "hello": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return "world", nil }, }, } rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: fields} schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)} schema, err := graphql.NewSchema(schemaConfig) if err != nil { log.Fatalf("failed to create new schema, error: %v", err) } // Query query := ` { hello } ` params := graphql.Params{Schema: schema, RequestString: query} r := graphql.Do(params) if len(r.Errors) > 0 { log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors) } rJSON, _ := json.Marshal(r) fmt.Printf("%s \n", rJSON) // {"data":{"hello":"world"}} } ``` For more complex examples, refer to the [examples/](https://github.com/graphql-go/graphql/tree/master/examples/) directory and [graphql_test.go](https://github.com/graphql-go/graphql/blob/master/graphql_test.go). ### Third Party Libraries | Name | Author | Description | |:-------------:|:-------------:|:------------:| | [graphql-go-handler](https://github.com/graphql-go/graphql-go-handler) | [Hafiz Ismail](https://github.com/sogko) | Middleware to handle GraphQL queries through HTTP requests. | | [graphql-relay-go](https://github.com/graphql-go/graphql-relay-go) | [Hafiz Ismail](https://github.com/sogko) | Lib to construct a graphql-go server supporting react-relay. | | [golang-relay-starter-kit](https://github.com/sogko/golang-relay-starter-kit) | [Hafiz Ismail](https://github.com/sogko) | Barebones starting point for a Relay application with Golang GraphQL server. | | [dataloader](https://github.com/nicksrandall/dataloader) | [Nick Randall](https://github.com/nicksrandall) | [DataLoader](https://github.com/facebook/dataloader) implementation in Go. | ### Blog Posts - [Golang + GraphQL + Relay](https://wehavefaces.net/learn-golang-graphql-relay-1-e59ea174a902) ================================================ FILE: abstract_test.go ================================================ package graphql_test import ( "reflect" "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/location" "github.com/graphql-go/graphql/testutil" ) type testDog struct { Name string `json:"name"` Woofs bool `json:"woofs"` } type testCat struct { Name string `json:"name"` Meows bool `json:"meows"` } type testHuman struct { Name string `json:"name"` } func TestIsTypeOfUsedToResolveRuntimeTypeForInterface(t *testing.T) { petType := graphql.NewInterface(graphql.InterfaceConfig{ Name: "Pet", Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, }, }, }) // ie declare that Dog belongs to Pet interface dogType := graphql.NewObject(graphql.ObjectConfig{ Name: "Dog", Interfaces: []*graphql.Interface{ petType, }, IsTypeOf: func(p graphql.IsTypeOfParams) bool { _, ok := p.Value.(*testDog) return ok }, Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { if dog, ok := p.Source.(*testDog); ok { return dog.Name, nil } return nil, nil }, }, "woofs": &graphql.Field{ Type: graphql.Boolean, Resolve: func(p graphql.ResolveParams) (interface{}, error) { if dog, ok := p.Source.(*testDog); ok { return dog.Woofs, nil } return nil, nil }, }, }, }) // ie declare that Cat belongs to Pet interface catType := graphql.NewObject(graphql.ObjectConfig{ Name: "Cat", Interfaces: []*graphql.Interface{ petType, }, IsTypeOf: func(p graphql.IsTypeOfParams) bool { _, ok := p.Value.(*testCat) return ok }, Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { if cat, ok := p.Source.(*testCat); ok { return cat.Name, nil } return nil, nil }, }, "meows": &graphql.Field{ Type: graphql.Boolean, Resolve: func(p graphql.ResolveParams) (interface{}, error) { if cat, ok := p.Source.(*testCat); ok { return cat.Meows, nil } return nil, nil }, }, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "pets": &graphql.Field{ Type: graphql.NewList(petType), Resolve: func(p graphql.ResolveParams) (interface{}, error) { return []interface{}{ &testDog{"Odie", true}, &testCat{"Garfield", false}, }, nil }, }, }, }), Types: []graphql.Type{catType, dogType}, }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } query := `{ pets { name ... on Dog { woofs } ... on Cat { meows } } }` expected := &graphql.Result{ Data: map[string]interface{}{ "pets": []interface{}{ map[string]interface{}{ "name": "Odie", "woofs": bool(true), }, map[string]interface{}{ "name": "Garfield", "meows": bool(false), }, }, }, Errors: nil, } result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) if len(result.Errors) != 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestAppendTypeUsedToAddRuntimeCustomScalarTypeForInterface(t *testing.T) { petType := graphql.NewInterface(graphql.InterfaceConfig{ Name: "Pet", Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, }, }, }) // ie declare that Dog belongs to Pet interface dogType := graphql.NewObject(graphql.ObjectConfig{ Name: "Dog", Interfaces: []*graphql.Interface{ petType, }, IsTypeOf: func(p graphql.IsTypeOfParams) bool { _, ok := p.Value.(*testDog) return ok }, Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { if dog, ok := p.Source.(*testDog); ok { return dog.Name, nil } return nil, nil }, }, "woofs": &graphql.Field{ Type: graphql.Boolean, Resolve: func(p graphql.ResolveParams) (interface{}, error) { if dog, ok := p.Source.(*testDog); ok { return dog.Woofs, nil } return nil, nil }, }, }, }) // ie declare that Cat belongs to Pet interface catType := graphql.NewObject(graphql.ObjectConfig{ Name: "Cat", Interfaces: []*graphql.Interface{ petType, }, IsTypeOf: func(p graphql.IsTypeOfParams) bool { _, ok := p.Value.(*testCat) return ok }, Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { if cat, ok := p.Source.(*testCat); ok { return cat.Name, nil } return nil, nil }, }, "meows": &graphql.Field{ Type: graphql.Boolean, Resolve: func(p graphql.ResolveParams) (interface{}, error) { if cat, ok := p.Source.(*testCat); ok { return cat.Meows, nil } return nil, nil }, }, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "pets": &graphql.Field{ Type: graphql.NewList(petType), Resolve: func(p graphql.ResolveParams) (interface{}, error) { return []interface{}{ &testDog{"Odie", true}, &testCat{"Garfield", false}, }, nil }, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } //Now add types catType and dogType at runtime. schema.AppendType(catType) schema.AppendType(dogType) query := `{ pets { name ... on Dog { woofs } ... on Cat { meows } } }` expected := &graphql.Result{ Data: map[string]interface{}{ "pets": []interface{}{ map[string]interface{}{ "name": "Odie", "woofs": bool(true), }, map[string]interface{}{ "name": "Garfield", "meows": bool(false), }, }, }, Errors: nil, } result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) if len(result.Errors) != 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestIsTypeOfUsedToResolveRuntimeTypeForUnion(t *testing.T) { dogType := graphql.NewObject(graphql.ObjectConfig{ Name: "Dog", IsTypeOf: func(p graphql.IsTypeOfParams) bool { _, ok := p.Value.(*testDog) return ok }, Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, }, "woofs": &graphql.Field{ Type: graphql.Boolean, }, }, }) catType := graphql.NewObject(graphql.ObjectConfig{ Name: "Cat", IsTypeOf: func(p graphql.IsTypeOfParams) bool { _, ok := p.Value.(*testCat) return ok }, Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, }, "meows": &graphql.Field{ Type: graphql.Boolean, }, }, }) // ie declare Pet has Dot and Cat object types petType := graphql.NewUnion(graphql.UnionConfig{ Name: "Pet", Types: []*graphql.Object{ dogType, catType, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "pets": &graphql.Field{ Type: graphql.NewList(petType), Resolve: func(p graphql.ResolveParams) (interface{}, error) { return []interface{}{ &testDog{"Odie", true}, &testCat{"Garfield", false}, }, nil }, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } query := `{ pets { ... on Dog { name woofs } ... on Cat { name meows } } }` expected := &graphql.Result{ Data: map[string]interface{}{ "pets": []interface{}{ map[string]interface{}{ "name": "Odie", "woofs": bool(true), }, map[string]interface{}{ "name": "Garfield", "meows": bool(false), }, }, }, Errors: nil, } result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) if len(result.Errors) != 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestResolveTypeOnInterfaceYieldsUsefulError(t *testing.T) { var dogType *graphql.Object var catType *graphql.Object var humanType *graphql.Object petType := graphql.NewInterface(graphql.InterfaceConfig{ Name: "Pet", Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, }, }, ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { if _, ok := p.Value.(*testCat); ok { return catType } if _, ok := p.Value.(*testDog); ok { return dogType } if _, ok := p.Value.(*testHuman); ok { return humanType } return nil }, }) humanType = graphql.NewObject(graphql.ObjectConfig{ Name: "Human", Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, }, }, }) dogType = graphql.NewObject(graphql.ObjectConfig{ Name: "Dog", Interfaces: []*graphql.Interface{ petType, }, Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, }, "woofs": &graphql.Field{ Type: graphql.Boolean, }, }, }) catType = graphql.NewObject(graphql.ObjectConfig{ Name: "Cat", Interfaces: []*graphql.Interface{ petType, }, Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, }, "meows": &graphql.Field{ Type: graphql.Boolean, }, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "pets": &graphql.Field{ Type: graphql.NewList(petType), Resolve: func(p graphql.ResolveParams) (interface{}, error) { return []interface{}{ &testDog{"Odie", true}, &testCat{"Garfield", false}, &testHuman{"Jon"}, }, nil }, }, }, }), Types: []graphql.Type{catType, dogType}, }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } query := `{ pets { name ... on Dog { woofs } ... on Cat { meows } } }` expected := &graphql.Result{ Data: map[string]interface{}{ "pets": []interface{}{ map[string]interface{}{ "name": "Odie", "woofs": bool(true), }, map[string]interface{}{ "name": "Garfield", "meows": bool(false), }, nil, }, }, Errors: []gqlerrors.FormattedError{ { Message: `Runtime Object type "Human" is not a possible type for "Pet".`, Locations: []location.SourceLocation{ { Line: 2, Column: 7, }, }, Path: []interface{}{ "pets", 2, }, }, }, } result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestResolveTypeOnUnionYieldsUsefulError(t *testing.T) { humanType := graphql.NewObject(graphql.ObjectConfig{ Name: "Human", Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, }, }, }) dogType := graphql.NewObject(graphql.ObjectConfig{ Name: "Dog", Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, }, "woofs": &graphql.Field{ Type: graphql.Boolean, }, }, }) catType := graphql.NewObject(graphql.ObjectConfig{ Name: "Cat", Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, }, "meows": &graphql.Field{ Type: graphql.Boolean, }, }, }) petType := graphql.NewUnion(graphql.UnionConfig{ Name: "Pet", Types: []*graphql.Object{ dogType, catType, }, ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { if _, ok := p.Value.(*testCat); ok { return catType } if _, ok := p.Value.(*testDog); ok { return dogType } if _, ok := p.Value.(*testHuman); ok { return humanType } return nil }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "pets": &graphql.Field{ Type: graphql.NewList(petType), Resolve: func(p graphql.ResolveParams) (interface{}, error) { return []interface{}{ &testDog{"Odie", true}, &testCat{"Garfield", false}, &testHuman{"Jon"}, }, nil }, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } query := `{ pets { ... on Dog { name woofs } ... on Cat { name meows } } }` expected := &graphql.Result{ Data: map[string]interface{}{ "pets": []interface{}{ map[string]interface{}{ "name": "Odie", "woofs": bool(true), }, map[string]interface{}{ "name": "Garfield", "meows": bool(false), }, nil, }, }, Errors: []gqlerrors.FormattedError{ { Message: `Runtime Object type "Human" is not a possible type for "Pet".`, Locations: []location.SourceLocation{ { Line: 2, Column: 7, }, }, Path: []interface{}{ "pets", 2, }, }, }, } result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } ================================================ FILE: benchutil/list_schema.go ================================================ package benchutil import ( "fmt" "github.com/graphql-go/graphql" ) type color struct { Hex string R int G int B int } func ListSchemaWithXItems(x int) graphql.Schema { list := generateXListItems(x) color := graphql.NewObject(graphql.ObjectConfig{ Name: "Color", Description: "A color", Fields: graphql.Fields{ "hex": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), Description: "Hex color code.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if c, ok := p.Source.(color); ok { return c.Hex, nil } return nil, nil }, }, "r": &graphql.Field{ Type: graphql.NewNonNull(graphql.Int), Description: "Red value.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if c, ok := p.Source.(color); ok { return c.R, nil } return nil, nil }, }, "g": &graphql.Field{ Type: graphql.NewNonNull(graphql.Int), Description: "Green value.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if c, ok := p.Source.(color); ok { return c.G, nil } return nil, nil }, }, "b": &graphql.Field{ Type: graphql.NewNonNull(graphql.Int), Description: "Blue value.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if c, ok := p.Source.(color); ok { return c.B, nil } return nil, nil }, }, }, }) queryType := graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "colors": { Type: graphql.NewList(color), Resolve: func(p graphql.ResolveParams) (interface{}, error) { return list, nil }, }, }, }) colorSchema, _ := graphql.NewSchema(graphql.SchemaConfig{ Query: queryType, }) return colorSchema } var colors []color func init() { colors = make([]color, 0, 256*16*16) for r := 0; r < 256; r++ { for g := 0; g < 16; g++ { for b := 0; b < 16; b++ { colors = append(colors, color{ Hex: fmt.Sprintf("#%x%x%x", r, g, b), R: r, G: g, B: b, }) } } } } func generateXListItems(x int) []color { if x > len(colors) { x = len(colors) } return colors[0:x] } ================================================ FILE: benchutil/wide_schema.go ================================================ package benchutil import ( "fmt" "github.com/graphql-go/graphql" ) func WideSchemaWithXFieldsAndYItems(x int, y int) graphql.Schema { wide := graphql.NewObject(graphql.ObjectConfig{ Name: "Wide", Description: "An object", Fields: generateXWideFields(x), }) queryType := graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "wide": { Type: graphql.NewList(wide), Resolve: func(p graphql.ResolveParams) (interface{}, error) { out := make([]struct{}, 0, y) for i := 0; i < y; i++ { out = append(out, struct{}{}) } return out, nil }, }, }, }) wideSchema, _ := graphql.NewSchema(graphql.SchemaConfig{ Query: queryType, }) return wideSchema } func generateXWideFields(x int) graphql.Fields { fields := graphql.Fields{} for i := 0; i < x; i++ { fields[generateFieldNameFromX(i)] = generateWideFieldFromX(i) } return fields } func generateWideFieldFromX(x int) *graphql.Field { return &graphql.Field{ Type: generateWideTypeFromX(x), Resolve: generateWideResolveFromX(x), } } func generateWideTypeFromX(x int) graphql.Type { switch x % 8 { case 0: return graphql.String case 1: return graphql.NewNonNull(graphql.String) case 2: return graphql.Int case 3: return graphql.NewNonNull(graphql.Int) case 4: return graphql.Float case 5: return graphql.NewNonNull(graphql.Float) case 6: return graphql.Boolean case 7: return graphql.NewNonNull(graphql.Boolean) } return nil } func generateFieldNameFromX(x int) string { var out string alphabet := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "z"} v := x for { r := v % 10 out = alphabet[r] + out v /= 10 if v == 0 { break } } return out } func generateWideResolveFromX(x int) func(p graphql.ResolveParams) (interface{}, error) { switch x % 8 { case 0: return func(p graphql.ResolveParams) (interface{}, error) { return fmt.Sprint(x), nil } case 1: return func(p graphql.ResolveParams) (interface{}, error) { return fmt.Sprint(x), nil } case 2: return func(p graphql.ResolveParams) (interface{}, error) { return x, nil } case 3: return func(p graphql.ResolveParams) (interface{}, error) { return x, nil } case 4: return func(p graphql.ResolveParams) (interface{}, error) { return float64(x), nil } case 5: return func(p graphql.ResolveParams) (interface{}, error) { return float64(x), nil } case 6: return func(p graphql.ResolveParams) (interface{}, error) { if x%2 == 0 { return false, nil } return true, nil } case 7: return func(p graphql.ResolveParams) (interface{}, error) { if x%2 == 0 { return false, nil } return true, nil } } return nil } func WideSchemaQuery(x int) string { var fields string for i := 0; i < x; i++ { fields = fields + generateFieldNameFromX(i) + " " } return fmt.Sprintf("query { wide { %s} }", fields) } ================================================ FILE: definition.go ================================================ package graphql import ( "context" "fmt" "reflect" "regexp" "github.com/graphql-go/graphql/language/ast" ) // Type interface for all of the possible kinds of GraphQL types type Type interface { Name() string Description() string String() string Error() error } var _ Type = (*Scalar)(nil) var _ Type = (*Object)(nil) var _ Type = (*Interface)(nil) var _ Type = (*Union)(nil) var _ Type = (*Enum)(nil) var _ Type = (*InputObject)(nil) var _ Type = (*List)(nil) var _ Type = (*NonNull)(nil) var _ Type = (*Argument)(nil) // Input interface for types that may be used as input types for arguments and directives. type Input interface { Name() string Description() string String() string Error() error } var _ Input = (*Scalar)(nil) var _ Input = (*Enum)(nil) var _ Input = (*InputObject)(nil) var _ Input = (*List)(nil) var _ Input = (*NonNull)(nil) // IsInputType determines if given type is a GraphQLInputType func IsInputType(ttype Type) bool { switch GetNamed(ttype).(type) { case *Scalar, *Enum, *InputObject: return true default: return false } } // IsOutputType determines if given type is a GraphQLOutputType func IsOutputType(ttype Type) bool { switch GetNamed(ttype).(type) { case *Scalar, *Object, *Interface, *Union, *Enum: return true default: return false } } // Leaf interface for types that may be leaf values type Leaf interface { Name() string Description() string String() string Error() error Serialize(value interface{}) interface{} } var _ Leaf = (*Scalar)(nil) var _ Leaf = (*Enum)(nil) // IsLeafType determines if given type is a leaf value func IsLeafType(ttype Type) bool { switch GetNamed(ttype).(type) { case *Scalar, *Enum: return true default: return false } } // Output interface for types that may be used as output types as the result of fields. type Output interface { Name() string Description() string String() string Error() error } var _ Output = (*Scalar)(nil) var _ Output = (*Object)(nil) var _ Output = (*Interface)(nil) var _ Output = (*Union)(nil) var _ Output = (*Enum)(nil) var _ Output = (*List)(nil) var _ Output = (*NonNull)(nil) // Composite interface for types that may describe the parent context of a selection set. type Composite interface { Name() string Description() string String() string Error() error } var _ Composite = (*Object)(nil) var _ Composite = (*Interface)(nil) var _ Composite = (*Union)(nil) // IsCompositeType determines if given type is a GraphQLComposite type func IsCompositeType(ttype interface{}) bool { switch ttype.(type) { case *Object, *Interface, *Union: return true default: return false } } // Abstract interface for types that may describe the parent context of a selection set. type Abstract interface { Name() string } var _ Abstract = (*Interface)(nil) var _ Abstract = (*Union)(nil) func IsAbstractType(ttype interface{}) bool { switch ttype.(type) { case *Interface, *Union: return true default: return false } } // Nullable interface for types that can accept null as a value. type Nullable interface { } var _ Nullable = (*Scalar)(nil) var _ Nullable = (*Object)(nil) var _ Nullable = (*Interface)(nil) var _ Nullable = (*Union)(nil) var _ Nullable = (*Enum)(nil) var _ Nullable = (*InputObject)(nil) var _ Nullable = (*List)(nil) // GetNullable returns the Nullable type of the given GraphQL type func GetNullable(ttype Type) Nullable { if ttype, ok := ttype.(*NonNull); ok { return ttype.OfType } return ttype } // Named interface for types that do not include modifiers like List or NonNull. type Named interface { String() string } var _ Named = (*Scalar)(nil) var _ Named = (*Object)(nil) var _ Named = (*Interface)(nil) var _ Named = (*Union)(nil) var _ Named = (*Enum)(nil) var _ Named = (*InputObject)(nil) // GetNamed returns the Named type of the given GraphQL type func GetNamed(ttype Type) Named { unmodifiedType := ttype for { switch typ := unmodifiedType.(type) { case *List: unmodifiedType = typ.OfType case *NonNull: unmodifiedType = typ.OfType default: return unmodifiedType } } } // Scalar Type Definition // // The leaf values of any request and input values to arguments are // Scalars (or Enums) and are defined with a name and a series of functions // used to parse input from ast or variables and to ensure validity. // // Example: // // var OddType = new Scalar({ // name: 'Odd', // serialize(value) { // return value % 2 === 1 ? value : null; // } // }); type Scalar struct { PrivateName string `json:"name"` PrivateDescription string `json:"description"` scalarConfig ScalarConfig err error } // SerializeFn is a function type for serializing a GraphQLScalar type value type SerializeFn func(value interface{}) interface{} // ParseValueFn is a function type for parsing the value of a GraphQLScalar type type ParseValueFn func(value interface{}) interface{} // ParseLiteralFn is a function type for parsing the literal value of a GraphQLScalar type type ParseLiteralFn func(valueAST ast.Value) interface{} // ScalarConfig options for creating a new GraphQLScalar type ScalarConfig struct { Name string `json:"name"` Description string `json:"description"` Serialize SerializeFn ParseValue ParseValueFn ParseLiteral ParseLiteralFn } // NewScalar creates a new GraphQLScalar func NewScalar(config ScalarConfig) *Scalar { st := &Scalar{} err := invariant(config.Name != "", "Type must be named.") if err != nil { st.err = err return st } err = assertValidName(config.Name) if err != nil { st.err = err return st } st.PrivateName = config.Name st.PrivateDescription = config.Description err = invariantf( config.Serialize != nil, `%v must provide "serialize" function. If this custom Scalar is `+ `also used as an input type, ensure "parseValue" and "parseLiteral" `+ `functions are also provided.`, st, ) if err != nil { st.err = err return st } if config.ParseValue != nil || config.ParseLiteral != nil { err = invariantf( config.ParseValue != nil && config.ParseLiteral != nil, `%v must provide both "parseValue" and "parseLiteral" functions.`, st, ) if err != nil { st.err = err return st } } st.scalarConfig = config return st } func (st *Scalar) Serialize(value interface{}) interface{} { if st.scalarConfig.Serialize == nil { return value } return st.scalarConfig.Serialize(value) } func (st *Scalar) ParseValue(value interface{}) interface{} { if st.scalarConfig.ParseValue == nil { return value } return st.scalarConfig.ParseValue(value) } func (st *Scalar) ParseLiteral(valueAST ast.Value) interface{} { if st.scalarConfig.ParseLiteral == nil { return nil } return st.scalarConfig.ParseLiteral(valueAST) } func (st *Scalar) Name() string { return st.PrivateName } func (st *Scalar) Description() string { return st.PrivateDescription } func (st *Scalar) String() string { return st.PrivateName } func (st *Scalar) Error() error { return st.err } // Object Type Definition // // Almost all of the GraphQL types you define will be object Object types // have a name, but most importantly describe their fields. // Example: // // var AddressType = new Object({ // name: 'Address', // fields: { // street: { type: String }, // number: { type: Int }, // formatted: { // type: String, // resolve(obj) { // return obj.number + ' ' + obj.street // } // } // } // }); // // When two types need to refer to each other, or a type needs to refer to // itself in a field, you can use a function expression (aka a closure or a // thunk) to supply the fields lazily. // // Example: // // var PersonType = new Object({ // name: 'Person', // fields: () => ({ // name: { type: String }, // bestFriend: { type: PersonType }, // }) // }); // // / type Object struct { PrivateName string `json:"name"` PrivateDescription string `json:"description"` IsTypeOf IsTypeOfFn typeConfig ObjectConfig initialisedFields bool fields FieldDefinitionMap initialisedInterfaces bool interfaces []*Interface // Interim alternative to throwing an error during schema definition at run-time err error } // IsTypeOfParams Params for IsTypeOfFn() type IsTypeOfParams struct { // Value that needs to be resolve. // Use this to decide which GraphQLObject this value maps to. Value interface{} // Info is a collection of information about the current execution state. Info ResolveInfo // Context argument is a context value that is provided to every resolve function within an execution. // It is commonly // used to represent an authenticated user, or request-specific caches. Context context.Context } type IsTypeOfFn func(p IsTypeOfParams) bool type InterfacesThunk func() []*Interface type ObjectConfig struct { Name string `json:"name"` Interfaces interface{} `json:"interfaces"` Fields interface{} `json:"fields"` IsTypeOf IsTypeOfFn `json:"isTypeOf"` Description string `json:"description"` } type FieldsThunk func() Fields func NewObject(config ObjectConfig) *Object { objectType := &Object{} err := invariant(config.Name != "", "Type must be named.") if err != nil { objectType.err = err return objectType } err = assertValidName(config.Name) if err != nil { objectType.err = err return objectType } objectType.PrivateName = config.Name objectType.PrivateDescription = config.Description objectType.IsTypeOf = config.IsTypeOf objectType.typeConfig = config return objectType } // ensureCache ensures that both fields and interfaces have been initialized properly, // to prevent races. func (gt *Object) ensureCache() { gt.Fields() gt.Interfaces() } func (gt *Object) AddFieldConfig(fieldName string, fieldConfig *Field) { if fieldName == "" || fieldConfig == nil { return } if fields, ok := gt.typeConfig.Fields.(Fields); ok { fields[fieldName] = fieldConfig gt.initialisedFields = false } } func (gt *Object) Name() string { return gt.PrivateName } func (gt *Object) Description() string { return gt.PrivateDescription } func (gt *Object) String() string { return gt.PrivateName } func (gt *Object) Fields() FieldDefinitionMap { if gt.initialisedFields { return gt.fields } var configureFields Fields switch fields := gt.typeConfig.Fields.(type) { case Fields: configureFields = fields case FieldsThunk: configureFields = fields() } gt.fields, gt.err = defineFieldMap(gt, configureFields) gt.initialisedFields = true return gt.fields } func (gt *Object) Interfaces() []*Interface { if gt.initialisedInterfaces { return gt.interfaces } var configInterfaces []*Interface switch iface := gt.typeConfig.Interfaces.(type) { case InterfacesThunk: configInterfaces = iface() case []*Interface: configInterfaces = iface case nil: default: gt.err = fmt.Errorf("Unknown Object.Interfaces type: %T", gt.typeConfig.Interfaces) gt.initialisedInterfaces = true return nil } gt.interfaces, gt.err = defineInterfaces(gt, configInterfaces) gt.initialisedInterfaces = true return gt.interfaces } func (gt *Object) Error() error { return gt.err } func defineInterfaces(ttype *Object, interfaces []*Interface) ([]*Interface, error) { ifaces := []*Interface{} if len(interfaces) == 0 { return ifaces, nil } for _, iface := range interfaces { err := invariantf( iface != nil, `%v may only implement Interface types, it cannot implement: %v.`, ttype, iface, ) if err != nil { return ifaces, err } if iface.ResolveType != nil { err = invariantf( iface.ResolveType != nil, `Interface Type %v does not provide a "resolveType" function `+ `and implementing Type %v does not provide a "isTypeOf" `+ `function. There is no way to resolve this implementing type `+ `during execution.`, iface, ttype, ) if err != nil { return ifaces, err } } ifaces = append(ifaces, iface) } return ifaces, nil } func defineFieldMap(ttype Named, fieldMap Fields) (FieldDefinitionMap, error) { resultFieldMap := FieldDefinitionMap{} err := invariantf( len(fieldMap) > 0, `%v fields must be an object with field names as keys or a function which return such an object.`, ttype, ) if err != nil { return resultFieldMap, err } for fieldName, field := range fieldMap { if field == nil { continue } err = invariantf( field.Type != nil, `%v.%v field type must be Output Type but got: %v.`, ttype, fieldName, field.Type, ) if err != nil { return resultFieldMap, err } if field.Type.Error() != nil { return resultFieldMap, field.Type.Error() } if err = assertValidName(fieldName); err != nil { return resultFieldMap, err } fieldDef := &FieldDefinition{ Name: fieldName, Description: field.Description, Type: field.Type, Resolve: field.Resolve, Subscribe: field.Subscribe, DeprecationReason: field.DeprecationReason, } fieldDef.Args = []*Argument{} for argName, arg := range field.Args { if err = assertValidName(argName); err != nil { return resultFieldMap, err } if err = invariantf( arg != nil, `%v.%v args must be an object with argument names as keys.`, ttype, fieldName, ); err != nil { return resultFieldMap, err } if err = invariantf( arg.Type != nil, `%v.%v(%v:) argument type must be Input Type but got: %v.`, ttype, fieldName, argName, arg.Type, ); err != nil { return resultFieldMap, err } fieldArg := &Argument{ PrivateName: argName, PrivateDescription: arg.Description, Type: arg.Type, DefaultValue: arg.DefaultValue, } fieldDef.Args = append(fieldDef.Args, fieldArg) } resultFieldMap[fieldName] = fieldDef } return resultFieldMap, nil } // ResolveParams Params for FieldResolveFn() type ResolveParams struct { // Source is the source value Source interface{} // Args is a map of arguments for current GraphQL request Args map[string]interface{} // Info is a collection of information about the current execution state. Info ResolveInfo // Context argument is a context value that is provided to every resolve function within an execution. // It is commonly // used to represent an authenticated user, or request-specific caches. Context context.Context } type FieldResolveFn func(p ResolveParams) (interface{}, error) type ResolveInfo struct { FieldName string FieldASTs []*ast.Field Path *ResponsePath ReturnType Output ParentType Composite Schema Schema Fragments map[string]ast.Definition RootValue interface{} Operation ast.Definition VariableValues map[string]interface{} } type Fields map[string]*Field type Field struct { Name string `json:"name"` // used by graphlql-relay Type Output `json:"type"` Args FieldConfigArgument `json:"args"` Resolve FieldResolveFn `json:"-"` Subscribe FieldResolveFn `json:"-"` DeprecationReason string `json:"deprecationReason"` Description string `json:"description"` } type FieldConfigArgument map[string]*ArgumentConfig type ArgumentConfig struct { Type Input `json:"type"` DefaultValue interface{} `json:"defaultValue"` Description string `json:"description"` } type FieldDefinitionMap map[string]*FieldDefinition type FieldDefinition struct { Name string `json:"name"` Description string `json:"description"` Type Output `json:"type"` Args []*Argument `json:"args"` Resolve FieldResolveFn `json:"-"` Subscribe FieldResolveFn `json:"-"` DeprecationReason string `json:"deprecationReason"` } type FieldArgument struct { Name string `json:"name"` Type Type `json:"type"` DefaultValue interface{} `json:"defaultValue"` Description string `json:"description"` } type Argument struct { PrivateName string `json:"name"` Type Input `json:"type"` DefaultValue interface{} `json:"defaultValue"` PrivateDescription string `json:"description"` } func (st *Argument) Name() string { return st.PrivateName } func (st *Argument) Description() string { return st.PrivateDescription } func (st *Argument) String() string { return st.PrivateName } func (st *Argument) Error() error { return nil } // Interface Type Definition // // When a field can return one of a heterogeneous set of types, a Interface type // is used to describe what types are possible, what fields are in common across // all types, as well as a function to determine which type is actually used // when the field is resolved. // // Example: // // var EntityType = new Interface({ // name: 'Entity', // fields: { // name: { type: String } // } // }); type Interface struct { PrivateName string `json:"name"` PrivateDescription string `json:"description"` ResolveType ResolveTypeFn typeConfig InterfaceConfig initialisedFields bool fields FieldDefinitionMap err error } type InterfaceConfig struct { Name string `json:"name"` Fields interface{} `json:"fields"` ResolveType ResolveTypeFn Description string `json:"description"` } // ResolveTypeParams Params for ResolveTypeFn() type ResolveTypeParams struct { // Value that needs to be resolve. // Use this to decide which GraphQLObject this value maps to. Value interface{} // Info is a collection of information about the current execution state. Info ResolveInfo // Context argument is a context value that is provided to every resolve function within an execution. // It is commonly // used to represent an authenticated user, or request-specific caches. Context context.Context } type ResolveTypeFn func(p ResolveTypeParams) *Object func NewInterface(config InterfaceConfig) *Interface { it := &Interface{} if it.err = invariant(config.Name != "", "Type must be named."); it.err != nil { return it } if it.err = assertValidName(config.Name); it.err != nil { return it } it.PrivateName = config.Name it.PrivateDescription = config.Description it.ResolveType = config.ResolveType it.typeConfig = config return it } func (it *Interface) AddFieldConfig(fieldName string, fieldConfig *Field) { if fieldName == "" || fieldConfig == nil { return } if fields, ok := it.typeConfig.Fields.(Fields); ok { fields[fieldName] = fieldConfig it.initialisedFields = false } } func (it *Interface) Name() string { return it.PrivateName } func (it *Interface) Description() string { return it.PrivateDescription } func (it *Interface) Fields() (fields FieldDefinitionMap) { if it.initialisedFields { return it.fields } var configureFields Fields switch fields := it.typeConfig.Fields.(type) { case Fields: configureFields = fields case FieldsThunk: configureFields = fields() } it.fields, it.err = defineFieldMap(it, configureFields) it.initialisedFields = true return it.fields } func (it *Interface) String() string { return it.PrivateName } func (it *Interface) Error() error { return it.err } // Union Type Definition // // When a field can return one of a heterogeneous set of types, a Union type // is used to describe what types are possible as well as providing a function // to determine which type is actually used when the field is resolved. // // Example: // // var PetType = new Union({ // name: 'Pet', // types: [ DogType, CatType ], // resolveType(value) { // if (value instanceof Dog) { // return DogType; // } // if (value instanceof Cat) { // return CatType; // } // } // }); type Union struct { PrivateName string `json:"name"` PrivateDescription string `json:"description"` ResolveType ResolveTypeFn typeConfig UnionConfig initalizedTypes bool types []*Object possibleTypes map[string]bool err error } type UnionTypesThunk func() []*Object type UnionConfig struct { Name string `json:"name"` Types interface{} `json:"types"` ResolveType ResolveTypeFn Description string `json:"description"` } func NewUnion(config UnionConfig) *Union { objectType := &Union{} if objectType.err = invariant(config.Name != "", "Type must be named."); objectType.err != nil { return objectType } if objectType.err = assertValidName(config.Name); objectType.err != nil { return objectType } objectType.PrivateName = config.Name objectType.PrivateDescription = config.Description objectType.ResolveType = config.ResolveType objectType.typeConfig = config return objectType } func (ut *Union) Types() []*Object { if ut.initalizedTypes { return ut.types } var unionTypes []*Object switch utype := ut.typeConfig.Types.(type) { case UnionTypesThunk: unionTypes = utype() case []*Object: unionTypes = utype case nil: default: ut.err = fmt.Errorf("Unknown Union.Types type: %T", ut.typeConfig.Types) ut.initalizedTypes = true return nil } ut.types, ut.err = defineUnionTypes(ut, unionTypes) ut.initalizedTypes = true return ut.types } func defineUnionTypes(objectType *Union, unionTypes []*Object) ([]*Object, error) { definedUnionTypes := []*Object{} if err := invariantf( len(unionTypes) > 0, `Must provide Array of types for Union %v.`, objectType.Name(), ); err != nil { return definedUnionTypes, err } for _, ttype := range unionTypes { if err := invariantf( ttype != nil, `%v may only contain Object types, it cannot contain: %v.`, objectType, ttype, ); err != nil { return definedUnionTypes, err } if objectType.ResolveType == nil { if err := invariantf( ttype.IsTypeOf != nil, `Union Type %v does not provide a "resolveType" function `+ `and possible Type %v does not provide a "isTypeOf" `+ `function. There is no way to resolve this possible type `+ `during execution.`, objectType, ttype, ); err != nil { return definedUnionTypes, err } } definedUnionTypes = append(definedUnionTypes, ttype) } return definedUnionTypes, nil } func (ut *Union) String() string { return ut.PrivateName } func (ut *Union) Name() string { return ut.PrivateName } func (ut *Union) Description() string { return ut.PrivateDescription } func (ut *Union) Error() error { return ut.err } // Enum Type Definition // // Some leaf values of requests and input values are Enums. GraphQL serializes // Enum values as strings, however internally Enums can be represented by any // kind of type, often integers. // // Example: // // var RGBType = new Enum({ // name: 'RGB', // values: { // RED: { value: 0 }, // GREEN: { value: 1 }, // BLUE: { value: 2 } // } // }); // // Note: If a value is not provided in a definition, the name of the enum value // will be used as its internal value. type Enum struct { PrivateName string `json:"name"` PrivateDescription string `json:"description"` enumConfig EnumConfig values []*EnumValueDefinition valuesLookup map[interface{}]*EnumValueDefinition nameLookup map[string]*EnumValueDefinition err error } type EnumValueConfigMap map[string]*EnumValueConfig type EnumValueConfig struct { Value interface{} `json:"value"` DeprecationReason string `json:"deprecationReason"` Description string `json:"description"` } type EnumConfig struct { Name string `json:"name"` Values EnumValueConfigMap `json:"values"` Description string `json:"description"` } type EnumValueDefinition struct { Name string `json:"name"` Value interface{} `json:"value"` DeprecationReason string `json:"deprecationReason"` Description string `json:"description"` } func NewEnum(config EnumConfig) *Enum { gt := &Enum{} gt.enumConfig = config if gt.err = assertValidName(config.Name); gt.err != nil { return gt } gt.PrivateName = config.Name gt.PrivateDescription = config.Description if gt.values, gt.err = gt.defineEnumValues(config.Values); gt.err != nil { return gt } return gt } func (gt *Enum) defineEnumValues(valueMap EnumValueConfigMap) ([]*EnumValueDefinition, error) { var err error values := []*EnumValueDefinition{} if err = invariantf( len(valueMap) > 0, `%v values must be an object with value names as keys.`, gt, ); err != nil { return values, err } for valueName, valueConfig := range valueMap { if err = invariantf( valueConfig != nil, `%v.%v must refer to an object with a "value" key `+ `representing an internal value but got: %v.`, gt, valueName, valueConfig, ); err != nil { return values, err } if err = assertValidName(valueName); err != nil { return values, err } value := &EnumValueDefinition{ Name: valueName, Value: valueConfig.Value, DeprecationReason: valueConfig.DeprecationReason, Description: valueConfig.Description, } if value.Value == nil { value.Value = valueName } values = append(values, value) } return values, nil } func (gt *Enum) Values() []*EnumValueDefinition { return gt.values } func (gt *Enum) Serialize(value interface{}) interface{} { v := value rv := reflect.ValueOf(v) if kind := rv.Kind(); kind == reflect.Ptr && rv.IsNil() { return nil } else if kind == reflect.Ptr { v = reflect.Indirect(reflect.ValueOf(v)).Interface() } if enumValue, ok := gt.getValueLookup()[v]; ok { return enumValue.Name } return nil } func (gt *Enum) ParseValue(value interface{}) interface{} { var v string switch value := value.(type) { case string: v = value case *string: v = *value default: return nil } if enumValue, ok := gt.getNameLookup()[v]; ok { return enumValue.Value } return nil } func (gt *Enum) ParseLiteral(valueAST ast.Value) interface{} { if valueAST, ok := valueAST.(*ast.EnumValue); ok { if enumValue, ok := gt.getNameLookup()[valueAST.Value]; ok { return enumValue.Value } } return nil } func (gt *Enum) Name() string { return gt.PrivateName } func (gt *Enum) Description() string { return gt.PrivateDescription } func (gt *Enum) String() string { return gt.PrivateName } func (gt *Enum) Error() error { return gt.err } func (gt *Enum) getValueLookup() map[interface{}]*EnumValueDefinition { if len(gt.valuesLookup) > 0 { return gt.valuesLookup } valuesLookup := map[interface{}]*EnumValueDefinition{} for _, value := range gt.Values() { valuesLookup[value.Value] = value } gt.valuesLookup = valuesLookup return gt.valuesLookup } func (gt *Enum) getNameLookup() map[string]*EnumValueDefinition { if len(gt.nameLookup) > 0 { return gt.nameLookup } nameLookup := map[string]*EnumValueDefinition{} for _, value := range gt.Values() { nameLookup[value.Name] = value } gt.nameLookup = nameLookup return gt.nameLookup } // InputObject Type Definition // // An input object defines a structured collection of fields which may be // supplied to a field argument. // // # Using `NonNull` will ensure that a value must be provided by the query // // Example: // // var GeoPoint = new InputObject({ // name: 'GeoPoint', // fields: { // lat: { type: new NonNull(Float) }, // lon: { type: new NonNull(Float) }, // alt: { type: Float, defaultValue: 0 }, // } // }); type InputObject struct { PrivateName string `json:"name"` PrivateDescription string `json:"description"` typeConfig InputObjectConfig fields InputObjectFieldMap init bool err error } type InputObjectFieldConfig struct { Type Input `json:"type"` DefaultValue interface{} `json:"defaultValue"` Description string `json:"description"` } type InputObjectField struct { PrivateName string `json:"name"` Type Input `json:"type"` DefaultValue interface{} `json:"defaultValue"` PrivateDescription string `json:"description"` } func (st *InputObjectField) Name() string { return st.PrivateName } func (st *InputObjectField) Description() string { return st.PrivateDescription } func (st *InputObjectField) String() string { return st.PrivateName } func (st *InputObjectField) Error() error { return nil } type InputObjectConfigFieldMap map[string]*InputObjectFieldConfig type InputObjectFieldMap map[string]*InputObjectField type InputObjectConfigFieldMapThunk func() InputObjectConfigFieldMap type InputObjectConfig struct { Name string `json:"name"` Fields interface{} `json:"fields"` Description string `json:"description"` } func NewInputObject(config InputObjectConfig) *InputObject { gt := &InputObject{} if gt.err = invariant(config.Name != "", "Type must be named."); gt.err != nil { return gt } gt.PrivateName = config.Name gt.PrivateDescription = config.Description gt.typeConfig = config return gt } func (gt *InputObject) defineFieldMap() InputObjectFieldMap { var ( fieldMap InputObjectConfigFieldMap err error ) switch fields := gt.typeConfig.Fields.(type) { case InputObjectConfigFieldMap: fieldMap = fields case InputObjectConfigFieldMapThunk: fieldMap = fields() } resultFieldMap := InputObjectFieldMap{} if gt.err = invariantf( len(fieldMap) > 0, `%v fields must be an object with field names as keys or a function which return such an object.`, gt, ); gt.err != nil { return resultFieldMap } for fieldName, fieldConfig := range fieldMap { if fieldConfig == nil { continue } if err = assertValidName(fieldName); err != nil { continue } if gt.err = invariantf( fieldConfig.Type != nil, `%v.%v field type must be Input Type but got: %v.`, gt, fieldName, fieldConfig.Type, ); gt.err != nil { return resultFieldMap } field := &InputObjectField{} field.PrivateName = fieldName field.Type = fieldConfig.Type field.PrivateDescription = fieldConfig.Description field.DefaultValue = fieldConfig.DefaultValue resultFieldMap[fieldName] = field } gt.init = true return resultFieldMap } func (gt *InputObject) AddFieldConfig(fieldName string, fieldConfig *InputObjectFieldConfig) { if fieldName == "" || fieldConfig == nil { return } fieldMap, ok := gt.typeConfig.Fields.(InputObjectConfigFieldMap) if gt.err = invariant(ok, "Cannot add field to a thunk"); gt.err != nil { return } fieldMap[fieldName] = fieldConfig gt.fields = gt.defineFieldMap() } func (gt *InputObject) Fields() InputObjectFieldMap { if !gt.init { gt.fields = gt.defineFieldMap() } return gt.fields } func (gt *InputObject) Name() string { return gt.PrivateName } func (gt *InputObject) Description() string { return gt.PrivateDescription } func (gt *InputObject) String() string { return gt.PrivateName } func (gt *InputObject) Error() error { return gt.err } // List Modifier // // A list is a kind of type marker, a wrapping type which points to another // type. Lists are often created within the context of defining the fields of // an object type. // // Example: // // var PersonType = new Object({ // name: 'Person', // fields: () => ({ // parents: { type: new List(Person) }, // children: { type: new List(Person) }, // }) // }) type List struct { OfType Type `json:"ofType"` err error } func NewList(ofType Type) *List { gl := &List{} gl.err = invariantf(ofType != nil, `Can only create List of a Type but got: %v.`, ofType) if gl.err != nil { return gl } gl.OfType = ofType return gl } func (gl *List) Name() string { return fmt.Sprintf("[%v]", gl.OfType) } func (gl *List) Description() string { return "" } func (gl *List) String() string { if gl.OfType != nil { return gl.Name() } return "" } func (gl *List) Error() error { return gl.err } // NonNull Modifier // // A non-null is a kind of type marker, a wrapping type which points to another // type. Non-null types enforce that their values are never null and can ensure // an error is raised if this ever occurs during a request. It is useful for // fields which you can make a strong guarantee on non-nullability, for example // usually the id field of a database row will never be null. // // Example: // // var RowType = new Object({ // name: 'Row', // fields: () => ({ // id: { type: new NonNull(String) }, // }) // }) // // Note: the enforcement of non-nullability occurs within the executor. type NonNull struct { OfType Type `json:"ofType"` err error } func NewNonNull(ofType Type) *NonNull { gl := &NonNull{} _, isOfTypeNonNull := ofType.(*NonNull) gl.err = invariantf(ofType != nil && !isOfTypeNonNull, `Can only create NonNull of a Nullable Type but got: %v.`, ofType) if gl.err != nil { return gl } gl.OfType = ofType return gl } func (gl *NonNull) Name() string { return fmt.Sprintf("%v!", gl.OfType) } func (gl *NonNull) Description() string { return "" } func (gl *NonNull) String() string { if gl.OfType != nil { return gl.Name() } return "" } func (gl *NonNull) Error() error { return gl.err } var NameRegExp = regexp.MustCompile("^[_a-zA-Z][_a-zA-Z0-9]*$") func assertValidName(name string) error { return invariantf( NameRegExp.MatchString(name), `Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "%v" does not.`, name) } type ResponsePath struct { Prev *ResponsePath Key interface{} } // WithKey returns a new responsePath containing the new key. func (p *ResponsePath) WithKey(key interface{}) *ResponsePath { return &ResponsePath{ Prev: p, Key: key, } } // AsArray returns an array of path keys. func (p *ResponsePath) AsArray() []interface{} { if p == nil { return nil } return append(p.Prev.AsArray(), p.Key) } ================================================ FILE: definition_test.go ================================================ package graphql_test import ( "fmt" "reflect" "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/testutil" ) var blogImage = graphql.NewObject(graphql.ObjectConfig{ Name: "Image", Fields: graphql.Fields{ "url": &graphql.Field{ Type: graphql.String, }, "width": &graphql.Field{ Type: graphql.Int, }, "height": &graphql.Field{ Type: graphql.Int, }, }, }) var blogAuthor = graphql.NewObject(graphql.ObjectConfig{ Name: "Author", Fields: graphql.Fields{ "id": &graphql.Field{ Type: graphql.String, }, "name": &graphql.Field{ Type: graphql.String, }, "pic": &graphql.Field{ Type: blogImage, Args: graphql.FieldConfigArgument{ "width": &graphql.ArgumentConfig{ Type: graphql.Int, }, "height": &graphql.ArgumentConfig{ Type: graphql.Int, }, }, }, "recentArticle": &graphql.Field{}, }, }) var blogArticle = graphql.NewObject(graphql.ObjectConfig{ Name: "Article", Fields: graphql.Fields{ "id": &graphql.Field{ Type: graphql.String, }, "isPublished": &graphql.Field{ Type: graphql.Boolean, }, "author": &graphql.Field{ Type: blogAuthor, }, "title": &graphql.Field{ Type: graphql.String, }, "body": &graphql.Field{ Type: graphql.String, }, }, }) var blogQuery = graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "article": &graphql.Field{ Type: blogArticle, Args: graphql.FieldConfigArgument{ "id": &graphql.ArgumentConfig{ Type: graphql.String, }, }, }, "feed": &graphql.Field{ Type: graphql.NewList(blogArticle), }, }, }) var blogMutation = graphql.NewObject(graphql.ObjectConfig{ Name: "Mutation", Fields: graphql.Fields{ "writeArticle": &graphql.Field{ Type: blogArticle, Args: graphql.FieldConfigArgument{ "title": &graphql.ArgumentConfig{ Type: graphql.String, }, }, }, }, }) var blogSubscription = graphql.NewObject(graphql.ObjectConfig{ Name: "Subscription", Fields: graphql.Fields{ "articleSubscribe": &graphql.Field{ Type: blogArticle, Args: graphql.FieldConfigArgument{ "id": &graphql.ArgumentConfig{ Type: graphql.String, }, }, }, }, }) var objectType = graphql.NewObject(graphql.ObjectConfig{ Name: "Object", IsTypeOf: func(p graphql.IsTypeOfParams) bool { return true }, }) var interfaceType = graphql.NewInterface(graphql.InterfaceConfig{ Name: "Interface", }) var unionType = graphql.NewUnion(graphql.UnionConfig{ Name: "Union", Types: []*graphql.Object{ objectType, }, }) var enumType = graphql.NewEnum(graphql.EnumConfig{ Name: "Enum", Values: graphql.EnumValueConfigMap{ "foo": &graphql.EnumValueConfig{}, }, }) var inputObjectType = graphql.NewInputObject(graphql.InputObjectConfig{ Name: "InputObject", }) func init() { blogAuthor.AddFieldConfig("recentArticle", &graphql.Field{ Type: blogArticle, }) } func TestTypeSystem_DefinitionExample_DefinesAQueryOnlySchema(t *testing.T) { blogSchema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: blogQuery, }) if err != nil { t.Fatalf("unexpected error, got: %v", err) } if blogSchema.QueryType() != blogQuery { t.Fatalf("expected blogSchema.GetQueryType() == blogQuery") } articleField, _ := blogQuery.Fields()["article"] if articleField == nil { t.Fatalf("articleField is nil") } articleFieldType := articleField.Type if articleFieldType != blogArticle { t.Fatalf("articleFieldType expected to equal blogArticle, got: %v", articleField.Type) } if articleFieldType.Name() != "Article" { t.Fatalf("articleFieldType.Name expected to equal `Article`, got: %v", articleField.Type.Name()) } if articleField.Name != "article" { t.Fatalf("articleField.Name expected to equal `article`, got: %v", articleField.Name) } articleFieldTypeObject, ok := articleFieldType.(*graphql.Object) if !ok { t.Fatalf("expected articleFieldType to be graphql.Object`, got: %v", articleField) } // TODO: expose a Object.GetField(key string), instead of this ghetto way of accessing a field map? titleField := articleFieldTypeObject.Fields()["title"] if titleField == nil { t.Fatalf("titleField is nil") } if titleField.Name != "title" { t.Fatalf("titleField.Name expected to equal title, got: %v", titleField.Name) } if titleField.Type != graphql.String { t.Fatalf("titleField.Type expected to equal graphql.String, got: %v", titleField.Type) } if titleField.Type.Name() != "String" { t.Fatalf("titleField.Type.GetName() expected to equal `String`, got: %v", titleField.Type.Name()) } authorField := articleFieldTypeObject.Fields()["author"] if authorField == nil { t.Fatalf("authorField is nil") } authorFieldObject, ok := authorField.Type.(*graphql.Object) if !ok { t.Fatalf("expected authorField.Type to be Object`, got: %v", authorField) } recentArticleField := authorFieldObject.Fields()["recentArticle"] if recentArticleField == nil { t.Fatalf("recentArticleField is nil") } if recentArticleField.Type != blogArticle { t.Fatalf("recentArticleField.Type expected to equal blogArticle, got: %v", recentArticleField.Type) } feedField := blogQuery.Fields()["feed"] feedFieldList, ok := feedField.Type.(*graphql.List) if !ok { t.Fatalf("expected feedFieldList to be List`, got: %v", authorField) } if feedFieldList.OfType != blogArticle { t.Fatalf("feedFieldList.OfType expected to equal blogArticle, got: %v", feedFieldList.OfType) } if feedField.Name != "feed" { t.Fatalf("feedField.Name expected to equal `feed`, got: %v", feedField.Name) } } func TestTypeSystem_DefinitionExample_DefinesAMutationScheme(t *testing.T) { blogSchema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: blogQuery, Mutation: blogMutation, }) if err != nil { t.Fatalf("unexpected error, got: %v", err) } if blogSchema.MutationType() != blogMutation { t.Fatalf("expected blogSchema.GetMutationType() == blogMutation") } writeMutation, _ := blogMutation.Fields()["writeArticle"] if writeMutation == nil { t.Fatalf("writeMutation is nil") } writeMutationType := writeMutation.Type if writeMutationType != blogArticle { t.Fatalf("writeMutationType expected to equal blogArticle, got: %v", writeMutationType) } if writeMutationType.Name() != "Article" { t.Fatalf("writeMutationType.Name expected to equal `Article`, got: %v", writeMutationType.Name()) } if writeMutation.Name != "writeArticle" { t.Fatalf("writeMutation.Name expected to equal `writeArticle`, got: %v", writeMutation.Name) } } func TestTypeSystem_DefinitionExample_DefinesASubscriptionScheme(t *testing.T) { blogSchema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: blogQuery, Subscription: blogSubscription, }) if err != nil { t.Fatalf("unexpected error, got: %v", err) } if blogSchema.SubscriptionType() != blogSubscription { t.Fatalf("expected blogSchema.SubscriptionType() == blogSubscription") } subMutation, _ := blogSubscription.Fields()["articleSubscribe"] if subMutation == nil { t.Fatalf("subMutation is nil") } subMutationType := subMutation.Type if subMutationType != blogArticle { t.Fatalf("subMutationType expected to equal blogArticle, got: %v", subMutationType) } if subMutationType.Name() != "Article" { t.Fatalf("subMutationType.Name expected to equal `Article`, got: %v", subMutationType.Name()) } if subMutation.Name != "articleSubscribe" { t.Fatalf("subMutation.Name expected to equal `articleSubscribe`, got: %v", subMutation.Name) } } func TestTypeSystem_DefinitionExample_IncludesNestedInputObjectsInTheMap(t *testing.T) { nestedInputObject := graphql.NewInputObject(graphql.InputObjectConfig{ Name: "NestedInputObject", Fields: graphql.InputObjectConfigFieldMap{ "value": &graphql.InputObjectFieldConfig{ Type: graphql.String, }, }, }) someInputObject := graphql.NewInputObject(graphql.InputObjectConfig{ Name: "SomeInputObject", Fields: graphql.InputObjectConfigFieldMap{ "nested": &graphql.InputObjectFieldConfig{ Type: nestedInputObject, }, }, }) someMutation := graphql.NewObject(graphql.ObjectConfig{ Name: "SomeMutation", Fields: graphql.Fields{ "mutateSomething": &graphql.Field{ Type: blogArticle, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: someInputObject, }, }, }, }, }) someSubscription := graphql.NewObject(graphql.ObjectConfig{ Name: "SomeSubscription", Fields: graphql.Fields{ "subscribeToSomething": &graphql.Field{ Type: blogArticle, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: someInputObject, }, }, }, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: blogQuery, Mutation: someMutation, Subscription: someSubscription, }) if err != nil { t.Fatalf("unexpected error, got: %v", err) } if schema.Type("NestedInputObject") != nestedInputObject { t.Fatalf(`schema.GetType("NestedInputObject") expected to equal nestedInputObject, got: %v`, schema.Type("NestedInputObject")) } } func TestTypeSystem_DefinitionExample_IncludesInterfacesSubTypesInTheTypeMap(t *testing.T) { someInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "SomeInterface", Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.Int, }, }, }) someSubType := graphql.NewObject(graphql.ObjectConfig{ Name: "SomeSubtype", Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.Int, }, }, Interfaces: []*graphql.Interface{someInterface}, IsTypeOf: func(p graphql.IsTypeOfParams) bool { return true }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "iface": &graphql.Field{ Type: someInterface, }, }, }), Types: []graphql.Type{someSubType}, }) if err != nil { t.Fatalf("unexpected error, got: %v", err) } if schema.Type("SomeSubtype") != someSubType { t.Fatalf(`schema.GetType("SomeSubtype") expected to equal someSubType, got: %v`, schema.Type("SomeSubtype")) } } func TestTypeSystem_DefinitionExample_IncludesInterfacesThunkSubtypesInTheTypeMap(t *testing.T) { someInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "SomeInterface", Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.Int, }, }, }) someSubType := graphql.NewObject(graphql.ObjectConfig{ Name: "SomeSubtype", Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.Int, }, }, Interfaces: (graphql.InterfacesThunk)(func() []*graphql.Interface { return []*graphql.Interface{someInterface} }), IsTypeOf: func(p graphql.IsTypeOfParams) bool { return true }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "iface": &graphql.Field{ Type: someInterface, }, }, }), Types: []graphql.Type{someSubType}, }) if err != nil { t.Fatalf("unexpected error, got: %v", err) } if schema.Type("SomeSubtype") != someSubType { t.Fatalf(`schema.GetType("SomeSubtype") expected to equal someSubType, got: %v`, schema.Type("SomeSubtype")) } } func TestTypeSystem_DefinitionExample_StringifiesSimpleTypes(t *testing.T) { type Test struct { ttype graphql.Type expected string } tests := []Test{ {graphql.Int, "Int"}, {blogArticle, "Article"}, {interfaceType, "Interface"}, {unionType, "Union"}, {enumType, "Enum"}, {inputObjectType, "InputObject"}, {graphql.NewNonNull(graphql.Int), "Int!"}, {graphql.NewList(graphql.Int), "[Int]"}, {graphql.NewNonNull(graphql.NewList(graphql.Int)), "[Int]!"}, {graphql.NewList(graphql.NewNonNull(graphql.Int)), "[Int!]"}, {graphql.NewList(graphql.NewList(graphql.Int)), "[[Int]]"}, } for _, test := range tests { ttypeStr := fmt.Sprintf("%v", test.ttype) if ttypeStr != test.expected { t.Fatalf(`expected %v , got: %v`, test.expected, ttypeStr) } } } func TestTypeSystem_DefinitionExample_IdentifiesInputTypes(t *testing.T) { type Test struct { ttype graphql.Type expected bool } tests := []Test{ {graphql.Int, true}, {objectType, false}, {interfaceType, false}, {unionType, false}, {enumType, true}, {inputObjectType, true}, } for _, test := range tests { ttypeStr := fmt.Sprintf("%v", test.ttype) if graphql.IsInputType(test.ttype) != test.expected { t.Fatalf(`expected %v , got: %v`, test.expected, ttypeStr) } if graphql.IsInputType(graphql.NewList(test.ttype)) != test.expected { t.Fatalf(`expected %v , got: %v`, test.expected, ttypeStr) } if graphql.IsInputType(graphql.NewNonNull(test.ttype)) != test.expected { t.Fatalf(`expected %v , got: %v`, test.expected, ttypeStr) } } } func TestTypeSystem_DefinitionExample_IdentifiesOutputTypes(t *testing.T) { type Test struct { ttype graphql.Type expected bool } tests := []Test{ {graphql.Int, true}, {objectType, true}, {interfaceType, true}, {unionType, true}, {enumType, true}, {inputObjectType, false}, } for _, test := range tests { ttypeStr := fmt.Sprintf("%v", test.ttype) if graphql.IsOutputType(test.ttype) != test.expected { t.Fatalf(`expected %v , got: %v`, test.expected, ttypeStr) } if graphql.IsOutputType(graphql.NewList(test.ttype)) != test.expected { t.Fatalf(`expected %v , got: %v`, test.expected, ttypeStr) } if graphql.IsOutputType(graphql.NewNonNull(test.ttype)) != test.expected { t.Fatalf(`expected %v , got: %v`, test.expected, ttypeStr) } } } func TestTypeSystem_DefinitionExample_ProhibitsNestingNonNullInsideNonNull(t *testing.T) { ttype := graphql.NewNonNull(graphql.NewNonNull(graphql.Int)) expected := `Can only create NonNull of a Nullable Type but got: Int!.` if ttype.Error().Error() != expected { t.Fatalf(`expected %v , got: %v`, expected, ttype.Error()) } } func TestTypeSystem_DefinitionExample_ProhibitsNilInNonNull(t *testing.T) { ttype := graphql.NewNonNull(nil) expected := `Can only create NonNull of a Nullable Type but got: .` if ttype.Error().Error() != expected { t.Fatalf(`expected %v , got: %v`, expected, ttype.Error()) } } func TestTypeSystem_DefinitionExample_ProhibitsNilTypeInUnions(t *testing.T) { ttype := graphql.NewUnion(graphql.UnionConfig{ Name: "BadUnion", Types: []*graphql.Object{nil}, }) ttype.Types() expected := `BadUnion may only contain Object types, it cannot contain: .` if ttype.Error().Error() != expected { t.Fatalf(`expected %v , got: %v`, expected, ttype.Error()) } } func TestTypeSystem_DefinitionExample_DoesNotMutatePassedFieldDefinitions(t *testing.T) { fields := graphql.Fields{ "field1": &graphql.Field{ Type: graphql.String, }, "field2": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "id": &graphql.ArgumentConfig{ Type: graphql.String, }, }, }, } testObject1 := graphql.NewObject(graphql.ObjectConfig{ Name: "Test1", Fields: fields, }) testObject2 := graphql.NewObject(graphql.ObjectConfig{ Name: "Test2", Fields: fields, }) if !reflect.DeepEqual(testObject1.Fields(), testObject2.Fields()) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(testObject1.Fields(), testObject2.Fields())) } expectedFields := graphql.Fields{ "field1": &graphql.Field{ Type: graphql.String, }, "field2": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "id": &graphql.ArgumentConfig{ Type: graphql.String, }, }, }, } if !reflect.DeepEqual(fields, expectedFields) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedFields, fields)) } inputFields := graphql.InputObjectConfigFieldMap{ "field1": &graphql.InputObjectFieldConfig{ Type: graphql.String, }, "field2": &graphql.InputObjectFieldConfig{ Type: graphql.String, }, } expectedInputFields := graphql.InputObjectConfigFieldMap{ "field1": &graphql.InputObjectFieldConfig{ Type: graphql.String, }, "field2": &graphql.InputObjectFieldConfig{ Type: graphql.String, }, } testInputObject1 := graphql.NewInputObject(graphql.InputObjectConfig{ Name: "Test1", Fields: inputFields, }) testInputObject2 := graphql.NewInputObject(graphql.InputObjectConfig{ Name: "Test2", Fields: inputFields, }) if !reflect.DeepEqual(testInputObject1.Fields(), testInputObject2.Fields()) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(testInputObject1.Fields(), testInputObject2.Fields())) } if !reflect.DeepEqual(inputFields, expectedInputFields) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedInputFields, fields)) } } func TestTypeSystem_DefinitionExample_IncludesFieldsThunk(t *testing.T) { var someObject *graphql.Object someObject = graphql.NewObject(graphql.ObjectConfig{ Name: "SomeObject", Fields: (graphql.FieldsThunk)(func() graphql.Fields { return graphql.Fields{ "f": &graphql.Field{ Type: graphql.Int, }, "s": &graphql.Field{ Type: someObject, }, } }), }) fieldMap := someObject.Fields() if !reflect.DeepEqual(fieldMap["s"].Type, someObject) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(fieldMap["s"].Type, someObject)) } } func TestTypeSystem_DefinitionExampe_AllowsCyclicFieldTypes(t *testing.T) { personType := graphql.NewObject(graphql.ObjectConfig{ Name: "Person", Fields: (graphql.FieldsThunk)(func() graphql.Fields { return graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, }, "bestFriend": &graphql.Field{ Type: personType, }, } }), }) fieldMap := personType.Fields() if !reflect.DeepEqual(fieldMap["name"].Type, graphql.String) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(fieldMap["bestFriend"].Type, personType)) } } func TestTypeSystem_DefinitionExample_CanAddInputObjectField(t *testing.T) { io := graphql.NewInputObject(graphql.InputObjectConfig{ Name: "inputObject", Fields: graphql.InputObjectConfigFieldMap{ "value": &graphql.InputObjectFieldConfig{ Type: graphql.String, }, }, }) io.AddFieldConfig("newValue", &graphql.InputObjectFieldConfig{ Type: graphql.Int, }) fieldMap := io.Fields() if len(fieldMap) < 2 { t.Fatalf("Unexpected result, inputObject should have two fields, has %d", len(fieldMap)) } if _, ok := fieldMap["value"]; !ok { t.Fatal("Unexpected result, inputObject should have a field named 'value'") } if _, ok := fieldMap["newValue"]; !ok { t.Fatal("Unexpected result, inputObject should have a field named 'newValue'") } } func TestTypeSystem_DefinitionExample_IncludesUnionTypesThunk(t *testing.T) { someObject := graphql.NewObject(graphql.ObjectConfig{ Name: "SomeObject", Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.Int, }, }, }) someOtherObject := graphql.NewObject(graphql.ObjectConfig{ Name: "SomeOtherObject", Fields: graphql.Fields{ "g": &graphql.Field{ Type: graphql.Int, }, }, }) someUnion := graphql.NewUnion(graphql.UnionConfig{ Name: "SomeUnion", Types: (graphql.UnionTypesThunk)(func() []*graphql.Object { return []*graphql.Object{someObject, someOtherObject} }), ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, }) unionTypes := someUnion.Types() if someUnion.Error() != nil { t.Fatalf("unexpected error, got: %v", someUnion.Error().Error()) } if len(unionTypes) != 2 { t.Fatalf("Unexpected result, someUnion should have two unionTypes, has %d", len(unionTypes)) } } func TestTypeSystem_DefinitionExample_HandlesInvalidUnionTypes(t *testing.T) { someUnion := graphql.NewUnion(graphql.UnionConfig{ Name: "SomeUnion", Types: (graphql.InterfacesThunk)(func() []*graphql.Interface { return []*graphql.Interface{} }), ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, }) unionTypes := someUnion.Types() expected := "Unknown Union.Types type: graphql.InterfacesThunk" if someUnion.Error().Error() != expected { t.Fatalf("Unexpected error, got: %v, want: %v", someUnion.Error().Error(), expected) } if unionTypes != nil { t.Fatalf("Unexpected result, got: %v, want: nil", unionTypes) } } func TestIsAbstractType(t *testing.T) { tests := []struct { name string ttype interface{} expected bool }{ { name: "Interface type should return true", ttype: graphql.NewInterface(graphql.InterfaceConfig{Name: "TestInterface"}), expected: true, }, { name: "Union type should return true", ttype: graphql.NewUnion(graphql.UnionConfig{Name: "TestUnion"}), expected: true, }, { name: "Scalar type should return false", ttype: graphql.NewScalar(graphql.ScalarConfig{Name: "TestScalar", Serialize: func(v interface{}) interface{} { return v }}), expected: false, }, { name: "Object type should return false", ttype: graphql.NewObject(graphql.ObjectConfig{Name: "TestObject"}), expected: false, }, { name: "Enum type should return false", ttype: graphql.NewEnum(graphql.EnumConfig{Name: "TestEnum", Values: graphql.EnumValueConfigMap{"A": &graphql.EnumValueConfig{}}}), expected: false, }, { name: "InputObject type should return false", ttype: graphql.NewInputObject(graphql.InputObjectConfig{Name: "TestInputObject"}), expected: false, }, { name: "List type should return false", ttype: graphql.NewList(graphql.NewScalar(graphql.ScalarConfig{Name: "TestScalar", Serialize: func(v interface{}) interface{} { return v }})), expected: false, }, { name: "NonNull type should return false", ttype: graphql.NewNonNull(graphql.NewScalar(graphql.ScalarConfig{Name: "TestScalar", Serialize: func(v interface{}) interface{} { return v }})), expected: false, }, { name: "nil type should return false", ttype: nil, expected: false, }, { name: "string type should return false", ttype: "not a type", expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := graphql.IsAbstractType(tt.ttype) if result != tt.expected { t.Errorf("IsAbstractType(%v) = %v; want %v", tt.ttype, result, tt.expected) } }) } } func TestGetNullable(t *testing.T) { scalarType := graphql.NewScalar(graphql.ScalarConfig{Name: "TestScalar", Serialize: func(v interface{}) interface{} { return v }}) objectType := graphql.NewObject(graphql.ObjectConfig{Name: "TestObject"}) listType := graphql.NewList(scalarType) tests := []struct { name string ttype graphql.Type expected graphql.Type }{ { name: "NonNull Scalar should return Scalar", ttype: graphql.NewNonNull(scalarType), expected: scalarType, }, { name: "NonNull Object should return Object", ttype: graphql.NewNonNull(objectType), expected: objectType, }, { name: "NonNull List should return List", ttype: graphql.NewNonNull(listType), expected: listType, }, { name: "Scalar should return Scalar", ttype: scalarType, expected: scalarType, }, { name: "Object should return Object", ttype: objectType, expected: objectType, }, { name: "List should return List", ttype: listType, expected: listType, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := graphql.GetNullable(tt.ttype) if result != tt.expected { t.Errorf("GetNullable(%v) = %v; want %v", tt.ttype, result, tt.expected) } }) } } func TestNewScalar(t *testing.T) { tests := []struct { name string config graphql.ScalarConfig expectedError bool }{ { name: "empty name should error", config: graphql.ScalarConfig{ Name: "", Serialize: func(v interface{}) interface{} { return v }, }, expectedError: true, }, { name: "invalid name starting with number should error", config: graphql.ScalarConfig{ Name: "123Invalid", Serialize: func(v interface{}) interface{} { return v }, }, expectedError: true, }, { name: "invalid name with special characters should error", config: graphql.ScalarConfig{ Name: "Invalid-Name", Serialize: func(v interface{}) interface{} { return v }, }, expectedError: true, }, { name: "valid scalar with underscore should succeed", config: graphql.ScalarConfig{ Name: "_ValidScalar", Serialize: func(v interface{}) interface{} { return v }, }, expectedError: false, }, { name: "valid scalar with alphanumeric should succeed", config: graphql.ScalarConfig{ Name: "ValidScalar123", Serialize: func(v interface{}) interface{} { return v }, }, expectedError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { scalar := graphql.NewScalar(tt.config) if tt.expectedError && scalar.Error() == nil { t.Errorf("NewScalar(%v) expected error but got none", tt.config.Name) } if !tt.expectedError && scalar.Error() != nil { t.Errorf("NewScalar(%v) unexpected error: %v", tt.config.Name, scalar.Error()) } }) } } ================================================ FILE: directives.go ================================================ package graphql const ( // Operations DirectiveLocationQuery = "QUERY" DirectiveLocationMutation = "MUTATION" DirectiveLocationSubscription = "SUBSCRIPTION" DirectiveLocationField = "FIELD" DirectiveLocationFragmentDefinition = "FRAGMENT_DEFINITION" DirectiveLocationFragmentSpread = "FRAGMENT_SPREAD" DirectiveLocationInlineFragment = "INLINE_FRAGMENT" // Schema Definitions DirectiveLocationSchema = "SCHEMA" DirectiveLocationScalar = "SCALAR" DirectiveLocationObject = "OBJECT" DirectiveLocationFieldDefinition = "FIELD_DEFINITION" DirectiveLocationArgumentDefinition = "ARGUMENT_DEFINITION" DirectiveLocationInterface = "INTERFACE" DirectiveLocationUnion = "UNION" DirectiveLocationEnum = "ENUM" DirectiveLocationEnumValue = "ENUM_VALUE" DirectiveLocationInputObject = "INPUT_OBJECT" DirectiveLocationInputFieldDefinition = "INPUT_FIELD_DEFINITION" ) // DefaultDeprecationReason Constant string used for default reason for a deprecation. const DefaultDeprecationReason = "No longer supported" // SpecifiedRules The full list of specified directives. var SpecifiedDirectives = []*Directive{ IncludeDirective, SkipDirective, DeprecatedDirective, } // Directive structs are used by the GraphQL runtime as a way of modifying execution // behavior. Type system creators will usually not create these directly. type Directive struct { Name string `json:"name"` Description string `json:"description"` Locations []string `json:"locations"` Args []*Argument `json:"args"` err error } // DirectiveConfig options for creating a new GraphQLDirective type DirectiveConfig struct { Name string `json:"name"` Description string `json:"description"` Locations []string `json:"locations"` Args FieldConfigArgument `json:"args"` } func NewDirective(config DirectiveConfig) *Directive { dir := &Directive{} // Ensure directive is named if dir.err = invariant(config.Name != "", "Directive must be named."); dir.err != nil { return dir } // Ensure directive name is valid if dir.err = assertValidName(config.Name); dir.err != nil { return dir } // Ensure locations are provided for directive if dir.err = invariant(len(config.Locations) > 0, "Must provide locations for directive."); dir.err != nil { return dir } args := []*Argument{} for argName, argConfig := range config.Args { if dir.err = assertValidName(argName); dir.err != nil { return dir } args = append(args, &Argument{ PrivateName: argName, PrivateDescription: argConfig.Description, Type: argConfig.Type, DefaultValue: argConfig.DefaultValue, }) } dir.Name = config.Name dir.Description = config.Description dir.Locations = config.Locations dir.Args = args return dir } // IncludeDirective is used to conditionally include fields or fragments. var IncludeDirective = NewDirective(DirectiveConfig{ Name: "include", Description: "Directs the executor to include this field or fragment only when " + "the `if` argument is true.", Locations: []string{ DirectiveLocationField, DirectiveLocationFragmentSpread, DirectiveLocationInlineFragment, }, Args: FieldConfigArgument{ "if": &ArgumentConfig{ Type: NewNonNull(Boolean), Description: "Included when true.", }, }, }) // SkipDirective Used to conditionally skip (exclude) fields or fragments. var SkipDirective = NewDirective(DirectiveConfig{ Name: "skip", Description: "Directs the executor to skip this field or fragment when the `if` " + "argument is true.", Args: FieldConfigArgument{ "if": &ArgumentConfig{ Type: NewNonNull(Boolean), Description: "Skipped when true.", }, }, Locations: []string{ DirectiveLocationField, DirectiveLocationFragmentSpread, DirectiveLocationInlineFragment, }, }) // DeprecatedDirective Used to declare element of a GraphQL schema as deprecated. var DeprecatedDirective = NewDirective(DirectiveConfig{ Name: "deprecated", Description: "Marks an element of a GraphQL schema as no longer supported.", Args: FieldConfigArgument{ "reason": &ArgumentConfig{ Type: String, Description: "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/).", DefaultValue: DefaultDeprecationReason, }, }, Locations: []string{ DirectiveLocationFieldDefinition, DirectiveLocationEnumValue, }, }) ================================================ FILE: directives_test.go ================================================ package graphql_test import ( "errors" "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) var directivesTestSchema, _ = graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "TestType", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, "b": &graphql.Field{ Type: graphql.String, }, }, }), }) var directivesTestData map[string]interface{} = map[string]interface{}{ "a": func() interface{} { return "a" }, "b": func() interface{} { return "b" }, } func executeDirectivesTestQuery(t *testing.T, doc string) *graphql.Result { ast := testutil.TestParse(t, doc) ep := graphql.ExecuteParams{ Schema: directivesTestSchema, AST: ast, Root: directivesTestData, } return testutil.TestExecute(t, ep) } func TestDirectives_DirectivesMustBeNamed(t *testing.T) { invalidDirective := graphql.NewDirective(graphql.DirectiveConfig{ Locations: []string{ graphql.DirectiveLocationField, }, }) _, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "TestType", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, }, }), Directives: []*graphql.Directive{invalidDirective}, }) actualErr := gqlerrors.FormatError(err) expectedErr := gqlerrors.FormatError(errors.New("Directive must be named.")) if !testutil.EqualFormattedError(expectedErr, actualErr) { t.Fatalf("Expected error to be equal, got: %v", testutil.Diff(expectedErr, actualErr)) } } func TestDirectives_DirectiveNameMustBeValid(t *testing.T) { invalidDirective := graphql.NewDirective(graphql.DirectiveConfig{ Name: "123invalid name", Locations: []string{ graphql.DirectiveLocationField, }, }) _, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "TestType", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, }, }), Directives: []*graphql.Directive{invalidDirective}, }) actualErr := gqlerrors.FormatError(err) expectedErr := gqlerrors.FormatError(errors.New(`Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "123invalid name" does not.`)) if !testutil.EqualFormattedError(expectedErr, actualErr) { t.Fatalf("Expected error to be equal, got: %v", testutil.Diff(expectedErr, actualErr)) } } func TestDirectives_DirectiveNameMustProvideLocations(t *testing.T) { invalidDirective := graphql.NewDirective(graphql.DirectiveConfig{ Name: "skip", }) _, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "TestType", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, }, }), Directives: []*graphql.Directive{invalidDirective}, }) actualErr := gqlerrors.FormatError(err) expectedErr := gqlerrors.FormatError(errors.New(`Must provide locations for directive.`)) if !testutil.EqualFormattedError(expectedErr, actualErr) { t.Fatalf("Expected error to be equal, got: %v", testutil.Diff(expectedErr, actualErr)) } } func TestDirectives_DirectiveArgNamesMustBeValid(t *testing.T) { invalidDirective := graphql.NewDirective(graphql.DirectiveConfig{ Name: "skip", Description: "Directs the executor to skip this field or fragment when the `if` " + "argument is true.", Args: graphql.FieldConfigArgument{ "123if": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.Boolean), Description: "Skipped when true.", }, }, Locations: []string{ graphql.DirectiveLocationField, graphql.DirectiveLocationFragmentSpread, graphql.DirectiveLocationInlineFragment, }, }) _, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "TestType", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, }, }), Directives: []*graphql.Directive{invalidDirective}, }) actualErr := gqlerrors.FormatError(err) expectedErr := gqlerrors.FormatError(errors.New(`Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "123if" does not.`)) if !testutil.EqualFormattedError(expectedErr, actualErr) { t.Fatalf("Expected error to be equal, got: %v", testutil.Diff(expectedErr, actualErr)) } } func TestDirectivesWorksWithoutDirectives(t *testing.T) { query := `{ a, b }` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", "b": "b", }, } result := executeDirectivesTestQuery(t, query) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestDirectivesWorksOnScalarsIfTrueIncludesScalar(t *testing.T) { query := `{ a, b @include(if: true) }` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", "b": "b", }, } result := executeDirectivesTestQuery(t, query) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestDirectivesWorksOnScalarsIfFalseOmitsOnScalar(t *testing.T) { query := `{ a, b @include(if: false) }` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", }, } result := executeDirectivesTestQuery(t, query) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestDirectivesWorksOnScalarsUnlessFalseIncludesScalar(t *testing.T) { query := `{ a, b @skip(if: false) }` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", "b": "b", }, } result := executeDirectivesTestQuery(t, query) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestDirectivesWorksOnScalarsUnlessTrueOmitsScalar(t *testing.T) { query := `{ a, b @skip(if: true) }` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", }, } result := executeDirectivesTestQuery(t, query) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestDirectivesWorksOnFragmentSpreadsIfFalseOmitsFragmentSpread(t *testing.T) { query := ` query Q { a ...Frag @include(if: false) } fragment Frag on TestType { b } ` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", }, } result := executeDirectivesTestQuery(t, query) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestDirectivesWorksOnFragmentSpreadsIfTrueIncludesFragmentSpread(t *testing.T) { query := ` query Q { a ...Frag @include(if: true) } fragment Frag on TestType { b } ` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", "b": "b", }, } result := executeDirectivesTestQuery(t, query) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestDirectivesWorksOnFragmentSpreadsUnlessFalseIncludesFragmentSpread(t *testing.T) { query := ` query Q { a ...Frag @skip(if: false) } fragment Frag on TestType { b } ` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", "b": "b", }, } result := executeDirectivesTestQuery(t, query) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestDirectivesWorksOnFragmentSpreadsUnlessTrueOmitsFragmentSpread(t *testing.T) { query := ` query Q { a ...Frag @skip(if: true) } fragment Frag on TestType { b } ` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", }, } result := executeDirectivesTestQuery(t, query) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestDirectivesWorksOnInlineFragmentIfFalseOmitsInlineFragment(t *testing.T) { query := ` query Q { a ... on TestType @include(if: false) { b } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", }, } result := executeDirectivesTestQuery(t, query) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestDirectivesWorksOnInlineFragmentIfTrueIncludesInlineFragment(t *testing.T) { query := ` query Q { a ... on TestType @include(if: true) { b } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", "b": "b", }, } result := executeDirectivesTestQuery(t, query) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestDirectivesWorksOnInlineFragmentUnlessFalseIncludesInlineFragment(t *testing.T) { query := ` query Q { a ... on TestType @skip(if: false) { b } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", "b": "b", }, } result := executeDirectivesTestQuery(t, query) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestDirectivesWorksOnInlineFragmentUnlessTrueIncludesInlineFragment(t *testing.T) { query := ` query Q { a ... on TestType @skip(if: true) { b } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", }, } result := executeDirectivesTestQuery(t, query) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestDirectivesWorksOnAnonymousInlineFragmentIfFalseOmitsAnonymousInlineFragment(t *testing.T) { query := ` query Q { a ... @include(if: false) { b } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", }, } result := executeDirectivesTestQuery(t, query) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestDirectivesWorksOnAnonymousInlineFragmentIfTrueIncludesAnonymousInlineFragment(t *testing.T) { query := ` query Q { a ... @include(if: true) { b } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", "b": "b", }, } result := executeDirectivesTestQuery(t, query) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestDirectivesWorksOnAnonymousInlineFragmentUnlessFalseIncludesAnonymousInlineFragment(t *testing.T) { query := ` query Q { a ... @skip(if: false) { b } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", "b": "b", }, } result := executeDirectivesTestQuery(t, query) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestDirectivesWorksOnAnonymousInlineFragmentUnlessTrueIncludesAnonymousInlineFragment(t *testing.T) { query := ` query Q { a ... @skip(if: true) { b } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", }, } result := executeDirectivesTestQuery(t, query) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestDirectivesWorksWithSkipAndIncludeDirectives_IncludeAndNoSkip(t *testing.T) { query := `{ a, b @include(if: true) @skip(if: false) }` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", "b": "b", }, } result := executeDirectivesTestQuery(t, query) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestDirectivesWorksWithSkipAndIncludeDirectives_IncludeAndSkip(t *testing.T) { query := `{ a, b @include(if: true) @skip(if: true) }` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", }, } result := executeDirectivesTestQuery(t, query) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestDirectivesWorksWithSkipAndIncludeDirectives_NoIncludeAndSkip(t *testing.T) { query := `{ a, b @include(if: false) @skip(if: true) }` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", }, } result := executeDirectivesTestQuery(t, query) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestDirectivesWorksWithSkipAndIncludeDirectives_NoIncludeOrSkip(t *testing.T) { query := `{ a, b @include(if: false) @skip(if: false) }` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", }, } result := executeDirectivesTestQuery(t, query) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } ================================================ FILE: enum_type_test.go ================================================ package graphql_test import ( "reflect" "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/location" "github.com/graphql-go/graphql/testutil" ) var enumTypeTestColorType = graphql.NewEnum(graphql.EnumConfig{ Name: "Color", Values: graphql.EnumValueConfigMap{ "RED": &graphql.EnumValueConfig{ Value: 0, }, "GREEN": &graphql.EnumValueConfig{ Value: 1, }, "BLUE": &graphql.EnumValueConfig{ Value: 2, }, }, }) var enumTypeTestQueryType = graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "colorEnum": &graphql.Field{ Type: enumTypeTestColorType, Args: graphql.FieldConfigArgument{ "fromEnum": &graphql.ArgumentConfig{ Type: enumTypeTestColorType, }, "fromInt": &graphql.ArgumentConfig{ Type: graphql.Int, }, "fromString": &graphql.ArgumentConfig{ Type: graphql.String, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { if fromInt, ok := p.Args["fromInt"]; ok { return fromInt, nil } if fromString, ok := p.Args["fromString"]; ok { return fromString, nil } if fromEnum, ok := p.Args["fromEnum"]; ok { return fromEnum, nil } return nil, nil }, }, "colorInt": &graphql.Field{ Type: graphql.Int, Args: graphql.FieldConfigArgument{ "fromEnum": &graphql.ArgumentConfig{ Type: enumTypeTestColorType, }, "fromInt": &graphql.ArgumentConfig{ Type: graphql.Int, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { if fromInt, ok := p.Args["fromInt"]; ok { return fromInt, nil } if fromEnum, ok := p.Args["fromEnum"]; ok { return fromEnum, nil } return nil, nil }, }, }, }) var enumTypeTestMutationType = graphql.NewObject(graphql.ObjectConfig{ Name: "Mutation", Fields: graphql.Fields{ "favoriteEnum": &graphql.Field{ Type: enumTypeTestColorType, Args: graphql.FieldConfigArgument{ "color": &graphql.ArgumentConfig{ Type: enumTypeTestColorType, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { if color, ok := p.Args["color"]; ok { return color, nil } return nil, nil }, }, }, }) var enumTypeTestSubscriptionType = graphql.NewObject(graphql.ObjectConfig{ Name: "Subscription", Fields: graphql.Fields{ "subscribeToEnum": &graphql.Field{ Type: enumTypeTestColorType, Args: graphql.FieldConfigArgument{ "color": &graphql.ArgumentConfig{ Type: enumTypeTestColorType, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { if color, ok := p.Args["color"]; ok { return color, nil } return nil, nil }, }, }, }) var enumTypeTestSchema, _ = graphql.NewSchema(graphql.SchemaConfig{ Query: enumTypeTestQueryType, Mutation: enumTypeTestMutationType, Subscription: enumTypeTestSubscriptionType, }) func executeEnumTypeTest(t *testing.T, query string) *graphql.Result { result := g(t, graphql.Params{ Schema: enumTypeTestSchema, RequestString: query, }) return result } func executeEnumTypeTestWithParams(t *testing.T, query string, params map[string]interface{}) *graphql.Result { result := g(t, graphql.Params{ Schema: enumTypeTestSchema, RequestString: query, VariableValues: params, }) return result } func TestTypeSystem_EnumValues_AcceptsEnumLiteralsAsInput(t *testing.T) { query := "{ colorInt(fromEnum: GREEN) }" expected := &graphql.Result{ Data: map[string]interface{}{ "colorInt": 1, }, } result := executeEnumTypeTest(t, query) if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestTypeSystem_EnumValues_EnumMayBeOutputType(t *testing.T) { query := "{ colorEnum(fromInt: 1) }" expected := &graphql.Result{ Data: map[string]interface{}{ "colorEnum": "GREEN", }, } result := executeEnumTypeTest(t, query) if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestTypeSystem_EnumValues_EnumMayBeBothInputAndOutputType(t *testing.T) { query := "{ colorEnum(fromEnum: GREEN) }" expected := &graphql.Result{ Data: map[string]interface{}{ "colorEnum": "GREEN", }, } result := executeEnumTypeTest(t, query) if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestTypeSystem_EnumValues_DoesNotAcceptStringLiterals(t *testing.T) { query := `{ colorEnum(fromEnum: "GREEN") }` expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: "Argument \"fromEnum\" has invalid value \"GREEN\".\nExpected type \"Color\", found \"GREEN\".", Locations: []location.SourceLocation{ {Line: 1, Column: 23}, }, }, }, } result := executeEnumTypeTest(t, query) if !testutil.EqualErrorMessage(expected, result, 0) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestTypeSystem_EnumValues_DoesNotAcceptIncorrectInternalValue(t *testing.T) { query := `{ colorEnum(fromString: "GREEN") }` expected := &graphql.Result{ Data: map[string]interface{}{ "colorEnum": nil, }, } result := executeEnumTypeTest(t, query) if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestTypeSystem_EnumValues_DoesNotAcceptInternalValueInPlaceOfEnumLiteral(t *testing.T) { query := `{ colorEnum(fromEnum: 1) }` expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: "Argument \"fromEnum\" has invalid value 1.\nExpected type \"Color\", found 1.", Locations: []location.SourceLocation{ {Line: 1, Column: 23}, }, }, }, } result := executeEnumTypeTest(t, query) if !testutil.EqualErrorMessage(expected, result, 0) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestTypeSystem_EnumValues_DoesNotAcceptEnumLiteralInPlaceOfInt(t *testing.T) { query := `{ colorEnum(fromInt: GREEN) }` expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: "Argument \"fromInt\" has invalid value GREEN.\nExpected type \"Int\", found GREEN.", Locations: []location.SourceLocation{ {Line: 1, Column: 23}, }, }, }, } result := executeEnumTypeTest(t, query) if !testutil.EqualErrorMessage(expected, result, 0) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestTypeSystem_EnumValues_AcceptsJSONStringAsEnumVariable(t *testing.T) { query := `query test($color: Color!) { colorEnum(fromEnum: $color) }` params := map[string]interface{}{ "color": "BLUE", } expected := &graphql.Result{ Data: map[string]interface{}{ "colorEnum": "BLUE", }, } result := executeEnumTypeTestWithParams(t, query, params) if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestTypeSystem_EnumValues_AcceptsEnumLiteralsAsInputArgumentsToMutations(t *testing.T) { query := `mutation x($color: Color!) { favoriteEnum(color: $color) }` params := map[string]interface{}{ "color": "GREEN", } expected := &graphql.Result{ Data: map[string]interface{}{ "favoriteEnum": "GREEN", }, } result := executeEnumTypeTestWithParams(t, query, params) if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestTypeSystem_EnumValues_AcceptsEnumLiteralsAsInputArgumentsToSubscriptions(t *testing.T) { query := `subscription x($color: Color!) { subscribeToEnum(color: $color) }` params := map[string]interface{}{ "color": "GREEN", } expected := &graphql.Result{ Data: map[string]interface{}{ "subscribeToEnum": "GREEN", }, } result := executeEnumTypeTestWithParams(t, query, params) if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestTypeSystem_EnumValues_DoesNotAcceptInternalValueAsEnumVariable(t *testing.T) { query := `query test($color: Color!) { colorEnum(fromEnum: $color) }` params := map[string]interface{}{ "color": 2, } expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: "Variable \"$color\" got invalid value 2.\nExpected type \"Color\", found \"2\".", Locations: []location.SourceLocation{ {Line: 1, Column: 12}, }, }, }, } result := executeEnumTypeTestWithParams(t, query, params) if !testutil.EqualErrorMessage(expected, result, 0) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestTypeSystem_EnumValues_DoesNotAcceptStringVariablesAsEnumInput(t *testing.T) { query := `query test($color: String!) { colorEnum(fromEnum: $color) }` params := map[string]interface{}{ "color": "BLUE", } expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Variable "$color" of type "String!" used in position expecting type "Color".`, }, }, } result := executeEnumTypeTestWithParams(t, query, params) if !testutil.EqualErrorMessage(expected, result, 0) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestTypeSystem_EnumValues_DoesNotAcceptInternalValueVariableAsEnumInput(t *testing.T) { query := `query test($color: Int!) { colorEnum(fromEnum: $color) }` params := map[string]interface{}{ "color": 2, } expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Variable "$color" of type "Int!" used in position expecting type "Color".`, }, }, } result := executeEnumTypeTestWithParams(t, query, params) if !testutil.EqualErrorMessage(expected, result, 0) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestTypeSystem_EnumValues_EnumValueMayHaveAnInternalValueOfZero(t *testing.T) { query := `{ colorEnum(fromEnum: RED) colorInt(fromEnum: RED) }` expected := &graphql.Result{ Data: map[string]interface{}{ "colorEnum": "RED", "colorInt": 0, }, } result := executeEnumTypeTest(t, query) if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestTypeSystem_EnumValues_EnumValueMayBeNullable(t *testing.T) { query := `{ colorEnum colorInt }` expected := &graphql.Result{ Data: map[string]interface{}{ "colorEnum": nil, "colorInt": nil, }, } result := executeEnumTypeTest(t, query) if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestTypeSystem_EnumValues_EnumValueMayBePointer(t *testing.T) { var enumTypeTestSchema, _ = graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "query": &graphql.Field{ Type: graphql.NewObject(graphql.ObjectConfig{ Name: "query", Fields: graphql.Fields{ "color": &graphql.Field{ Type: enumTypeTestColorType, }, "foo": &graphql.Field{ Description: "foo field", Type: graphql.Int, }, }, }), Resolve: func(_ graphql.ResolveParams) (interface{}, error) { one := 1 return struct { Color *int `graphql:"color"` Foo *int `graphql:"foo"` }{&one, &one}, nil }, }, }, }), }) query := "{ query { color foo } }" expected := &graphql.Result{ Data: map[string]interface{}{ "query": map[string]interface{}{ "color": "GREEN", "foo": 1}}} result := g(t, graphql.Params{ Schema: enumTypeTestSchema, RequestString: query, }) if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestTypeSystem_EnumValues_EnumValueMayBeNilPointer(t *testing.T) { var enumTypeTestSchema, _ = graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "query": &graphql.Field{ Type: graphql.NewObject(graphql.ObjectConfig{ Name: "query", Fields: graphql.Fields{ "color": &graphql.Field{ Type: enumTypeTestColorType, }, }, }), Resolve: func(_ graphql.ResolveParams) (interface{}, error) { return struct { Color *int `graphql:"color"` }{nil}, nil }, }, }, }), }) query := "{ query { color } }" expected := &graphql.Result{ Data: map[string]interface{}{ "query": map[string]interface{}{ "color": nil, }}, } result := g(t, graphql.Params{ Schema: enumTypeTestSchema, RequestString: query, }) if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } ================================================ FILE: examples/concurrent-resolvers/main.go ================================================ package main import ( "encoding/json" "fmt" "log" "github.com/graphql-go/graphql" ) type Foo struct { Name string } var FieldFooType = graphql.NewObject(graphql.ObjectConfig{ Name: "Foo", Fields: graphql.Fields{ "name": &graphql.Field{Type: graphql.String}, }, }) type Bar struct { Name string } var FieldBarType = graphql.NewObject(graphql.ObjectConfig{ Name: "Bar", Fields: graphql.Fields{ "name": &graphql.Field{Type: graphql.String}, }, }) // QueryType fields: `concurrentFieldFoo` and `concurrentFieldBar` are resolved // concurrently because they belong to the same field-level and their `Resolve` // function returns a function (thunk). var QueryType = graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "concurrentFieldFoo": &graphql.Field{ Type: FieldFooType, Resolve: func(p graphql.ResolveParams) (interface{}, error) { var foo = Foo{Name: "Foo's name"} return func() (interface{}, error) { return &foo, nil }, nil }, }, "concurrentFieldBar": &graphql.Field{ Type: FieldBarType, Resolve: func(p graphql.ResolveParams) (interface{}, error) { type result struct { data interface{} err error } ch := make(chan *result, 1) go func() { defer close(ch) bar := &Bar{Name: "Bar's name"} ch <- &result{data: bar, err: nil} }() return func() (interface{}, error) { r := <-ch return r.data, r.err }, nil }, }, }, }) func main() { schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: QueryType, }) if err != nil { log.Fatal(err) } query := ` query { concurrentFieldFoo { name } concurrentFieldBar { name } } ` result := graphql.Do(graphql.Params{ RequestString: query, Schema: schema, }) b, err := json.Marshal(result) if err != nil { log.Fatal(err) } fmt.Printf("%s", b) /* { "data": { "concurrentFieldBar": { "name": "Bar's name" }, "concurrentFieldFoo": { "name": "Foo's name" } } } */ } ================================================ FILE: examples/context/main.go ================================================ package main import ( "context" "encoding/json" "fmt" "log" "net/http" "github.com/graphql-go/graphql" ) var Schema graphql.Schema var userType = graphql.NewObject( graphql.ObjectConfig{ Name: "User", Fields: graphql.Fields{ "id": &graphql.Field{ Type: graphql.String, }, "name": &graphql.Field{ Type: graphql.String, }, }, }, ) var queryType = graphql.NewObject( graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "me": &graphql.Field{ Type: userType, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return p.Context.Value("currentUser"), nil }, }, }, }) func graphqlHandler(w http.ResponseWriter, r *http.Request) { user := struct { ID int `json:"id"` Name string `json:"name"` }{1, "cool user"} result := graphql.Do(graphql.Params{ Schema: Schema, RequestString: r.URL.Query().Get("query"), Context: context.WithValue(context.Background(), "currentUser", user), }) if len(result.Errors) > 0 { log.Printf("wrong result, unexpected errors: %v", result.Errors) return } json.NewEncoder(w).Encode(result) } func main() { http.HandleFunc("/graphql", graphqlHandler) fmt.Println("Now server is running on port 8080") fmt.Println("Test with Get : curl -g 'http://localhost:8080/graphql?query={me{id,name}}'") http.ListenAndServe(":8080", nil) } func init() { s, err := graphql.NewSchema(graphql.SchemaConfig{ Query: queryType, }) if err != nil { log.Fatalf("failed to create schema, error: %v", err) } Schema = s } ================================================ FILE: examples/crud/Readme.md ================================================ # Go GraphQL CRUD example Implement create, read, update and delete on Go. To run the program: 1. go to the directory: `cd examples/crud` 2. Run the example: `go run main.go` ## Create `http://localhost:8080/product?query=mutation+_{create(name:"Inca Kola",info:"Inca Kola is a soft drink that was created in Peru in 1935 by British immigrant Joseph Robinson Lindley using lemon verbena (wiki)",price:1.99){id,name,info,price}}` ## Read * Get single product by id: `http://localhost:8080/product?query={product(id:1){name,info,price}}` * Get product list: `http://localhost:8080/product?query={list{id,name,info,price}}` ## Update `http://localhost:8080/product?query=mutation+_{update(id:1,price:3.95){id,name,info,price}}` ## Delete `http://localhost:8080/product?query=mutation+_{delete(id:1){id,name,info,price}}` ================================================ FILE: examples/crud/main.go ================================================ package main import ( "encoding/json" "fmt" "math/rand" "net/http" "time" "github.com/graphql-go/graphql" ) // Product contains information about one product type Product struct { ID int64 `json:"id"` Name string `json:"name"` Info string `json:"info,omitempty"` Price float64 `json:"price"` } var products = []Product{ { ID: 1, Name: "Chicha Morada", Info: "Chicha morada is a beverage originated in the Andean regions of Perú but is actually consumed at a national level (wiki)", Price: 7.99, }, { ID: 2, Name: "Chicha de jora", Info: "Chicha de jora is a corn beer chicha prepared by germinating maize, extracting the malt sugars, boiling the wort, and fermenting it in large vessels (traditionally huge earthenware vats) for several days (wiki)", Price: 5.95, }, { ID: 3, Name: "Pisco", Info: "Pisco is a colorless or yellowish-to-amber colored brandy produced in winemaking regions of Peru and Chile (wiki)", Price: 9.95, }, } var productType = graphql.NewObject( graphql.ObjectConfig{ Name: "Product", Fields: graphql.Fields{ "id": &graphql.Field{ Type: graphql.Int, }, "name": &graphql.Field{ Type: graphql.String, }, "info": &graphql.Field{ Type: graphql.String, }, "price": &graphql.Field{ Type: graphql.Float, }, }, }, ) var queryType = graphql.NewObject( graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ /* Get (read) single product by id http://localhost:8080/product?query={product(id:1){name,info,price}} */ "product": &graphql.Field{ Type: productType, Description: "Get product by id", Args: graphql.FieldConfigArgument{ "id": &graphql.ArgumentConfig{ Type: graphql.Int, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { id, ok := p.Args["id"].(int) if ok { // Find product for _, product := range products { if int(product.ID) == id { return product, nil } } } return nil, nil }, }, /* Get (read) product list http://localhost:8080/product?query={list{id,name,info,price}} */ "list": &graphql.Field{ Type: graphql.NewList(productType), Description: "Get product list", Resolve: func(params graphql.ResolveParams) (interface{}, error) { return products, nil }, }, }, }) var mutationType = graphql.NewObject(graphql.ObjectConfig{ Name: "Mutation", Fields: graphql.Fields{ /* Create new product item http://localhost:8080/product?query=mutation+_{create(name:"Inca Kola",info:"Inca Kola is a soft drink that was created in Peru in 1935 by British immigrant Joseph Robinson Lindley using lemon verbena (wiki)",price:1.99){id,name,info,price}} */ "create": &graphql.Field{ Type: productType, Description: "Create new product", Args: graphql.FieldConfigArgument{ "name": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, "info": &graphql.ArgumentConfig{ Type: graphql.String, }, "price": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.Float), }, }, Resolve: func(params graphql.ResolveParams) (interface{}, error) { rand.Seed(time.Now().UnixNano()) product := Product{ ID: int64(rand.Intn(100000)), // generate random ID Name: params.Args["name"].(string), Info: params.Args["info"].(string), Price: params.Args["price"].(float64), } products = append(products, product) return product, nil }, }, /* Update product by id http://localhost:8080/product?query=mutation+_{update(id:1,price:3.95){id,name,info,price}} */ "update": &graphql.Field{ Type: productType, Description: "Update product by id", Args: graphql.FieldConfigArgument{ "id": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.Int), }, "name": &graphql.ArgumentConfig{ Type: graphql.String, }, "info": &graphql.ArgumentConfig{ Type: graphql.String, }, "price": &graphql.ArgumentConfig{ Type: graphql.Float, }, }, Resolve: func(params graphql.ResolveParams) (interface{}, error) { id, _ := params.Args["id"].(int) name, nameOk := params.Args["name"].(string) info, infoOk := params.Args["info"].(string) price, priceOk := params.Args["price"].(float64) product := Product{} for i, p := range products { if int64(id) == p.ID { if nameOk { products[i].Name = name } if infoOk { products[i].Info = info } if priceOk { products[i].Price = price } product = products[i] break } } return product, nil }, }, /* Delete product by id http://localhost:8080/product?query=mutation+_{delete(id:1){id,name,info,price}} */ "delete": &graphql.Field{ Type: productType, Description: "Delete product by id", Args: graphql.FieldConfigArgument{ "id": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.Int), }, }, Resolve: func(params graphql.ResolveParams) (interface{}, error) { id, _ := params.Args["id"].(int) product := Product{} for i, p := range products { if int64(id) == p.ID { product = products[i] // Remove from product list products = append(products[:i], products[i+1:]...) } } return product, nil }, }, }, }) var schema, _ = graphql.NewSchema( graphql.SchemaConfig{ Query: queryType, Mutation: mutationType, }, ) func executeQuery(query string, schema graphql.Schema) *graphql.Result { result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) if len(result.Errors) > 0 { fmt.Printf("errors: %v", result.Errors) } return result } func main() { http.HandleFunc("/product", func(w http.ResponseWriter, r *http.Request) { result := executeQuery(r.URL.Query().Get("query"), schema) json.NewEncoder(w).Encode(result) }) fmt.Println("Server is running on port 8080") http.ListenAndServe(":8080", nil) } ================================================ FILE: examples/custom-scalar-type/main.go ================================================ package main import ( "encoding/json" "fmt" "log" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/language/ast" ) type CustomID struct { value string } func (id *CustomID) String() string { return id.value } func NewCustomID(v string) *CustomID { return &CustomID{value: v} } var CustomScalarType = graphql.NewScalar(graphql.ScalarConfig{ Name: "CustomScalarType", Description: "The `CustomScalarType` scalar type represents an ID Object.", // Serialize serializes `CustomID` to string. Serialize: func(value interface{}) interface{} { switch value := value.(type) { case CustomID: return value.String() case *CustomID: v := *value return v.String() default: return nil } }, // ParseValue parses GraphQL variables from `string` to `CustomID`. ParseValue: func(value interface{}) interface{} { switch value := value.(type) { case string: return NewCustomID(value) case *string: return NewCustomID(*value) default: return nil } }, // ParseLiteral parses GraphQL AST value to `CustomID`. ParseLiteral: func(valueAST ast.Value) interface{} { switch valueAST := valueAST.(type) { case *ast.StringValue: return NewCustomID(valueAST.Value) default: return nil } }, }) type Customer struct { ID *CustomID `json:"id"` } var CustomerType = graphql.NewObject(graphql.ObjectConfig{ Name: "Customer", Fields: graphql.Fields{ "id": &graphql.Field{ Type: CustomScalarType, }, }, }) func main() { schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "customers": &graphql.Field{ Type: graphql.NewList(CustomerType), Args: graphql.FieldConfigArgument{ "id": &graphql.ArgumentConfig{ Type: CustomScalarType, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { // id := p.Args["id"] // log.Printf("id from arguments: %+v", id) customers := []Customer{ Customer{ID: NewCustomID("fb278f2a4a13f")}, } return customers, nil }, }, }, }), }) if err != nil { log.Fatal(err) } query := ` query { customers { id } } ` /* queryWithVariable := ` query($id: CustomScalarType) { customers(id: $id) { id } } ` */ /* queryWithArgument := ` query { customers(id: "5b42ba57289") { id } } ` */ result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, VariableValues: map[string]interface{}{ "id": "5b42ba57289", }, }) if len(result.Errors) > 0 { log.Fatal(result) } b, err := json.Marshal(result) if err != nil { log.Fatal(err) } fmt.Println(string(b)) } ================================================ FILE: examples/hello-world/main.go ================================================ package main import ( "encoding/json" "fmt" "log" "github.com/graphql-go/graphql" ) func main() { // Schema fields := graphql.Fields{ "hello": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return "world", nil }, }, } rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: fields} schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)} schema, err := graphql.NewSchema(schemaConfig) if err != nil { log.Fatalf("failed to create new schema, error: %v", err) } // Query query := ` { hello } ` params := graphql.Params{Schema: schema, RequestString: query} r := graphql.Do(params) if len(r.Errors) > 0 { log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors) } rJSON, _ := json.Marshal(r) fmt.Printf("%s \n", rJSON) // {“data”:{“hello”:”world”}} } ================================================ FILE: examples/http/data.json ================================================ { "1": { "id": "1", "name": "Dan" }, "2": { "id": "2", "name": "Lee" }, "3": { "id": "3", "name": "Nick" } } ================================================ FILE: examples/http/main.go ================================================ package main import ( "encoding/json" "fmt" "io/ioutil" "net/http" "github.com/graphql-go/graphql" ) type user struct { ID string `json:"id"` Name string `json:"name"` } var data map[string]user /* Create User object type with fields "id" and "name" by using GraphQLObjectTypeConfig: - Name: name of object type - Fields: a map of fields by using GraphQLFields Setup type of field use GraphQLFieldConfig */ var userType = graphql.NewObject( graphql.ObjectConfig{ Name: "User", Fields: graphql.Fields{ "id": &graphql.Field{ Type: graphql.String, }, "name": &graphql.Field{ Type: graphql.String, }, }, }, ) /* Create Query object type with fields "user" has type [userType] by using GraphQLObjectTypeConfig: - Name: name of object type - Fields: a map of fields by using GraphQLFields Setup type of field use GraphQLFieldConfig to define: - Type: type of field - Args: arguments to query with current field - Resolve: function to query data using params from [Args] and return value with current type */ var queryType = graphql.NewObject( graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "user": &graphql.Field{ Type: userType, Args: graphql.FieldConfigArgument{ "id": &graphql.ArgumentConfig{ Type: graphql.String, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { idQuery, isOK := p.Args["id"].(string) if isOK { return data[idQuery], nil } return nil, nil }, }, }, }) var schema, _ = graphql.NewSchema( graphql.SchemaConfig{ Query: queryType, }, ) func executeQuery(query string, schema graphql.Schema) *graphql.Result { result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) if len(result.Errors) > 0 { fmt.Printf("wrong result, unexpected errors: %v", result.Errors) } return result } func main() { _ = importJSONDataFromFile("data.json", &data) http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) { result := executeQuery(r.URL.Query().Get("query"), schema) json.NewEncoder(w).Encode(result) }) fmt.Println("Now server is running on port 8080") fmt.Println("Test with Get : curl -g 'http://localhost:8080/graphql?query={user(id:\"1\"){name}}'") http.ListenAndServe(":8080", nil) } //Helper function to import json from file to map func importJSONDataFromFile(fileName string, result interface{}) (isOK bool) { isOK = true content, err := ioutil.ReadFile(fileName) if err != nil { fmt.Print("Error:", err) isOK = false } err = json.Unmarshal(content, result) if err != nil { isOK = false fmt.Print("Error:", err) } return } ================================================ FILE: examples/http-post/main.go ================================================ package main import ( "encoding/json" "fmt" "net/http" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/examples/todo/schema" ) type postData struct { Query string `json:"query"` Operation string `json:"operationName"` Variables map[string]interface{} `json:"variables"` } func main() { http.HandleFunc("/graphql", func(w http.ResponseWriter, req *http.Request) { var p postData if err := json.NewDecoder(req.Body).Decode(&p); err != nil { w.WriteHeader(400) return } result := graphql.Do(graphql.Params{ Context: req.Context(), Schema: schema.TodoSchema, RequestString: p.Query, VariableValues: p.Variables, OperationName: p.Operation, }) if err := json.NewEncoder(w).Encode(result); err != nil { fmt.Printf("could not write result to response: %s", err) } }) fmt.Println("Now server is running on port 8080") fmt.Println("") fmt.Println(`Get single todo: curl \ -X POST \ -H "Content-Type: application/json" \ --data '{ "query": "{ todo(id:\"b\") { id text done } }" }' \ http://localhost:8080/graphql`) fmt.Println("") fmt.Println(`Create new todo: curl \ -X POST \ -H "Content-Type: application/json" \ --data '{ "query": "mutation { createTodo(text:\"My New todo\") { id text done } }" }' \ http://localhost:8080/graphql`) fmt.Println("") fmt.Println(`Update todo: curl \ -X POST \ -H "Content-Type: application/json" \ --data '{ "query": "mutation { updateTodo(id:\"a\", done: true) { id text done } }" }' \ http://localhost:8080/graphql`) fmt.Println("") fmt.Println(`Load todo list: curl \ -X POST \ -H "Content-Type: application/json" \ --data '{ "query": "{ todoList { id text done } }" }' \ http://localhost:8080/graphql`) http.ListenAndServe(":8080", nil) } ================================================ FILE: examples/httpdynamic/README.md ================================================ Basically, if we have `data.json` like this: [ { "id": "1", "name": "Dan" }, { "id": "2", "name": "Lee" }, { "id": "3", "name": "Nick" } ] ... and `go run main.go`, we can query records: $ curl -g 'http://localhost:8080/graphql?query={user(name:"Dan"){id}}' {"data":{"user":{"id":"1"}}} ... now let's give Dan a surname: [ { "id": "1", "name": "Dan", "surname": "Jones" }, { "id": "2", "name": "Lee" }, { "id": "3", "name": "Nick" } ] ... and kick the server: kill -SIGUSR1 52114 And ask for Dan's surname: $ curl -g 'http://localhost:8080/graphql?query={user(name:"Dan"){id,surname}}' {"data":{"user":{"id":"1","surname":"Jones"}}} ... or ask Jones's name and ID: $ curl -g 'http://localhost:8080/graphql?query={user(surname:"Jones"){id,name}}' {"data":{"user":{"id":"1","name":"Dan"}}} If you look at `main.go`, the file is not field-aware. That is, all it knows is how to work with `[]map[string]string` type. With this, we are not that far from exposing dynamic fields and filters which fully depend on what we have stored, all without changing our tooling. ================================================ FILE: examples/httpdynamic/data.json ================================================ [ { "id": "1", "name": "Dan", "surname": "Jones" }, { "id": "2", "name": "Lee" }, { "id": "3", "name": "Nick" } ] ================================================ FILE: examples/httpdynamic/main.go ================================================ package main import ( "encoding/json" "fmt" "io/ioutil" "net/http" "os" "os/signal" "strconv" "syscall" "github.com/graphql-go/graphql" ) /*****************************************************************************/ /* Shared data variables to allow dynamic reloads /*****************************************************************************/ var schema graphql.Schema const jsonDataFile = "data.json" func handleSIGUSR1(c chan os.Signal) { for { <-c fmt.Printf("Caught SIGUSR1. Reloading %s\n", jsonDataFile) err := importJSONDataFromFile(jsonDataFile) if err != nil { fmt.Printf("Error: %s\n", err.Error()) return } } } func filterUser(data []map[string]interface{}, args map[string]interface{}) map[string]interface{} { for _, user := range data { for k, v := range args { if user[k] != v { goto nextuser } return user } nextuser: } return nil } func executeQuery(query string, schema graphql.Schema) *graphql.Result { result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) if len(result.Errors) > 0 { fmt.Printf("wrong result, unexpected errors: %v\n", result.Errors) } return result } func importJSONDataFromFile(fileName string) error { content, err := ioutil.ReadFile(fileName) if err != nil { return err } var data []map[string]interface{} err = json.Unmarshal(content, &data) if err != nil { return err } fields := make(graphql.Fields) args := make(graphql.FieldConfigArgument) for _, item := range data { for k := range item { fields[k] = &graphql.Field{ Type: graphql.String, } args[k] = &graphql.ArgumentConfig{ Type: graphql.String, } } } var userType = graphql.NewObject( graphql.ObjectConfig{ Name: "User", Fields: fields, }, ) var queryType = graphql.NewObject( graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "user": &graphql.Field{ Type: userType, Args: args, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return filterUser(data, p.Args), nil }, }, }, }) schema, _ = graphql.NewSchema( graphql.SchemaConfig{ Query: queryType, }, ) return nil } func main() { // Catch SIGUSR1 and reload the data file c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGUSR1) go handleSIGUSR1(c) err := importJSONDataFromFile(jsonDataFile) if err != nil { fmt.Printf("Error: %s\n", err.Error()) return } http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) { result := executeQuery(r.URL.Query().Get("query"), schema) json.NewEncoder(w).Encode(result) }) fmt.Println("Now server is running on port 8080") fmt.Println("Test with Get : curl -g 'http://localhost:8080/graphql?query={user(name:\"Dan\"){id,surname}}'") fmt.Printf("Reload json file : kill -SIGUSR1 %s\n", strconv.Itoa(os.Getpid())) http.ListenAndServe(":8080", nil) } ================================================ FILE: examples/modify-context/main.go ================================================ package main import ( "context" "encoding/json" "fmt" "log" "github.com/graphql-go/graphql" ) type User struct { ID int `json:"id"` } var UserType = graphql.NewObject(graphql.ObjectConfig{ Name: "User", Fields: graphql.Fields{ "id": &graphql.Field{ Type: graphql.Int, Resolve: func(p graphql.ResolveParams) (interface{}, error) { rootValue := p.Info.RootValue.(map[string]interface{}) if rootValue["data-from-parent"] == "ok" && rootValue["data-before-execution"] == "ok" { user := p.Source.(User) return user.ID, nil } return nil, nil }, }, }, }) func main() { schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "users": &graphql.Field{ Type: graphql.NewList(UserType), Resolve: func(p graphql.ResolveParams) (interface{}, error) { rootValue := p.Info.RootValue.(map[string]interface{}) rootValue["data-from-parent"] = "ok" result := []User{ User{ID: 1}, } return result, nil }, }, }, }), }) if err != nil { log.Fatal(err) } ctx := context.WithValue(context.Background(), "currentUser", User{ID: 100}) // Instead of trying to modify context within a resolve function, use: // `graphql.Params.RootObject` is a mutable optional variable and available on // each resolve function via: `graphql.ResolveParams.Info.RootValue`. rootObject := map[string]interface{}{ "data-before-execution": "ok", } result := graphql.Do(graphql.Params{ Context: ctx, RequestString: "{ users { id } }", RootObject: rootObject, Schema: schema, }) b, err := json.Marshal(result) if err != nil { log.Fatal(err) } fmt.Printf("%s\n", string(b)) // {"data":{"users":[{"id":1}]}} } ================================================ FILE: examples/sql-nullstring/README.md ================================================ # Go GraphQL SQL null string example database/sql Nullstring implementation, with JSON marshalling interfaces. To run the program, go to the directory `cd examples/sql-nullstring` Run the example `go run main.go` ## sql.NullString On occasion you will encounter sql fields that are nullable, as in ```sql CREATE TABLE persons ( id INT PRIMARY KEY, name TEXT NOT NULL, favorite_dog TEXT -- this field can have a NULL value ) ``` For the struct ```golang import "database/sql" type Person struct { ID int `json:"id" sql:"id"` Name string `json:"name" sql:"name"` FavoriteDog sql.NullString `json:"favorite_dog" sql:"favorite_dog"` } ``` But `graphql` would render said field as an object `{{ false}}` or `{{Bulldog true}}`, depending on their validity. With this implementation, `graphql` would render the null items as an empty string (`""`), but would be saved in the database as `NULL`, appropriately. The pattern can be extended to include other `database/sql` null types. ================================================ FILE: examples/sql-nullstring/main.go ================================================ package main import ( "database/sql" "encoding/json" "fmt" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/language/ast" "log" ) // NullString to be used in place of sql.NullString type NullString struct { sql.NullString } // MarshalJSON from the json.Marshaler interface func (v NullString) MarshalJSON() ([]byte, error) { if v.Valid { return json.Marshal(v.String) } return json.Marshal(nil) } // UnmarshalJSON from the json.Unmarshaler interface func (v *NullString) UnmarshalJSON(data []byte) error { var x *string if err := json.Unmarshal(data, &x); err != nil { return err } if x != nil { v.String = *x v.Valid = true } else { v.Valid = false } return nil } // NewNullString create a new null string. Empty string evaluates to an // "invalid" NullString func NewNullString(value string) *NullString { var null NullString if value != "" { null.String = value null.Valid = true return &null } null.Valid = false return &null } // SerializeNullString serializes `NullString` to a string func SerializeNullString(value interface{}) interface{} { switch value := value.(type) { case NullString: return value.String case *NullString: v := *value return v.String default: return nil } } // ParseNullString parses GraphQL variables from `string` to `CustomID` func ParseNullString(value interface{}) interface{} { switch value := value.(type) { case string: return NewNullString(value) case *string: return NewNullString(*value) default: return nil } } // ParseLiteralNullString parses GraphQL AST value to `NullString`. func ParseLiteralNullString(valueAST ast.Value) interface{} { switch valueAST := valueAST.(type) { case *ast.StringValue: return NewNullString(valueAST.Value) default: return nil } } // NullableString graphql *Scalar type based of NullString var NullableString = graphql.NewScalar(graphql.ScalarConfig{ Name: "NullableString", Description: "The `NullableString` type repesents a nullable SQL string.", Serialize: SerializeNullString, ParseValue: ParseNullString, ParseLiteral: ParseLiteralNullString, }) /* CREATE TABLE persons ( favorite_dog TEXT -- is a nullable field ); */ // Person noqa type Person struct { Name string `json:"name"` FavoriteDog *NullString `json:"favorite_dog"` // Some people don't like dogs ¯\_(ツ)_/¯ } // PersonType noqa var PersonType = graphql.NewObject(graphql.ObjectConfig{ Name: "Person", Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, }, "favorite_dog": &graphql.Field{ Type: NullableString, }, }, }) func main() { schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "people": &graphql.Field{ Type: graphql.NewList(PersonType), Args: graphql.FieldConfigArgument{ "favorite_dog": &graphql.ArgumentConfig{ Type: NullableString, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { dog, dogOk := p.Args["favorite_dog"].(*NullString) people := []Person{ Person{Name: "Alice", FavoriteDog: NewNullString("Yorkshire Terrier")}, // `Bob`'s favorite dog will be saved as null in the database Person{Name: "Bob", FavoriteDog: NewNullString("")}, Person{Name: "Chris", FavoriteDog: NewNullString("French Bulldog")}, } switch { case dogOk: log.Printf("favorite_dog from arguments: %+v", dog) dogPeople := make([]Person, 0) for _, p := range people { if p.FavoriteDog.Valid { if p.FavoriteDog.String == dog.String { dogPeople = append(dogPeople, p) } } } return dogPeople, nil default: return people, nil } }, }, }, }), }) if err != nil { log.Fatal(err) } query := ` query { people { name favorite_dog } }` queryWithArgument := ` query { people(favorite_dog: "Yorkshire Terrier") { name favorite_dog } }` r1 := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) r2 := graphql.Do(graphql.Params{ Schema: schema, RequestString: queryWithArgument, }) if len(r1.Errors) > 0 { log.Fatal(r1) } if len(r2.Errors) > 0 { log.Fatal(r1) } b1, err := json.MarshalIndent(r1, "", " ") if err != nil { log.Fatal(err) } b2, err := json.MarshalIndent(r2, "", " ") if err != nil { log.Fatal(err) } fmt.Printf("\nQuery: %+v\n", string(query)) fmt.Printf("\nResult: %+v\n", string(b1)) fmt.Printf("\nQuery (with arguments): %+v\n", string(queryWithArgument)) fmt.Printf("\nResult (with arguments): %+v\n", string(b2)) } /* Output: Query: query { people { name favorite_dog } } Result: { "data": { "people": [ { "favorite_dog": "Yorkshire Terrier", "name": "Alice" }, { "favorite_dog": "", "name": "Bob" }, { "favorite_dog": "French Bulldog", "name": "Chris" } ] } } Query (with arguments): query { people(favorite_dog: "Yorkshire Terrier") { name favorite_dog } } Result (with arguments): { "data": { "people": [ { "favorite_dog": "Yorkshire Terrier", "name": "Alice" } ] } } */ ================================================ FILE: examples/star-wars/main.go ================================================ package main import ( "encoding/json" "fmt" "net/http" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/testutil" ) func main() { http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) { query := r.URL.Query().Get("query") result := graphql.Do(graphql.Params{ Schema: testutil.StarWarsSchema, RequestString: query, }) json.NewEncoder(w).Encode(result) }) fmt.Println("Now server is running on port 8080") fmt.Println("Test with Get : curl -g 'http://localhost:8080/graphql?query={hero{name}}'") http.ListenAndServe(":8080", nil) } ================================================ FILE: examples/todo/README.md ================================================ # Go GraphQL ToDo example An example that consists of basic core GraphQL queries and mutations. To run the example navigate to the example directory by using your shell of choice. ``` cd examples/todo ``` Run the example, it will spawn a GraphQL HTTP endpoint ``` go run main.go ``` Execute queries via shell. ``` // To get single ToDo item by ID curl -g 'http://localhost:8080/graphql?query={todo(id:"b"){id,text,done}}' // To create a ToDo item curl -g 'http://localhost:8080/graphql?query=mutation+_{createTodo(text:"My+new+todo"){id,text,done}}' // To get a list of ToDo items curl -g 'http://localhost:8080/graphql?query={todoList{id,text,done}}' // To update a ToDo curl -g 'http://localhost:8080/graphql?query=mutation+_{updateTodo(id:"b",text:"My+new+todo+updated",done:true){id,text,done}}' ``` ## Web App Access the web app at `http://localhost:8080/`. It is work in progress and currently is simply loading todos by using jQuery ajax call. ================================================ FILE: examples/todo/main.go ================================================ package main import ( "encoding/json" "fmt" "math/rand" "net/http" "time" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/examples/todo/schema" ) func init() { todo1 := schema.Todo{ID: "a", Text: "A todo not to forget", Done: false} todo2 := schema.Todo{ID: "b", Text: "This is the most important", Done: false} todo3 := schema.Todo{ID: "c", Text: "Please do this or else", Done: false} schema.TodoList = append(schema.TodoList, todo1, todo2, todo3) rand.Seed(time.Now().UnixNano()) } func executeQuery(query string, schema graphql.Schema) *graphql.Result { result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) if len(result.Errors) > 0 { fmt.Printf("wrong result, unexpected errors: %v", result.Errors) } return result } func main() { http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) { result := executeQuery(r.URL.Query().Get("query"), schema.TodoSchema) json.NewEncoder(w).Encode(result) }) // Serve static files fs := http.FileServer(http.Dir("static")) http.Handle("/", fs) // Display some basic instructions fmt.Println("Now server is running on port 8080") fmt.Println("Get single todo: curl -g 'http://localhost:8080/graphql?query={todo(id:\"b\"){id,text,done}}'") fmt.Println("Create new todo: curl -g 'http://localhost:8080/graphql?query=mutation+_{createTodo(text:\"My+new+todo\"){id,text,done}}'") fmt.Println("Update todo: curl -g 'http://localhost:8080/graphql?query=mutation+_{updateTodo(id:\"a\",done:true){id,text,done}}'") fmt.Println("Load todo list: curl -g 'http://localhost:8080/graphql?query={todoList{id,text,done}}'") fmt.Println("Access the web app via browser at 'http://localhost:8080'") http.ListenAndServe(":8080", nil) } ================================================ FILE: examples/todo/schema/schema.go ================================================ package schema import ( "math/rand" "github.com/graphql-go/graphql" ) var TodoList []Todo type Todo struct { ID string `json:"id"` Text string `json:"text"` Done bool `json:"done"` } var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") func RandStringRunes(n int) string { b := make([]rune, n) for i := range b { b[i] = letterRunes[rand.Intn(len(letterRunes))] } return string(b) } // define custom GraphQL ObjectType `todoType` for our Golang struct `Todo` // Note that // - the fields in our todoType maps with the json tags for the fields in our struct // - the field type matches the field type in our struct var todoType = graphql.NewObject(graphql.ObjectConfig{ Name: "Todo", Fields: graphql.Fields{ "id": &graphql.Field{ Type: graphql.String, }, "text": &graphql.Field{ Type: graphql.String, }, "done": &graphql.Field{ Type: graphql.Boolean, }, }, }) // root mutation var rootMutation = graphql.NewObject(graphql.ObjectConfig{ Name: "RootMutation", Fields: graphql.Fields{ /* curl -g 'http://localhost:8080/graphql?query=mutation+_{createTodo(text:"My+new+todo"){id,text,done}}' */ "createTodo": &graphql.Field{ Type: todoType, // the return type for this field Description: "Create new todo", Args: graphql.FieldConfigArgument{ "text": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, }, Resolve: func(params graphql.ResolveParams) (interface{}, error) { // marshall and cast the argument value text, _ := params.Args["text"].(string) // figure out new id newID := RandStringRunes(8) // perform mutation operation here // for e.g. create a Todo and save to DB. newTodo := Todo{ ID: newID, Text: text, Done: false, } TodoList = append(TodoList, newTodo) // return the new Todo object that we supposedly save to DB // Note here that // - we are returning a `Todo` struct instance here // - we previously specified the return Type to be `todoType` // - `Todo` struct maps to `todoType`, as defined in `todoType` ObjectConfig` return newTodo, nil }, }, /* curl -g 'http://localhost:8080/graphql?query=mutation+_{updateTodo(id:"a",done:true){id,text,done}}' */ "updateTodo": &graphql.Field{ Type: todoType, // the return type for this field Description: "Update existing todo, mark it done or not done", Args: graphql.FieldConfigArgument{ "done": &graphql.ArgumentConfig{ Type: graphql.Boolean, }, "id": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, }, Resolve: func(params graphql.ResolveParams) (interface{}, error) { // marshall and cast the argument value done, _ := params.Args["done"].(bool) id, _ := params.Args["id"].(string) affectedTodo := Todo{} // Search list for todo with id and change the done variable for i := 0; i < len(TodoList); i++ { if TodoList[i].ID == id { TodoList[i].Done = done // Assign updated todo so we can return it affectedTodo = TodoList[i] break } } // Return affected todo return affectedTodo, nil }, }, }, }) // root query // we just define a trivial example here, since root query is required. // Test with curl // curl -g 'http://localhost:8080/graphql?query={lastTodo{id,text,done}}' var rootQuery = graphql.NewObject(graphql.ObjectConfig{ Name: "RootQuery", Fields: graphql.Fields{ /* curl -g 'http://localhost:8080/graphql?query={todo(id:"b"){id,text,done}}' */ "todo": &graphql.Field{ Type: todoType, Description: "Get single todo", Args: graphql.FieldConfigArgument{ "id": &graphql.ArgumentConfig{ Type: graphql.String, }, }, Resolve: func(params graphql.ResolveParams) (interface{}, error) { idQuery, isOK := params.Args["id"].(string) if isOK { // Search for el with id for _, todo := range TodoList { if todo.ID == idQuery { return todo, nil } } } return Todo{}, nil }, }, "lastTodo": &graphql.Field{ Type: todoType, Description: "Last todo added", Resolve: func(params graphql.ResolveParams) (interface{}, error) { return TodoList[len(TodoList)-1], nil }, }, /* curl -g 'http://localhost:8080/graphql?query={todoList{id,text,done}}' */ "todoList": &graphql.Field{ Type: graphql.NewList(todoType), Description: "List of todos", Resolve: func(p graphql.ResolveParams) (interface{}, error) { return TodoList, nil }, }, }, }) // define schema, with our rootQuery and rootMutation var TodoSchema, _ = graphql.NewSchema(graphql.SchemaConfig{ Query: rootQuery, Mutation: rootMutation, }) ================================================ FILE: examples/todo/static/assets/css/style.css ================================================ body { background-color: #cccccc; color: #333333; font-family: sans-serif; } .todo-done { opacity: 0.5; } ================================================ FILE: examples/todo/static/assets/js/app.js ================================================ var updateTodo = function(id, isDone){ $.ajax({ url: '/graphql?query=mutation+_{updateTodo(id:"' + id + '",done:' + isDone + '){id,text,done}}' }).done(function(data) { console.log(data); var dataParsed = JSON.parse(data); var updatedTodo = dataParsed.data.updateTodo; if (updatedTodo.done) { $('#' + updatedTodo.id).parent().parent().parent().addClass('todo-done'); } else { $('#' + updatedTodo.id).parent().parent().parent().removeClass('todo-done'); } }); }; var handleTodoList = function(object) { var todos = object; if (!todos.length) { $('.todo-list-container').append('

There are no tasks for you today

'); return } else { $('.todo-list-container p').remove(); } $.each(todos, function(i, v) { var todoTemplate = $('#todoItemTemplate').html(); var todo = todoTemplate.replace('{{todo-id}}', v.id); todo = todo.replace('{{todo-text}}', v.text); todo = todo.replace('{{todo-checked}}', (v.done ? ' checked="checked"' : '')); todo = todo.replace('{{todo-done}}', (v.done ? ' todo-done' : '')); $('.todo-list-container').append(todo); $('#' + v.id).click(function(){ var id = $(this).prop('id'); var isDone = $(this).prop('checked'); updateTodo(id, isDone); }); }); }; var loadTodos = function() { $.ajax({ url: "/graphql?query={todoList{id,text,done}}" }).done(function(data) { console.log(data); var dataParsed = JSON.parse(data); handleTodoList(dataParsed.data.todoList); }); }; var addTodo = function(todoText) { if (!todoText || todoText === "") { alert('Please specify a task'); return; } $.ajax({ url: '/graphql?query=mutation+_{createTodo(text:"' + todoText + '"){id,text,done}}' }).done(function(data) { console.log(data); var dataParsed = JSON.parse(data); var todoList = [dataParsed.data.createTodo]; handleTodoList(todoList); }); }; $(document).ready(function() { $('.todo-add-form').submit(function(e){ e.preventDefault(); addTodo($('.todo-add-form #task').val()); $('.todo-add-form #task').val(''); }); loadTodos(); }); ================================================ FILE: examples/todo/static/index.html ================================================ ToDo

ToDo

Tasks

Add task

================================================ FILE: executor.go ================================================ package graphql import ( "context" "errors" "fmt" "reflect" "sort" "strings" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/ast" ) type ExecuteParams struct { Schema Schema Root interface{} AST *ast.Document OperationName string Args map[string]interface{} // Context may be provided to pass application-specific per-request // information to resolve functions. Context context.Context } func Execute(p ExecuteParams) (result *Result) { // Use background context if no context was provided ctx := p.Context if ctx == nil { ctx = context.Background() } // run executionDidStart functions from extensions extErrs, executionFinishFn := handleExtensionsExecutionDidStart(&p) if len(extErrs) != 0 { return &Result{ Errors: extErrs, } } defer func() { extErrs = executionFinishFn(result) if len(extErrs) != 0 { result.Errors = append(result.Errors, extErrs...) } addExtensionResults(&p, result) }() resultChannel := make(chan *Result, 2) go func() { result := &Result{} defer func() { if err := recover(); err != nil { result.Errors = append(result.Errors, gqlerrors.FormatError(err.(error))) } resultChannel <- result }() exeContext, err := buildExecutionContext(buildExecutionCtxParams{ Schema: p.Schema, Root: p.Root, AST: p.AST, OperationName: p.OperationName, Args: p.Args, Result: result, Context: p.Context, }) if err != nil { result.Errors = append(result.Errors, gqlerrors.FormatError(err.(error))) resultChannel <- result return } resultChannel <- executeOperation(executeOperationParams{ ExecutionContext: exeContext, Root: p.Root, Operation: exeContext.Operation, }) }() select { case <-ctx.Done(): result := &Result{} result.Errors = append(result.Errors, gqlerrors.FormatError(ctx.Err())) return result case r := <-resultChannel: return r } } type buildExecutionCtxParams struct { Schema Schema Root interface{} AST *ast.Document OperationName string Args map[string]interface{} Result *Result Context context.Context } type executionContext struct { Schema Schema Fragments map[string]ast.Definition Root interface{} Operation ast.Definition VariableValues map[string]interface{} Errors []gqlerrors.FormattedError Context context.Context } func buildExecutionContext(p buildExecutionCtxParams) (*executionContext, error) { eCtx := &executionContext{} var operation *ast.OperationDefinition fragments := map[string]ast.Definition{} for _, definition := range p.AST.Definitions { switch definition := definition.(type) { case *ast.OperationDefinition: if (p.OperationName == "") && operation != nil { return nil, errors.New("Must provide operation name if query contains multiple operations.") } if p.OperationName == "" || definition.GetName() != nil && definition.GetName().Value == p.OperationName { operation = definition } case *ast.FragmentDefinition: key := "" if definition.GetName() != nil && definition.GetName().Value != "" { key = definition.GetName().Value } fragments[key] = definition default: return nil, fmt.Errorf("GraphQL cannot execute a request containing a %v", definition.GetKind()) } } if operation == nil { if p.OperationName != "" { return nil, fmt.Errorf(`Unknown operation named "%v".`, p.OperationName) } return nil, fmt.Errorf(`Must provide an operation.`) } variableValues, err := getVariableValues(p.Schema, operation.GetVariableDefinitions(), p.Args) if err != nil { return nil, err } eCtx.Schema = p.Schema eCtx.Fragments = fragments eCtx.Root = p.Root eCtx.Operation = operation eCtx.VariableValues = variableValues eCtx.Context = p.Context return eCtx, nil } type executeOperationParams struct { ExecutionContext *executionContext Root interface{} Operation ast.Definition } func executeOperation(p executeOperationParams) *Result { operationType, err := getOperationRootType(p.ExecutionContext.Schema, p.Operation) if err != nil { return &Result{Errors: gqlerrors.FormatErrors(err)} } fields := collectFields(collectFieldsParams{ ExeContext: p.ExecutionContext, RuntimeType: operationType, SelectionSet: p.Operation.GetSelectionSet(), }) executeFieldsParams := executeFieldsParams{ ExecutionContext: p.ExecutionContext, ParentType: operationType, Source: p.Root, Fields: fields, } if p.Operation.GetOperation() == ast.OperationTypeMutation { return executeFieldsSerially(executeFieldsParams) } return executeFields(executeFieldsParams) } // Extracts the root type of the operation from the schema. func getOperationRootType(schema Schema, operation ast.Definition) (*Object, error) { if operation == nil { return nil, errors.New("Can only execute queries, mutations and subscription") } switch operation.GetOperation() { case ast.OperationTypeQuery: return schema.QueryType(), nil case ast.OperationTypeMutation: mutationType := schema.MutationType() if mutationType == nil || mutationType.PrivateName == "" { return nil, gqlerrors.NewError( "Schema is not configured for mutations", []ast.Node{operation}, "", nil, []int{}, nil, ) } return mutationType, nil case ast.OperationTypeSubscription: subscriptionType := schema.SubscriptionType() if subscriptionType == nil || subscriptionType.PrivateName == "" { return nil, gqlerrors.NewError( "Schema is not configured for subscriptions", []ast.Node{operation}, "", nil, []int{}, nil, ) } return subscriptionType, nil default: return nil, gqlerrors.NewError( "Can only execute queries, mutations and subscription", []ast.Node{operation}, "", nil, []int{}, nil, ) } } type executeFieldsParams struct { ExecutionContext *executionContext ParentType *Object Source interface{} Fields map[string][]*ast.Field Path *ResponsePath } // Implements the "Evaluating selection sets" section of the spec for "write" mode. func executeFieldsSerially(p executeFieldsParams) *Result { if p.Source == nil { p.Source = map[string]interface{}{} } if p.Fields == nil { p.Fields = map[string][]*ast.Field{} } finalResults := make(map[string]interface{}, len(p.Fields)) for _, orderedField := range orderedFields(p.Fields) { responseName := orderedField.responseName fieldASTs := orderedField.fieldASTs fieldPath := p.Path.WithKey(responseName) resolved, state := resolveField(p.ExecutionContext, p.ParentType, p.Source, fieldASTs, fieldPath) if state.hasNoFieldDefs { continue } finalResults[responseName] = resolved } dethunkMapDepthFirst(finalResults) return &Result{ Data: finalResults, Errors: p.ExecutionContext.Errors, } } // Implements the "Evaluating selection sets" section of the spec for "read" mode. func executeFields(p executeFieldsParams) *Result { finalResults := executeSubFields(p) dethunkMapWithBreadthFirstTraversal(finalResults) return &Result{ Data: finalResults, Errors: p.ExecutionContext.Errors, } } func executeSubFields(p executeFieldsParams) map[string]interface{} { if p.Source == nil { p.Source = map[string]interface{}{} } if p.Fields == nil { p.Fields = map[string][]*ast.Field{} } finalResults := make(map[string]interface{}, len(p.Fields)) for responseName, fieldASTs := range p.Fields { fieldPath := p.Path.WithKey(responseName) resolved, state := resolveField(p.ExecutionContext, p.ParentType, p.Source, fieldASTs, fieldPath) if state.hasNoFieldDefs { continue } finalResults[responseName] = resolved } return finalResults } // dethunkQueue is a structure that allows us to execute a classic breadth-first traversal. type dethunkQueue struct { DethunkFuncs []func() } func (d *dethunkQueue) push(f func()) { d.DethunkFuncs = append(d.DethunkFuncs, f) } func (d *dethunkQueue) shift() func() { f := d.DethunkFuncs[0] d.DethunkFuncs = d.DethunkFuncs[1:] return f } // dethunkWithBreadthFirstTraversal performs a breadth-first descent of the map, calling any thunks // in the map values and replacing each thunk with that thunk's return value. This parallels // the reference graphql-js implementation, which calls Promise.all on thunks at each depth (which // is an implicit parallel descent). func dethunkMapWithBreadthFirstTraversal(finalResults map[string]interface{}) { dethunkQueue := &dethunkQueue{DethunkFuncs: []func(){}} dethunkMapBreadthFirst(finalResults, dethunkQueue) for len(dethunkQueue.DethunkFuncs) > 0 { f := dethunkQueue.shift() f() } } func dethunkMapBreadthFirst(m map[string]interface{}, dethunkQueue *dethunkQueue) { for k, v := range m { if f, ok := v.(func() interface{}); ok { m[k] = f() } switch val := m[k].(type) { case map[string]interface{}: dethunkQueue.push(func() { dethunkMapBreadthFirst(val, dethunkQueue) }) case []interface{}: dethunkQueue.push(func() { dethunkListBreadthFirst(val, dethunkQueue) }) } } } func dethunkListBreadthFirst(list []interface{}, dethunkQueue *dethunkQueue) { for i, v := range list { if f, ok := v.(func() interface{}); ok { list[i] = f() } switch val := list[i].(type) { case map[string]interface{}: dethunkQueue.push(func() { dethunkMapBreadthFirst(val, dethunkQueue) }) case []interface{}: dethunkQueue.push(func() { dethunkListBreadthFirst(val, dethunkQueue) }) } } } // dethunkMapDepthFirst performs a serial descent of the map, calling any thunks // in the map values and replacing each thunk with that thunk's return value. This is needed // to conform to the graphql-js reference implementation, which requires serial (depth-first) // implementations for mutation selects. func dethunkMapDepthFirst(m map[string]interface{}) { for k, v := range m { if f, ok := v.(func() interface{}); ok { m[k] = f() } switch val := m[k].(type) { case map[string]interface{}: dethunkMapDepthFirst(val) case []interface{}: dethunkListDepthFirst(val) } } } func dethunkListDepthFirst(list []interface{}) { for i, v := range list { if f, ok := v.(func() interface{}); ok { list[i] = f() } switch val := list[i].(type) { case map[string]interface{}: dethunkMapDepthFirst(val) case []interface{}: dethunkListDepthFirst(val) } } } type collectFieldsParams struct { ExeContext *executionContext RuntimeType *Object // previously known as OperationType SelectionSet *ast.SelectionSet Fields map[string][]*ast.Field VisitedFragmentNames map[string]bool } // Given a selectionSet, adds all of the fields in that selection to // the passed in map of fields, and returns it at the end. // CollectFields requires the "runtime type" of an object. For a field which // returns and Interface or Union type, the "runtime type" will be the actual // Object type returned by that field. func collectFields(p collectFieldsParams) (fields map[string][]*ast.Field) { // overlying SelectionSet & Fields to fields if p.SelectionSet == nil { return p.Fields } fields = p.Fields if fields == nil { fields = map[string][]*ast.Field{} } if p.VisitedFragmentNames == nil { p.VisitedFragmentNames = map[string]bool{} } for _, iSelection := range p.SelectionSet.Selections { switch selection := iSelection.(type) { case *ast.Field: if !shouldIncludeNode(p.ExeContext, selection.Directives) { continue } name := getFieldEntryKey(selection) if _, ok := fields[name]; !ok { fields[name] = []*ast.Field{} } fields[name] = append(fields[name], selection) case *ast.InlineFragment: if !shouldIncludeNode(p.ExeContext, selection.Directives) || !doesFragmentConditionMatch(p.ExeContext, selection, p.RuntimeType) { continue } innerParams := collectFieldsParams{ ExeContext: p.ExeContext, RuntimeType: p.RuntimeType, SelectionSet: selection.SelectionSet, Fields: fields, VisitedFragmentNames: p.VisitedFragmentNames, } collectFields(innerParams) case *ast.FragmentSpread: fragName := "" if selection.Name != nil { fragName = selection.Name.Value } if visited, ok := p.VisitedFragmentNames[fragName]; (ok && visited) || !shouldIncludeNode(p.ExeContext, selection.Directives) { continue } p.VisitedFragmentNames[fragName] = true fragment, hasFragment := p.ExeContext.Fragments[fragName] if !hasFragment { continue } if fragment, ok := fragment.(*ast.FragmentDefinition); ok { if !doesFragmentConditionMatch(p.ExeContext, fragment, p.RuntimeType) { continue } innerParams := collectFieldsParams{ ExeContext: p.ExeContext, RuntimeType: p.RuntimeType, SelectionSet: fragment.GetSelectionSet(), Fields: fields, VisitedFragmentNames: p.VisitedFragmentNames, } collectFields(innerParams) } } } return fields } // Determines if a field should be included based on the @include and @skip // directives, where @skip has higher precedence than @include. func shouldIncludeNode(eCtx *executionContext, directives []*ast.Directive) bool { var ( skipAST, includeAST *ast.Directive argValues map[string]interface{} ) for _, directive := range directives { if directive == nil || directive.Name == nil { continue } switch directive.Name.Value { case SkipDirective.Name: skipAST = directive case IncludeDirective.Name: includeAST = directive } } // precedence: skipAST > includeAST if skipAST != nil { argValues = getArgumentValues(SkipDirective.Args, skipAST.Arguments, eCtx.VariableValues) if skipIf, ok := argValues["if"].(bool); ok && skipIf { return false // excluded selectionSet's fields } } if includeAST != nil { argValues = getArgumentValues(IncludeDirective.Args, includeAST.Arguments, eCtx.VariableValues) if includeIf, ok := argValues["if"].(bool); ok && !includeIf { return false // excluded selectionSet's fields } } return true } // Determines if a fragment is applicable to the given type. func doesFragmentConditionMatch(eCtx *executionContext, fragment ast.Node, ttype *Object) bool { switch fragment := fragment.(type) { case *ast.FragmentDefinition: typeConditionAST := fragment.TypeCondition if typeConditionAST == nil { return true } conditionalType, err := typeFromAST(eCtx.Schema, typeConditionAST) if err != nil { return false } if conditionalType == ttype { return true } if conditionalType.Name() == ttype.Name() { return true } if conditionalType, ok := conditionalType.(*Interface); ok { return eCtx.Schema.IsPossibleType(conditionalType, ttype) } if conditionalType, ok := conditionalType.(*Union); ok { return eCtx.Schema.IsPossibleType(conditionalType, ttype) } case *ast.InlineFragment: typeConditionAST := fragment.TypeCondition if typeConditionAST == nil { return true } conditionalType, err := typeFromAST(eCtx.Schema, typeConditionAST) if err != nil { return false } if conditionalType == ttype { return true } if conditionalType.Name() == ttype.Name() { return true } if conditionalType, ok := conditionalType.(*Interface); ok { return eCtx.Schema.IsPossibleType(conditionalType, ttype) } if conditionalType, ok := conditionalType.(*Union); ok { return eCtx.Schema.IsPossibleType(conditionalType, ttype) } } return false } // Implements the logic to compute the key of a given field’s entry func getFieldEntryKey(node *ast.Field) string { if node.Alias != nil && node.Alias.Value != "" { return node.Alias.Value } if node.Name != nil && node.Name.Value != "" { return node.Name.Value } return "" } // Internal resolveField state type resolveFieldResultState struct { hasNoFieldDefs bool } func handleFieldError(r interface{}, fieldNodes []ast.Node, path *ResponsePath, returnType Output, eCtx *executionContext) { err := NewLocatedErrorWithPath(r, fieldNodes, path.AsArray()) // send panic upstream if _, ok := returnType.(*NonNull); ok { panic(err) } eCtx.Errors = append(eCtx.Errors, gqlerrors.FormatError(err)) } // Resolves the field on the given source object. In particular, this // figures out the value that the field returns by calling its resolve function, // then calls completeValue to complete promises, serialize scalars, or execute // the sub-selection-set for objects. func resolveField(eCtx *executionContext, parentType *Object, source interface{}, fieldASTs []*ast.Field, path *ResponsePath) (result interface{}, resultState resolveFieldResultState) { // catch panic from resolveFn var returnType Output defer func() (interface{}, resolveFieldResultState) { if r := recover(); r != nil { handleFieldError(r, FieldASTsToNodeASTs(fieldASTs), path, returnType, eCtx) return result, resultState } return result, resultState }() fieldAST := fieldASTs[0] fieldName := "" if fieldAST.Name != nil { fieldName = fieldAST.Name.Value } fieldDef := getFieldDef(eCtx.Schema, parentType, fieldName) if fieldDef == nil { resultState.hasNoFieldDefs = true return nil, resultState } returnType = fieldDef.Type resolveFn := fieldDef.Resolve if resolveFn == nil { resolveFn = DefaultResolveFn } // Build a map of arguments from the field.arguments AST, using the // variables scope to fulfill any variable references. // TODO: find a way to memoize, in case this field is within a List type. args := getArgumentValues(fieldDef.Args, fieldAST.Arguments, eCtx.VariableValues) info := ResolveInfo{ FieldName: fieldName, FieldASTs: fieldASTs, Path: path, ReturnType: returnType, ParentType: parentType, Schema: eCtx.Schema, Fragments: eCtx.Fragments, RootValue: eCtx.Root, Operation: eCtx.Operation, VariableValues: eCtx.VariableValues, } var resolveFnError error extErrs, resolveFieldFinishFn := handleExtensionsResolveFieldDidStart(eCtx.Schema.extensions, eCtx, &info) if len(extErrs) != 0 { eCtx.Errors = append(eCtx.Errors, extErrs...) } result, resolveFnError = resolveFn(ResolveParams{ Source: source, Args: args, Info: info, Context: eCtx.Context, }) extErrs = resolveFieldFinishFn(result, resolveFnError) if len(extErrs) != 0 { eCtx.Errors = append(eCtx.Errors, extErrs...) } if resolveFnError != nil { panic(resolveFnError) } completed := completeValueCatchingError(eCtx, returnType, fieldASTs, info, path, result) return completed, resultState } func completeValueCatchingError(eCtx *executionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, path *ResponsePath, result interface{}) (completed interface{}) { // catch panic defer func() interface{} { if r := recover(); r != nil { handleFieldError(r, FieldASTsToNodeASTs(fieldASTs), path, returnType, eCtx) return completed } return completed }() if returnType, ok := returnType.(*NonNull); ok { completed := completeValue(eCtx, returnType, fieldASTs, info, path, result) return completed } completed = completeValue(eCtx, returnType, fieldASTs, info, path, result) return completed } func completeValue(eCtx *executionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, path *ResponsePath, result interface{}) interface{} { resultVal := reflect.ValueOf(result) if resultVal.IsValid() && resultVal.Kind() == reflect.Func { return func() interface{} { return completeThunkValueCatchingError(eCtx, returnType, fieldASTs, info, path, result) } } // If field type is NonNull, complete for inner type, and throw field error // if result is null. if returnType, ok := returnType.(*NonNull); ok { completed := completeValue(eCtx, returnType.OfType, fieldASTs, info, path, result) if completed == nil { err := NewLocatedErrorWithPath( fmt.Sprintf("Cannot return null for non-nullable field %v.%v.", info.ParentType, info.FieldName), FieldASTsToNodeASTs(fieldASTs), path.AsArray(), ) panic(gqlerrors.FormatError(err)) } return completed } // If result value is null-ish (null, undefined, or NaN) then return null. if isNullish(result) { return nil } // If field type is List, complete each item in the list with the inner type if returnType, ok := returnType.(*List); ok { return completeListValue(eCtx, returnType, fieldASTs, info, path, result) } // If field type is a leaf type, Scalar or Enum, serialize to a valid value, // returning null if serialization is not possible. if returnType, ok := returnType.(*Scalar); ok { return completeLeafValue(returnType, result) } if returnType, ok := returnType.(*Enum); ok { return completeLeafValue(returnType, result) } // If field type is an abstract type, Interface or Union, determine the // runtime Object type and complete for that type. if returnType, ok := returnType.(*Union); ok { return completeAbstractValue(eCtx, returnType, fieldASTs, info, path, result) } if returnType, ok := returnType.(*Interface); ok { return completeAbstractValue(eCtx, returnType, fieldASTs, info, path, result) } // If field type is Object, execute and complete all sub-selections. if returnType, ok := returnType.(*Object); ok { return completeObjectValue(eCtx, returnType, fieldASTs, info, path, result) } // Not reachable. All possible output types have been considered. err := invariantf(false, `Cannot complete value of unexpected type "%v."`, returnType) if err != nil { panic(gqlerrors.FormatError(err)) } return nil } func completeThunkValueCatchingError(eCtx *executionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, path *ResponsePath, result interface{}) (completed interface{}) { // catch any panic invoked from the propertyFn (thunk) defer func() { if r := recover(); r != nil { handleFieldError(r, FieldASTsToNodeASTs(fieldASTs), path, returnType, eCtx) } }() propertyFn, ok := result.(func() (interface{}, error)) if !ok { err := gqlerrors.NewFormattedError("Error resolving func. Expected `func() (interface{}, error)` signature") panic(gqlerrors.FormatError(err)) } fnResult, err := propertyFn() if err != nil { panic(gqlerrors.FormatError(err)) } result = fnResult if returnType, ok := returnType.(*NonNull); ok { completed := completeValue(eCtx, returnType, fieldASTs, info, path, result) return completed } completed = completeValue(eCtx, returnType, fieldASTs, info, path, result) return completed } // completeAbstractValue completes value of an Abstract type (Union / Interface) by determining the runtime type // of that value, then completing based on that type. func completeAbstractValue(eCtx *executionContext, returnType Abstract, fieldASTs []*ast.Field, info ResolveInfo, path *ResponsePath, result interface{}) interface{} { var runtimeType *Object resolveTypeParams := ResolveTypeParams{ Value: result, Info: info, Context: eCtx.Context, } if unionReturnType, ok := returnType.(*Union); ok && unionReturnType.ResolveType != nil { runtimeType = unionReturnType.ResolveType(resolveTypeParams) } else if interfaceReturnType, ok := returnType.(*Interface); ok && interfaceReturnType.ResolveType != nil { runtimeType = interfaceReturnType.ResolveType(resolveTypeParams) } else { runtimeType = defaultResolveTypeFn(resolveTypeParams, returnType) } err := invariantf(runtimeType != nil, `Abstract type %v must resolve to an Object type at runtime `+ `for field %v.%v with value "%v", received "%v".`, returnType, info.ParentType, info.FieldName, result, runtimeType, ) if err != nil { panic(err) } if !eCtx.Schema.IsPossibleType(returnType, runtimeType) { panic(gqlerrors.NewFormattedError( fmt.Sprintf(`Runtime Object type "%v" is not a possible type `+ `for "%v".`, runtimeType, returnType), )) } return completeObjectValue(eCtx, runtimeType, fieldASTs, info, path, result) } // completeObjectValue complete an Object value by executing all sub-selections. func completeObjectValue(eCtx *executionContext, returnType *Object, fieldASTs []*ast.Field, info ResolveInfo, path *ResponsePath, result interface{}) interface{} { // If there is an isTypeOf predicate function, call it with the // current result. If isTypeOf returns false, then raise an error rather // than continuing execution. if returnType.IsTypeOf != nil { p := IsTypeOfParams{ Value: result, Info: info, Context: eCtx.Context, } if !returnType.IsTypeOf(p) { panic(gqlerrors.NewFormattedError( fmt.Sprintf(`Expected value of type "%v" but got: %T.`, returnType, result), )) } } // Collect sub-fields to execute to complete this value. subFieldASTs := map[string][]*ast.Field{} visitedFragmentNames := map[string]bool{} for _, fieldAST := range fieldASTs { if fieldAST == nil { continue } selectionSet := fieldAST.SelectionSet if selectionSet != nil { innerParams := collectFieldsParams{ ExeContext: eCtx, RuntimeType: returnType, SelectionSet: selectionSet, Fields: subFieldASTs, VisitedFragmentNames: visitedFragmentNames, } subFieldASTs = collectFields(innerParams) } } executeFieldsParams := executeFieldsParams{ ExecutionContext: eCtx, ParentType: returnType, Source: result, Fields: subFieldASTs, Path: path, } return executeSubFields(executeFieldsParams) } // completeLeafValue complete a leaf value (Scalar / Enum) by serializing to a valid value, returning nil if serialization is not possible. func completeLeafValue(returnType Leaf, result interface{}) interface{} { serializedResult := returnType.Serialize(result) if isNullish(serializedResult) { return nil } return serializedResult } // completeListValue complete a list value by completing each item in the list with the inner type func completeListValue(eCtx *executionContext, returnType *List, fieldASTs []*ast.Field, info ResolveInfo, path *ResponsePath, result interface{}) interface{} { resultVal := reflect.ValueOf(result) if resultVal.Kind() == reflect.Ptr { resultVal = resultVal.Elem() } parentTypeName := "" if info.ParentType != nil { parentTypeName = info.ParentType.Name() } err := invariantf( resultVal.IsValid() && isIterable(result), "User Error: expected iterable, but did not find one "+ "for field %v.%v.", parentTypeName, info.FieldName) if err != nil { panic(gqlerrors.FormatError(err)) } itemType := returnType.OfType completedResults := make([]interface{}, 0, resultVal.Len()) for i := 0; i < resultVal.Len(); i++ { val := resultVal.Index(i).Interface() fieldPath := path.WithKey(i) completedItem := completeValueCatchingError(eCtx, itemType, fieldASTs, info, fieldPath, val) completedResults = append(completedResults, completedItem) } return completedResults } // defaultResolveTypeFn If a resolveType function is not given, then a default resolve behavior is // used which tests each possible type for the abstract type by calling // isTypeOf for the object being coerced, returning the first type that matches. func defaultResolveTypeFn(p ResolveTypeParams, abstractType Abstract) *Object { possibleTypes := p.Info.Schema.PossibleTypes(abstractType) for _, possibleType := range possibleTypes { if possibleType.IsTypeOf == nil { continue } isTypeOfParams := IsTypeOfParams{ Value: p.Value, Info: p.Info, Context: p.Context, } if res := possibleType.IsTypeOf(isTypeOfParams); res { return possibleType } } return nil } // FieldResolver is used in DefaultResolveFn when the the source value implements this interface. type FieldResolver interface { // Resolve resolves the value for the given ResolveParams. It has the same semantics as FieldResolveFn. Resolve(p ResolveParams) (interface{}, error) } // DefaultResolveFn If a resolve function is not given, then a default resolve behavior is used // which takes the property of the source object of the same name as the field // and returns it as the result, or if it's a function, returns the result // of calling that function. func DefaultResolveFn(p ResolveParams) (interface{}, error) { sourceVal := reflect.ValueOf(p.Source) // Check if value implements 'Resolver' interface if resolver, ok := sourceVal.Interface().(FieldResolver); ok { return resolver.Resolve(p) } // try to resolve p.Source as a struct if sourceVal.IsValid() && sourceVal.Type().Kind() == reflect.Ptr { sourceVal = sourceVal.Elem() } if !sourceVal.IsValid() { return nil, nil } if sourceVal.Type().Kind() == reflect.Struct { for i := 0; i < sourceVal.NumField(); i++ { valueField := sourceVal.Field(i) typeField := sourceVal.Type().Field(i) // try matching the field name first if strings.EqualFold(typeField.Name, p.Info.FieldName) { return valueField.Interface(), nil } tag := typeField.Tag checkTag := func(tagName string) bool { t := tag.Get(tagName) tOptions := strings.Split(t, ",") if len(tOptions) == 0 { return false } if tOptions[0] != p.Info.FieldName { return false } return true } if checkTag("json") || checkTag("graphql") { return valueField.Interface(), nil } else { continue } } return nil, nil } // try p.Source as a map[string]interface if sourceMap, ok := p.Source.(map[string]interface{}); ok { property := sourceMap[p.Info.FieldName] val := reflect.ValueOf(property) if val.IsValid() && val.Type().Kind() == reflect.Func { // try type casting the func to the most basic func signature // for more complex signatures, user have to define ResolveFn if propertyFn, ok := property.(func() interface{}); ok { return propertyFn(), nil } } return property, nil } // Try accessing as map via reflection if r := reflect.ValueOf(p.Source); r.Kind() == reflect.Map && r.Type().Key().Kind() == reflect.String { val := r.MapIndex(reflect.ValueOf(p.Info.FieldName)) if val.IsValid() { property := val.Interface() if val.Type().Kind() == reflect.Func { // try type casting the func to the most basic func signature // for more complex signatures, user have to define ResolveFn if propertyFn, ok := property.(func() interface{}); ok { return propertyFn(), nil } } return property, nil } } // last resort, return nil return nil, nil } // This method looks up the field on the given type definition. // It has special casing for the two introspection fields, __schema // and __typename. __typename is special because it can always be // queried as a field, even in situations where no other fields // are allowed, like on a Union. __schema could get automatically // added to the query type, but that would require mutating type // definitions, which would cause issues. func getFieldDef(schema Schema, parentType *Object, fieldName string) *FieldDefinition { if parentType == nil { return nil } if fieldName == SchemaMetaFieldDef.Name && schema.QueryType() == parentType { return SchemaMetaFieldDef } if fieldName == TypeMetaFieldDef.Name && schema.QueryType() == parentType { return TypeMetaFieldDef } if fieldName == TypeNameMetaFieldDef.Name { return TypeNameMetaFieldDef } return parentType.Fields()[fieldName] } // contains field information that will be placed in an ordered slice type orderedField struct { responseName string fieldASTs []*ast.Field } // orders fields from a fields map by location in the source func orderedFields(fields map[string][]*ast.Field) []*orderedField { orderedFields := []*orderedField{} fieldMap := map[int]*orderedField{} startLocs := []int{} for responseName, fieldASTs := range fields { // find the lowest location in the current fieldASTs lowest := -1 for _, fieldAST := range fieldASTs { loc := fieldAST.GetLoc().Start if lowest == -1 || loc < lowest { lowest = loc } } startLocs = append(startLocs, lowest) fieldMap[lowest] = &orderedField{ responseName: responseName, fieldASTs: fieldASTs, } } sort.Ints(startLocs) for _, startLoc := range startLocs { orderedFields = append(orderedFields, fieldMap[startLoc]) } return orderedFields } ================================================ FILE: executor_resolve_test.go ================================================ package graphql_test import ( "encoding/json" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/testutil" "reflect" "testing" ) func testSchema(t *testing.T, testField *graphql.Field) graphql.Schema { schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "test": testField, }, }), }) if err != nil { t.Fatalf("Invalid schema: %v", err) } return schema } func TestExecutesResolveFunction_DefaultFunctionAccessesProperties(t *testing.T) { schema := testSchema(t, &graphql.Field{Type: graphql.String}) source := map[string]interface{}{ "test": "testValue", } expected := map[string]interface{}{ "test": "testValue", } result := graphql.Do(graphql.Params{ Schema: schema, RequestString: `{ test }`, RootObject: source, }) if !reflect.DeepEqual(expected, result.Data) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result.Data)) } } func TestExecutesResolveFunction_DefaultFunctionCallsMethods(t *testing.T) { schema := testSchema(t, &graphql.Field{Type: graphql.String}) source := map[string]interface{}{ "test": func() interface{} { return "testValue" }, } expected := map[string]interface{}{ "test": "testValue", } result := graphql.Do(graphql.Params{ Schema: schema, RequestString: `{ test }`, RootObject: source, }) if !reflect.DeepEqual(expected, result.Data) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result.Data)) } } func TestExecutesResolveFunction_UsesProvidedResolveFunction(t *testing.T) { schema := testSchema(t, &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "aStr": &graphql.ArgumentConfig{Type: graphql.String}, "aInt": &graphql.ArgumentConfig{Type: graphql.Int}, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { b, err := json.Marshal(p.Args) return string(b), err }, }) expected := map[string]interface{}{ "test": "{}", } result := graphql.Do(graphql.Params{ Schema: schema, RequestString: `{ test }`, }) if !reflect.DeepEqual(expected, result.Data) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result.Data)) } expected = map[string]interface{}{ "test": `{"aStr":"String!"}`, } result = graphql.Do(graphql.Params{ Schema: schema, RequestString: `{ test(aStr: "String!") }`, }) if !reflect.DeepEqual(expected, result.Data) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result.Data)) } expected = map[string]interface{}{ "test": `{"aInt":-123,"aStr":"String!"}`, } result = graphql.Do(graphql.Params{ Schema: schema, RequestString: `{ test(aInt: -123, aStr: "String!") }`, }) if !reflect.DeepEqual(expected, result.Data) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result.Data)) } } func TestExecutesResolveFunction_UsesProvidedResolveFunction_SourceIsStruct_WithoutJSONTags(t *testing.T) { // For structs without JSON tags, it will map to upper-cased exported field names type SubObjectWithoutJSONTags struct { Str string Int int } schema := testSchema(t, &graphql.Field{ Type: graphql.NewObject(graphql.ObjectConfig{ Name: "SubObject", Description: "Maps GraphQL Object `SubObject` to Go struct `SubObjectWithoutJSONTags`", Fields: graphql.Fields{ "Str": &graphql.Field{Type: graphql.String}, "Int": &graphql.Field{Type: graphql.Int}, }, }), Args: graphql.FieldConfigArgument{ "aStr": &graphql.ArgumentConfig{Type: graphql.String}, "aInt": &graphql.ArgumentConfig{Type: graphql.Int}, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { aStr, _ := p.Args["aStr"].(string) aInt, _ := p.Args["aInt"].(int) return &SubObjectWithoutJSONTags{ Str: aStr, Int: aInt, }, nil }, }) expected := map[string]interface{}{ "test": map[string]interface{}{ "Str": "", "Int": 0, }, } result := graphql.Do(graphql.Params{ Schema: schema, RequestString: `{ test { Str, Int } }`, }) if !reflect.DeepEqual(expected, result.Data) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result.Data)) } expected = map[string]interface{}{ "test": map[string]interface{}{ "Str": "String!", "Int": 0, }, } result = graphql.Do(graphql.Params{ Schema: schema, RequestString: `{ test(aStr: "String!") { Str, Int } }`, }) if !reflect.DeepEqual(expected, result.Data) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result.Data)) } expected = map[string]interface{}{ "test": map[string]interface{}{ "Str": "String!", "Int": -123, }, } result = graphql.Do(graphql.Params{ Schema: schema, RequestString: `{ test(aInt: -123, aStr: "String!") { Str, Int } }`, }) if !reflect.DeepEqual(expected, result.Data) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result.Data)) } } func TestExecutesResolveFunction_UsesProvidedResolveFunction_SourceIsStruct_WithJSONTags(t *testing.T) { // For structs without JSON tags, it will map to upper-cased exported field names type SubObjectWithJSONTags struct { OtherField string `json:""` Str string `json:"str"` Int int `json:"int"` } schema := testSchema(t, &graphql.Field{ Type: graphql.NewObject(graphql.ObjectConfig{ Name: "SubObject", Description: "Maps GraphQL Object `SubObject` to Go struct `SubObjectWithJSONTags`", Fields: graphql.Fields{ "str": &graphql.Field{Type: graphql.String}, "int": &graphql.Field{Type: graphql.Int}, }, }), Args: graphql.FieldConfigArgument{ "aStr": &graphql.ArgumentConfig{Type: graphql.String}, "aInt": &graphql.ArgumentConfig{Type: graphql.Int}, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { aStr, _ := p.Args["aStr"].(string) aInt, _ := p.Args["aInt"].(int) return &SubObjectWithJSONTags{ Str: aStr, Int: aInt, }, nil }, }) expected := map[string]interface{}{ "test": map[string]interface{}{ "str": "", "int": 0, }, } result := graphql.Do(graphql.Params{ Schema: schema, RequestString: `{ test { str, int } }`, }) if !reflect.DeepEqual(expected, result.Data) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result.Data)) } expected = map[string]interface{}{ "test": map[string]interface{}{ "str": "String!", "int": 0, }, } result = graphql.Do(graphql.Params{ Schema: schema, RequestString: `{ test(aStr: "String!") { str, int } }`, }) if !reflect.DeepEqual(expected, result.Data) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result.Data)) } expected = map[string]interface{}{ "test": map[string]interface{}{ "str": "String!", "int": -123, }, } result = graphql.Do(graphql.Params{ Schema: schema, RequestString: `{ test(aInt: -123, aStr: "String!") { str, int } }`, }) if !reflect.DeepEqual(expected, result.Data) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result.Data)) } } ================================================ FILE: executor_schema_test.go ================================================ package graphql_test import ( "fmt" "reflect" "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/testutil" ) // TODO: have a separate package for other tests for eg `parser` // maybe for: // - tests that supposed to be black-boxed (no reason to access private identifiers) // - tests that create internal tests structs, we might not want to pollute the package with too many test structs type testPic struct { Url string `json:"url"` Width string `json:"width"` Height string `json:"height"` } type testPicFn func(width, height string) *testPic type testAuthor struct { Id int `json:"id"` Name string `json:"name"` Pic testPicFn `json:"pic"` RecentArticle *testArticle `json:"recentArticle"` } type testArticle struct { Id string `json:"id"` IsPublished string `json:"isPublished"` Author *testAuthor `json:"author"` Title string `json:"title"` Body string `json:"body"` Hidden string `json:"hidden"` Keywords []interface{} `json:"keywords"` } func getPic(id int, width, height string) *testPic { return &testPic{ Url: fmt.Sprintf("cdn://%v", id), Width: width, Height: height, } } var johnSmith *testAuthor func article(id interface{}) *testArticle { return &testArticle{ Id: fmt.Sprintf("%v", id), IsPublished: "true", Author: johnSmith, Title: fmt.Sprintf("My Article %v", id), Body: "This is a post", Hidden: "This data is not exposed in the schema", Keywords: []interface{}{ "foo", "bar", 1, true, nil, }, } } func TestExecutesUsingAComplexSchema(t *testing.T) { johnSmith = &testAuthor{ Id: 123, Name: "John Smith", Pic: func(width string, height string) *testPic { return getPic(123, width, height) }, RecentArticle: article("1"), } blogImage := graphql.NewObject(graphql.ObjectConfig{ Name: "Image", Fields: graphql.Fields{ "url": &graphql.Field{ Type: graphql.String, }, "width": &graphql.Field{ Type: graphql.Int, }, "height": &graphql.Field{ Type: graphql.Int, }, }, }) blogAuthor := graphql.NewObject(graphql.ObjectConfig{ Name: "Author", Fields: graphql.Fields{ "id": &graphql.Field{ Type: graphql.String, }, "name": &graphql.Field{ Type: graphql.String, }, "pic": &graphql.Field{ Type: blogImage, Args: graphql.FieldConfigArgument{ "width": &graphql.ArgumentConfig{ Type: graphql.Int, }, "height": &graphql.ArgumentConfig{ Type: graphql.Int, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { if author, ok := p.Source.(*testAuthor); ok { width := fmt.Sprintf("%v", p.Args["width"]) height := fmt.Sprintf("%v", p.Args["height"]) return author.Pic(width, height), nil } return nil, nil }, }, "recentArticle": &graphql.Field{}, }, }) blogArticle := graphql.NewObject(graphql.ObjectConfig{ Name: "Article", Fields: graphql.Fields{ "id": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), }, "isPublished": &graphql.Field{ Type: graphql.Boolean, }, "author": &graphql.Field{ Type: blogAuthor, }, "title": &graphql.Field{ Type: graphql.String, }, "body": &graphql.Field{ Type: graphql.String, }, "keywords": &graphql.Field{ Type: graphql.NewList(graphql.String), }, }, }) blogAuthor.AddFieldConfig("recentArticle", &graphql.Field{ Type: blogArticle, }) blogQuery := graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "article": &graphql.Field{ Type: blogArticle, Args: graphql.FieldConfigArgument{ "id": &graphql.ArgumentConfig{ Type: graphql.ID, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { id := p.Args["id"] return article(id), nil }, }, "feed": &graphql.Field{ Type: graphql.NewList(blogArticle), Resolve: func(p graphql.ResolveParams) (interface{}, error) { return []*testArticle{ article(1), article(2), article(3), article(4), article(5), article(6), article(7), article(8), article(9), article(10), }, nil }, }, }, }) blogSchema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: blogQuery, }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } request := ` { feed { id, title }, article(id: "1") { ...articleFields, author { id, name, pic(width: 640, height: 480) { url, width, height }, recentArticle { ...articleFields, keywords } } } } fragment articleFields on Article { id, isPublished, title, body, hidden, notdefined } ` expected := &graphql.Result{ Data: map[string]interface{}{ "article": map[string]interface{}{ "title": "My Article 1", "body": "This is a post", "author": map[string]interface{}{ "id": "123", "name": "John Smith", "pic": map[string]interface{}{ "url": "cdn://123", "width": 640, "height": 480, }, "recentArticle": map[string]interface{}{ "id": "1", "isPublished": bool(true), "title": "My Article 1", "body": "This is a post", "keywords": []interface{}{ "foo", "bar", "1", "true", nil, }, }, }, "id": "1", "isPublished": bool(true), }, "feed": []interface{}{ map[string]interface{}{ "id": "1", "title": "My Article 1", }, map[string]interface{}{ "id": "2", "title": "My Article 2", }, map[string]interface{}{ "id": "3", "title": "My Article 3", }, map[string]interface{}{ "id": "4", "title": "My Article 4", }, map[string]interface{}{ "id": "5", "title": "My Article 5", }, map[string]interface{}{ "id": "6", "title": "My Article 6", }, map[string]interface{}{ "id": "7", "title": "My Article 7", }, map[string]interface{}{ "id": "8", "title": "My Article 8", }, map[string]interface{}{ "id": "9", "title": "My Article 9", }, map[string]interface{}{ "id": "10", "title": "My Article 10", }, }, }, } // parse query ast := testutil.TestParse(t, request) // execute ep := graphql.ExecuteParams{ Schema: blogSchema, AST: ast, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } ================================================ FILE: executor_test.go ================================================ package graphql_test import ( "context" "encoding/json" "errors" "fmt" "reflect" "testing" "time" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/location" "github.com/graphql-go/graphql/testutil" ) func TestExecutesArbitraryCode(t *testing.T) { deepData := map[string]interface{}{} data := map[string]interface{}{ "a": func() interface{} { return "Apple" }, "b": func() interface{} { return "Banana" }, "c": func() interface{} { return "Cookie" }, "d": func() interface{} { return "Donut" }, "e": func() interface{} { return "Egg" }, "f": "Fish", "pic": func(size int) string { return fmt.Sprintf("Pic of size: %v", size) }, "deep": func() interface{} { return deepData }, } data["promise"] = func() interface{} { return data } deepData = map[string]interface{}{ "a": func() interface{} { return "Already Been Done" }, "b": func() interface{} { return "Boring" }, "c": func() interface{} { return []string{"Contrived", "", "Confusing"} }, "deeper": func() interface{} { return []interface{}{data, nil, data} }, } query := ` query Example($size: Int) { a, b, x: c ...c f ...on DataType { pic(size: $size) promise { a } } deep { a b c deeper { a b } } } fragment c on DataType { d e } ` expected := &graphql.Result{ Data: map[string]interface{}{ "b": "Banana", "x": "Cookie", "d": "Donut", "e": "Egg", "promise": map[string]interface{}{ "a": "Apple", }, "a": "Apple", "deep": map[string]interface{}{ "a": "Already Been Done", "b": "Boring", "c": []interface{}{ "Contrived", "", "Confusing", }, "deeper": []interface{}{ map[string]interface{}{ "a": "Apple", "b": "Banana", }, nil, map[string]interface{}{ "a": "Apple", "b": "Banana", }, }, }, "f": "Fish", "pic": "Pic of size: 100", }, } // Schema Definitions picResolverFn := func(p graphql.ResolveParams) (interface{}, error) { // get and type assert ResolveFn for this field picResolver, ok := p.Source.(map[string]interface{})["pic"].(func(size int) string) if !ok { return nil, nil } // get and type assert argument sizeArg, ok := p.Args["size"].(int) if !ok { return nil, nil } return picResolver(sizeArg), nil } dataType := graphql.NewObject(graphql.ObjectConfig{ Name: "DataType", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, "b": &graphql.Field{ Type: graphql.String, }, "c": &graphql.Field{ Type: graphql.String, }, "d": &graphql.Field{ Type: graphql.String, }, "e": &graphql.Field{ Type: graphql.String, }, "f": &graphql.Field{ Type: graphql.String, }, "pic": &graphql.Field{ Args: graphql.FieldConfigArgument{ "size": &graphql.ArgumentConfig{ Type: graphql.Int, }, }, Type: graphql.String, Resolve: picResolverFn, }, }, }) deepDataType := graphql.NewObject(graphql.ObjectConfig{ Name: "DeepDataType", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, "b": &graphql.Field{ Type: graphql.String, }, "c": &graphql.Field{ Type: graphql.NewList(graphql.String), }, "deeper": &graphql.Field{ Type: graphql.NewList(dataType), }, }, }) // Exploring a way to have a Object within itself // in this case DataType has DeepDataType has DataType dataType.AddFieldConfig("deep", &graphql.Field{ Type: deepDataType, }) // in this case DataType has DataType dataType.AddFieldConfig("promise", &graphql.Field{ Type: dataType, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: dataType, }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query astDoc := testutil.TestParse(t, query) // execute args := map[string]interface{}{ "size": 100, } operationName := "Example" ep := graphql.ExecuteParams{ Schema: schema, Root: data, AST: astDoc, OperationName: operationName, Args: args, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestMergesParallelFragments(t *testing.T) { query := ` { a, ...FragOne, ...FragTwo } fragment FragOne on Type { b deep { b, deeper: deep { b } } } fragment FragTwo on Type { c deep { c, deeper: deep { c } } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "Apple", "b": "Banana", "deep": map[string]interface{}{ "c": "Cherry", "b": "Banana", "deeper": map[string]interface{}{ "b": "Banana", "c": "Cherry", }, }, "c": "Cherry", }, } typeObjectType := graphql.NewObject(graphql.ObjectConfig{ Name: "Type", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return "Apple", nil }, }, "b": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return "Banana", nil }, }, "c": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return "Cherry", nil }, }, }, }) deepTypeFieldConfig := &graphql.Field{ Type: typeObjectType, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return p.Source, nil }, } typeObjectType.AddFieldConfig("deep", deepTypeFieldConfig) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: typeObjectType, }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query ast := testutil.TestParse(t, query) // execute ep := graphql.ExecuteParams{ Schema: schema, AST: ast, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } type CustomMap map[string]interface{} func TestCustomMapType(t *testing.T) { query := ` query Example { data { a } } ` data := CustomMap{ "a": "1", "b": "2", } schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "RootQuery", Fields: graphql.Fields{ "data": &graphql.Field{ Type: graphql.NewObject(graphql.ObjectConfig{ Name: "Data", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, "b": &graphql.Field{ Type: graphql.String, }, }, }), Resolve: func(p graphql.ResolveParams) (interface{}, error) { return data, nil }, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } result := testutil.TestExecute(t, graphql.ExecuteParams{ Schema: schema, Root: data, AST: testutil.TestParse(t, query), }) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } expected := map[string]interface{}{ "data": map[string]interface{}{ "a": "1", }, } if !reflect.DeepEqual(result.Data, expected) { t.Fatalf("Expected context.key to equal %v, got %v", expected, result.Data) } } func TestThreadsSourceCorrectly(t *testing.T) { query := ` query Example { a } ` data := map[string]interface{}{ "key": "value", } var resolvedSource map[string]interface{} schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Type", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { resolvedSource = p.Source.(map[string]interface{}) return resolvedSource, nil }, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query ast := testutil.TestParse(t, query) // execute ep := graphql.ExecuteParams{ Schema: schema, Root: data, AST: ast, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } expected := "value" if resolvedSource["key"] != expected { t.Fatalf("Expected context.key to equal %v, got %v", expected, resolvedSource["key"]) } } func TestCorrectlyThreadsArguments(t *testing.T) { query := ` query Example { b(numArg: 123, stringArg: "foo") } ` var resolvedArgs map[string]interface{} schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Type", Fields: graphql.Fields{ "b": &graphql.Field{ Args: graphql.FieldConfigArgument{ "numArg": &graphql.ArgumentConfig{ Type: graphql.Int, }, "stringArg": &graphql.ArgumentConfig{ Type: graphql.String, }, }, Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { resolvedArgs = p.Args return resolvedArgs, nil }, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query ast := testutil.TestParse(t, query) // execute ep := graphql.ExecuteParams{ Schema: schema, AST: ast, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } expectedNum := 123 expectedString := "foo" if resolvedArgs["numArg"] != expectedNum { t.Fatalf("Expected args.numArg to equal `%v`, got `%v`", expectedNum, resolvedArgs["numArg"]) } if resolvedArgs["stringArg"] != expectedString { t.Fatalf("Expected args.stringArg to equal `%v`, got `%v`", expectedNum, resolvedArgs["stringArg"]) } } func TestThreadsRootValueContextCorrectly(t *testing.T) { query := ` query Example { a } ` schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Type", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { val, _ := p.Info.RootValue.(map[string]interface{})["stringKey"].(string) return val, nil }, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query ast := testutil.TestParse(t, query) // execute ep := graphql.ExecuteParams{ Schema: schema, AST: ast, Root: map[string]interface{}{ "stringKey": "stringValue", }, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } expected := &graphql.Result{ Data: map[string]interface{}{ "a": "stringValue", }, } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestThreadsContextCorrectly(t *testing.T) { query := ` query Example { a } ` schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Type", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return p.Context.Value("foo"), nil }, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query ast := testutil.TestParse(t, query) // execute ep := graphql.ExecuteParams{ Schema: schema, AST: ast, Context: context.WithValue(context.Background(), "foo", "bar"), } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } expected := &graphql.Result{ Data: map[string]interface{}{ "a": "bar", }, } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestNullsOutErrorSubtrees(t *testing.T) { // TODO: TestNullsOutErrorSubtrees test for go-routines if implemented query := `{ sync, syncError, }` expectedData := map[string]interface{}{ "sync": "sync", "syncError": nil, } expectedErrors := []gqlerrors.FormattedError{{ Message: "Error getting syncError", Locations: []location.SourceLocation{ { Line: 3, Column: 7, }, }, Path: []interface{}{ "syncError", }, }, } data := map[string]interface{}{ "sync": func() interface{} { return "sync" }, "syncError": func() interface{} { panic("Error getting syncError") }, } schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Type", Fields: graphql.Fields{ "sync": &graphql.Field{ Type: graphql.String, }, "syncError": &graphql.Field{ Type: graphql.String, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query ast := testutil.TestParse(t, query) // execute ep := graphql.ExecuteParams{ Schema: schema, AST: ast, Root: data, } result := testutil.TestExecute(t, ep) if !reflect.DeepEqual(expectedData, result.Data) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedData, result.Data)) } if !testutil.EqualFormattedErrors(expectedErrors, result.Errors) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedErrors, result.Errors)) } } func TestUsesTheInlineOperationIfNoOperationNameIsProvided(t *testing.T) { doc := `{ a }` data := map[string]interface{}{ "a": "b", } expected := &graphql.Result{ Data: map[string]interface{}{ "a": "b", }, } schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Type", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: schema, AST: ast, Root: data, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestUsesTheOnlyOperationIfNoOperationNameIsProvided(t *testing.T) { doc := `query Example { a }` data := map[string]interface{}{ "a": "b", } expected := &graphql.Result{ Data: map[string]interface{}{ "a": "b", }, } schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Type", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: schema, AST: ast, Root: data, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestUsesTheNamedOperationIfOperationNameIsProvided(t *testing.T) { doc := `query Example { first: a } query OtherExample { second: a }` data := map[string]interface{}{ "a": "b", } expected := &graphql.Result{ Data: map[string]interface{}{ "second": "b", }, } schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Type", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: schema, AST: ast, Root: data, OperationName: "OtherExample", } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestThrowsIfNoOperationIsProvided(t *testing.T) { doc := `fragment Example on Type { a }` data := map[string]interface{}{ "a": "b", } expectedErrors := []gqlerrors.FormattedError{ { Message: "Must provide an operation.", Locations: []location.SourceLocation{}, }, } schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Type", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: schema, AST: ast, Root: data, } result := testutil.TestExecute(t, ep) if result.Data != nil { t.Fatalf("wrong result, expected nil result.Data, got %v", result.Data) } if !testutil.EqualFormattedErrors(expectedErrors, result.Errors) { t.Fatalf("unexpected result, Diff: %v", testutil.Diff(expectedErrors, result.Errors)) } } func TestThrowsIfNoOperationNameIsProvidedWithMultipleOperations(t *testing.T) { doc := `query Example { a } query OtherExample { a }` data := map[string]interface{}{ "a": "b", } expectedErrors := []gqlerrors.FormattedError{ { Message: "Must provide operation name if query contains multiple operations.", Locations: []location.SourceLocation{}, }, } schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Type", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: schema, AST: ast, Root: data, } result := testutil.TestExecute(t, ep) if result.Data != nil { t.Fatalf("wrong result, expected nil result.Data, got %v", result.Data) } if !testutil.EqualFormattedErrors(expectedErrors, result.Errors) { t.Fatalf("unexpected result, Diff: %v", testutil.Diff(expectedErrors, result.Errors)) } } func TestThrowsIfUnknownOperationNameIsProvided(t *testing.T) { doc := `query Example { a } query OtherExample { a }` data := map[string]interface{}{ "a": "b", } expectedErrors := []gqlerrors.FormattedError{ { Message: `Unknown operation named "UnknownExample".`, Locations: []location.SourceLocation{}, }, } schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Type", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: schema, AST: ast, Root: data, OperationName: "UnknownExample", } result := testutil.TestExecute(t, ep) if result.Data != nil { t.Fatalf("wrong result, expected nil result.Data, got %v", result.Data) } if !testutil.EqualFormattedErrors(expectedErrors, result.Errors) { t.Fatalf("unexpected result, Diff: %v", testutil.Diff(expectedErrors, result.Errors)) } } func TestThrowsIfOperationTypeIsUnsupported(t *testing.T) { query := `mutation Mut { a } subscription Sub { a }` operations := []string{"Mut", "Sub"} expectedErrors := [][]gqlerrors.FormattedError{ {{ Message: `Schema is not configured for mutations`, Locations: []location.SourceLocation{{Line: 1, Column: 1}}, }}, {{ Message: `Schema is not configured for subscriptions`, Locations: []location.SourceLocation{{Line: 1, Column: 20}}, }}, } schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query ast := testutil.TestParse(t, query) for opIndex, operation := range operations { expectedErrors := expectedErrors[opIndex] // execute ep := graphql.ExecuteParams{ Schema: schema, AST: ast, OperationName: operation, } result := testutil.TestExecute(t, ep) if result.Data != nil { t.Fatalf("wrong result, expected nil result.Data, got %v", result.Data) } if !testutil.EqualFormattedErrors(expectedErrors, result.Errors) { t.Fatalf("unexpected result, Diff: %v", testutil.Diff(expectedErrors, result.Errors)) } } } func TestUsesTheQuerySchemaForQueries(t *testing.T) { doc := `query Q { a } mutation M { c } subscription S { a }` data := map[string]interface{}{ "a": "b", "c": "d", } expected := &graphql.Result{ Data: map[string]interface{}{ "a": "b", }, } schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Q", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, }, }), Mutation: graphql.NewObject(graphql.ObjectConfig{ Name: "M", Fields: graphql.Fields{ "c": &graphql.Field{ Type: graphql.String, }, }, }), Subscription: graphql.NewObject(graphql.ObjectConfig{ Name: "S", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: schema, AST: ast, Root: data, OperationName: "Q", } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestUsesTheMutationSchemaForMutations(t *testing.T) { doc := `query Q { a } mutation M { c }` data := map[string]interface{}{ "a": "b", "c": "d", } expected := &graphql.Result{ Data: map[string]interface{}{ "c": "d", }, } schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Q", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, }, }), Mutation: graphql.NewObject(graphql.ObjectConfig{ Name: "M", Fields: graphql.Fields{ "c": &graphql.Field{ Type: graphql.String, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: schema, AST: ast, Root: data, OperationName: "M", } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestUsesTheSubscriptionSchemaForSubscriptions(t *testing.T) { doc := `query Q { a } subscription S { a }` data := map[string]interface{}{ "a": "b", "c": "d", } expected := &graphql.Result{ Data: map[string]interface{}{ "a": "b", }, } schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Q", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, }, }), Subscription: graphql.NewObject(graphql.ObjectConfig{ Name: "S", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: schema, AST: ast, Root: data, OperationName: "S", } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestCorrectFieldOrderingDespiteExecutionOrder(t *testing.T) { doc := ` { b, a, c, d, e } ` data := map[string]interface{}{ "a": func() interface{} { return "a" }, "b": func() interface{} { return "b" }, "c": func() interface{} { return "c" }, "d": func() interface{} { return "d" }, "e": func() interface{} { return "e" }, } expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", "b": "b", "c": "c", "d": "d", "e": "e", }, } schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Type", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, "b": &graphql.Field{ Type: graphql.String, }, "c": &graphql.Field{ Type: graphql.String, }, "d": &graphql.Field{ Type: graphql.String, }, "e": &graphql.Field{ Type: graphql.String, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: schema, AST: ast, Root: data, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } // TODO: test to ensure key ordering // The following does not work // - iterating over result.Data map // Note that golang's map iteration order is randomized // So, iterating over result.Data won't do it for a test // - Marshal the result.Data to json string and assert it // json.Marshal seems to re-sort the keys automatically // t.Skipf("TODO: Ensure key ordering") } func TestAvoidsRecursion(t *testing.T) { doc := ` query Q { a ...Frag ...Frag } fragment Frag on Type { a, ...Frag } ` data := map[string]interface{}{ "a": "b", } expected := &graphql.Result{ Data: map[string]interface{}{ "a": "b", }, } schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Type", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: schema, AST: ast, Root: data, OperationName: "Q", } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestDoesNotIncludeIllegalFieldsInOutput(t *testing.T) { doc := `mutation M { thisIsIllegalDontIncludeMe }` expected := &graphql.Result{ Data: map[string]interface{}{}, } schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Q", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, }, }), Mutation: graphql.NewObject(graphql.ObjectConfig{ Name: "M", Fields: graphql.Fields{ "c": &graphql.Field{ Type: graphql.String, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: schema, AST: ast, } result := testutil.TestExecute(t, ep) if len(result.Errors) != 0 { t.Fatalf("wrong result, expected len(%v) errors, got len(%v)", len(expected.Errors), len(result.Errors)) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestDoesNotIncludeArgumentsThatWereNotSet(t *testing.T) { doc := `{ field(a: true, c: false, e: 0) }` expected := &graphql.Result{ Data: map[string]interface{}{ "field": `{"a":true,"c":false,"e":0}`, }, } schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Type", Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "a": &graphql.ArgumentConfig{ Type: graphql.Boolean, }, "b": &graphql.ArgumentConfig{ Type: graphql.Boolean, }, "c": &graphql.ArgumentConfig{ Type: graphql.Boolean, }, "d": &graphql.ArgumentConfig{ Type: graphql.Int, }, "e": &graphql.ArgumentConfig{ Type: graphql.Int, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { args, _ := json.Marshal(p.Args) return string(args), nil }, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: schema, AST: ast, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } type testSpecialType struct { Value string } type testNotSpecialType struct { Value string } func TestFailsWhenAnIsTypeOfCheckIsNotMet(t *testing.T) { query := `{ specials { value } }` data := map[string]interface{}{ "specials": []interface{}{ testSpecialType{"foo"}, testNotSpecialType{"bar"}, }, } expected := &graphql.Result{ Data: map[string]interface{}{ "specials": []interface{}{ map[string]interface{}{ "value": "foo", }, nil, }, }, Errors: []gqlerrors.FormattedError{{ Message: `Expected value of type "SpecialType" but got: graphql_test.testNotSpecialType.`, Locations: []location.SourceLocation{ { Line: 1, Column: 3, }, }, Path: []interface{}{ "specials", 1, }, }, }, } specialType := graphql.NewObject(graphql.ObjectConfig{ Name: "SpecialType", IsTypeOf: func(p graphql.IsTypeOfParams) bool { if _, ok := p.Value.(testSpecialType); ok { return true } return false }, Fields: graphql.Fields{ "value": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return p.Source.(testSpecialType).Value, nil }, }, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "specials": &graphql.Field{ Type: graphql.NewList(specialType), Resolve: func(p graphql.ResolveParams) (interface{}, error) { return p.Source.(map[string]interface{})["specials"], nil }, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query ast := testutil.TestParse(t, query) // execute ep := graphql.ExecuteParams{ Schema: schema, AST: ast, Root: data, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestFailsToExecuteQueryContainingATypeDefinition(t *testing.T) { query := ` { foo } type Query { foo: String } ` expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: "GraphQL cannot execute a request containing a ObjectDefinition", Locations: []location.SourceLocation{}, }, }, } schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "foo": &graphql.Field{ Type: graphql.String, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query ast := testutil.TestParse(t, query) // execute ep := graphql.ExecuteParams{ Schema: schema, AST: ast, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestQuery_ExecutionAddsErrorsFromFieldResolveFn(t *testing.T) { qError := errors.New("queryError") q := graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return nil, qError }, }, "b": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return "ok", nil }, }, }, }) blogSchema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: q, }) if err != nil { t.Fatalf("unexpected error, got: %v", err) } query := "{ a }" result := graphql.Do(graphql.Params{ Schema: blogSchema, RequestString: query, }) if len(result.Errors) == 0 { t.Fatal("wrong result, expected errors, got no errors") } if result.Errors[0].Error() != qError.Error() { t.Fatalf("wrong result, unexpected error, got: %v, expected: %v", result.Errors[0], qError) } } func TestQuery_ExecutionDoesNotAddErrorsFromFieldResolveFn(t *testing.T) { qError := errors.New("queryError") q := graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return nil, qError }, }, "b": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return "ok", nil }, }, }, }) blogSchema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: q, }) if err != nil { t.Fatalf("unexpected error, got: %v", err) } query := "{ b }" result := graphql.Do(graphql.Params{ Schema: blogSchema, RequestString: query, }) if len(result.Errors) != 0 { t.Fatalf("wrong result, unexpected errors: %+v", result.Errors) } } func TestQuery_InputObjectUsesFieldDefaultValueFn(t *testing.T) { inputType := graphql.NewInputObject(graphql.InputObjectConfig{ Name: "Input", Fields: graphql.InputObjectConfigFieldMap{ "default": &graphql.InputObjectFieldConfig{ Type: graphql.String, DefaultValue: "bar", }, }, }) q := graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "foo": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(inputType), }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { val := p.Args["foo"].(map[string]interface{}) def, ok := val["default"] if !ok || def == nil { return nil, errors.New("queryError: No 'default' param") } if def.(string) != "bar" { return nil, errors.New("queryError: 'default' param has wrong value") } return "ok", nil }, }, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: q, }) if err != nil { t.Fatalf("unexpected error, got: %v", err) } query := `{ a(foo: {}) }` result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) if len(result.Errors) != 0 { t.Fatalf("wrong result, unexpected errors: %+v", result.Errors) } } func TestMutation_ExecutionAddsErrorsFromFieldResolveFn(t *testing.T) { mError := errors.New("mutationError") q := graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, }, }) m := graphql.NewObject(graphql.ObjectConfig{ Name: "Mutation", Fields: graphql.Fields{ "foo": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "f": &graphql.ArgumentConfig{ Type: graphql.String, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return nil, mError }, }, "bar": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "b": &graphql.ArgumentConfig{ Type: graphql.String, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return "ok", nil }, }, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: q, Mutation: m, }) if err != nil { t.Fatalf("unexpected error, got: %v", err) } query := "mutation _ { newFoo: foo(f:\"title\") }" result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) if len(result.Errors) == 0 { t.Fatal("wrong result, expected errors, got no errors") } if result.Errors[0].Error() != mError.Error() { t.Fatalf("wrong result, unexpected error, got: %v, expected: %v", result.Errors[0], mError) } } func TestMutation_ExecutionDoesNotAddErrorsFromFieldResolveFn(t *testing.T) { mError := errors.New("mutationError") q := graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, }, }, }) m := graphql.NewObject(graphql.ObjectConfig{ Name: "Mutation", Fields: graphql.Fields{ "foo": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "f": &graphql.ArgumentConfig{ Type: graphql.String, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return nil, mError }, }, "bar": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "b": &graphql.ArgumentConfig{ Type: graphql.String, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return "ok", nil }, }, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: q, Mutation: m, }) if err != nil { t.Fatalf("unexpected error, got: %v", err) } query := "mutation _ { newBar: bar(b:\"title\") }" result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) if len(result.Errors) != 0 { t.Fatalf("wrong result, unexpected errors: %+v", result.Errors) } } func TestGraphqlTag(t *testing.T) { typeObjectType := graphql.NewObject(graphql.ObjectConfig{ Name: "Type", Fields: graphql.Fields{ "fooBar": &graphql.Field{Type: graphql.String}, }, }) var baz = &graphql.Field{ Type: typeObjectType, Description: "typeObjectType", Resolve: func(p graphql.ResolveParams) (interface{}, error) { t := struct { FooBar string `graphql:"fooBar"` }{"foo bar value"} return t, nil }, } q := graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "baz": baz, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: q, }) if err != nil { t.Fatalf("unexpected error, got: %v", err) } query := "{ baz { fooBar } }" result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) if len(result.Errors) != 0 { t.Fatalf("wrong result, unexpected errors: %+v", result.Errors) } expectedData := map[string]interface{}{ "baz": map[string]interface{}{ "fooBar": "foo bar value", }, } if !reflect.DeepEqual(result.Data, expectedData) { t.Fatalf("unexpected result, got: %+v, expected: %+v", expectedData, result.Data) } } func TestFieldResolver(t *testing.T) { typeObjectType := graphql.NewObject(graphql.ObjectConfig{ Name: "Type", Fields: graphql.Fields{ "fooBar": &graphql.Field{Type: graphql.String}, }, }) var baz = &graphql.Field{ Type: typeObjectType, Description: "typeObjectType", Resolve: func(p graphql.ResolveParams) (interface{}, error) { return testCustomResolver{}, nil }, } var bazPtr = &graphql.Field{ Type: typeObjectType, Description: "typeObjectType", Resolve: func(p graphql.ResolveParams) (interface{}, error) { return &testCustomResolver{}, nil }, } q := graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "baz": baz, "bazPtr": bazPtr, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: q, }) if err != nil { t.Fatalf("unexpected error, got: %v", err) } query := "{ baz { fooBar }, bazPtr { fooBar } }" result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) if len(result.Errors) != 0 { t.Fatalf("wrong result, unexpected errors: %+v", result.Errors) } expectedData := map[string]interface{}{ "baz": map[string]interface{}{ "fooBar": "foo bar value", }, "bazPtr": map[string]interface{}{ "fooBar": "foo bar value", }, } if !reflect.DeepEqual(result.Data, expectedData) { t.Fatalf("unexpected result, got: %+v, expected: %+v", result.Data, expectedData) } } type testCustomResolver struct{} func (r testCustomResolver) Resolve(p graphql.ResolveParams) (interface{}, error) { if p.Info.FieldName == "fooBar" { return "foo bar value", nil } return "", errors.New("invalid field " + p.Info.FieldName) } func TestContextDeadline(t *testing.T) { timeout := time.Millisecond * time.Duration(100) acceptableDelay := time.Millisecond * time.Duration(10) expectedErrors := []gqlerrors.FormattedError{ { Message: context.DeadlineExceeded.Error(), Locations: []location.SourceLocation{}, }, } // Query type includes a field that won't resolve within the deadline var queryType = graphql.NewObject( graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "hello": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { time.Sleep(2 * time.Second) return "world", nil }, }, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: queryType, }) if err != nil { t.Fatalf("unexpected error, got: %v", err) } ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() startTime := time.Now() result := graphql.Do(graphql.Params{ Schema: schema, RequestString: "{hello}", Context: ctx, }) duration := time.Since(startTime) if duration > timeout+acceptableDelay { t.Fatalf("graphql.Do completed in %s, should have completed in %s", duration, timeout) } if !result.HasErrors() || len(result.Errors) == 0 { t.Fatalf("Result should include errors when deadline is exceeded") } if !testutil.EqualFormattedErrors(expectedErrors, result.Errors) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedErrors, result.Errors)) } } func TestThunkResultsProcessedCorrectly(t *testing.T) { barType := graphql.NewObject(graphql.ObjectConfig{ Name: "Bar", Fields: graphql.Fields{ "bazA": &graphql.Field{ Type: graphql.String, }, "bazB": &graphql.Field{ Type: graphql.String, }, }, }) fooType := graphql.NewObject(graphql.ObjectConfig{ Name: "Foo", Fields: graphql.Fields{ "bar": &graphql.Field{ Type: barType, Resolve: func(params graphql.ResolveParams) (interface{}, error) { var bar struct { BazA string BazB string } bar.BazA = "A" bar.BazB = "B" thunk := func() (interface{}, error) { return &bar, nil } return thunk, nil }, }, }, }) queryType := graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "foo": &graphql.Field{ Type: fooType, Resolve: func(params graphql.ResolveParams) (interface{}, error) { var foo struct{} return foo, nil }, }, }, }) expectNoError := func(err error) { if err != nil { t.Fatalf("expected no error, got %v", err) } } schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: queryType, }) expectNoError(err) query := "{ foo { bar { bazA bazB } } }" result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) if len(result.Errors) != 0 { t.Fatalf("expected no errors, got %v", result.Errors) } foo := result.Data.(map[string]interface{})["foo"].(map[string]interface{}) bar, ok := foo["bar"].(map[string]interface{}) if !ok { t.Errorf("expected bar to be a map[string]interface{}: actual = %v", reflect.TypeOf(foo["bar"])) } else { if got, want := bar["bazA"], "A"; got != want { t.Errorf("foo.bar.bazA: got=%v, want=%v", got, want) } if got, want := bar["bazB"], "B"; got != want { t.Errorf("foo.bar.bazB: got=%v, want=%v", got, want) } } if t.Failed() { b, err := json.Marshal(result.Data) expectNoError(err) t.Log(string(b)) } } func TestThunkErrorsAreHandledCorrectly(t *testing.T) { var bazCError = errors.New("barC error") barType := graphql.NewObject(graphql.ObjectConfig{ Name: "Bar", Fields: graphql.Fields{ "bazA": &graphql.Field{ Type: graphql.String, }, "bazB": &graphql.Field{ Type: graphql.String, }, "bazC": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { thunk := func() (interface{}, error) { return nil, bazCError } return thunk, nil }, }, }, }) fooType := graphql.NewObject(graphql.ObjectConfig{ Name: "Foo", Fields: graphql.Fields{ "bar": &graphql.Field{ Type: barType, Resolve: func(params graphql.ResolveParams) (interface{}, error) { var bar struct { BazA string BazB string } bar.BazA = "A" bar.BazB = "B" thunk := func() (interface{}, error) { return &bar, nil } return thunk, nil }, }, }, }) queryType := graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "foo": &graphql.Field{ Type: fooType, Resolve: func(params graphql.ResolveParams) (interface{}, error) { var foo struct{} return foo, nil }, }, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: queryType, }) if err != nil { t.Fatalf("unexpected error, got: %v", err) } query := "{ foo { bar { bazA bazB bazC } } }" result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) foo := result.Data.(map[string]interface{})["foo"].(map[string]interface{}) bar, ok := foo["bar"].(map[string]interface{}) if !ok { t.Errorf("expected bar to be a map[string]interface{}: actual = %v", reflect.TypeOf(foo["bar"])) } else { if got, want := bar["bazA"], "A"; got != want { t.Errorf("foo.bar.bazA: got=%v, want=%v", got, want) } if got, want := bar["bazB"], "B"; got != want { t.Errorf("foo.bar.bazB: got=%v, want=%v", got, want) } if got := bar["bazC"]; got != nil { t.Errorf("foo.bar.bazC: got=%v, want=nil", got) } var errs = result.Errors if len(errs) != 1 { t.Fatalf("expected 1 error, got %v", result.Errors) } if got, want := errs[0].Message, bazCError.Error(); got != want { t.Errorf("expected error: got=%v, want=%v", got, want) } } if t.Failed() { b, err := json.Marshal(result.Data) if err != nil { t.Fatalf("unexpected error: %v", err) } t.Log(string(b)) } } func assertJSON(t *testing.T, expected string, actual interface{}) { var e interface{} if err := json.Unmarshal([]byte(expected), &e); err != nil { t.Fatalf(err.Error()) } aJSON, err := json.MarshalIndent(actual, "", " ") if err != nil { t.Fatalf(err.Error()) } var a interface{} if err := json.Unmarshal(aJSON, &a); err != nil { t.Fatalf(err.Error()) } if !reflect.DeepEqual(e, a) { eNormalizedJSON, err := json.MarshalIndent(e, "", " ") if err != nil { t.Fatalf(err.Error()) } t.Fatalf("Expected JSON:\n\n%v\n\nActual JSON:\n\n%v", string(eNormalizedJSON), string(aJSON)) } } type extendedError struct { error extensions map[string]interface{} } func (err extendedError) Extensions() map[string]interface{} { return err.extensions } var _ gqlerrors.ExtendedError = &extendedError{} func testErrors(t *testing.T, nameType graphql.Output, extensions map[string]interface{}, formatErrorFn func(err error) error) *graphql.Result { type Hero struct { Id string `graphql:"id"` Name string Friends []Hero `graphql:"friends"` } var heroFields graphql.Fields heroType := graphql.NewObject(graphql.ObjectConfig{ Name: "Hero", Fields: graphql.FieldsThunk(func() graphql.Fields { return heroFields }), }) heroFields = graphql.Fields{ "id": &graphql.Field{ Type: graphql.ID, }, "name": &graphql.Field{ Type: nameType, Resolve: func(p graphql.ResolveParams) (interface{}, error) { hero := p.Source.(Hero) if hero.Name != "" { return hero.Name, nil } err := fmt.Errorf("Name for character with ID %v could not be fetched.", hero.Id) if formatErrorFn != nil { err = formatErrorFn(err) } if extensions != nil { return nil, &extendedError{ error: err, extensions: extensions, } } return nil, err }, }, "friends": &graphql.Field{ Type: graphql.NewList(heroType), }, } queryType := graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "hero": &graphql.Field{ Type: heroType, Resolve: func(params graphql.ResolveParams) (interface{}, error) { return Hero{ Name: "R2-D2", Friends: []Hero{ {Id: "1000", Name: "Luke Skywalker"}, {Id: "1002"}, {Id: "1003", Name: "Leia Organa"}, }, }, nil }, }, }, }) expectNoError := func(err error) { if err != nil { t.Fatalf("expected no error, got %v", err) } } schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: queryType, }) expectNoError(err) return graphql.Do(graphql.Params{ Schema: schema, RequestString: `{ hero { name heroFriends: friends { id name } } }`, }) } // http://facebook.github.io/graphql/June2018/#example-bc485 func TestQuery_ErrorPath(t *testing.T) { result := testErrors(t, graphql.String, nil, nil) assertJSON(t, `{ "errors": [ { "message": "Name for character with ID 1002 could not be fetched.", "locations": [ { "line": 6, "column": 7 } ], "path": [ "hero", "heroFriends", 1, "name" ] } ], "data": { "hero": { "name": "R2-D2", "heroFriends": [ { "id": "1000", "name": "Luke Skywalker" }, { "id": "1002", "name": null }, { "id": "1003", "name": "Leia Organa" } ] } } }`, result) } // http://facebook.github.io/graphql/June2018/#example-08b62 func TestQuery_ErrorPathForNonNullField(t *testing.T) { result := testErrors(t, graphql.NewNonNull(graphql.String), nil, nil) assertJSON(t, `{ "errors": [ { "message": "Name for character with ID 1002 could not be fetched.", "locations": [ { "line": 6, "column": 7 } ], "path": [ "hero", "heroFriends", 1, "name" ] } ], "data": { "hero": { "name": "R2-D2", "heroFriends": [ { "id": "1000", "name": "Luke Skywalker" }, null, { "id": "1003", "name": "Leia Organa" } ] } } }`, result) } // http://facebook.github.io/graphql/June2018/#example-fce18 func TestQuery_ErrorExtensions(t *testing.T) { result := testErrors(t, graphql.NewNonNull(graphql.String), map[string]interface{}{ "code": "CAN_NOT_FETCH_BY_ID", "timestamp": "Fri Feb 9 14:33:09 UTC 2018", }, nil) assertJSON(t, `{ "errors": [ { "message": "Name for character with ID 1002 could not be fetched.", "locations": [ { "line": 6, "column": 7 } ], "path": [ "hero", "heroFriends", 1, "name" ], "extensions": { "code": "CAN_NOT_FETCH_BY_ID", "timestamp": "Fri Feb 9 14:33:09 UTC 2018" }} ], "data": { "hero": { "name": "R2-D2", "heroFriends": [ { "id": "1000", "name": "Luke Skywalker" }, null, { "id": "1003", "name": "Leia Organa" } ] } } }`, result) } func TestQuery_OriginalErrorBuiltin(t *testing.T) { result := testErrors(t, graphql.String, nil, nil) switch err := result.Errors[0].OriginalError().(type) { case *gqlerrors.Error: switch err := err.OriginalError.(type) { case error: default: t.Fatalf("unexpected error: %v", reflect.TypeOf(err)) } default: t.Fatalf("unexpected error: %v", reflect.TypeOf(err)) } } func TestQuery_OriginalErrorExtended(t *testing.T) { result := testErrors(t, graphql.String, map[string]interface{}{ "code": "CAN_NOT_FETCH_BY_ID", }, nil) switch err := result.Errors[0].OriginalError().(type) { case *gqlerrors.Error: switch err := err.OriginalError.(type) { case *extendedError: case extendedError: default: t.Fatalf("unexpected error: %v", reflect.TypeOf(err)) } default: t.Fatalf("unexpected error: %v", reflect.TypeOf(err)) } } type customError struct { error } func (e customError) Error() string { return e.error.Error() } func TestQuery_OriginalErrorCustom(t *testing.T) { result := testErrors(t, graphql.String, nil, func(err error) error { return customError{error: err} }) switch err := result.Errors[0].OriginalError().(type) { case *gqlerrors.Error: switch err := err.OriginalError.(type) { case customError: default: t.Fatalf("unexpected error: %v", reflect.TypeOf(err)) } default: t.Fatalf("unexpected error: %v", reflect.TypeOf(err)) } } func TestQuery_OriginalErrorCustomPtr(t *testing.T) { result := testErrors(t, graphql.String, nil, func(err error) error { return &customError{error: err} }) switch err := result.Errors[0].OriginalError().(type) { case *gqlerrors.Error: switch err := err.OriginalError.(type) { case *customError: default: t.Fatalf("unexpected error: %v", reflect.TypeOf(err)) } default: t.Fatalf("unexpected error: %v", reflect.TypeOf(err)) } } func TestQuery_OriginalErrorPanic(t *testing.T) { result := testErrors(t, graphql.String, nil, func(err error) error { panic(errors.New("panic error")) }) switch err := result.Errors[0].OriginalError().(type) { case *gqlerrors.Error: switch err := err.OriginalError.(type) { case error: default: t.Fatalf("unexpected error: %v", reflect.TypeOf(err)) } default: t.Fatalf("unexpected error: %v", reflect.TypeOf(err)) } } ================================================ FILE: extensions.go ================================================ package graphql import ( "context" "fmt" "github.com/graphql-go/graphql/gqlerrors" ) type ( // ParseFinishFunc is called when the parse of the query is done ParseFinishFunc func(error) // parseFinishFuncHandler handles the call of all the ParseFinishFuncs from the extenisons parseFinishFuncHandler func(error) []gqlerrors.FormattedError // ValidationFinishFunc is called when the Validation of the query is finished ValidationFinishFunc func([]gqlerrors.FormattedError) // validationFinishFuncHandler responsible for the call of all the ValidationFinishFuncs validationFinishFuncHandler func([]gqlerrors.FormattedError) []gqlerrors.FormattedError // ExecutionFinishFunc is called when the execution is done ExecutionFinishFunc func(*Result) // executionFinishFuncHandler calls all the ExecutionFinishFuncs from each extension executionFinishFuncHandler func(*Result) []gqlerrors.FormattedError // ResolveFieldFinishFunc is called with the result of the ResolveFn and the error it returned ResolveFieldFinishFunc func(interface{}, error) // resolveFieldFinishFuncHandler calls the resolveFieldFinishFns for all the extensions resolveFieldFinishFuncHandler func(interface{}, error) []gqlerrors.FormattedError ) // Extension is an interface for extensions in graphql type Extension interface { // Init is used to help you initialize the extension Init(context.Context, *Params) context.Context // Name returns the name of the extension (make sure it's custom) Name() string // ParseDidStart is being called before starting the parse ParseDidStart(context.Context) (context.Context, ParseFinishFunc) // ValidationDidStart is called just before the validation begins ValidationDidStart(context.Context) (context.Context, ValidationFinishFunc) // ExecutionDidStart notifies about the start of the execution ExecutionDidStart(context.Context) (context.Context, ExecutionFinishFunc) // ResolveFieldDidStart notifies about the start of the resolving of a field ResolveFieldDidStart(context.Context, *ResolveInfo) (context.Context, ResolveFieldFinishFunc) // HasResult returns if the extension wants to add data to the result HasResult() bool // GetResult returns the data that the extension wants to add to the result GetResult(context.Context) interface{} } // handleExtensionsInits handles all the init functions for all the extensions in the schema func handleExtensionsInits(p *Params) gqlerrors.FormattedErrors { errs := gqlerrors.FormattedErrors{} for _, ext := range p.Schema.extensions { func() { // catch panic from an extension init fn defer func() { if r := recover(); r != nil { errs = append(errs, gqlerrors.FormatError(fmt.Errorf("%s.Init: %v", ext.Name(), r.(error)))) } }() // update context p.Context = ext.Init(p.Context, p) }() } return errs } // handleExtensionsParseDidStart runs the ParseDidStart functions for each extension func handleExtensionsParseDidStart(p *Params) ([]gqlerrors.FormattedError, parseFinishFuncHandler) { fs := map[string]ParseFinishFunc{} errs := gqlerrors.FormattedErrors{} for _, ext := range p.Schema.extensions { var ( ctx context.Context finishFn ParseFinishFunc ) // catch panic from an extension's parseDidStart functions func() { defer func() { if r := recover(); r != nil { errs = append(errs, gqlerrors.FormatError(fmt.Errorf("%s.ParseDidStart: %v", ext.Name(), r.(error)))) } }() ctx, finishFn = ext.ParseDidStart(p.Context) // update context p.Context = ctx fs[ext.Name()] = finishFn }() } return errs, func(err error) []gqlerrors.FormattedError { errs := gqlerrors.FormattedErrors{} for name, fn := range fs { func() { // catch panic from a finishFn defer func() { if r := recover(); r != nil { errs = append(errs, gqlerrors.FormatError(fmt.Errorf("%s.ParseFinishFunc: %v", name, r.(error)))) } }() fn(err) }() } return errs } } // handleExtensionsValidationDidStart notifies the extensions about the start of the validation process func handleExtensionsValidationDidStart(p *Params) ([]gqlerrors.FormattedError, validationFinishFuncHandler) { fs := map[string]ValidationFinishFunc{} errs := gqlerrors.FormattedErrors{} for _, ext := range p.Schema.extensions { var ( ctx context.Context finishFn ValidationFinishFunc ) // catch panic from an extension's validationDidStart function func() { defer func() { if r := recover(); r != nil { errs = append(errs, gqlerrors.FormatError(fmt.Errorf("%s.ValidationDidStart: %v", ext.Name(), r.(error)))) } }() ctx, finishFn = ext.ValidationDidStart(p.Context) // update context p.Context = ctx fs[ext.Name()] = finishFn }() } return errs, func(errs []gqlerrors.FormattedError) []gqlerrors.FormattedError { extErrs := gqlerrors.FormattedErrors{} for name, finishFn := range fs { func() { // catch panic from a finishFn defer func() { if r := recover(); r != nil { extErrs = append(extErrs, gqlerrors.FormatError(fmt.Errorf("%s.ValidationFinishFunc: %v", name, r.(error)))) } }() finishFn(errs) }() } return extErrs } } // handleExecutionDidStart handles the ExecutionDidStart functions func handleExtensionsExecutionDidStart(p *ExecuteParams) ([]gqlerrors.FormattedError, executionFinishFuncHandler) { fs := map[string]ExecutionFinishFunc{} errs := gqlerrors.FormattedErrors{} for _, ext := range p.Schema.extensions { var ( ctx context.Context finishFn ExecutionFinishFunc ) // catch panic from an extension's executionDidStart function func() { defer func() { if r := recover(); r != nil { errs = append(errs, gqlerrors.FormatError(fmt.Errorf("%s.ExecutionDidStart: %v", ext.Name(), r.(error)))) } }() ctx, finishFn = ext.ExecutionDidStart(p.Context) // update context p.Context = ctx fs[ext.Name()] = finishFn }() } return errs, func(result *Result) []gqlerrors.FormattedError { extErrs := gqlerrors.FormattedErrors{} for name, finishFn := range fs { func() { // catch panic from a finishFn defer func() { if r := recover(); r != nil { extErrs = append(extErrs, gqlerrors.FormatError(fmt.Errorf("%s.ExecutionFinishFunc: %v", name, r.(error)))) } }() finishFn(result) }() } return extErrs } } // handleResolveFieldDidStart handles the notification of the extensions about the start of a resolve function func handleExtensionsResolveFieldDidStart(exts []Extension, p *executionContext, i *ResolveInfo) ([]gqlerrors.FormattedError, resolveFieldFinishFuncHandler) { fs := map[string]ResolveFieldFinishFunc{} errs := gqlerrors.FormattedErrors{} for _, ext := range p.Schema.extensions { var ( ctx context.Context finishFn ResolveFieldFinishFunc ) // catch panic from an extension's resolveFieldDidStart function func() { defer func() { if r := recover(); r != nil { errs = append(errs, gqlerrors.FormatError(fmt.Errorf("%s.ResolveFieldDidStart: %v", ext.Name(), r.(error)))) } }() ctx, finishFn = ext.ResolveFieldDidStart(p.Context, i) // update context p.Context = ctx fs[ext.Name()] = finishFn }() } return errs, func(val interface{}, err error) []gqlerrors.FormattedError { extErrs := gqlerrors.FormattedErrors{} for name, finishFn := range fs { func() { // catch panic from a finishFn defer func() { if r := recover(); r != nil { extErrs = append(extErrs, gqlerrors.FormatError(fmt.Errorf("%s.ResolveFieldFinishFunc: %v", name, r.(error)))) } }() finishFn(val, err) }() } return extErrs } } func addExtensionResults(p *ExecuteParams, result *Result) { if len(p.Schema.extensions) != 0 { for _, ext := range p.Schema.extensions { func() { defer func() { if r := recover(); r != nil { result.Errors = append(result.Errors, gqlerrors.FormatError(fmt.Errorf("%s.GetResult: %v", ext.Name(), r.(error)))) } }() if ext.HasResult() { if result.Extensions == nil { result.Extensions = make(map[string]interface{}) } result.Extensions[ext.Name()] = ext.GetResult(p.Context) } }() } } } ================================================ FILE: extensions_test.go ================================================ package graphql_test import ( "context" "errors" "fmt" "reflect" "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func tinit(t *testing.T) graphql.Schema { schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Type", Fields: graphql.Fields{ "a": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return "foo", nil }, }, "erred": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return "", errors.New("ooops") }, }, }, }), }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } return schema } func TestExtensionInitPanic(t *testing.T) { ext := newtestExt("testExt") ext.initFn = func(ctx context.Context, p *graphql.Params) context.Context { if true { panic(errors.New("test error")) } return ctx } schema := tinit(t) query := `query Example { a }` schema.AddExtensions(ext) result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ gqlerrors.FormatError(fmt.Errorf("%s.Init: %v", ext.Name(), errors.New("test error"))), }, } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestExtensionParseDidStartPanic(t *testing.T) { ext := newtestExt("testExt") ext.parseDidStartFn = func(ctx context.Context) (context.Context, graphql.ParseFinishFunc) { if true { panic(errors.New("test error")) } return ctx, func(err error) { } } schema := tinit(t) query := `query Example { a }` schema.AddExtensions(ext) result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ gqlerrors.FormatError(fmt.Errorf("%s.ParseDidStart: %v", ext.Name(), errors.New("test error"))), }, } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestExtensionParseFinishFuncPanic(t *testing.T) { ext := newtestExt("testExt") ext.parseDidStartFn = func(ctx context.Context) (context.Context, graphql.ParseFinishFunc) { return ctx, func(err error) { panic(errors.New("test error")) } } schema := tinit(t) query := `query Example { a }` schema.AddExtensions(ext) result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ gqlerrors.FormatError(fmt.Errorf("%s.ParseFinishFunc: %v", ext.Name(), errors.New("test error"))), }, } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestExtensionValidationDidStartPanic(t *testing.T) { ext := newtestExt("testExt") ext.validationDidStartFn = func(ctx context.Context) (context.Context, graphql.ValidationFinishFunc) { if true { panic(errors.New("test error")) } return ctx, func([]gqlerrors.FormattedError) { } } schema := tinit(t) query := `query Example { a }` schema.AddExtensions(ext) result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ gqlerrors.FormatError(fmt.Errorf("%s.ValidationDidStart: %v", ext.Name(), errors.New("test error"))), }, } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestExtensionValidationFinishFuncPanic(t *testing.T) { ext := newtestExt("testExt") ext.validationDidStartFn = func(ctx context.Context) (context.Context, graphql.ValidationFinishFunc) { return ctx, func([]gqlerrors.FormattedError) { panic(errors.New("test error")) } } schema := tinit(t) query := `query Example { a }` schema.AddExtensions(ext) result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ gqlerrors.FormatError(fmt.Errorf("%s.ValidationFinishFunc: %v", ext.Name(), errors.New("test error"))), }, } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestExtensionExecutionDidStartPanic(t *testing.T) { ext := newtestExt("testExt") ext.executionDidStartFn = func(ctx context.Context) (context.Context, graphql.ExecutionFinishFunc) { if true { panic(errors.New("test error")) } return ctx, func(r *graphql.Result) { } } schema := tinit(t) query := `query Example { a }` schema.AddExtensions(ext) result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ gqlerrors.FormatError(fmt.Errorf("%s.ExecutionDidStart: %v", ext.Name(), errors.New("test error"))), }, } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestExtensionExecutionFinishFuncPanic(t *testing.T) { ext := newtestExt("testExt") ext.executionDidStartFn = func(ctx context.Context) (context.Context, graphql.ExecutionFinishFunc) { return ctx, func(r *graphql.Result) { panic(errors.New("test error")) } } schema := tinit(t) query := `query Example { a }` schema.AddExtensions(ext) result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) expected := &graphql.Result{ Data: map[string]interface{}{ "a": "foo", }, Errors: []gqlerrors.FormattedError{ gqlerrors.FormatError(fmt.Errorf("%s.ExecutionFinishFunc: %v", ext.Name(), errors.New("test error"))), }, } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestExtensionResolveFieldDidStartPanic(t *testing.T) { ext := newtestExt("testExt") ext.resolveFieldDidStartFn = func(ctx context.Context, i *graphql.ResolveInfo) (context.Context, graphql.ResolveFieldFinishFunc) { if true { panic(errors.New("test error")) } return ctx, func(v interface{}, err error) { } } schema := tinit(t) query := `query Example { a }` schema.AddExtensions(ext) result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) expected := &graphql.Result{ Data: map[string]interface{}{ "a": "foo", }, Errors: []gqlerrors.FormattedError{ gqlerrors.FormatError(fmt.Errorf("%s.ResolveFieldDidStart: %v", ext.Name(), errors.New("test error"))), }, } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestExtensionResolveFieldFinishFuncPanic(t *testing.T) { ext := newtestExt("testExt") ext.resolveFieldDidStartFn = func(ctx context.Context, i *graphql.ResolveInfo) (context.Context, graphql.ResolveFieldFinishFunc) { return ctx, func(v interface{}, err error) { panic(errors.New("test error")) } } schema := tinit(t) query := `query Example { a }` schema.AddExtensions(ext) result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) expected := &graphql.Result{ Data: map[string]interface{}{ "a": "foo", }, Errors: []gqlerrors.FormattedError{ gqlerrors.FormatError(fmt.Errorf("%s.ResolveFieldFinishFunc: %v", ext.Name(), errors.New("test error"))), }, } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestExtensionResolveFieldFinishFuncAfterError(t *testing.T) { var fnErrs int ext := newtestExt("testExt") ext.resolveFieldDidStartFn = func(ctx context.Context, i *graphql.ResolveInfo) (context.Context, graphql.ResolveFieldFinishFunc) { return ctx, func(v interface{}, err error) { if err != nil { fnErrs++ } } } schema := tinit(t) query := `query Example { erred }` schema.AddExtensions(ext) result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) if resErrs := len(result.Errors); resErrs != 1 { t.Errorf("Incorrect number of returned result errors: %d", resErrs) } if fnErrs != 1 { t.Errorf("Incorrect number of errors captured: %d", fnErrs) } } func TestExtensionGetResultPanic(t *testing.T) { ext := newtestExt("testExt") ext.getResultFn = func(context.Context) interface{} { if true { panic(errors.New("test error")) } return nil } ext.hasResultFn = func() bool { return true } schema := tinit(t) query := `query Example { a }` schema.AddExtensions(ext) result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) expected := &graphql.Result{ Data: map[string]interface{}{ "a": "foo", }, Errors: []gqlerrors.FormattedError{ gqlerrors.FormatError(fmt.Errorf("%s.GetResult: %v", ext.Name(), errors.New("test error"))), }, Extensions: make(map[string]interface{}), } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func newtestExt(name string) *testExt { ext := &testExt{ name: name, } if ext.initFn == nil { ext.initFn = func(ctx context.Context, p *graphql.Params) context.Context { return ctx } } if ext.parseDidStartFn == nil { ext.parseDidStartFn = func(ctx context.Context) (context.Context, graphql.ParseFinishFunc) { return ctx, func(err error) { } } } if ext.validationDidStartFn == nil { ext.validationDidStartFn = func(ctx context.Context) (context.Context, graphql.ValidationFinishFunc) { return ctx, func([]gqlerrors.FormattedError) { } } } if ext.executionDidStartFn == nil { ext.executionDidStartFn = func(ctx context.Context) (context.Context, graphql.ExecutionFinishFunc) { return ctx, func(r *graphql.Result) { } } } if ext.resolveFieldDidStartFn == nil { ext.resolveFieldDidStartFn = func(ctx context.Context, i *graphql.ResolveInfo) (context.Context, graphql.ResolveFieldFinishFunc) { return ctx, func(v interface{}, err error) { } } } if ext.hasResultFn == nil { ext.hasResultFn = func() bool { return false } } if ext.getResultFn == nil { ext.getResultFn = func(context.Context) interface{} { return nil } } return ext } type testExt struct { name string initFn func(ctx context.Context, p *graphql.Params) context.Context hasResultFn func() bool getResultFn func(context.Context) interface{} parseDidStartFn func(ctx context.Context) (context.Context, graphql.ParseFinishFunc) validationDidStartFn func(ctx context.Context) (context.Context, graphql.ValidationFinishFunc) executionDidStartFn func(ctx context.Context) (context.Context, graphql.ExecutionFinishFunc) resolveFieldDidStartFn func(ctx context.Context, i *graphql.ResolveInfo) (context.Context, graphql.ResolveFieldFinishFunc) } func (t *testExt) Init(ctx context.Context, p *graphql.Params) context.Context { return t.initFn(ctx, p) } func (t *testExt) Name() string { return t.name } func (t *testExt) HasResult() bool { return t.hasResultFn() } func (t *testExt) GetResult(ctx context.Context) interface{} { return t.getResultFn(ctx) } func (t *testExt) ParseDidStart(ctx context.Context) (context.Context, graphql.ParseFinishFunc) { return t.parseDidStartFn(ctx) } func (t *testExt) ValidationDidStart(ctx context.Context) (context.Context, graphql.ValidationFinishFunc) { return t.validationDidStartFn(ctx) } func (t *testExt) ExecutionDidStart(ctx context.Context) (context.Context, graphql.ExecutionFinishFunc) { return t.executionDidStartFn(ctx) } func (t *testExt) ResolveFieldDidStart(ctx context.Context, i *graphql.ResolveInfo) (context.Context, graphql.ResolveFieldFinishFunc) { return t.resolveFieldDidStartFn(ctx, i) } ================================================ FILE: go.mod ================================================ module github.com/graphql-go/graphql go 1.13 ================================================ FILE: gqlerrors/error.go ================================================ package gqlerrors import ( "fmt" "reflect" "github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/location" "github.com/graphql-go/graphql/language/source" ) type Error struct { Message string Stack string Nodes []ast.Node Source *source.Source Positions []int Locations []location.SourceLocation OriginalError error Path []interface{} } // implements Golang's built-in `error` interface func (g Error) Error() string { return fmt.Sprintf("%v", g.Message) } func NewError(message string, nodes []ast.Node, stack string, source *source.Source, positions []int, origError error) *Error { return newError(message, nodes, stack, source, positions, nil, origError) } func NewErrorWithPath(message string, nodes []ast.Node, stack string, source *source.Source, positions []int, path []interface{}, origError error) *Error { return newError(message, nodes, stack, source, positions, path, origError) } func newError(message string, nodes []ast.Node, stack string, source *source.Source, positions []int, path []interface{}, origError error) *Error { if stack == "" && message != "" { stack = message } if source == nil { for _, node := range nodes { // get source from first node if node == nil || reflect.ValueOf(node).IsNil() { continue } if node.GetLoc() != nil { source = node.GetLoc().Source } break } } if len(positions) == 0 && len(nodes) > 0 { for _, node := range nodes { if node == nil || reflect.ValueOf(node).IsNil() { continue } if node.GetLoc() == nil { continue } positions = append(positions, node.GetLoc().Start) } } locations := []location.SourceLocation{} for _, pos := range positions { loc := location.GetLocation(source, pos) locations = append(locations, loc) } return &Error{ Message: message, Stack: stack, Nodes: nodes, Source: source, Positions: positions, Locations: locations, OriginalError: origError, Path: path, } } ================================================ FILE: gqlerrors/formatted.go ================================================ package gqlerrors import ( "errors" "github.com/graphql-go/graphql/language/location" ) type ExtendedError interface { error Extensions() map[string]interface{} } type FormattedError struct { Message string `json:"message"` Locations []location.SourceLocation `json:"locations"` Path []interface{} `json:"path,omitempty"` Extensions map[string]interface{} `json:"extensions,omitempty"` originalError error } func (g FormattedError) OriginalError() error { return g.originalError } func (g FormattedError) Error() string { return g.Message } func NewFormattedError(message string) FormattedError { err := errors.New(message) return FormatError(err) } func FormatError(err error) FormattedError { switch err := err.(type) { case FormattedError: return err case *Error: ret := FormattedError{ Message: err.Error(), Locations: err.Locations, Path: err.Path, originalError: err, } if err := err.OriginalError; err != nil { if extended, ok := err.(ExtendedError); ok { ret.Extensions = extended.Extensions() } } return ret case Error: return FormatError(&err) default: return FormattedError{ Message: err.Error(), Locations: []location.SourceLocation{}, originalError: err, } } } func FormatErrors(errs ...error) []FormattedError { formattedErrors := []FormattedError{} for _, err := range errs { formattedErrors = append(formattedErrors, FormatError(err)) } return formattedErrors } ================================================ FILE: gqlerrors/located.go ================================================ package gqlerrors import ( "errors" "github.com/graphql-go/graphql/language/ast" ) // NewLocatedError creates a graphql.Error with location info // @deprecated 0.4.18 // Already exists in `graphql.NewLocatedError()` func NewLocatedError(err interface{}, nodes []ast.Node) *Error { var origError error message := "An unknown error occurred." if err, ok := err.(error); ok { message = err.Error() origError = err } if err, ok := err.(string); ok { message = err origError = errors.New(err) } stack := message return NewError( message, nodes, stack, nil, []int{}, origError, ) } func FieldASTsToNodeASTs(fieldASTs []*ast.Field) []ast.Node { nodes := []ast.Node{} for _, fieldAST := range fieldASTs { nodes = append(nodes, fieldAST) } return nodes } ================================================ FILE: gqlerrors/sortutil.go ================================================ package gqlerrors import "bytes" type FormattedErrors []FormattedError func (errs FormattedErrors) Len() int { return len(errs) } func (errs FormattedErrors) Swap(i, j int) { errs[i], errs[j] = errs[j], errs[i] } func (errs FormattedErrors) Less(i, j int) bool { mCompare := bytes.Compare([]byte(errs[i].Message), []byte(errs[j].Message)) lesserLine := errs[i].Locations[0].Line < errs[j].Locations[0].Line eqLine := errs[i].Locations[0].Line == errs[j].Locations[0].Line lesserColumn := errs[i].Locations[0].Column < errs[j].Locations[0].Column if mCompare < 0 { return true } if mCompare == 0 && lesserLine { return true } if mCompare == 0 && eqLine && lesserColumn { return true } return false } ================================================ FILE: gqlerrors/syntax.go ================================================ package gqlerrors import ( "fmt" "regexp" "strings" "github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/location" "github.com/graphql-go/graphql/language/source" ) func NewSyntaxError(s *source.Source, position int, description string) *Error { l := location.GetLocation(s, position) return NewError( fmt.Sprintf("Syntax Error %s (%d:%d) %s\n\n%s", s.Name, l.Line, l.Column, description, highlightSourceAtLocation(s, l)), []ast.Node{}, "", s, []int{position}, nil, ) } // printCharCode here is slightly different from lexer.printCharCode() func printCharCode(code rune) string { // print as ASCII for printable range if code >= 0x0020 { return fmt.Sprintf(`%c`, code) } // Otherwise print the escaped form. e.g. `"\\u0007"` return fmt.Sprintf(`\u%04X`, code) } func printLine(str string) string { strSlice := []string{} for _, runeValue := range str { strSlice = append(strSlice, printCharCode(runeValue)) } return fmt.Sprintf(`%s`, strings.Join(strSlice, "")) } func highlightSourceAtLocation(s *source.Source, l location.SourceLocation) string { line := l.Line prevLineNum := fmt.Sprintf("%d", (line - 1)) lineNum := fmt.Sprintf("%d", line) nextLineNum := fmt.Sprintf("%d", (line + 1)) padLen := len(nextLineNum) lines := regexp.MustCompile("\r\n|[\n\r]").Split(string(s.Body), -1) var highlight string if line >= 2 { highlight += fmt.Sprintf("%s: %s\n", lpad(padLen, prevLineNum), printLine(lines[line-2])) } highlight += fmt.Sprintf("%s: %s\n", lpad(padLen, lineNum), printLine(lines[line-1])) for i := 1; i < (2 + padLen + l.Column); i++ { highlight += " " } highlight += "^\n" if line < len(lines) { highlight += fmt.Sprintf("%s: %s\n", lpad(padLen, nextLineNum), printLine(lines[line])) } return highlight } func lpad(l int, s string) string { var r string for i := 1; i < (l - len(s) + 1); i++ { r += " " } return r + s } ================================================ FILE: graphql.go ================================================ package graphql import ( "context" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/parser" "github.com/graphql-go/graphql/language/source" ) type Params struct { // The GraphQL type system to use when validating and executing a query. Schema Schema // A GraphQL language formatted string representing the requested operation. RequestString string // The value provided as the first argument to resolver functions on the top // level type (e.g. the query object type). RootObject map[string]interface{} // A mapping of variable name to runtime value to use for all variables // defined in the requestString. VariableValues map[string]interface{} // The name of the operation to use if requestString contains multiple // possible operations. Can be omitted if requestString contains only // one operation. OperationName string // Context may be provided to pass application-specific per-request // information to resolve functions. Context context.Context } func Do(p Params) *Result { source := source.NewSource(&source.Source{ Body: []byte(p.RequestString), Name: "GraphQL request", }) // run init on the extensions extErrs := handleExtensionsInits(&p) if len(extErrs) != 0 { return &Result{ Errors: extErrs, } } extErrs, parseFinishFn := handleExtensionsParseDidStart(&p) if len(extErrs) != 0 { return &Result{ Errors: extErrs, } } // parse the source AST, err := parser.Parse(parser.ParseParams{Source: source}) if err != nil { // run parseFinishFuncs for extensions extErrs = parseFinishFn(err) // merge the errors from extensions and the original error from parser extErrs = append(extErrs, gqlerrors.FormatErrors(err)...) return &Result{ Errors: extErrs, } } // run parseFinish functions for extensions extErrs = parseFinishFn(err) if len(extErrs) != 0 { return &Result{ Errors: extErrs, } } // notify extensions about the start of the validation extErrs, validationFinishFn := handleExtensionsValidationDidStart(&p) if len(extErrs) != 0 { return &Result{ Errors: extErrs, } } // validate document validationResult := ValidateDocument(&p.Schema, AST, nil) if !validationResult.IsValid { // run validation finish functions for extensions extErrs = validationFinishFn(validationResult.Errors) // merge the errors from extensions and the original error from parser extErrs = append(extErrs, validationResult.Errors...) return &Result{ Errors: extErrs, } } // run the validationFinishFuncs for extensions extErrs = validationFinishFn(validationResult.Errors) if len(extErrs) != 0 { return &Result{ Errors: extErrs, } } return Execute(ExecuteParams{ Schema: p.Schema, Root: p.RootObject, AST: AST, OperationName: p.OperationName, Args: p.VariableValues, Context: p.Context, }) } ================================================ FILE: graphql_bench_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/benchutil" ) type B struct { Query string Schema graphql.Schema } func benchGraphql(bench B, p graphql.Params, t testing.TB) { result := graphql.Do(p) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } } // Benchmark a reasonably large list of small items. func BenchmarkListQuery_1(b *testing.B) { nItemsListQueryBenchmark(1)(b) } func BenchmarkListQuery_100(b *testing.B) { nItemsListQueryBenchmark(100)(b) } func BenchmarkListQuery_1K(b *testing.B) { nItemsListQueryBenchmark(1000)(b) } func BenchmarkListQuery_10K(b *testing.B) { nItemsListQueryBenchmark(10 * 1000)(b) } func BenchmarkListQuery_100K(b *testing.B) { nItemsListQueryBenchmark(100 * 1000)(b) } func nItemsListQueryBenchmark(x int) func(b *testing.B) { return func(b *testing.B) { schema := benchutil.ListSchemaWithXItems(x) bench := B{ Query: ` query { colors { hex r g b } } `, Schema: schema, } for i := 0; i < b.N; i++ { params := graphql.Params{ Schema: schema, RequestString: bench.Query, } benchGraphql(bench, params, b) } } } func BenchmarkWideQuery_1_1(b *testing.B) { nFieldsyItemsQueryBenchmark(1, 1)(b) } func BenchmarkWideQuery_10_1(b *testing.B) { nFieldsyItemsQueryBenchmark(10, 1)(b) } func BenchmarkWideQuery_100_1(b *testing.B) { nFieldsyItemsQueryBenchmark(100, 1)(b) } func BenchmarkWideQuery_1K_1(b *testing.B) { nFieldsyItemsQueryBenchmark(1000, 1)(b) } func BenchmarkWideQuery_1_10(b *testing.B) { nFieldsyItemsQueryBenchmark(1, 10)(b) } func BenchmarkWideQuery_10_10(b *testing.B) { nFieldsyItemsQueryBenchmark(10, 10)(b) } func BenchmarkWideQuery_100_10(b *testing.B) { nFieldsyItemsQueryBenchmark(100, 10)(b) } func BenchmarkWideQuery_1K_10(b *testing.B) { nFieldsyItemsQueryBenchmark(1000, 10)(b) } func nFieldsyItemsQueryBenchmark(x int, y int) func(b *testing.B) { return func(b *testing.B) { schema := benchutil.WideSchemaWithXFieldsAndYItems(x, y) query := benchutil.WideSchemaQuery(x) bench := B{ Query: query, Schema: schema, } b.ResetTimer() for i := 0; i < b.N; i++ { params := graphql.Params{ Schema: schema, RequestString: bench.Query, } benchGraphql(bench, params, b) } } } ================================================ FILE: graphql_test.go ================================================ package graphql_test import ( "context" "reflect" "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/testutil" ) type T struct { Query string Schema graphql.Schema Expected interface{} Variables map[string]interface{} } var Tests = []T{} func init() { Tests = []T{ { Query: ` query HeroNameQuery { hero { name } } `, Schema: testutil.StarWarsSchema, Expected: &graphql.Result{ Data: map[string]interface{}{ "hero": map[string]interface{}{ "name": "R2-D2", }, }, }, }, { Query: ` query HeroNameAndFriendsQuery { hero { id name friends { name } } } `, Schema: testutil.StarWarsSchema, Expected: &graphql.Result{ Data: map[string]interface{}{ "hero": map[string]interface{}{ "id": "2001", "name": "R2-D2", "friends": []interface{}{ map[string]interface{}{ "name": "Luke Skywalker", }, map[string]interface{}{ "name": "Han Solo", }, map[string]interface{}{ "name": "Leia Organa", }, }, }, }, }, }, { Query: ` query HumanByIdQuery($id: String!) { human(id: $id) { name } } `, Schema: testutil.StarWarsSchema, Expected: &graphql.Result{ Data: map[string]interface{}{ "human": map[string]interface{}{ "name": "Darth Vader", }, }, }, Variables: map[string]interface{}{ "id": "1001", }, }, } } func TestQuery(t *testing.T) { for _, test := range Tests { params := graphql.Params{ Schema: test.Schema, RequestString: test.Query, VariableValues: test.Variables, } testGraphql(test, params, t) } } func testGraphql(test T, p graphql.Params, t *testing.T) { result := graphql.Do(p) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(result, test.Expected) { t.Fatalf("wrong result, query: %v, graphql result diff: %v", test.Query, testutil.Diff(test.Expected, result)) } } func TestBasicGraphQLExample(t *testing.T) { // taken from `graphql-js` README helloFieldResolved := func(p graphql.ResolveParams) (interface{}, error) { return "world", nil } schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "RootQueryType", Fields: graphql.Fields{ "hello": &graphql.Field{ Description: "Returns `world`", Type: graphql.String, Resolve: helloFieldResolved, }, }, }), }) if err != nil { t.Fatalf("wrong result, unexpected errors: %v", err.Error()) } query := "{ hello }" var expected interface{} expected = map[string]interface{}{ "hello": "world", } result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(result.Data, expected) { t.Fatalf("wrong result, query: %v, graphql result diff: %v", query, testutil.Diff(expected, result)) } } func TestThreadsContextFromParamsThrough(t *testing.T) { extractFieldFromContextFn := func(p graphql.ResolveParams) (interface{}, error) { return p.Context.Value(p.Args["key"]), nil } schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "value": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "key": &graphql.ArgumentConfig{Type: graphql.String}, }, Resolve: extractFieldFromContextFn, }, }, }), }) if err != nil { t.Fatalf("wrong result, unexpected errors: %v", err.Error()) } query := `{ value(key:"a") }` result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, Context: context.WithValue(context.TODO(), "a", "xyz"), }) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } expected := map[string]interface{}{"value": "xyz"} if !reflect.DeepEqual(result.Data, expected) { t.Fatalf("wrong result, query: %v, graphql result diff: %v", query, testutil.Diff(expected, result)) } } func TestNewErrorChecksNilNodes(t *testing.T) { schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "graphql_is": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return "", nil }, }, }, }), }) if err != nil { t.Fatalf("unexpected errors: %v", err.Error()) } query := `{graphql_is:great(sort:ByPopularity)}{stars}` result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) if len(result.Errors) == 0 { t.Fatalf("expected errors, got: %v", result) } } func TestEmptyStringIsNotNull(t *testing.T) { checkForEmptyString := func(p graphql.ResolveParams) (interface{}, error) { arg := p.Args["arg"] if arg == nil || arg.(string) != "" { t.Errorf("Expected empty string for input arg, got %#v", arg) } return "yay", nil } returnEmptyString := func(p graphql.ResolveParams) (interface{}, error) { return "", nil } schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "checkEmptyArg": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "arg": &graphql.ArgumentConfig{Type: graphql.String}, }, Resolve: checkForEmptyString, }, "checkEmptyResult": &graphql.Field{ Type: graphql.String, Resolve: returnEmptyString, }, }, }), }) if err != nil { t.Fatalf("wrong result, unexpected errors: %v", err.Error()) } query := `{ checkEmptyArg(arg:"") checkEmptyResult }` result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } expected := map[string]interface{}{"checkEmptyArg": "yay", "checkEmptyResult": ""} if !reflect.DeepEqual(result.Data, expected) { t.Errorf("wrong result, query: %v, graphql result diff: %v", query, testutil.Diff(expected, result)) } } ================================================ FILE: introspection.go ================================================ package graphql import ( "fmt" "reflect" "sort" "github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/printer" ) const ( TypeKindScalar = "SCALAR" TypeKindObject = "OBJECT" TypeKindInterface = "INTERFACE" TypeKindUnion = "UNION" TypeKindEnum = "ENUM" TypeKindInputObject = "INPUT_OBJECT" TypeKindList = "LIST" TypeKindNonNull = "NON_NULL" ) // SchemaType is type definition for __Schema var SchemaType *Object // DirectiveType is type definition for __Directive var DirectiveType *Object // TypeType is type definition for __Type var TypeType *Object // FieldType is type definition for __Field var FieldType *Object // InputValueType is type definition for __InputValue var InputValueType *Object // EnumValueType is type definition for __EnumValue var EnumValueType *Object // TypeKindEnumType is type definition for __TypeKind var TypeKindEnumType *Enum // DirectiveLocationEnumType is type definition for __DirectiveLocation var DirectiveLocationEnumType *Enum // Meta-field definitions. // SchemaMetaFieldDef Meta field definition for Schema var SchemaMetaFieldDef *FieldDefinition // TypeMetaFieldDef Meta field definition for types var TypeMetaFieldDef *FieldDefinition // TypeNameMetaFieldDef Meta field definition for type names var TypeNameMetaFieldDef *FieldDefinition func init() { TypeKindEnumType = NewEnum(EnumConfig{ Name: "__TypeKind", Description: "An enum describing what kind of type a given `__Type` is.", Values: EnumValueConfigMap{ "SCALAR": &EnumValueConfig{ Value: TypeKindScalar, Description: "Indicates this type is a scalar.", }, "OBJECT": &EnumValueConfig{ Value: TypeKindObject, Description: "Indicates this type is an object. " + "`fields` and `interfaces` are valid fields.", }, "INTERFACE": &EnumValueConfig{ Value: TypeKindInterface, Description: "Indicates this type is an interface. " + "`fields` and `possibleTypes` are valid fields.", }, "UNION": &EnumValueConfig{ Value: TypeKindUnion, Description: "Indicates this type is a union. " + "`possibleTypes` is a valid field.", }, "ENUM": &EnumValueConfig{ Value: TypeKindEnum, Description: "Indicates this type is an enum. " + "`enumValues` is a valid field.", }, "INPUT_OBJECT": &EnumValueConfig{ Value: TypeKindInputObject, Description: "Indicates this type is an input object. " + "`inputFields` is a valid field.", }, "LIST": &EnumValueConfig{ Value: TypeKindList, Description: "Indicates this type is a list. " + "`ofType` is a valid field.", }, "NON_NULL": &EnumValueConfig{ Value: TypeKindNonNull, Description: "Indicates this type is a non-null. " + "`ofType` is a valid field.", }, }, }) DirectiveLocationEnumType = NewEnum(EnumConfig{ Name: "__DirectiveLocation", Description: "A Directive can be adjacent to many parts of the GraphQL language, a " + "__DirectiveLocation describes one such possible adjacencies.", Values: EnumValueConfigMap{ "QUERY": &EnumValueConfig{ Value: DirectiveLocationQuery, Description: "Location adjacent to a query operation.", }, "MUTATION": &EnumValueConfig{ Value: DirectiveLocationMutation, Description: "Location adjacent to a mutation operation.", }, "SUBSCRIPTION": &EnumValueConfig{ Value: DirectiveLocationSubscription, Description: "Location adjacent to a subscription operation.", }, "FIELD": &EnumValueConfig{ Value: DirectiveLocationField, Description: "Location adjacent to a field.", }, "FRAGMENT_DEFINITION": &EnumValueConfig{ Value: DirectiveLocationFragmentDefinition, Description: "Location adjacent to a fragment definition.", }, "FRAGMENT_SPREAD": &EnumValueConfig{ Value: DirectiveLocationFragmentSpread, Description: "Location adjacent to a fragment spread.", }, "INLINE_FRAGMENT": &EnumValueConfig{ Value: DirectiveLocationInlineFragment, Description: "Location adjacent to an inline fragment.", }, "SCHEMA": &EnumValueConfig{ Value: DirectiveLocationSchema, Description: "Location adjacent to a schema definition.", }, "SCALAR": &EnumValueConfig{ Value: DirectiveLocationScalar, Description: "Location adjacent to a scalar definition.", }, "OBJECT": &EnumValueConfig{ Value: DirectiveLocationObject, Description: "Location adjacent to an object type definition.", }, "FIELD_DEFINITION": &EnumValueConfig{ Value: DirectiveLocationFieldDefinition, Description: "Location adjacent to a field definition.", }, "ARGUMENT_DEFINITION": &EnumValueConfig{ Value: DirectiveLocationArgumentDefinition, Description: "Location adjacent to an argument definition.", }, "INTERFACE": &EnumValueConfig{ Value: DirectiveLocationInterface, Description: "Location adjacent to an interface definition.", }, "UNION": &EnumValueConfig{ Value: DirectiveLocationUnion, Description: "Location adjacent to a union definition.", }, "ENUM": &EnumValueConfig{ Value: DirectiveLocationEnum, Description: "Location adjacent to an enum definition.", }, "ENUM_VALUE": &EnumValueConfig{ Value: DirectiveLocationEnumValue, Description: "Location adjacent to an enum value definition.", }, "INPUT_OBJECT": &EnumValueConfig{ Value: DirectiveLocationInputObject, Description: "Location adjacent to an input object type definition.", }, "INPUT_FIELD_DEFINITION": &EnumValueConfig{ Value: DirectiveLocationInputFieldDefinition, Description: "Location adjacent to an input object field definition.", }, }, }) // Note: some fields (for e.g "fields", "interfaces") are defined later due to cyclic reference TypeType = NewObject(ObjectConfig{ Name: "__Type", Description: "The fundamental unit of any GraphQL Schema is the type. There are " + "many kinds of types in GraphQL as represented by the `__TypeKind` enum." + "\n\nDepending 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.", Fields: Fields{ "kind": &Field{ Type: NewNonNull(TypeKindEnumType), Resolve: func(p ResolveParams) (interface{}, error) { switch p.Source.(type) { case *Scalar: return TypeKindScalar, nil case *Object: return TypeKindObject, nil case *Interface: return TypeKindInterface, nil case *Union: return TypeKindUnion, nil case *Enum: return TypeKindEnum, nil case *InputObject: return TypeKindInputObject, nil case *List: return TypeKindList, nil case *NonNull: return TypeKindNonNull, nil } return nil, fmt.Errorf("Unknown kind of type: %v", p.Source) }, }, "name": &Field{ Type: String, }, "description": &Field{ Type: String, }, "fields": &Field{}, "interfaces": &Field{}, "possibleTypes": &Field{}, "enumValues": &Field{}, "inputFields": &Field{}, "ofType": &Field{}, }, }) InputValueType = NewObject(ObjectConfig{ Name: "__InputValue", Description: "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.", Fields: Fields{ "name": &Field{ Type: NewNonNull(String), }, "description": &Field{ Type: String, }, "type": &Field{ Type: NewNonNull(TypeType), }, "defaultValue": &Field{ Type: String, Description: "A GraphQL-formatted string representing the default value for this " + "input value.", Resolve: func(p ResolveParams) (interface{}, error) { if inputVal, ok := p.Source.(*Argument); ok { if inputVal.DefaultValue == nil { return nil, nil } if isNullish(inputVal.DefaultValue) { return nil, nil } astVal := astFromValue(inputVal.DefaultValue, inputVal) return printer.Print(astVal), nil } if inputVal, ok := p.Source.(*InputObjectField); ok { if inputVal.DefaultValue == nil { return nil, nil } astVal := astFromValue(inputVal.DefaultValue, inputVal) return printer.Print(astVal), nil } return nil, nil }, }, }, }) FieldType = NewObject(ObjectConfig{ Name: "__Field", Description: "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.", Fields: Fields{ "name": &Field{ Type: NewNonNull(String), }, "description": &Field{ Type: String, }, "args": &Field{ Type: NewNonNull(NewList(NewNonNull(InputValueType))), Resolve: func(p ResolveParams) (interface{}, error) { if field, ok := p.Source.(*FieldDefinition); ok { return field.Args, nil } return []interface{}{}, nil }, }, "type": &Field{ Type: NewNonNull(TypeType), }, "isDeprecated": &Field{ Type: NewNonNull(Boolean), Resolve: func(p ResolveParams) (interface{}, error) { if field, ok := p.Source.(*FieldDefinition); ok { return (field.DeprecationReason != ""), nil } return false, nil }, }, "deprecationReason": &Field{ Type: String, Resolve: func(p ResolveParams) (interface{}, error) { if field, ok := p.Source.(*FieldDefinition); ok { if field.DeprecationReason != "" { return field.DeprecationReason, nil } } return nil, nil }, }, }, }) DirectiveType = NewObject(ObjectConfig{ Name: "__Directive", Description: "A Directive provides a way to describe alternate runtime execution and " + "type validation behavior in a GraphQL document." + "\n\nIn 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.", Fields: Fields{ "name": &Field{ Type: NewNonNull(String), }, "description": &Field{ Type: String, }, "locations": &Field{ Type: NewNonNull(NewList( NewNonNull(DirectiveLocationEnumType), )), }, "args": &Field{ Type: NewNonNull(NewList( NewNonNull(InputValueType), )), }, // NOTE: the following three fields are deprecated and are no longer part // of the GraphQL specification. "onOperation": &Field{ DeprecationReason: "Use `locations`.", Type: NewNonNull(Boolean), Resolve: func(p ResolveParams) (interface{}, error) { if dir, ok := p.Source.(*Directive); ok { res := false for _, loc := range dir.Locations { if loc == DirectiveLocationQuery || loc == DirectiveLocationMutation || loc == DirectiveLocationSubscription { res = true break } } return res, nil } return false, nil }, }, "onFragment": &Field{ DeprecationReason: "Use `locations`.", Type: NewNonNull(Boolean), Resolve: func(p ResolveParams) (interface{}, error) { if dir, ok := p.Source.(*Directive); ok { res := false for _, loc := range dir.Locations { if loc == DirectiveLocationFragmentSpread || loc == DirectiveLocationInlineFragment || loc == DirectiveLocationFragmentDefinition { res = true break } } return res, nil } return false, nil }, }, "onField": &Field{ DeprecationReason: "Use `locations`.", Type: NewNonNull(Boolean), Resolve: func(p ResolveParams) (interface{}, error) { if dir, ok := p.Source.(*Directive); ok { res := false for _, loc := range dir.Locations { if loc == DirectiveLocationField { res = true break } } return res, nil } return false, nil }, }, }, }) SchemaType = NewObject(ObjectConfig{ Name: "__Schema", Description: `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.`, Fields: Fields{ "types": &Field{ Description: "A list of all types supported by this server.", Type: NewNonNull(NewList( NewNonNull(TypeType), )), Resolve: func(p ResolveParams) (interface{}, error) { if schema, ok := p.Source.(Schema); ok { results := []Type{} for _, ttype := range schema.TypeMap() { results = append(results, ttype) } return results, nil } return []Type{}, nil }, }, "queryType": &Field{ Description: "The type that query operations will be rooted at.", Type: NewNonNull(TypeType), Resolve: func(p ResolveParams) (interface{}, error) { if schema, ok := p.Source.(Schema); ok { return schema.QueryType(), nil } return nil, nil }, }, "mutationType": &Field{ Description: `If this server supports mutation, the type that ` + `mutation operations will be rooted at.`, Type: TypeType, Resolve: func(p ResolveParams) (interface{}, error) { if schema, ok := p.Source.(Schema); ok { if schema.MutationType() != nil { return schema.MutationType(), nil } } return nil, nil }, }, "subscriptionType": &Field{ Description: `If this server support subscription, the type that ` + `subscription operations will be rooted at.`, Type: TypeType, Resolve: func(p ResolveParams) (interface{}, error) { if schema, ok := p.Source.(Schema); ok { if schema.SubscriptionType() != nil { return schema.SubscriptionType(), nil } } return nil, nil }, }, "directives": &Field{ Description: `A list of all directives supported by this server.`, Type: NewNonNull(NewList( NewNonNull(DirectiveType), )), Resolve: func(p ResolveParams) (interface{}, error) { if schema, ok := p.Source.(Schema); ok { return schema.Directives(), nil } return nil, nil }, }, }, }) EnumValueType = NewObject(ObjectConfig{ Name: "__EnumValue", Description: "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.", Fields: Fields{ "name": &Field{ Type: NewNonNull(String), }, "description": &Field{ Type: String, }, "isDeprecated": &Field{ Type: NewNonNull(Boolean), Resolve: func(p ResolveParams) (interface{}, error) { if field, ok := p.Source.(*EnumValueDefinition); ok { return (field.DeprecationReason != ""), nil } return false, nil }, }, "deprecationReason": &Field{ Type: String, Resolve: func(p ResolveParams) (interface{}, error) { if field, ok := p.Source.(*EnumValueDefinition); ok { if field.DeprecationReason != "" { return field.DeprecationReason, nil } } return nil, nil }, }, }, }) // Again, adding field configs to __Type that have cyclic reference here // because golang don't like them too much during init/compile-time TypeType.AddFieldConfig("fields", &Field{ Type: NewList(NewNonNull(FieldType)), Args: FieldConfigArgument{ "includeDeprecated": &ArgumentConfig{ Type: Boolean, DefaultValue: false, }, }, Resolve: func(p ResolveParams) (interface{}, error) { includeDeprecated, _ := p.Args["includeDeprecated"].(bool) switch ttype := p.Source.(type) { case *Object: if ttype == nil { return nil, nil } fields := []*FieldDefinition{} var fieldNames sort.StringSlice for name, field := range ttype.Fields() { if !includeDeprecated && field.DeprecationReason != "" { continue } fieldNames = append(fieldNames, name) } sort.Sort(fieldNames) for _, name := range fieldNames { fields = append(fields, ttype.Fields()[name]) } return fields, nil case *Interface: if ttype == nil { return nil, nil } fields := []*FieldDefinition{} for _, field := range ttype.Fields() { if !includeDeprecated && field.DeprecationReason != "" { continue } fields = append(fields, field) } return fields, nil } return nil, nil }, }) TypeType.AddFieldConfig("interfaces", &Field{ Type: NewList(NewNonNull(TypeType)), Resolve: func(p ResolveParams) (interface{}, error) { if ttype, ok := p.Source.(*Object); ok { return ttype.Interfaces(), nil } return nil, nil }, }) TypeType.AddFieldConfig("possibleTypes", &Field{ Type: NewList(NewNonNull(TypeType)), Resolve: func(p ResolveParams) (interface{}, error) { switch ttype := p.Source.(type) { case *Interface: return p.Info.Schema.PossibleTypes(ttype), nil case *Union: return p.Info.Schema.PossibleTypes(ttype), nil } return nil, nil }, }) TypeType.AddFieldConfig("enumValues", &Field{ Type: NewList(NewNonNull(EnumValueType)), Args: FieldConfigArgument{ "includeDeprecated": &ArgumentConfig{ Type: Boolean, DefaultValue: false, }, }, Resolve: func(p ResolveParams) (interface{}, error) { includeDeprecated, _ := p.Args["includeDeprecated"].(bool) if ttype, ok := p.Source.(*Enum); ok { if includeDeprecated { return ttype.Values(), nil } values := []*EnumValueDefinition{} for _, value := range ttype.Values() { if value.DeprecationReason != "" { continue } values = append(values, value) } return values, nil } return nil, nil }, }) TypeType.AddFieldConfig("inputFields", &Field{ Type: NewList(NewNonNull(InputValueType)), Resolve: func(p ResolveParams) (interface{}, error) { if ttype, ok := p.Source.(*InputObject); ok { fields := []*InputObjectField{} for _, field := range ttype.Fields() { fields = append(fields, field) } return fields, nil } return nil, nil }, }) TypeType.AddFieldConfig("ofType", &Field{ Type: TypeType, }) SchemaType.ensureCache() DirectiveType.ensureCache() TypeType.ensureCache() FieldType.ensureCache() InputValueType.ensureCache() EnumValueType.ensureCache() // Note that these are FieldDefinition and not FieldConfig, // so the format for args is different. SchemaMetaFieldDef = &FieldDefinition{ Name: "__schema", Type: NewNonNull(SchemaType), Description: "Access the current type schema of this server.", Args: []*Argument{}, Resolve: func(p ResolveParams) (interface{}, error) { return p.Info.Schema, nil }, } TypeMetaFieldDef = &FieldDefinition{ Name: "__type", Type: TypeType, Description: "Request the type information of a single type.", Args: []*Argument{ { PrivateName: "name", Type: NewNonNull(String), }, }, Resolve: func(p ResolveParams) (interface{}, error) { name, ok := p.Args["name"].(string) if !ok { return nil, nil } return p.Info.Schema.Type(name), nil }, } TypeNameMetaFieldDef = &FieldDefinition{ Name: "__typename", Type: NewNonNull(String), Description: "The name of the current Object type at runtime.", Args: []*Argument{}, Resolve: func(p ResolveParams) (interface{}, error) { return p.Info.ParentType.Name(), nil }, } } // Produces a GraphQL Value AST given a Golang value. // // Optionally, a GraphQL type may be provided, which will be used to // disambiguate between value primitives. // // | JSON Value | GraphQL Value | // | ------------- | -------------------- | // | Object | Input Object | // | Array | List | // | Boolean | Boolean | // | String | String / Enum Value | // | Number | Int / Float | func astFromValue(value interface{}, ttype Type) ast.Value { if ttype, ok := ttype.(*NonNull); ok { // Note: we're not checking that the result is non-null. // This function is not responsible for validating the input value. val := astFromValue(value, ttype.OfType) return val } if isNullish(value) { return nil } valueVal := reflect.ValueOf(value) if !valueVal.IsValid() { return nil } if valueVal.Type().Kind() == reflect.Ptr { valueVal = valueVal.Elem() } if !valueVal.IsValid() { return nil } // Convert Golang slice to GraphQL list. If the Type is a list, but // the value is not an array, convert the value using the list's item type. if ttype, ok := ttype.(*List); ok { if valueVal.Type().Kind() == reflect.Slice { itemType := ttype.OfType values := []ast.Value{} for i := 0; i < valueVal.Len(); i++ { item := valueVal.Index(i).Interface() itemAST := astFromValue(item, itemType) if itemAST != nil { values = append(values, itemAST) } } return ast.NewListValue(&ast.ListValue{ Values: values, }) } // Because GraphQL will accept single values as a "list of one" when // expecting a list, if there's a non-array value and an expected list type, // create an AST using the list's item type. val := astFromValue(value, ttype.OfType) return val } if valueVal.Type().Kind() == reflect.Map { // TODO: implement astFromValue from Map to Value } if value, ok := value.(bool); ok { return ast.NewBooleanValue(&ast.BooleanValue{ Value: value, }) } if value, ok := value.(int); ok { if ttype == Float { return ast.NewIntValue(&ast.IntValue{ Value: fmt.Sprintf("%v.0", value), }) } return ast.NewIntValue(&ast.IntValue{ Value: fmt.Sprintf("%v", value), }) } if value, ok := value.(float32); ok { return ast.NewFloatValue(&ast.FloatValue{ Value: fmt.Sprintf("%v", value), }) } if value, ok := value.(float64); ok { return ast.NewFloatValue(&ast.FloatValue{ Value: fmt.Sprintf("%v", value), }) } if value, ok := value.(string); ok { if _, ok := ttype.(*Enum); ok { return ast.NewEnumValue(&ast.EnumValue{ Value: fmt.Sprintf("%v", value), }) } return ast.NewStringValue(&ast.StringValue{ Value: fmt.Sprintf("%v", value), }) } // fallback, treat as string return ast.NewStringValue(&ast.StringValue{ Value: fmt.Sprintf("%v", value), }) } ================================================ FILE: introspection_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/location" "github.com/graphql-go/graphql/testutil" ) func g(t *testing.T, p graphql.Params) *graphql.Result { return graphql.Do(p) } func TestIntrospection_ExecutesAnIntrospectionQuery(t *testing.T) { emptySchema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "QueryRoot", Fields: graphql.Fields{ "onlyField": &graphql.Field{ Type: graphql.String, }, }, }), }) if err != nil { t.Fatalf("Error creating Schema: %v", err.Error()) } expectedDataSubSet := map[string]interface{}{ "__schema": map[string]interface{}{ "mutationType": nil, "subscriptionType": nil, "queryType": map[string]interface{}{ "name": "QueryRoot", }, "types": []interface{}{ map[string]interface{}{ "kind": "OBJECT", "name": "QueryRoot", "inputFields": nil, "interfaces": []interface{}{}, "enumValues": nil, "possibleTypes": nil, }, map[string]interface{}{ "kind": "OBJECT", "name": "__Schema", "fields": []interface{}{ map[string]interface{}{ "name": "types", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "LIST", "name": nil, "ofType": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "OBJECT", "name": "__Type", }, }, }, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "queryType", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "OBJECT", "name": "__Type", }, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "mutationType", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "OBJECT", "name": "__Type", }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "subscriptionType", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "OBJECT", "name": "__Type", }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "directives", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "LIST", "name": nil, "ofType": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "OBJECT", "name": "__Directive", }, }, }, }, "isDeprecated": false, "deprecationReason": nil, }, }, "inputFields": nil, "interfaces": []interface{}{}, "enumValues": nil, "possibleTypes": nil, }, map[string]interface{}{ "kind": "OBJECT", "name": "__Type", "fields": []interface{}{ map[string]interface{}{ "name": "kind", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "ENUM", "name": "__TypeKind", "ofType": nil, }, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "name", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "SCALAR", "name": "String", "ofType": nil, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "description", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "SCALAR", "name": "String", "ofType": nil, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "fields", "args": []interface{}{ map[string]interface{}{ "name": "includeDeprecated", "type": map[string]interface{}{ "kind": "SCALAR", "name": "Boolean", "ofType": nil, }, "defaultValue": "false", }, }, "type": map[string]interface{}{ "kind": "LIST", "name": nil, "ofType": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "OBJECT", "name": "__Field", "ofType": nil, }, }, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "interfaces", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "LIST", "name": nil, "ofType": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "OBJECT", "name": "__Type", "ofType": nil, }, }, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "possibleTypes", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "LIST", "name": nil, "ofType": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "OBJECT", "name": "__Type", "ofType": nil, }, }, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "enumValues", "args": []interface{}{ map[string]interface{}{ "name": "includeDeprecated", "type": map[string]interface{}{ "kind": "SCALAR", "name": "Boolean", "ofType": nil, }, "defaultValue": "false", }, }, "type": map[string]interface{}{ "kind": "LIST", "name": nil, "ofType": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "OBJECT", "name": "__EnumValue", "ofType": nil, }, }, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "inputFields", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "LIST", "name": nil, "ofType": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "OBJECT", "name": "__InputValue", "ofType": nil, }, }, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "ofType", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "OBJECT", "name": "__Type", "ofType": nil, }, "isDeprecated": false, "deprecationReason": nil, }, }, "inputFields": nil, "interfaces": []interface{}{}, "enumValues": nil, "possibleTypes": nil, }, map[string]interface{}{ "kind": "ENUM", "name": "__TypeKind", "fields": nil, "inputFields": nil, "interfaces": nil, "enumValues": []interface{}{ map[string]interface{}{ "name": "SCALAR", "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "OBJECT", "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "INTERFACE", "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "UNION", "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "ENUM", "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "INPUT_OBJECT", "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "LIST", "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "NON_NULL", "isDeprecated": false, "deprecationReason": nil, }, }, "possibleTypes": nil, }, map[string]interface{}{ "kind": "SCALAR", "name": "String", "fields": nil, "inputFields": nil, "interfaces": nil, "enumValues": nil, "possibleTypes": nil, }, map[string]interface{}{ "kind": "SCALAR", "name": "Boolean", "fields": nil, "inputFields": nil, "interfaces": nil, "enumValues": nil, "possibleTypes": nil, }, map[string]interface{}{ "kind": "OBJECT", "name": "__Field", "fields": []interface{}{ map[string]interface{}{ "name": "name", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "SCALAR", "name": "String", "ofType": nil, }, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "description", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "SCALAR", "name": "String", "ofType": nil, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "args", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "LIST", "name": nil, "ofType": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "OBJECT", "name": "__InputValue", }, }, }, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "type", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "OBJECT", "name": "__Type", "ofType": nil, }, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "isDeprecated", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "SCALAR", "name": "Boolean", "ofType": nil, }, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "deprecationReason", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "SCALAR", "name": "String", "ofType": nil, }, "isDeprecated": false, "deprecationReason": nil, }, }, "inputFields": nil, "interfaces": []interface{}{}, "enumValues": nil, "possibleTypes": nil, }, map[string]interface{}{ "kind": "OBJECT", "name": "__InputValue", "fields": []interface{}{ map[string]interface{}{ "name": "name", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "SCALAR", "name": "String", "ofType": nil, }, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "description", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "SCALAR", "name": "String", "ofType": nil, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "type", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "OBJECT", "name": "__Type", "ofType": nil, }, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "defaultValue", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "SCALAR", "name": "String", "ofType": nil, }, "isDeprecated": false, "deprecationReason": nil, }, }, "inputFields": nil, "interfaces": []interface{}{}, "enumValues": nil, "possibleTypes": nil, }, map[string]interface{}{ "kind": "OBJECT", "name": "__EnumValue", "fields": []interface{}{ map[string]interface{}{ "name": "name", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "SCALAR", "name": "String", "ofType": nil, }, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "description", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "SCALAR", "name": "String", "ofType": nil, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "isDeprecated", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "SCALAR", "name": "Boolean", "ofType": nil, }, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "deprecationReason", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "SCALAR", "name": "String", "ofType": nil, }, "isDeprecated": false, "deprecationReason": nil, }, }, "inputFields": nil, "interfaces": []interface{}{}, "enumValues": nil, "possibleTypes": nil, }, map[string]interface{}{ "kind": "OBJECT", "name": "__Directive", "fields": []interface{}{ map[string]interface{}{ "name": "name", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "SCALAR", "name": "String", "ofType": nil, }, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "description", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "SCALAR", "name": "String", "ofType": nil, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "locations", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "LIST", "name": nil, "ofType": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "ENUM", "name": "__DirectiveLocation", }, }, }, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "args", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "LIST", "name": nil, "ofType": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "OBJECT", "name": "__InputValue", }, }, }, }, "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "onOperation", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "SCALAR", "name": "Boolean", "ofType": nil, }, }, "isDeprecated": true, "deprecationReason": "Use `locations`.", }, map[string]interface{}{ "name": "onFragment", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "SCALAR", "name": "Boolean", "ofType": nil, }, }, "isDeprecated": true, "deprecationReason": "Use `locations`.", }, map[string]interface{}{ "name": "onField", "args": []interface{}{}, "type": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "SCALAR", "name": "Boolean", "ofType": nil, }, }, "isDeprecated": true, "deprecationReason": "Use `locations`.", }, }, "inputFields": nil, "interfaces": []interface{}{}, "enumValues": nil, "possibleTypes": nil, }, map[string]interface{}{ "kind": "ENUM", "name": "__DirectiveLocation", "fields": nil, "inputFields": nil, "interfaces": nil, "enumValues": []interface{}{ map[string]interface{}{ "name": "QUERY", "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "MUTATION", "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "SUBSCRIPTION", "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "FIELD", "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "FRAGMENT_DEFINITION", "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "FRAGMENT_SPREAD", "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "INLINE_FRAGMENT", "isDeprecated": false, "deprecationReason": nil, }, }, "possibleTypes": nil, }, }, "directives": []interface{}{ map[string]interface{}{ "name": "include", "locations": []interface{}{ "FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT", }, "args": []interface{}{ map[string]interface{}{ "defaultValue": nil, "name": "if", "type": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "SCALAR", "name": "Boolean", "ofType": nil, }, }, }, }, // deprecated, but included for coverage till removed "onOperation": false, "onFragment": true, "onField": true, }, map[string]interface{}{ "name": "skip", "locations": []interface{}{ "FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT", }, "args": []interface{}{ map[string]interface{}{ "defaultValue": nil, "name": "if", "type": map[string]interface{}{ "kind": "NON_NULL", "name": nil, "ofType": map[string]interface{}{ "kind": "SCALAR", "name": "Boolean", "ofType": nil, }, }, }, }, // deprecated, but included for coverage till removed "onOperation": false, "onFragment": true, "onField": true, }, }, }, } result := g(t, graphql.Params{ Schema: emptySchema, RequestString: testutil.IntrospectionQuery, }) if !testutil.ContainSubset(result.Data.(map[string]interface{}), expectedDataSubSet) { t.Fatalf("unexpected, result does not contain subset of expected data") } } func TestIntrospection_ExecutesAnInputObject(t *testing.T) { testInputObject := graphql.NewInputObject(graphql.InputObjectConfig{ Name: "TestInputObject", Fields: graphql.InputObjectConfigFieldMap{ "a": &graphql.InputObjectFieldConfig{ Type: graphql.String, DefaultValue: "foo", }, "b": &graphql.InputObjectFieldConfig{ Type: graphql.NewList(graphql.String), }, }, }) testType := graphql.NewObject(graphql.ObjectConfig{ Name: "TestType", Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "complex": &graphql.ArgumentConfig{ Type: testInputObject, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return p.Args["complex"], nil }, }, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: testType, }) if err != nil { t.Fatalf("Error creating Schema: %v", err.Error()) } query := ` { __schema { types { kind name inputFields { name type { ...TypeRef } defaultValue } } } } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name } } } } ` expectedDataSubSet := map[string]interface{}{ "__schema": map[string]interface{}{ "types": []interface{}{ map[string]interface{}{ "kind": "INPUT_OBJECT", "name": "TestInputObject", "inputFields": []interface{}{ map[string]interface{}{ "name": "a", "type": map[string]interface{}{ "kind": "SCALAR", "name": "String", "ofType": nil, }, "defaultValue": `"foo"`, }, map[string]interface{}{ "name": "b", "type": map[string]interface{}{ "kind": "LIST", "name": nil, "ofType": map[string]interface{}{ "kind": "SCALAR", "name": "String", "ofType": nil, }, }, "defaultValue": nil, }, }, }, }, }, } result := g(t, graphql.Params{ Schema: schema, RequestString: query, }) if !testutil.ContainSubset(result.Data.(map[string]interface{}), expectedDataSubSet) { t.Fatalf("unexpected, result does not contain subset of expected data") } } func TestIntrospection_SupportsThe__TypeRootField(t *testing.T) { testType := graphql.NewObject(graphql.ObjectConfig{ Name: "TestType", Fields: graphql.Fields{ "testField": &graphql.Field{ Type: graphql.String, }, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: testType, }) if err != nil { t.Fatalf("Error creating Schema: %v", err.Error()) } query := ` { __type(name: "TestType") { name } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "__type": map[string]interface{}{ "name": "TestType", }, }, } result := g(t, graphql.Params{ Schema: schema, RequestString: query, }) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestIntrospection_IdentifiesDeprecatedFields(t *testing.T) { testType := graphql.NewObject(graphql.ObjectConfig{ Name: "TestType", Fields: graphql.Fields{ "nonDeprecated": &graphql.Field{ Type: graphql.String, }, "deprecated": &graphql.Field{ Type: graphql.String, DeprecationReason: "Removed in 1.0", }, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: testType, }) if err != nil { t.Fatalf("Error creating Schema: %v", err.Error()) } query := ` { __type(name: "TestType") { name fields(includeDeprecated: true) { name isDeprecated, deprecationReason } } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "__type": map[string]interface{}{ "name": "TestType", "fields": []interface{}{ map[string]interface{}{ "name": "nonDeprecated", "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "deprecated", "isDeprecated": true, "deprecationReason": "Removed in 1.0", }, }, }, }, } result := g(t, graphql.Params{ Schema: schema, RequestString: query, }) if !testutil.ContainSubset(result.Data.(map[string]interface{}), expected.Data.(map[string]interface{})) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestIntrospection_RespectsTheIncludeDeprecatedParameterForFields(t *testing.T) { testType := graphql.NewObject(graphql.ObjectConfig{ Name: "TestType", Fields: graphql.Fields{ "nonDeprecated": &graphql.Field{ Type: graphql.String, }, "deprecated": &graphql.Field{ Type: graphql.String, DeprecationReason: "Removed in 1.0", }, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: testType, }) if err != nil { t.Fatalf("Error creating Schema: %v", err.Error()) } query := ` { __type(name: "TestType") { name trueFields: fields(includeDeprecated: true) { name } falseFields: fields(includeDeprecated: false) { name } omittedFields: fields { name } } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "__type": map[string]interface{}{ "name": "TestType", "trueFields": []interface{}{ map[string]interface{}{ "name": "nonDeprecated", }, map[string]interface{}{ "name": "deprecated", }, }, "falseFields": []interface{}{ map[string]interface{}{ "name": "nonDeprecated", }, }, "omittedFields": []interface{}{ map[string]interface{}{ "name": "nonDeprecated", }, }, }, }, } result := g(t, graphql.Params{ Schema: schema, RequestString: query, }) if !testutil.ContainSubset(result.Data.(map[string]interface{}), expected.Data.(map[string]interface{})) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestIntrospection_IdentifiesDeprecatedEnumValues(t *testing.T) { testEnum := graphql.NewEnum(graphql.EnumConfig{ Name: "TestEnum", Values: graphql.EnumValueConfigMap{ "NONDEPRECATED": &graphql.EnumValueConfig{ Value: 0, }, "DEPRECATED": &graphql.EnumValueConfig{ Value: 1, DeprecationReason: "Removed in 1.0", }, "ALSONONDEPRECATED": &graphql.EnumValueConfig{ Value: 2, }, }, }) testType := graphql.NewObject(graphql.ObjectConfig{ Name: "TestType", Fields: graphql.Fields{ "testEnum": &graphql.Field{ Type: testEnum, }, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: testType, }) if err != nil { t.Fatalf("Error creating Schema: %v", err.Error()) } query := ` { __type(name: "TestEnum") { name enumValues(includeDeprecated: true) { name isDeprecated, deprecationReason } } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "__type": map[string]interface{}{ "name": "TestEnum", "enumValues": []interface{}{ map[string]interface{}{ "name": "NONDEPRECATED", "isDeprecated": false, "deprecationReason": nil, }, map[string]interface{}{ "name": "DEPRECATED", "isDeprecated": true, "deprecationReason": "Removed in 1.0", }, map[string]interface{}{ "name": "ALSONONDEPRECATED", "isDeprecated": false, "deprecationReason": nil, }, }, }, }, } result := g(t, graphql.Params{ Schema: schema, RequestString: query, }) if !testutil.ContainSubset(result.Data.(map[string]interface{}), expected.Data.(map[string]interface{})) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestIntrospection_RespectsTheIncludeDeprecatedParameterForEnumValues(t *testing.T) { testEnum := graphql.NewEnum(graphql.EnumConfig{ Name: "TestEnum", Values: graphql.EnumValueConfigMap{ "NONDEPRECATED": &graphql.EnumValueConfig{ Value: 0, }, "DEPRECATED": &graphql.EnumValueConfig{ Value: 1, DeprecationReason: "Removed in 1.0", }, "ALSONONDEPRECATED": &graphql.EnumValueConfig{ Value: 2, }, }, }) testType := graphql.NewObject(graphql.ObjectConfig{ Name: "TestType", Fields: graphql.Fields{ "testEnum": &graphql.Field{ Type: testEnum, }, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: testType, }) if err != nil { t.Fatalf("Error creating Schema: %v", err.Error()) } query := ` { __type(name: "TestEnum") { name trueValues: enumValues(includeDeprecated: true) { name } falseValues: enumValues(includeDeprecated: false) { name } omittedValues: enumValues { name } } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "__type": map[string]interface{}{ "name": "TestEnum", "trueValues": []interface{}{ map[string]interface{}{ "name": "NONDEPRECATED", }, map[string]interface{}{ "name": "DEPRECATED", }, map[string]interface{}{ "name": "ALSONONDEPRECATED", }, }, "falseValues": []interface{}{ map[string]interface{}{ "name": "NONDEPRECATED", }, map[string]interface{}{ "name": "ALSONONDEPRECATED", }, }, "omittedValues": []interface{}{ map[string]interface{}{ "name": "NONDEPRECATED", }, map[string]interface{}{ "name": "ALSONONDEPRECATED", }, }, }, }, } result := g(t, graphql.Params{ Schema: schema, RequestString: query, }) if !testutil.ContainSubset(result.Data.(map[string]interface{}), expected.Data.(map[string]interface{})) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestIntrospection_FailsAsExpectedOnThe__TypeRootFieldWithoutAnArg(t *testing.T) { testType := graphql.NewObject(graphql.ObjectConfig{ Name: "TestType", Fields: graphql.Fields{ "testField": &graphql.Field{ Type: graphql.String, }, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: testType, }) if err != nil { t.Fatalf("Error creating Schema: %v", err.Error()) } query := ` { __type { name } } ` expected := &graphql.Result{ Errors: []gqlerrors.FormattedError{ { Message: `Field "__type" argument "name" of type "String!" ` + `is required but not provided.`, Locations: []location.SourceLocation{ {Line: 3, Column: 9}, }, }, }, } result := g(t, graphql.Params{ Schema: schema, RequestString: query, }) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestIntrospection_ExposesDescriptionsOnTypesAndFields(t *testing.T) { queryRoot := graphql.NewObject(graphql.ObjectConfig{ Name: "QueryRoot", Fields: graphql.Fields{ "onlyField": &graphql.Field{ Type: graphql.String, }, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: queryRoot, }) if err != nil { t.Fatalf("Error creating Schema: %v", err.Error()) } query := ` { schemaType: __type(name: "__Schema") { name, description, fields { name, description } } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "schemaType": map[string]interface{}{ "name": "__Schema", "description": `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.`, "fields": []interface{}{ map[string]interface{}{ "name": "types", "description": "A list of all types supported by this server.", }, map[string]interface{}{ "name": "queryType", "description": "The type that query operations will be rooted at.", }, map[string]interface{}{ "name": "mutationType", "description": "If this server supports mutation, the type that " + "mutation operations will be rooted at.", }, map[string]interface{}{ "name": "subscriptionType", "description": "If this server support subscription, the type that " + "subscription operations will be rooted at.", }, map[string]interface{}{ "name": "directives", "description": "A list of all directives supported by this server.", }, }, }, }, } result := g(t, graphql.Params{ Schema: schema, RequestString: query, }) if !testutil.ContainSubset(result.Data.(map[string]interface{}), expected.Data.(map[string]interface{})) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestIntrospection_ExposesDescriptionsOnEnums(t *testing.T) { queryRoot := graphql.NewObject(graphql.ObjectConfig{ Name: "QueryRoot", Fields: graphql.Fields{ "onlyField": &graphql.Field{ Type: graphql.String, }, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: queryRoot, }) if err != nil { t.Fatalf("Error creating Schema: %v", err.Error()) } query := ` { typeKindType: __type(name: "__TypeKind") { name, description, enumValues { name, description } } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "typeKindType": map[string]interface{}{ "name": "__TypeKind", "description": "An enum describing what kind of type a given `__Type` is.", "enumValues": []interface{}{ map[string]interface{}{ "name": "SCALAR", "description": "Indicates this type is a scalar.", }, map[string]interface{}{ "name": "OBJECT", "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", }, map[string]interface{}{ "name": "INTERFACE", "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", }, map[string]interface{}{ "name": "UNION", "description": "Indicates this type is a union. `possibleTypes` is a valid field.", }, map[string]interface{}{ "name": "ENUM", "description": "Indicates this type is an enum. `enumValues` is a valid field.", }, map[string]interface{}{ "name": "INPUT_OBJECT", "description": "Indicates this type is an input object. `inputFields` is a valid field.", }, map[string]interface{}{ "name": "LIST", "description": "Indicates this type is a list. `ofType` is a valid field.", }, map[string]interface{}{ "name": "NON_NULL", "description": "Indicates this type is a non-null. `ofType` is a valid field.", }, }, }, }, } result := g(t, graphql.Params{ Schema: schema, RequestString: query, }) if !testutil.ContainSubset(result.Data.(map[string]interface{}), expected.Data.(map[string]interface{})) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } ================================================ FILE: kitchen-sink.graphql ================================================ # Filename: kitchen-sink.graphql query namedQuery($foo: ComplexFooType, $bar: Bar = DefaultBarValue) { customUser: user(id: [987, 654]) { id, ... on User @defer { field2 { id , alias: field1(first:10, after:$foo,) @include(if: $foo) { id, ...frag } } } ... @skip(unless: $foo) { id } ... { id } } } mutation favPost { fav(post: 123) @defer { post { id } } } subscription PostFavSubscription($input: StoryLikeSubscribeInput) { postFavSubscribe(input: $input) { post { favers { count } favSentence { text } } } } fragment frag on Follower { foo(size: $size, bar: $b, obj: {key: "value"}) } { unnamed(truthyVal: true, falseyVal: false), query } ================================================ FILE: language/ast/arguments.go ================================================ package ast import ( "github.com/graphql-go/graphql/language/kinds" ) // Argument implements Node type Argument struct { Kind string Loc *Location Name *Name Value Value } func NewArgument(arg *Argument) *Argument { if arg == nil { arg = &Argument{} } arg.Kind = kinds.Argument return arg } func (arg *Argument) GetKind() string { return arg.Kind } func (arg *Argument) GetLoc() *Location { return arg.Loc } ================================================ FILE: language/ast/definitions.go ================================================ package ast import ( "github.com/graphql-go/graphql/language/kinds" ) type Definition interface { GetOperation() string GetVariableDefinitions() []*VariableDefinition GetSelectionSet() *SelectionSet GetKind() string GetLoc() *Location } // Ensure that all definition types implements Definition interface var _ Definition = (*OperationDefinition)(nil) var _ Definition = (*FragmentDefinition)(nil) var _ Definition = (TypeSystemDefinition)(nil) // experimental non-spec addition. // Note: subscription is an experimental non-spec addition. const ( OperationTypeQuery = "query" OperationTypeMutation = "mutation" OperationTypeSubscription = "subscription" ) // OperationDefinition implements Node, Definition type OperationDefinition struct { Kind string Loc *Location Operation string Name *Name VariableDefinitions []*VariableDefinition Directives []*Directive SelectionSet *SelectionSet } func NewOperationDefinition(op *OperationDefinition) *OperationDefinition { if op == nil { op = &OperationDefinition{} } op.Kind = kinds.OperationDefinition return op } func (op *OperationDefinition) GetKind() string { return op.Kind } func (op *OperationDefinition) GetLoc() *Location { return op.Loc } func (op *OperationDefinition) GetOperation() string { return op.Operation } func (op *OperationDefinition) GetName() *Name { return op.Name } func (op *OperationDefinition) GetVariableDefinitions() []*VariableDefinition { return op.VariableDefinitions } func (op *OperationDefinition) GetDirectives() []*Directive { return op.Directives } func (op *OperationDefinition) GetSelectionSet() *SelectionSet { return op.SelectionSet } // FragmentDefinition implements Node, Definition type FragmentDefinition struct { Kind string Loc *Location Operation string Name *Name VariableDefinitions []*VariableDefinition TypeCondition *Named Directives []*Directive SelectionSet *SelectionSet } func NewFragmentDefinition(fd *FragmentDefinition) *FragmentDefinition { if fd == nil { fd = &FragmentDefinition{} } return &FragmentDefinition{ Kind: kinds.FragmentDefinition, Loc: fd.Loc, Operation: fd.Operation, Name: fd.Name, VariableDefinitions: fd.VariableDefinitions, TypeCondition: fd.TypeCondition, Directives: fd.Directives, SelectionSet: fd.SelectionSet, } } func (fd *FragmentDefinition) GetKind() string { return fd.Kind } func (fd *FragmentDefinition) GetLoc() *Location { return fd.Loc } func (fd *FragmentDefinition) GetOperation() string { return fd.Operation } func (fd *FragmentDefinition) GetName() *Name { return fd.Name } func (fd *FragmentDefinition) GetVariableDefinitions() []*VariableDefinition { return fd.VariableDefinitions } func (fd *FragmentDefinition) GetSelectionSet() *SelectionSet { return fd.SelectionSet } // VariableDefinition implements Node type VariableDefinition struct { Kind string Loc *Location Variable *Variable Type Type DefaultValue Value } func NewVariableDefinition(vd *VariableDefinition) *VariableDefinition { if vd == nil { vd = &VariableDefinition{} } vd.Kind = kinds.VariableDefinition return vd } func (vd *VariableDefinition) GetKind() string { return vd.Kind } func (vd *VariableDefinition) GetLoc() *Location { return vd.Loc } // TypeExtensionDefinition implements Node, Definition type TypeExtensionDefinition struct { Kind string Loc *Location Definition *ObjectDefinition } func NewTypeExtensionDefinition(def *TypeExtensionDefinition) *TypeExtensionDefinition { if def == nil { def = &TypeExtensionDefinition{} } return &TypeExtensionDefinition{ Kind: kinds.TypeExtensionDefinition, Loc: def.Loc, Definition: def.Definition, } } func (def *TypeExtensionDefinition) GetKind() string { return def.Kind } func (def *TypeExtensionDefinition) GetLoc() *Location { return def.Loc } func (def *TypeExtensionDefinition) GetVariableDefinitions() []*VariableDefinition { return []*VariableDefinition{} } func (def *TypeExtensionDefinition) GetSelectionSet() *SelectionSet { return &SelectionSet{} } func (def *TypeExtensionDefinition) GetOperation() string { return "" } // DirectiveDefinition implements Node, Definition type DirectiveDefinition struct { Kind string Loc *Location Name *Name Description *StringValue Arguments []*InputValueDefinition Locations []*Name } func NewDirectiveDefinition(def *DirectiveDefinition) *DirectiveDefinition { if def == nil { def = &DirectiveDefinition{} } return &DirectiveDefinition{ Kind: kinds.DirectiveDefinition, Loc: def.Loc, Name: def.Name, Description: def.Description, Arguments: def.Arguments, Locations: def.Locations, } } func (def *DirectiveDefinition) GetKind() string { return def.Kind } func (def *DirectiveDefinition) GetLoc() *Location { return def.Loc } func (def *DirectiveDefinition) GetVariableDefinitions() []*VariableDefinition { return []*VariableDefinition{} } func (def *DirectiveDefinition) GetSelectionSet() *SelectionSet { return &SelectionSet{} } func (def *DirectiveDefinition) GetOperation() string { return "" } func (def *DirectiveDefinition) GetDescription() *StringValue { return def.Description } ================================================ FILE: language/ast/directives.go ================================================ package ast import ( "github.com/graphql-go/graphql/language/kinds" ) // Directive implements Node type Directive struct { Kind string Loc *Location Name *Name Arguments []*Argument } func NewDirective(dir *Directive) *Directive { if dir == nil { dir = &Directive{} } return &Directive{ Kind: kinds.Directive, Loc: dir.Loc, Name: dir.Name, Arguments: dir.Arguments, } } func (dir *Directive) GetKind() string { return dir.Kind } func (dir *Directive) GetLoc() *Location { return dir.Loc } ================================================ FILE: language/ast/document.go ================================================ package ast import ( "github.com/graphql-go/graphql/language/kinds" ) // Document implements Node type Document struct { Kind string Loc *Location Definitions []Node } func NewDocument(d *Document) *Document { if d == nil { d = &Document{} } return &Document{ Kind: kinds.Document, Loc: d.Loc, Definitions: d.Definitions, } } func (node *Document) GetKind() string { return node.Kind } func (node *Document) GetLoc() *Location { return node.Loc } ================================================ FILE: language/ast/location.go ================================================ package ast import ( "github.com/graphql-go/graphql/language/source" ) type Location struct { Start int End int Source *source.Source } func NewLocation(loc *Location) *Location { if loc == nil { loc = &Location{} } return &Location{ Start: loc.Start, End: loc.End, Source: loc.Source, } } ================================================ FILE: language/ast/name.go ================================================ package ast import ( "github.com/graphql-go/graphql/language/kinds" ) // Name implements Node type Name struct { Kind string Loc *Location Value string } func NewName(node *Name) *Name { if node == nil { node = &Name{} } node.Kind = kinds.Name return node } func (node *Name) GetKind() string { return node.Kind } func (node *Name) GetLoc() *Location { return node.Loc } ================================================ FILE: language/ast/node.go ================================================ package ast type Node interface { GetKind() string GetLoc() *Location } // The list of all possible AST node graphql. // Ensure that all node types implements Node interface var _ Node = (*Name)(nil) var _ Node = (*Document)(nil) var _ Node = (*OperationDefinition)(nil) var _ Node = (*VariableDefinition)(nil) var _ Node = (*Variable)(nil) var _ Node = (*SelectionSet)(nil) var _ Node = (*Field)(nil) var _ Node = (*Argument)(nil) var _ Node = (*FragmentSpread)(nil) var _ Node = (*InlineFragment)(nil) var _ Node = (*FragmentDefinition)(nil) var _ Node = (*IntValue)(nil) var _ Node = (*FloatValue)(nil) var _ Node = (*StringValue)(nil) var _ Node = (*BooleanValue)(nil) var _ Node = (*EnumValue)(nil) var _ Node = (*ListValue)(nil) var _ Node = (*ObjectValue)(nil) var _ Node = (*ObjectField)(nil) var _ Node = (*Directive)(nil) var _ Node = (*Named)(nil) var _ Node = (*List)(nil) var _ Node = (*NonNull)(nil) var _ Node = (*SchemaDefinition)(nil) var _ Node = (*OperationTypeDefinition)(nil) var _ Node = (*ScalarDefinition)(nil) var _ Node = (*ObjectDefinition)(nil) var _ Node = (*FieldDefinition)(nil) var _ Node = (*InputValueDefinition)(nil) var _ Node = (*InterfaceDefinition)(nil) var _ Node = (*UnionDefinition)(nil) var _ Node = (*EnumDefinition)(nil) var _ Node = (*EnumValueDefinition)(nil) var _ Node = (*InputObjectDefinition)(nil) var _ Node = (*TypeExtensionDefinition)(nil) var _ Node = (*DirectiveDefinition)(nil) ================================================ FILE: language/ast/selections.go ================================================ package ast import ( "github.com/graphql-go/graphql/language/kinds" ) type Selection interface { GetSelectionSet() *SelectionSet } // Ensure that all definition types implements Selection interface var _ Selection = (*Field)(nil) var _ Selection = (*FragmentSpread)(nil) var _ Selection = (*InlineFragment)(nil) // Field implements Node, Selection type Field struct { Kind string Loc *Location Alias *Name Name *Name Arguments []*Argument Directives []*Directive SelectionSet *SelectionSet } func NewField(f *Field) *Field { if f == nil { f = &Field{} } f.Kind = kinds.Field return f } func (f *Field) GetKind() string { return f.Kind } func (f *Field) GetLoc() *Location { return f.Loc } func (f *Field) GetSelectionSet() *SelectionSet { return f.SelectionSet } // FragmentSpread implements Node, Selection type FragmentSpread struct { Kind string Loc *Location Name *Name Directives []*Directive } func NewFragmentSpread(fs *FragmentSpread) *FragmentSpread { if fs == nil { fs = &FragmentSpread{} } return &FragmentSpread{ Kind: kinds.FragmentSpread, Loc: fs.Loc, Name: fs.Name, Directives: fs.Directives, } } func (fs *FragmentSpread) GetKind() string { return fs.Kind } func (fs *FragmentSpread) GetLoc() *Location { return fs.Loc } func (fs *FragmentSpread) GetSelectionSet() *SelectionSet { return nil } // InlineFragment implements Node, Selection type InlineFragment struct { Kind string Loc *Location TypeCondition *Named Directives []*Directive SelectionSet *SelectionSet } func NewInlineFragment(f *InlineFragment) *InlineFragment { if f == nil { f = &InlineFragment{} } return &InlineFragment{ Kind: kinds.InlineFragment, Loc: f.Loc, TypeCondition: f.TypeCondition, Directives: f.Directives, SelectionSet: f.SelectionSet, } } func (f *InlineFragment) GetKind() string { return f.Kind } func (f *InlineFragment) GetLoc() *Location { return f.Loc } func (f *InlineFragment) GetSelectionSet() *SelectionSet { return f.SelectionSet } // SelectionSet implements Node type SelectionSet struct { Kind string Loc *Location Selections []Selection } func NewSelectionSet(ss *SelectionSet) *SelectionSet { if ss == nil { ss = &SelectionSet{} } ss.Kind = kinds.SelectionSet return ss } func (ss *SelectionSet) GetKind() string { return ss.Kind } func (ss *SelectionSet) GetLoc() *Location { return ss.Loc } ================================================ FILE: language/ast/type_definitions.go ================================================ package ast import ( "github.com/graphql-go/graphql/language/kinds" ) // DescribableNode are nodes that have descriptions associated with them. type DescribableNode interface { GetDescription() *StringValue } type TypeDefinition interface { DescribableNode GetOperation() string GetVariableDefinitions() []*VariableDefinition GetSelectionSet() *SelectionSet GetKind() string GetLoc() *Location } var _ TypeDefinition = (*ScalarDefinition)(nil) var _ TypeDefinition = (*ObjectDefinition)(nil) var _ TypeDefinition = (*InterfaceDefinition)(nil) var _ TypeDefinition = (*UnionDefinition)(nil) var _ TypeDefinition = (*EnumDefinition)(nil) var _ TypeDefinition = (*InputObjectDefinition)(nil) type TypeSystemDefinition interface { GetOperation() string GetVariableDefinitions() []*VariableDefinition GetSelectionSet() *SelectionSet GetKind() string GetLoc() *Location } var _ TypeSystemDefinition = (*SchemaDefinition)(nil) var _ TypeSystemDefinition = (TypeDefinition)(nil) var _ TypeSystemDefinition = (*TypeExtensionDefinition)(nil) var _ TypeSystemDefinition = (*DirectiveDefinition)(nil) // SchemaDefinition implements Node, Definition type SchemaDefinition struct { Kind string Loc *Location Directives []*Directive OperationTypes []*OperationTypeDefinition } func NewSchemaDefinition(def *SchemaDefinition) *SchemaDefinition { if def == nil { def = &SchemaDefinition{} } return &SchemaDefinition{ Kind: kinds.SchemaDefinition, Loc: def.Loc, Directives: def.Directives, OperationTypes: def.OperationTypes, } } func (def *SchemaDefinition) GetKind() string { return def.Kind } func (def *SchemaDefinition) GetLoc() *Location { return def.Loc } func (def *SchemaDefinition) GetVariableDefinitions() []*VariableDefinition { return []*VariableDefinition{} } func (def *SchemaDefinition) GetSelectionSet() *SelectionSet { return &SelectionSet{} } func (def *SchemaDefinition) GetOperation() string { return "" } // OperationTypeDefinition implements Node, Definition type OperationTypeDefinition struct { Kind string Loc *Location Operation string Type *Named } func NewOperationTypeDefinition(def *OperationTypeDefinition) *OperationTypeDefinition { if def == nil { def = &OperationTypeDefinition{} } return &OperationTypeDefinition{ Kind: kinds.OperationTypeDefinition, Loc: def.Loc, Operation: def.Operation, Type: def.Type, } } func (def *OperationTypeDefinition) GetKind() string { return def.Kind } func (def *OperationTypeDefinition) GetLoc() *Location { return def.Loc } // ScalarDefinition implements Node, Definition type ScalarDefinition struct { Kind string Loc *Location Description *StringValue Name *Name Directives []*Directive } func NewScalarDefinition(def *ScalarDefinition) *ScalarDefinition { if def == nil { def = &ScalarDefinition{} } return &ScalarDefinition{ Kind: kinds.ScalarDefinition, Loc: def.Loc, Description: def.Description, Name: def.Name, Directives: def.Directives, } } func (def *ScalarDefinition) GetKind() string { return def.Kind } func (def *ScalarDefinition) GetLoc() *Location { return def.Loc } func (def *ScalarDefinition) GetName() *Name { return def.Name } func (def *ScalarDefinition) GetVariableDefinitions() []*VariableDefinition { return []*VariableDefinition{} } func (def *ScalarDefinition) GetSelectionSet() *SelectionSet { return &SelectionSet{} } func (def *ScalarDefinition) GetOperation() string { return "" } func (def *ScalarDefinition) GetDescription() *StringValue { return def.Description } // ObjectDefinition implements Node, Definition type ObjectDefinition struct { Kind string Loc *Location Name *Name Description *StringValue Interfaces []*Named Directives []*Directive Fields []*FieldDefinition } func NewObjectDefinition(def *ObjectDefinition) *ObjectDefinition { if def == nil { def = &ObjectDefinition{} } return &ObjectDefinition{ Kind: kinds.ObjectDefinition, Loc: def.Loc, Name: def.Name, Description: def.Description, Interfaces: def.Interfaces, Directives: def.Directives, Fields: def.Fields, } } func (def *ObjectDefinition) GetKind() string { return def.Kind } func (def *ObjectDefinition) GetLoc() *Location { return def.Loc } func (def *ObjectDefinition) GetName() *Name { return def.Name } func (def *ObjectDefinition) GetVariableDefinitions() []*VariableDefinition { return []*VariableDefinition{} } func (def *ObjectDefinition) GetSelectionSet() *SelectionSet { return &SelectionSet{} } func (def *ObjectDefinition) GetOperation() string { return "" } func (def *ObjectDefinition) GetDescription() *StringValue { return def.Description } // FieldDefinition implements Node type FieldDefinition struct { Kind string Loc *Location Name *Name Description *StringValue Arguments []*InputValueDefinition Type Type Directives []*Directive } func NewFieldDefinition(def *FieldDefinition) *FieldDefinition { if def == nil { def = &FieldDefinition{} } return &FieldDefinition{ Kind: kinds.FieldDefinition, Loc: def.Loc, Name: def.Name, Description: def.Description, Arguments: def.Arguments, Type: def.Type, Directives: def.Directives, } } func (def *FieldDefinition) GetKind() string { return def.Kind } func (def *FieldDefinition) GetLoc() *Location { return def.Loc } func (def *FieldDefinition) GetDescription() *StringValue { return def.Description } // InputValueDefinition implements Node type InputValueDefinition struct { Kind string Loc *Location Name *Name Description *StringValue Type Type DefaultValue Value Directives []*Directive } func NewInputValueDefinition(def *InputValueDefinition) *InputValueDefinition { if def == nil { def = &InputValueDefinition{} } return &InputValueDefinition{ Kind: kinds.InputValueDefinition, Loc: def.Loc, Name: def.Name, Description: def.Description, Type: def.Type, DefaultValue: def.DefaultValue, Directives: def.Directives, } } func (def *InputValueDefinition) GetKind() string { return def.Kind } func (def *InputValueDefinition) GetLoc() *Location { return def.Loc } func (def *InputValueDefinition) GetDescription() *StringValue { return def.Description } // InterfaceDefinition implements Node, Definition type InterfaceDefinition struct { Kind string Loc *Location Name *Name Description *StringValue Directives []*Directive Fields []*FieldDefinition } func NewInterfaceDefinition(def *InterfaceDefinition) *InterfaceDefinition { if def == nil { def = &InterfaceDefinition{} } return &InterfaceDefinition{ Kind: kinds.InterfaceDefinition, Loc: def.Loc, Name: def.Name, Description: def.Description, Directives: def.Directives, Fields: def.Fields, } } func (def *InterfaceDefinition) GetKind() string { return def.Kind } func (def *InterfaceDefinition) GetLoc() *Location { return def.Loc } func (def *InterfaceDefinition) GetName() *Name { return def.Name } func (def *InterfaceDefinition) GetVariableDefinitions() []*VariableDefinition { return []*VariableDefinition{} } func (def *InterfaceDefinition) GetSelectionSet() *SelectionSet { return &SelectionSet{} } func (def *InterfaceDefinition) GetOperation() string { return "" } func (def *InterfaceDefinition) GetDescription() *StringValue { return def.Description } // UnionDefinition implements Node, Definition type UnionDefinition struct { Kind string Loc *Location Name *Name Description *StringValue Directives []*Directive Types []*Named } func NewUnionDefinition(def *UnionDefinition) *UnionDefinition { if def == nil { def = &UnionDefinition{} } return &UnionDefinition{ Kind: kinds.UnionDefinition, Loc: def.Loc, Name: def.Name, Description: def.Description, Directives: def.Directives, Types: def.Types, } } func (def *UnionDefinition) GetKind() string { return def.Kind } func (def *UnionDefinition) GetLoc() *Location { return def.Loc } func (def *UnionDefinition) GetName() *Name { return def.Name } func (def *UnionDefinition) GetVariableDefinitions() []*VariableDefinition { return []*VariableDefinition{} } func (def *UnionDefinition) GetSelectionSet() *SelectionSet { return &SelectionSet{} } func (def *UnionDefinition) GetOperation() string { return "" } func (def *UnionDefinition) GetDescription() *StringValue { return def.Description } // EnumDefinition implements Node, Definition type EnumDefinition struct { Kind string Loc *Location Name *Name Description *StringValue Directives []*Directive Values []*EnumValueDefinition } func NewEnumDefinition(def *EnumDefinition) *EnumDefinition { if def == nil { def = &EnumDefinition{} } return &EnumDefinition{ Kind: kinds.EnumDefinition, Loc: def.Loc, Name: def.Name, Description: def.Description, Directives: def.Directives, Values: def.Values, } } func (def *EnumDefinition) GetKind() string { return def.Kind } func (def *EnumDefinition) GetLoc() *Location { return def.Loc } func (def *EnumDefinition) GetName() *Name { return def.Name } func (def *EnumDefinition) GetVariableDefinitions() []*VariableDefinition { return []*VariableDefinition{} } func (def *EnumDefinition) GetSelectionSet() *SelectionSet { return &SelectionSet{} } func (def *EnumDefinition) GetOperation() string { return "" } func (def *EnumDefinition) GetDescription() *StringValue { return def.Description } // EnumValueDefinition implements Node, Definition type EnumValueDefinition struct { Kind string Loc *Location Name *Name Description *StringValue Directives []*Directive } func NewEnumValueDefinition(def *EnumValueDefinition) *EnumValueDefinition { if def == nil { def = &EnumValueDefinition{} } return &EnumValueDefinition{ Kind: kinds.EnumValueDefinition, Loc: def.Loc, Name: def.Name, Description: def.Description, Directives: def.Directives, } } func (def *EnumValueDefinition) GetKind() string { return def.Kind } func (def *EnumValueDefinition) GetLoc() *Location { return def.Loc } func (def *EnumValueDefinition) GetDescription() *StringValue { return def.Description } // InputObjectDefinition implements Node, Definition type InputObjectDefinition struct { Kind string Loc *Location Name *Name Description *StringValue Directives []*Directive Fields []*InputValueDefinition } func NewInputObjectDefinition(def *InputObjectDefinition) *InputObjectDefinition { if def == nil { def = &InputObjectDefinition{} } return &InputObjectDefinition{ Kind: kinds.InputObjectDefinition, Loc: def.Loc, Name: def.Name, Description: def.Description, Directives: def.Directives, Fields: def.Fields, } } func (def *InputObjectDefinition) GetKind() string { return def.Kind } func (def *InputObjectDefinition) GetLoc() *Location { return def.Loc } func (def *InputObjectDefinition) GetName() *Name { return def.Name } func (def *InputObjectDefinition) GetVariableDefinitions() []*VariableDefinition { return []*VariableDefinition{} } func (def *InputObjectDefinition) GetSelectionSet() *SelectionSet { return &SelectionSet{} } func (def *InputObjectDefinition) GetOperation() string { return "" } func (def *InputObjectDefinition) GetDescription() *StringValue { return def.Description } ================================================ FILE: language/ast/types.go ================================================ package ast import ( "github.com/graphql-go/graphql/language/kinds" ) type Type interface { GetKind() string GetLoc() *Location String() string } // Ensure that all value types implements Value interface var _ Type = (*Named)(nil) var _ Type = (*List)(nil) var _ Type = (*NonNull)(nil) // Named implements Node, Type type Named struct { Kind string Loc *Location Name *Name } func NewNamed(t *Named) *Named { if t == nil { t = &Named{} } t.Kind = kinds.Named return t } func (t *Named) GetKind() string { return t.Kind } func (t *Named) GetLoc() *Location { return t.Loc } func (t *Named) String() string { return t.GetKind() } // List implements Node, Type type List struct { Kind string Loc *Location Type Type } func NewList(t *List) *List { if t == nil { t = &List{} } return &List{ Kind: kinds.List, Loc: t.Loc, Type: t.Type, } } func (t *List) GetKind() string { return t.Kind } func (t *List) GetLoc() *Location { return t.Loc } func (t *List) String() string { return t.GetKind() } // NonNull implements Node, Type type NonNull struct { Kind string Loc *Location Type Type } func NewNonNull(t *NonNull) *NonNull { if t == nil { t = &NonNull{} } return &NonNull{ Kind: kinds.NonNull, Loc: t.Loc, Type: t.Type, } } func (t *NonNull) GetKind() string { return t.Kind } func (t *NonNull) GetLoc() *Location { return t.Loc } func (t *NonNull) String() string { return t.GetKind() } ================================================ FILE: language/ast/values.go ================================================ package ast import ( "github.com/graphql-go/graphql/language/kinds" ) type Value interface { GetValue() interface{} GetKind() string GetLoc() *Location } // Ensure that all value types implements Value interface var _ Value = (*Variable)(nil) var _ Value = (*IntValue)(nil) var _ Value = (*FloatValue)(nil) var _ Value = (*StringValue)(nil) var _ Value = (*BooleanValue)(nil) var _ Value = (*EnumValue)(nil) var _ Value = (*ListValue)(nil) var _ Value = (*ObjectValue)(nil) // Variable implements Node, Value type Variable struct { Kind string Loc *Location Name *Name } func NewVariable(v *Variable) *Variable { if v == nil { v = &Variable{} } v.Kind = kinds.Variable return v } func (v *Variable) GetKind() string { return v.Kind } func (v *Variable) GetLoc() *Location { return v.Loc } // GetValue alias to Variable.GetName() func (v *Variable) GetValue() interface{} { return v.GetName() } func (v *Variable) GetName() interface{} { return v.Name } // IntValue implements Node, Value type IntValue struct { Kind string Loc *Location Value string } func NewIntValue(v *IntValue) *IntValue { if v == nil { v = &IntValue{} } return &IntValue{ Kind: kinds.IntValue, Loc: v.Loc, Value: v.Value, } } func (v *IntValue) GetKind() string { return v.Kind } func (v *IntValue) GetLoc() *Location { return v.Loc } func (v *IntValue) GetValue() interface{} { return v.Value } // FloatValue implements Node, Value type FloatValue struct { Kind string Loc *Location Value string } func NewFloatValue(v *FloatValue) *FloatValue { if v == nil { v = &FloatValue{} } return &FloatValue{ Kind: kinds.FloatValue, Loc: v.Loc, Value: v.Value, } } func (v *FloatValue) GetKind() string { return v.Kind } func (v *FloatValue) GetLoc() *Location { return v.Loc } func (v *FloatValue) GetValue() interface{} { return v.Value } // StringValue implements Node, Value type StringValue struct { Kind string Loc *Location Value string } func NewStringValue(v *StringValue) *StringValue { if v == nil { v = &StringValue{} } return &StringValue{ Kind: kinds.StringValue, Loc: v.Loc, Value: v.Value, } } func (v *StringValue) GetKind() string { return v.Kind } func (v *StringValue) GetLoc() *Location { return v.Loc } func (v *StringValue) GetValue() interface{} { return v.Value } // BooleanValue implements Node, Value type BooleanValue struct { Kind string Loc *Location Value bool } func NewBooleanValue(v *BooleanValue) *BooleanValue { if v == nil { v = &BooleanValue{} } return &BooleanValue{ Kind: kinds.BooleanValue, Loc: v.Loc, Value: v.Value, } } func (v *BooleanValue) GetKind() string { return v.Kind } func (v *BooleanValue) GetLoc() *Location { return v.Loc } func (v *BooleanValue) GetValue() interface{} { return v.Value } // EnumValue implements Node, Value type EnumValue struct { Kind string Loc *Location Value string } func NewEnumValue(v *EnumValue) *EnumValue { if v == nil { v = &EnumValue{} } return &EnumValue{ Kind: kinds.EnumValue, Loc: v.Loc, Value: v.Value, } } func (v *EnumValue) GetKind() string { return v.Kind } func (v *EnumValue) GetLoc() *Location { return v.Loc } func (v *EnumValue) GetValue() interface{} { return v.Value } // ListValue implements Node, Value type ListValue struct { Kind string Loc *Location Values []Value } func NewListValue(v *ListValue) *ListValue { if v == nil { v = &ListValue{} } return &ListValue{ Kind: kinds.ListValue, Loc: v.Loc, Values: v.Values, } } func (v *ListValue) GetKind() string { return v.Kind } func (v *ListValue) GetLoc() *Location { return v.Loc } // GetValue alias to ListValue.GetValues() func (v *ListValue) GetValue() interface{} { return v.GetValues() } func (v *ListValue) GetValues() interface{} { // TODO: verify ObjectValue.GetValue() return v.Values } // ObjectValue implements Node, Value type ObjectValue struct { Kind string Loc *Location Fields []*ObjectField } func NewObjectValue(v *ObjectValue) *ObjectValue { if v == nil { v = &ObjectValue{} } return &ObjectValue{ Kind: kinds.ObjectValue, Loc: v.Loc, Fields: v.Fields, } } func (v *ObjectValue) GetKind() string { return v.Kind } func (v *ObjectValue) GetLoc() *Location { return v.Loc } func (v *ObjectValue) GetValue() interface{} { // TODO: verify ObjectValue.GetValue() return v.Fields } // ObjectField implements Node, Value type ObjectField struct { Kind string Name *Name Loc *Location Value Value } func NewObjectField(f *ObjectField) *ObjectField { if f == nil { f = &ObjectField{} } return &ObjectField{ Kind: kinds.ObjectField, Loc: f.Loc, Name: f.Name, Value: f.Value, } } func (f *ObjectField) GetKind() string { return f.Kind } func (f *ObjectField) GetLoc() *Location { return f.Loc } func (f *ObjectField) GetValue() interface{} { return f.Value } ================================================ FILE: language/kinds/kinds.go ================================================ package kinds const ( // Name Name = "Name" // Document Document = "Document" OperationDefinition = "OperationDefinition" VariableDefinition = "VariableDefinition" Variable = "Variable" SelectionSet = "SelectionSet" Field = "Field" Argument = "Argument" // Fragments FragmentSpread = "FragmentSpread" InlineFragment = "InlineFragment" FragmentDefinition = "FragmentDefinition" // Values IntValue = "IntValue" FloatValue = "FloatValue" StringValue = "StringValue" BooleanValue = "BooleanValue" EnumValue = "EnumValue" ListValue = "ListValue" ObjectValue = "ObjectValue" ObjectField = "ObjectField" // Directives Directive = "Directive" // Types Named = "Named" // previously NamedType List = "List" // previously ListType NonNull = "NonNull" // previously NonNull // Type System Definitions SchemaDefinition = "SchemaDefinition" OperationTypeDefinition = "OperationTypeDefinition" // Types Definitions ScalarDefinition = "ScalarDefinition" // previously ScalarTypeDefinition ObjectDefinition = "ObjectDefinition" // previously ObjectTypeDefinition FieldDefinition = "FieldDefinition" InputValueDefinition = "InputValueDefinition" InterfaceDefinition = "InterfaceDefinition" // previously InterfaceTypeDefinition UnionDefinition = "UnionDefinition" // previously UnionTypeDefinition EnumDefinition = "EnumDefinition" // previously EnumTypeDefinition EnumValueDefinition = "EnumValueDefinition" InputObjectDefinition = "InputObjectDefinition" // previously InputObjectTypeDefinition // Types Extensions TypeExtensionDefinition = "TypeExtensionDefinition" // Directive Definitions DirectiveDefinition = "DirectiveDefinition" ) ================================================ FILE: language/lexer/lexer.go ================================================ package lexer import ( "bytes" "fmt" "regexp" "strings" "unicode/utf8" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/source" ) type TokenKind int const ( EOF TokenKind = iota + 1 BANG DOLLAR PAREN_L PAREN_R SPREAD COLON EQUALS AT BRACKET_L BRACKET_R BRACE_L PIPE BRACE_R NAME INT FLOAT STRING BLOCK_STRING AMP ) var tokenDescription = map[TokenKind]string{ EOF: "EOF", BANG: "!", DOLLAR: "$", PAREN_L: "(", PAREN_R: ")", SPREAD: "...", COLON: ":", EQUALS: "=", AT: "@", BRACKET_L: "[", BRACKET_R: "]", BRACE_L: "{", PIPE: "|", BRACE_R: "}", NAME: "Name", INT: "Int", FLOAT: "Float", STRING: "String", BLOCK_STRING: "BlockString", AMP: "&", } func (kind TokenKind) String() string { return tokenDescription[kind] } // NAME -> keyword relationship const ( FRAGMENT = "fragment" QUERY = "query" MUTATION = "mutation" SUBSCRIPTION = "subscription" SCHEMA = "schema" SCALAR = "scalar" TYPE = "type" INTERFACE = "interface" UNION = "union" ENUM = "enum" INPUT = "input" EXTEND = "extend" DIRECTIVE = "directive" ) // Token is a representation of a lexed Token. Value only appears for non-punctuation // tokens: NAME, INT, FLOAT, and STRING. type Token struct { Kind TokenKind Start int End int Value string } type Lexer func(resetPosition int) (Token, error) func Lex(s *source.Source) Lexer { var prevPosition int return func(resetPosition int) (Token, error) { if resetPosition == 0 { resetPosition = prevPosition } token, err := readToken(s, resetPosition) if err != nil { return token, err } prevPosition = token.End return token, nil } } // Reads an alphanumeric + underscore name from the source. // [_A-Za-z][_0-9A-Za-z]* // position: Points to the byte position in the byte array // runePosition: Points to the rune position in the byte array func readName(source *source.Source, position, runePosition int) Token { body := source.Body bodyLength := len(body) endByte := position + 1 endRune := runePosition + 1 for { code, _ := runeAt(body, endByte) if (endByte != bodyLength) && (code == '_' || // _ code >= '0' && code <= '9' || // 0-9 code >= 'A' && code <= 'Z' || // A-Z code >= 'a' && code <= 'z') { // a-z endByte++ endRune++ continue } else { break } } return makeToken(NAME, runePosition, endRune, string(body[position:endByte])) } // Reads a number token from the source file, either a float // or an int depending on whether a decimal point appears. // Int: -?(0|[1-9][0-9]*) // Float: -?(0|[1-9][0-9]*)(\.[0-9]+)?((E|e)(+|-)?[0-9]+)? func readNumber(s *source.Source, start int, firstCode rune, codeLength int) (Token, error) { code := firstCode body := s.Body position := start isFloat := false if code == '-' { // - position += codeLength code, codeLength = runeAt(body, position) } if code == '0' { // 0 position += codeLength code, codeLength = runeAt(body, position) if code >= '0' && code <= '9' { description := fmt.Sprintf("Invalid number, unexpected digit after 0: %v.", printCharCode(code)) return Token{}, gqlerrors.NewSyntaxError(s, position, description) } } else { p, err := readDigits(s, position, code, codeLength) if err != nil { return Token{}, err } position = p code, codeLength = runeAt(body, position) } if code == '.' { // . isFloat = true position += codeLength code, codeLength = runeAt(body, position) p, err := readDigits(s, position, code, codeLength) if err != nil { return Token{}, err } position = p code, codeLength = runeAt(body, position) } if code == 'E' || code == 'e' { // E e isFloat = true position += codeLength code, codeLength = runeAt(body, position) if code == '+' || code == '-' { // + - position += codeLength code, codeLength = runeAt(body, position) } p, err := readDigits(s, position, code, codeLength) if err != nil { return Token{}, err } position = p } kind := INT if isFloat { kind = FLOAT } return makeToken(kind, start, position, string(body[start:position])), nil } // Returns the new position in the source after reading digits. func readDigits(s *source.Source, start int, firstCode rune, codeLength int) (int, error) { body := s.Body position := start code := firstCode if code >= '0' && code <= '9' { // 0 - 9 for { if code >= '0' && code <= '9' { // 0 - 9 position += codeLength code, codeLength = runeAt(body, position) continue } else { break } } return position, nil } var description string description = fmt.Sprintf("Invalid number, expected digit but got: %v.", printCharCode(code)) return position, gqlerrors.NewSyntaxError(s, position, description) } func readString(s *source.Source, start int) (Token, error) { body := s.Body position := start + 1 runePosition := start + 1 chunkStart := position var code rune var n int var valueBuffer bytes.Buffer for { code, n = runeAt(body, position) if position < len(body) && // not LineTerminator code != 0x000A && code != 0x000D && // not Quote (") code != '"' { // SourceCharacter if code < 0x0020 && code != 0x0009 { return Token{}, gqlerrors.NewSyntaxError(s, runePosition, fmt.Sprintf(`Invalid character within String: %v.`, printCharCode(code))) } position += n runePosition++ if code == '\\' { // \ valueBuffer.Write(body[chunkStart : position-1]) code, n = runeAt(body, position) switch code { case '"': valueBuffer.WriteRune('"') break case '/': valueBuffer.WriteRune('/') break case '\\': valueBuffer.WriteRune('\\') break case 'b': valueBuffer.WriteRune('\b') break case 'f': valueBuffer.WriteRune('\f') break case 'n': valueBuffer.WriteRune('\n') break case 'r': valueBuffer.WriteRune('\r') break case 't': valueBuffer.WriteRune('\t') break case 'u': // Check if there are at least 4 bytes available if len(body) <= position+4 { return Token{}, gqlerrors.NewSyntaxError(s, runePosition, fmt.Sprintf("Invalid character escape sequence: "+ "\\u%v", string(body[position+1:]))) } charCode := uniCharCode( rune(body[position+1]), rune(body[position+2]), rune(body[position+3]), rune(body[position+4]), ) if charCode < 0 { return Token{}, gqlerrors.NewSyntaxError(s, runePosition, fmt.Sprintf("Invalid character escape sequence: "+ "\\u%v", string(body[position+1:position+5]))) } valueBuffer.WriteRune(charCode) position += 4 runePosition += 4 break default: return Token{}, gqlerrors.NewSyntaxError(s, runePosition, fmt.Sprintf(`Invalid character escape sequence: \\%c.`, code)) } position += n runePosition++ chunkStart = position } continue } else { break } } if code != '"' { // quote (") return Token{}, gqlerrors.NewSyntaxError(s, runePosition, "Unterminated string.") } stringContent := body[chunkStart:position] valueBuffer.Write(stringContent) value := valueBuffer.String() return makeToken(STRING, start, position+1, value), nil } // readBlockString reads a block string token from the source file. // // """("?"?(\\"""|\\(?!=""")|[^"\\]))*""" func readBlockString(s *source.Source, start int) (Token, error) { body := s.Body position := start + 3 runePosition := start + 3 chunkStart := position var valueBuffer bytes.Buffer for { // Stop if we've reached the end of the buffer if position >= len(body) { break } code, n := runeAt(body, position) // Closing Triple-Quote (""") if code == '"' { x, _ := runeAt(body, position+1) y, _ := runeAt(body, position+2) if x == '"' && y == '"' { stringContent := body[chunkStart:position] valueBuffer.Write(stringContent) value := blockStringValue(valueBuffer.String()) return makeToken(BLOCK_STRING, start, position+3, value), nil } } // SourceCharacter if code < 0x0020 && code != 0x0009 && code != 0x000a && code != 0x000d { return Token{}, gqlerrors.NewSyntaxError(s, runePosition, fmt.Sprintf(`Invalid character within String: %v.`, printCharCode(code))) } // Escape Triple-Quote (\""") if code == '\\' { // \ x, _ := runeAt(body, position+1) y, _ := runeAt(body, position+2) z, _ := runeAt(body, position+3) if x == '"' && y == '"' && z == '"' { stringContent := append(body[chunkStart:position], []byte(`"""`)...) valueBuffer.Write(stringContent) position += 4 // account for `"""` characters runePosition += 4 // " " " " chunkStart = position continue } } position += n runePosition++ } return Token{}, gqlerrors.NewSyntaxError(s, runePosition, "Unterminated string.") } var splitLinesRegex = regexp.MustCompile("\r\n|[\n\r]") // This implements the GraphQL spec's BlockStringValue() static algorithm. // // 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. // // Spec: http://facebook.github.io/graphql/draft/#BlockStringValue() // Heavily borrows from: https://github.com/graphql/graphql-js/blob/8e0c599ceccfa8c40d6edf3b72ee2a71490b10e0/src/language/blockStringValue.js func blockStringValue(in string) string { // Expand a block string's raw value into independent lines. lines := splitLinesRegex.Split(in, -1) // Remove common indentation from all lines but first commonIndent := -1 for i := 1; i < len(lines); i++ { line := lines[i] indent := leadingWhitespaceLen(line) if indent < len(line) && (commonIndent == -1 || indent < commonIndent) { commonIndent = indent if commonIndent == 0 { break } } } if commonIndent > 0 { for i, line := range lines { if commonIndent > len(line) { continue } lines[i] = line[commonIndent:] } } // Remove leading blank lines. for len(lines) > 0 && lineIsBlank(lines[0]) { lines = lines[1:] } // Remove trailing blank lines. for len(lines) > 0 && lineIsBlank(lines[len(lines)-1]) { i := len(lines) - 1 lines = append(lines[:i], lines[i+1:]...) } // Return a string of the lines joined with U+000A. return strings.Join(lines, "\n") } // leadingWhitespaceLen returns count of whitespace characters on given line. func leadingWhitespaceLen(in string) (n int) { for _, ch := range in { if ch == ' ' || ch == '\t' { n++ } else { break } } return } // lineIsBlank returns true when given line has no content. func lineIsBlank(in string) bool { return leadingWhitespaceLen(in) == len(in) } // Converts four hexadecimal chars to the integer that the // string represents. For example, uniCharCode('0','0','0','f') // will return 15, and uniCharCode('0','0','f','f') returns 255. // Returns a negative number on error, if a char was invalid. // This is implemented by noting that char2hex() returns -1 on error, // which means the result of ORing the char2hex() will also be negative. func uniCharCode(a, b, c, d rune) rune { return rune(char2hex(a)<<12 | char2hex(b)<<8 | char2hex(c)<<4 | char2hex(d)) } // Converts a hex character to its integer value. // '0' becomes 0, '9' becomes 9 // 'A' becomes 10, 'F' becomes 15 // 'a' becomes 10, 'f' becomes 15 // Returns -1 on error. func char2hex(a rune) int { if a >= 48 && a <= 57 { // 0-9 return int(a) - 48 } else if a >= 65 && a <= 70 { // A-F return int(a) - 55 } else if a >= 97 && a <= 102 { // a-f return int(a) - 87 } return -1 } func makeToken(kind TokenKind, start int, end int, value string) Token { return Token{Kind: kind, Start: start, End: end, Value: value} } func printCharCode(code rune) string { // NaN/undefined represents access beyond the end of the file. if code < 0 { return "" } // print as ASCII for printable range if code >= 0x0020 && code < 0x007F { return fmt.Sprintf(`"%c"`, code) } // Otherwise print the escaped form. e.g. `"\\u0007"` return fmt.Sprintf(`"\\u%04X"`, code) } func readToken(s *source.Source, fromPosition int) (Token, error) { body := s.Body bodyLength := len(body) position, runePosition := positionAfterWhitespace(body, fromPosition) if position >= bodyLength { return makeToken(EOF, position, position, ""), nil } code, codeLength := runeAt(body, position) // SourceCharacter if code < 0x0020 && code != 0x0009 && code != 0x000A && code != 0x000D { return Token{}, gqlerrors.NewSyntaxError(s, runePosition, fmt.Sprintf(`Invalid character %v`, printCharCode(code))) } switch code { // ! case '!': return makeToken(BANG, position, position+1, ""), nil // $ case '$': return makeToken(DOLLAR, position, position+1, ""), nil // & case '&': return makeToken(AMP, position, position+1, ""), nil // ( case '(': return makeToken(PAREN_L, position, position+1, ""), nil // ) case ')': return makeToken(PAREN_R, position, position+1, ""), nil // . case '.': next1, _ := runeAt(body, position+1) next2, _ := runeAt(body, position+2) if next1 == '.' && next2 == '.' { return makeToken(SPREAD, position, position+3, ""), nil } break // : case ':': return makeToken(COLON, position, position+1, ""), nil // = case '=': return makeToken(EQUALS, position, position+1, ""), nil // @ case '@': return makeToken(AT, position, position+1, ""), nil // [ case '[': return makeToken(BRACKET_L, position, position+1, ""), nil // ] case ']': return makeToken(BRACKET_R, position, position+1, ""), nil // { case '{': return makeToken(BRACE_L, position, position+1, ""), nil // | case '|': return makeToken(PIPE, position, position+1, ""), nil // } case '}': return makeToken(BRACE_R, position, position+1, ""), nil // A-Z case 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z': return readName(s, position, runePosition), nil // _ // a-z case '_', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z': return readName(s, position, runePosition), nil // - // 0-9 case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': token, err := readNumber(s, position, code, codeLength) if err != nil { return token, err } return token, nil // " case '"': var token Token var err error x, _ := runeAt(body, position+1) y, _ := runeAt(body, position+2) if x == '"' && y == '"' { token, err = readBlockString(s, position) } else { token, err = readString(s, position) } return token, err } description := fmt.Sprintf("Unexpected character %v.", printCharCode(code)) return Token{}, gqlerrors.NewSyntaxError(s, runePosition, description) } // Gets the rune from the byte array at given byte position and it's width in bytes func runeAt(body []byte, position int) (code rune, charWidth int) { if len(body) <= position { // return -1, utf8.RuneError } c := body[position] if c < utf8.RuneSelf { return rune(c), 1 } r, n := utf8.DecodeRune(body[position:]) return r, n } // Reads from body starting at startPosition until it finds a non-whitespace // or commented character, then returns the position of that character for lexing. // lexing. // Returns both byte positions and rune position func positionAfterWhitespace(body []byte, startPosition int) (position int, runePosition int) { bodyLength := len(body) position = startPosition runePosition = startPosition for { if position < bodyLength { code, n := runeAt(body, position) // Skip Ignored if code == 0xFEFF || // BOM // White Space code == 0x0009 || // tab code == 0x0020 || // space // Line Terminator code == 0x000A || // new line code == 0x000D || // carriage return // Comma code == 0x002C { position += n runePosition++ } else if code == 35 { // # position += n runePosition++ for { code, n := runeAt(body, position) if position < bodyLength && code != 0 && // SourceCharacter but not LineTerminator (code > 0x001F || code == 0x0009) && code != 0x000A && code != 0x000D { position += n runePosition++ continue } else { break } } } else { break } continue } else { break } } return position, runePosition } func GetTokenDesc(token Token) string { if token.Value == "" { return token.Kind.String() } return fmt.Sprintf("%s \"%s\"", token.Kind.String(), token.Value) } ================================================ FILE: language/lexer/lexer_test.go ================================================ package lexer import ( "reflect" "testing" "github.com/graphql-go/graphql/language/source" ) type Test struct { Body string Expected interface{} } func createSource(body string) *source.Source { return source.NewSource(&source.Source{Body: []byte(body)}) } func TestLexer_GetTokenDesc(t *testing.T) { expected := `Name "foo"` tokenDescription := GetTokenDesc(Token{ Kind: NAME, Start: 2, End: 5, Value: "foo", }) if expected != tokenDescription { t.Errorf("Expected %v, got %v", expected, tokenDescription) } expected = `Name` tokenDescription = GetTokenDesc(Token{ Kind: NAME, Start: 0, End: 0, Value: "", }) if expected != tokenDescription { t.Errorf("Expected %v, got %v", expected, tokenDescription) } expected = `String "foo"` tokenDescription = GetTokenDesc(Token{ Kind: STRING, Start: 2, End: 5, Value: "foo", }) if expected != tokenDescription { t.Errorf("Expected %v, got %v", expected, tokenDescription) } expected = `String` tokenDescription = GetTokenDesc(Token{ Kind: STRING, Start: 0, End: 0, Value: "", }) if expected != tokenDescription { t.Errorf("Expected %v, got %v", expected, tokenDescription) } } func TestLexer_DisallowsUncommonControlCharacters(t *testing.T) { tests := []Test{ { Body: "\u0007", Expected: `Syntax Error GraphQL (1:1) Invalid character "\\u0007" 1: \u0007 ^ `, }, } for _, test := range tests { _, err := Lex(createSource(test.Body))(0) if err == nil { t.Errorf("unexpected nil error\nexpected:\n%v\n\ngot:\n%v", test.Expected, err) } if err.Error() != test.Expected { t.Errorf("unexpected error.\nexpected:\n%v\n\ngot:\n%v", test.Expected, err.Error()) } } } func TestLexer_AcceptsBOMHeader(t *testing.T) { tests := []Test{ { Body: "\uFEFF foo", Expected: Token{ Kind: NAME, Start: 2, End: 5, Value: "foo", }, }, } for _, test := range tests { token, err := Lex(&source.Source{Body: []byte(test.Body)})(0) if err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual(token, test.Expected) { t.Errorf("unexpected token, expected: %v, got: %v", test.Expected, token) } } } func TestLexer_SkipsWhiteSpace(t *testing.T) { tests := []Test{ { Body: ` foo `, Expected: Token{ Kind: NAME, Start: 6, End: 9, Value: "foo", }, }, { Body: ` #comment foo#comment `, Expected: Token{ Kind: NAME, Start: 18, End: 21, Value: "foo", }, }, { Body: `,,,foo,,,`, Expected: Token{ Kind: NAME, Start: 3, End: 6, Value: "foo", }, }, { Body: ``, Expected: Token{ Kind: EOF, Start: 0, End: 0, Value: "", }, }, } for _, test := range tests { token, err := Lex(&source.Source{Body: []byte(test.Body)})(0) if err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual(token, test.Expected) { t.Errorf("unexpected token, expected: %v, got: %v, body: %s", test.Expected, token, test.Body) } } } func TestLexer_ErrorsRespectWhitespace(t *testing.T) { body := ` ? ` _, err := Lex(createSource(body))(0) expected := "Syntax Error GraphQL (3:5) Unexpected character \"?\".\n\n2: \n3: ?\n ^\n4: \n" if err == nil { t.Fatalf("unexpected nil error\nexpected:\n%v\n\ngot:\n%v", expected, err) } if err.Error() != expected { t.Fatalf("unexpected error.\nexpected:\n%v\n\ngot:\n%v", expected, err.Error()) } } func TestLexer_LexesNames(t *testing.T) { tests := []Test{ { Body: "simple", Expected: Token{ Kind: NAME, Start: 0, End: 6, Value: "simple", }, }, { Body: "Capital", Expected: Token{ Kind: NAME, Start: 0, End: 7, Value: "Capital", }, }, } for _, test := range tests { token, err := Lex(&source.Source{Body: []byte(test.Body)})(0) if err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual(token, test.Expected) { t.Errorf("unexpected token, expected: %v, got: %v", test.Expected, token) } } } func TestLexer_LexesStrings(t *testing.T) { tests := []Test{ { Body: "\"simple\"", Expected: Token{ Kind: STRING, Start: 0, End: 8, Value: "simple", }, }, { Body: "\" white space \"", Expected: Token{ Kind: STRING, Start: 0, End: 15, Value: " white space ", }, }, { Body: "\"quote \\\"\"", Expected: Token{ Kind: STRING, Start: 0, End: 10, Value: `quote "`, }, }, { Body: "\"escaped \\n\\r\\b\\t\\f\"", Expected: Token{ Kind: STRING, Start: 0, End: 20, Value: "escaped \n\r\b\t\f", }, }, { Body: "\"slashes \\\\ \\/\"", Expected: Token{ Kind: STRING, Start: 0, End: 15, Value: "slashes \\ /", }, }, { Body: "\"unicode \\u1234\\u5678\\u90AB\\uCDEF\"", Expected: Token{ Kind: STRING, Start: 0, End: 34, Value: "unicode \u1234\u5678\u90AB\uCDEF", }, }, { Body: "\"unicode фы世界\"", Expected: Token{ Kind: STRING, Start: 0, End: 20, Value: "unicode фы世界", }, }, { Body: "\"фы世界\"", Expected: Token{ Kind: STRING, Start: 0, End: 12, Value: "фы世界", }, }, { Body: "\"Has a фы世界 multi-byte character.\"", Expected: Token{ Kind: STRING, Start: 0, End: 40, Value: "Has a фы世界 multi-byte character.", }, }, } for _, test := range tests { token, err := Lex(&source.Source{Body: []byte(test.Body)})(0) if err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual(token, test.Expected) { t.Errorf("unexpected token, expected: %v, got: %v", test.Expected, token) } } } func TestLexer_ReportsUsefulStringErrors(t *testing.T) { tests := []Test{ { Body: "\"", Expected: `Syntax Error GraphQL (1:2) Unterminated string. 1: " ^ `, }, { Body: "\"no end quote", Expected: `Syntax Error GraphQL (1:14) Unterminated string. 1: "no end quote ^ `, }, { Body: "\"contains unescaped \u0007 control char\"", Expected: `Syntax Error GraphQL (1:21) Invalid character within String: "\\u0007". 1: "contains unescaped \u0007 control char" ^ `, }, { Body: "\"null-byte is not \u0000 end of file\"", Expected: `Syntax Error GraphQL (1:19) Invalid character within String: "\\u0000". 1: "null-byte is not \u0000 end of file" ^ `, }, { Body: "\"multi\nline\"", Expected: `Syntax Error GraphQL (1:7) Unterminated string. 1: "multi ^ 2: line" `, }, { Body: "\"multi\rline\"", Expected: `Syntax Error GraphQL (1:7) Unterminated string. 1: "multi ^ 2: line" `, }, { Body: "\"bad \\z esc\"", Expected: `Syntax Error GraphQL (1:7) Invalid character escape sequence: \\z. 1: "bad \z esc" ^ `, }, { Body: "\"bad \\x esc\"", Expected: `Syntax Error GraphQL (1:7) Invalid character escape sequence: \\x. 1: "bad \x esc" ^ `, }, { Body: "\"bad \\u1 esc\"", Expected: `Syntax Error GraphQL (1:7) Invalid character escape sequence: \u1 es 1: "bad \u1 esc" ^ `, }, { Body: "\"bad \\u0XX1 esc\"", Expected: `Syntax Error GraphQL (1:7) Invalid character escape sequence: \u0XX1 1: "bad \u0XX1 esc" ^ `, }, { Body: "\"bad \\uXXXX esc\"", Expected: `Syntax Error GraphQL (1:7) Invalid character escape sequence: \uXXXX 1: "bad \uXXXX esc" ^ `, }, { Body: "\"bad \\uFXXX esc\"", Expected: `Syntax Error GraphQL (1:7) Invalid character escape sequence: \uFXXX 1: "bad \uFXXX esc" ^ `, }, { Body: "\"bad \\uXXXF esc\"", Expected: `Syntax Error GraphQL (1:7) Invalid character escape sequence: \uXXXF 1: "bad \uXXXF esc" ^ `, }, { Body: "\"bad \\u123", Expected: `Syntax Error GraphQL (1:7) Invalid character escape sequence: \u123 1: "bad \u123 ^ `, }, { // some unicode chars take more than one column of text // current implementation does not handle this Body: "\"bфы世ыы𠱸d \\uXXXF esc\"", Expected: `Syntax Error GraphQL (1:12) Invalid character escape sequence: \uXXXF 1: "bфы世ыы𠱸d \uXXXF esc" ^ `, }, } for _, test := range tests { _, err := Lex(createSource(test.Body))(0) if err == nil { t.Errorf("unexpected nil error\nexpected:\n%v\n\ngot:\n%v", test.Expected, err) } if err.Error() != test.Expected { t.Errorf("unexpected error.\nexpected:\n%v\n\ngot:\n%v", test.Expected, err.Error()) } } } func TestLexer_LexesBlockStrings(t *testing.T) { tests := []Test{ { Body: `""""""`, Expected: Token{ Kind: BLOCK_STRING, Start: 0, End: 6, Value: "", }, }, { Body: `"""simple"""`, Expected: Token{ Kind: BLOCK_STRING, Start: 0, End: 12, Value: "simple", }, }, { Body: `""" white space """`, Expected: Token{ Kind: BLOCK_STRING, Start: 0, End: 19, Value: " white space ", }, }, { Body: ` """ white space """ """ white space """ """ white space """ `, Expected: Token{ Kind: BLOCK_STRING, Start: 5, End: 25, Value: " white space ", }, }, { Body: ` """ my great description spans multiple lines with breaks """ `, Expected: Token{ Kind: BLOCK_STRING, Start: 5, End: 89, Value: "my great description\nspans multiple lines\n\nwith breaks", }, }, { Body: `"""contains " quote"""`, Expected: Token{ Kind: BLOCK_STRING, Start: 0, End: 22, Value: `contains " quote`, }, }, { Body: `"""contains \""" triplequote"""`, Expected: Token{ Kind: BLOCK_STRING, Start: 0, End: 31, Value: `contains """ triplequote`, }, }, { Body: "\"\"\"multi\nline\"\"\"", Expected: Token{ Kind: BLOCK_STRING, Start: 0, End: 16, Value: "multi\nline", }, }, { Body: "\"\"\"multi\rline\r\nnormalized\"\"\"", Expected: Token{ Kind: BLOCK_STRING, Start: 0, End: 28, Value: "multi\nline\nnormalized", }, }, { Body: "\"\"\"unescaped \\n\\r\\b\\t\\f\\u1234\"\"\"", Expected: Token{ Kind: BLOCK_STRING, Start: 0, End: 32, Value: "unescaped \\n\\r\\b\\t\\f\\u1234", }, }, { Body: "\"\"\"slashes \\\\ \\/\"\"\"", Expected: Token{ Kind: BLOCK_STRING, Start: 0, End: 19, Value: "slashes \\\\ \\/", }, }, } for _, test := range tests { token, err := Lex(&source.Source{Body: []byte(test.Body)})(0) if err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual(token, test.Expected) { t.Errorf("unexpected token, expected: %v, got: %v", test.Expected, token) } } } func TestLexer_ReportsUsefulBlockStringErrors(t *testing.T) { tests := []Test{ { Body: `"""`, Expected: `Syntax Error GraphQL (1:4) Unterminated string. 1: """ ^ `, }, { Body: `"""no end quote`, Expected: `Syntax Error GraphQL (1:16) Unterminated string. 1: """no end quote ^ `, }, { Body: "\"\"\"contains unescaped \u0007 control char\"\"\"", Expected: `Syntax Error GraphQL (1:23) Invalid character within String: "\\u0007". 1: """contains unescaped \u0007 control char""" ^ `, }, { Body: "\"\"\"null-byte is not \u0000 end of file\"\"\"", Expected: `Syntax Error GraphQL (1:21) Invalid character within String: "\\u0000". 1: """null-byte is not \u0000 end of file""" ^ `, }, } for _, test := range tests { _, err := Lex(createSource(test.Body))(0) if err == nil { t.Errorf("unexpected nil error\nexpected:\n%v\n\ngot:\n%v", test.Expected, err) } if err.Error() != test.Expected { t.Errorf("unexpected error.\nexpected:\n%v\n\ngot:\n%v", test.Expected, err.Error()) } } } func TestLexer_LexesNumbers(t *testing.T) { tests := []Test{ { Body: "4", Expected: Token{ Kind: INT, Start: 0, End: 1, Value: "4", }, }, { Body: "4.123", Expected: Token{ Kind: FLOAT, Start: 0, End: 5, Value: "4.123", }, }, { Body: "-4", Expected: Token{ Kind: INT, Start: 0, End: 2, Value: "-4", }, }, { Body: "9", Expected: Token{ Kind: INT, Start: 0, End: 1, Value: "9", }, }, { Body: "0", Expected: Token{ Kind: INT, Start: 0, End: 1, Value: "0", }, }, { Body: "-4.123", Expected: Token{ Kind: FLOAT, Start: 0, End: 6, Value: "-4.123", }, }, { Body: "0.123", Expected: Token{ Kind: FLOAT, Start: 0, End: 5, Value: "0.123", }, }, { Body: "123e4", Expected: Token{ Kind: FLOAT, Start: 0, End: 5, Value: "123e4", }, }, { Body: "123E4", Expected: Token{ Kind: FLOAT, Start: 0, End: 5, Value: "123E4", }, }, { Body: "123e-4", Expected: Token{ Kind: FLOAT, Start: 0, End: 6, Value: "123e-4", }, }, { Body: "123e+4", Expected: Token{ Kind: FLOAT, Start: 0, End: 6, Value: "123e+4", }, }, { Body: "-1.123e4", Expected: Token{ Kind: FLOAT, Start: 0, End: 8, Value: "-1.123e4", }, }, { Body: "-1.123E4", Expected: Token{ Kind: FLOAT, Start: 0, End: 8, Value: "-1.123E4", }, }, { Body: "-1.123e-4", Expected: Token{ Kind: FLOAT, Start: 0, End: 9, Value: "-1.123e-4", }, }, { Body: "-1.123e+4", Expected: Token{ Kind: FLOAT, Start: 0, End: 9, Value: "-1.123e+4", }, }, { Body: "-1.123e4567", Expected: Token{ Kind: FLOAT, Start: 0, End: 11, Value: "-1.123e4567", }, }, } for _, test := range tests { token, err := Lex(createSource(test.Body))(0) if err != nil { t.Errorf("unexpected error: %v, test: %s", err, test) } if !reflect.DeepEqual(token, test.Expected) { t.Errorf("unexpected token, expected: %v, got: %v, test: %v", test.Expected, token, test) } } } func TestLexer_ReportsUsefulNumberErrors(t *testing.T) { tests := []Test{ { Body: "00", Expected: `Syntax Error GraphQL (1:2) Invalid number, unexpected digit after 0: "0". 1: 00 ^ `, }, { Body: "+1", Expected: `Syntax Error GraphQL (1:1) Unexpected character "+". 1: +1 ^ `, }, { Body: "1.", Expected: `Syntax Error GraphQL (1:3) Invalid number, expected digit but got: . 1: 1. ^ `, }, { Body: ".123", Expected: `Syntax Error GraphQL (1:1) Unexpected character ".". 1: .123 ^ `, }, { Body: "1.A", Expected: `Syntax Error GraphQL (1:3) Invalid number, expected digit but got: "A". 1: 1.A ^ `, }, { Body: "-A", Expected: `Syntax Error GraphQL (1:2) Invalid number, expected digit but got: "A". 1: -A ^ `, }, { Body: "1.0e", Expected: `Syntax Error GraphQL (1:5) Invalid number, expected digit but got: . 1: 1.0e ^ `, }, { Body: "1.0eA", Expected: `Syntax Error GraphQL (1:5) Invalid number, expected digit but got: "A". 1: 1.0eA ^ `, }, } for _, test := range tests { _, err := Lex(createSource(test.Body))(0) if err == nil { t.Errorf("unexpected nil error\nexpected:\n%v\n\ngot:\n%v", test.Expected, err) } if err.Error() != test.Expected { t.Errorf("unexpected error.\nexpected:\n%v\n\ngot:\n%v", test.Expected, err.Error()) } } } func TestLexer_LexesPunctuation(t *testing.T) { tests := []Test{ { Body: "!", Expected: Token{ Kind: BANG, Start: 0, End: 1, Value: "", }, }, { Body: "$", Expected: Token{ Kind: DOLLAR, Start: 0, End: 1, Value: "", }, }, { Body: "(", Expected: Token{ Kind: PAREN_L, Start: 0, End: 1, Value: "", }, }, { Body: ")", Expected: Token{ Kind: PAREN_R, Start: 0, End: 1, Value: "", }, }, { Body: "...", Expected: Token{ Kind: SPREAD, Start: 0, End: 3, Value: "", }, }, { Body: ":", Expected: Token{ Kind: COLON, Start: 0, End: 1, Value: "", }, }, { Body: "=", Expected: Token{ Kind: EQUALS, Start: 0, End: 1, Value: "", }, }, { Body: "@", Expected: Token{ Kind: AT, Start: 0, End: 1, Value: "", }, }, { Body: "[", Expected: Token{ Kind: BRACKET_L, Start: 0, End: 1, Value: "", }, }, { Body: "]", Expected: Token{ Kind: BRACKET_R, Start: 0, End: 1, Value: "", }, }, { Body: "{", Expected: Token{ Kind: BRACE_L, Start: 0, End: 1, Value: "", }, }, { Body: "|", Expected: Token{ Kind: PIPE, Start: 0, End: 1, Value: "", }, }, { Body: "}", Expected: Token{ Kind: BRACE_R, Start: 0, End: 1, Value: "", }, }, } for _, test := range tests { token, err := Lex(createSource(test.Body))(0) if err != nil { t.Errorf("unexpected error :%v, test: %v", err, test) } if !reflect.DeepEqual(token, test.Expected) { t.Errorf("unexpected token, expected: %v, got: %v, test: %v", test.Expected, token, test) } } } func TestLexer_ReportsUsefulUnknownCharacterError(t *testing.T) { tests := []Test{ { Body: "..", Expected: `Syntax Error GraphQL (1:1) Unexpected character ".". 1: .. ^ `, }, { Body: "?", Expected: `Syntax Error GraphQL (1:1) Unexpected character "?". 1: ? ^ `, }, { Body: "\u203B", Expected: `Syntax Error GraphQL (1:1) Unexpected character "\\u203B". 1: ※ ^ `, }, { Body: "\u203b", Expected: `Syntax Error GraphQL (1:1) Unexpected character "\\u203B". 1: ※ ^ `, }, { Body: "ф", Expected: `Syntax Error GraphQL (1:1) Unexpected character "\\u0444". 1: ф ^ `, }, } for _, test := range tests { _, err := Lex(createSource(test.Body))(0) if err == nil { t.Errorf("unexpected nil error\nexpected:\n%v\n\ngot:\n%v", test.Expected, err) } if err.Error() != test.Expected { t.Errorf("unexpected error.\nexpected:\n%v\n\ngot:\n%v", test.Expected, err.Error()) } } } func TestLexer_ReportsUsefulInformationForDashesInNames(t *testing.T) { q := "a-b" lexer := Lex(createSource(q)) firstToken, err := lexer(0) if err != nil { t.Fatalf("unexpected error: %v", err) } firstTokenExpected := Token{ Kind: NAME, Start: 0, End: 1, Value: "a", } if !reflect.DeepEqual(firstToken, firstTokenExpected) { t.Fatalf("unexpected token, expected: %v, got: %v", firstTokenExpected, firstToken) } errExpected := `Syntax Error GraphQL (1:3) Invalid number, expected digit but got: "b". 1: a-b ^ ` token, err := lexer(0) if err == nil { t.Fatalf("unexpected nil error: %v", err) } if err.Error() != errExpected { t.Fatalf("unexpected error, token:%v\nexpected:\n%v\n\ngot:\n%v", token, errExpected, err.Error()) } } ================================================ FILE: language/location/location.go ================================================ package location import ( "regexp" "github.com/graphql-go/graphql/language/source" ) type SourceLocation struct { Line int `json:"line"` Column int `json:"column"` } func GetLocation(s *source.Source, position int) SourceLocation { body := []byte{} if s != nil { body = s.Body } line := 1 column := position + 1 lineRegexp := regexp.MustCompile("\r\n|[\n\r]") matches := lineRegexp.FindAllIndex(body, -1) for _, match := range matches { matchIndex := match[0] if matchIndex < position { line++ l := len(s.Body[match[0]:match[1]]) column = position + 1 - (matchIndex + l) continue } else { break } } return SourceLocation{Line: line, Column: column} } ================================================ FILE: language/parser/parser.go ================================================ package parser import ( "fmt" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/lexer" "github.com/graphql-go/graphql/language/source" ) type parseFn func(parser *Parser) (interface{}, error) // parse operation, fragment, typeSystem{schema, type..., extension, directives} definition type parseDefinitionFn func(parser *Parser) (ast.Node, error) var tokenDefinitionFn map[string]parseDefinitionFn func init() { tokenDefinitionFn = make(map[string]parseDefinitionFn) { tokenDefinitionFn[lexer.FRAGMENT] = parseFragmentDefinition tokenDefinitionFn[lexer.QUERY] = parseOperationDefinition tokenDefinitionFn[lexer.MUTATION] = parseOperationDefinition tokenDefinitionFn[lexer.SUBSCRIPTION] = parseOperationDefinition tokenDefinitionFn[lexer.SCHEMA] = parseSchemaDefinition tokenDefinitionFn[lexer.SCALAR] = parseScalarTypeDefinition tokenDefinitionFn[lexer.TYPE] = parseObjectTypeDefinition tokenDefinitionFn[lexer.INTERFACE] = parseInterfaceTypeDefinition tokenDefinitionFn[lexer.UNION] = parseUnionTypeDefinition tokenDefinitionFn[lexer.ENUM] = parseEnumTypeDefinition tokenDefinitionFn[lexer.INPUT] = parseInputObjectTypeDefinition tokenDefinitionFn[lexer.EXTEND] = parseTypeExtensionDefinition tokenDefinitionFn[lexer.DIRECTIVE] = parseDirectiveDefinition } } type ParseOptions struct { NoLocation bool NoSource bool } type ParseParams struct { Source interface{} Options ParseOptions } type Parser struct { LexToken lexer.Lexer Source *source.Source Options ParseOptions PrevEnd int Token lexer.Token } func Parse(p ParseParams) (*ast.Document, error) { var sourceObj *source.Source switch src := p.Source.(type) { case *source.Source: sourceObj = src default: body, _ := p.Source.(string) sourceObj = source.NewSource(&source.Source{Body: []byte(body)}) } parser, err := makeParser(sourceObj, p.Options) if err != nil { return nil, err } doc, err := parseDocument(parser) if err != nil { return nil, err } return doc, nil } // ParseValue parses params and returns ast value func ParseValue(p ParseParams) (ast.Value, error) { var value ast.Value var sourceObj *source.Source switch src := p.Source.(type) { case *source.Source: sourceObj = src default: body, _ := p.Source.(string) sourceObj = source.NewSource(&source.Source{Body: []byte(body)}) } parser, err := makeParser(sourceObj, p.Options) if err != nil { return value, err } value, err = parseValueLiteral(parser, false) if err != nil { return value, err } return value, nil } // Converts a name lex token into a name parse node. func parseName(parser *Parser) (*ast.Name, error) { token, err := expect(parser, lexer.NAME) if err != nil { return nil, err } return ast.NewName(&ast.Name{ Value: token.Value, Loc: loc(parser, token.Start), }), nil } func makeParser(s *source.Source, opts ParseOptions) (*Parser, error) { lexToken := lexer.Lex(s) token, err := lexToken(0) if err != nil { return &Parser{}, err } return &Parser{ LexToken: lexToken, Source: s, Options: opts, PrevEnd: 0, Token: token, }, nil } /* Implements the parsing rules in the Document section. */ func parseDocument(parser *Parser) (*ast.Document, error) { var ( nodes []ast.Node node ast.Node item parseDefinitionFn err error ) start := parser.Token.Start for { if skp, err := skip(parser, lexer.EOF); err != nil { return nil, err } else if skp { break } switch kind := parser.Token.Kind; kind { case lexer.BRACE_L: item = parseOperationDefinition case lexer.NAME, lexer.STRING, lexer.BLOCK_STRING: item = parseTypeSystemDefinition default: return nil, unexpected(parser, lexer.Token{}) } if node, err = item(parser); err != nil { return nil, err } nodes = append(nodes, node) } return ast.NewDocument(&ast.Document{ Loc: loc(parser, start), Definitions: nodes, }), nil } /* Implements the parsing rules in the Operations section. */ /** * OperationDefinition : * - SelectionSet * - OperationType Name? VariableDefinitions? Directives? SelectionSet */ func parseOperationDefinition(parser *Parser) (ast.Node, error) { var ( operation string variableDefinitions []*ast.VariableDefinition name *ast.Name directives []*ast.Directive selectionSet *ast.SelectionSet err error ) start := parser.Token.Start if peek(parser, lexer.BRACE_L) { selectionSet, err := parseSelectionSet(parser) if err != nil { return nil, err } return ast.NewOperationDefinition(&ast.OperationDefinition{ Operation: ast.OperationTypeQuery, Directives: []*ast.Directive{}, SelectionSet: selectionSet, Loc: loc(parser, start), }), nil } if operation, err = parseOperationType(parser); err != nil { return nil, err } if peek(parser, lexer.NAME) { if name, err = parseName(parser); err != nil { return nil, err } } if variableDefinitions, err = parseVariableDefinitions(parser); err != nil { return nil, err } if directives, err = parseDirectives(parser); err != nil { return nil, err } if selectionSet, err = parseSelectionSet(parser); err != nil { return nil, err } return ast.NewOperationDefinition(&ast.OperationDefinition{ Operation: operation, Name: name, VariableDefinitions: variableDefinitions, Directives: directives, SelectionSet: selectionSet, Loc: loc(parser, start), }), nil } /** * OperationType : one of query mutation subscription */ func parseOperationType(parser *Parser) (string, error) { operationToken, err := expect(parser, lexer.NAME) if err != nil { return "", err } switch operationToken.Value { case ast.OperationTypeQuery: return operationToken.Value, nil case ast.OperationTypeMutation: return operationToken.Value, nil case ast.OperationTypeSubscription: return operationToken.Value, nil default: return "", unexpected(parser, operationToken) } } /** * VariableDefinitions : ( VariableDefinition+ ) */ func parseVariableDefinitions(parser *Parser) ([]*ast.VariableDefinition, error) { variableDefinitions := []*ast.VariableDefinition{} if !peek(parser, lexer.PAREN_L) { return variableDefinitions, nil } if vdefs, err := reverse(parser, lexer.PAREN_L, parseVariableDefinition, lexer.PAREN_R, true, ); err != nil { return variableDefinitions, err } else { for _, vdef := range vdefs { variableDefinitions = append(variableDefinitions, vdef.(*ast.VariableDefinition)) } } return variableDefinitions, nil } /** * VariableDefinition : Variable : Type DefaultValue? */ func parseVariableDefinition(parser *Parser) (interface{}, error) { var ( variable *ast.Variable ttype ast.Type err error ) start := parser.Token.Start if variable, err = parseVariable(parser); err != nil { return nil, err } if _, err = expect(parser, lexer.COLON); err != nil { return nil, err } if ttype, err = parseType(parser); err != nil { return nil, err } var defaultValue ast.Value if skp, err := skip(parser, lexer.EQUALS); err != nil { return nil, err } else if skp { if defaultValue, err = parseValueLiteral(parser, true); err != nil { return nil, err } } return ast.NewVariableDefinition(&ast.VariableDefinition{ Variable: variable, Type: ttype, DefaultValue: defaultValue, Loc: loc(parser, start), }), nil } /** * Variable : $ Name */ func parseVariable(parser *Parser) (*ast.Variable, error) { var ( err error name *ast.Name ) start := parser.Token.Start if _, err = expect(parser, lexer.DOLLAR); err != nil { return nil, err } if name, err = parseName(parser); err != nil { return nil, err } return ast.NewVariable(&ast.Variable{ Name: name, Loc: loc(parser, start), }), nil } /** * SelectionSet : { Selection+ } */ func parseSelectionSet(parser *Parser) (*ast.SelectionSet, error) { start := parser.Token.Start selections := []ast.Selection{} if iSelections, err := reverse(parser, lexer.BRACE_L, parseSelection, lexer.BRACE_R, true, ); err != nil { return nil, err } else { for _, iSelection := range iSelections { selections = append(selections, iSelection.(ast.Selection)) } } return ast.NewSelectionSet(&ast.SelectionSet{ Selections: selections, Loc: loc(parser, start), }), nil } /** * Selection : * - Field * - FragmentSpread * - InlineFragment */ func parseSelection(parser *Parser) (interface{}, error) { if peek(parser, lexer.SPREAD) { return parseFragment(parser) } return parseField(parser) } /** * Field : Alias? Name Arguments? Directives? SelectionSet? * * Alias : Name : */ func parseField(parser *Parser) (*ast.Field, error) { var ( name *ast.Name alias *ast.Name arguments []*ast.Argument directives []*ast.Directive err error ) start := parser.Token.Start if name, err = parseName(parser); err != nil { return nil, err } if skp, err := skip(parser, lexer.COLON); err != nil { return nil, err } else if skp { alias = name if name, err = parseName(parser); err != nil { return nil, err } } if arguments, err = parseArguments(parser); err != nil { return nil, err } if directives, err = parseDirectives(parser); err != nil { return nil, err } var selectionSet *ast.SelectionSet if peek(parser, lexer.BRACE_L) { if selectionSet, err = parseSelectionSet(parser); err != nil { return nil, err } } return ast.NewField(&ast.Field{ Alias: alias, Name: name, Arguments: arguments, Directives: directives, SelectionSet: selectionSet, Loc: loc(parser, start), }), nil } /** * Arguments : ( Argument+ ) */ func parseArguments(parser *Parser) ([]*ast.Argument, error) { arguments := []*ast.Argument{} if peek(parser, lexer.PAREN_L) { if iArguments, err := reverse(parser, lexer.PAREN_L, parseArgument, lexer.PAREN_R, true, ); err != nil { return arguments, err } else { for _, iArgument := range iArguments { arguments = append(arguments, iArgument.(*ast.Argument)) } } } return arguments, nil } /** * Argument : Name : Value */ func parseArgument(parser *Parser) (interface{}, error) { var ( err error name *ast.Name value ast.Value ) start := parser.Token.Start if name, err = parseName(parser); err != nil { return nil, err } if _, err = expect(parser, lexer.COLON); err != nil { return nil, err } if value, err = parseValueLiteral(parser, false); err != nil { return nil, err } return ast.NewArgument(&ast.Argument{ Name: name, Value: value, Loc: loc(parser, start), }), nil } /* Implements the parsing rules in the Fragments section. */ /** * Corresponds to both FragmentSpread and InlineFragment in the spec. * * FragmentSpread : ... FragmentName Directives? * * InlineFragment : ... TypeCondition? Directives? SelectionSet */ func parseFragment(parser *Parser) (interface{}, error) { var ( err error ) start := parser.Token.Start if _, err = expect(parser, lexer.SPREAD); err != nil { return nil, err } if peek(parser, lexer.NAME) && parser.Token.Value != "on" { name, err := parseFragmentName(parser) if err != nil { return nil, err } directives, err := parseDirectives(parser) if err != nil { return nil, err } return ast.NewFragmentSpread(&ast.FragmentSpread{ Name: name, Directives: directives, Loc: loc(parser, start), }), nil } var typeCondition *ast.Named if parser.Token.Value == "on" { if err := advance(parser); err != nil { return nil, err } name, err := parseNamed(parser) if err != nil { return nil, err } typeCondition = name } directives, err := parseDirectives(parser) if err != nil { return nil, err } selectionSet, err := parseSelectionSet(parser) if err != nil { return nil, err } return ast.NewInlineFragment(&ast.InlineFragment{ TypeCondition: typeCondition, Directives: directives, SelectionSet: selectionSet, Loc: loc(parser, start), }), nil } /** * FragmentDefinition : * - fragment FragmentName on TypeCondition Directives? SelectionSet * * TypeCondition : NamedType */ func parseFragmentDefinition(parser *Parser) (ast.Node, error) { start := parser.Token.Start _, err := expectKeyWord(parser, lexer.FRAGMENT) if err != nil { return nil, err } name, err := parseFragmentName(parser) if err != nil { return nil, err } _, err = expectKeyWord(parser, "on") if err != nil { return nil, err } typeCondition, err := parseNamed(parser) if err != nil { return nil, err } directives, err := parseDirectives(parser) if err != nil { return nil, err } selectionSet, err := parseSelectionSet(parser) if err != nil { return nil, err } return ast.NewFragmentDefinition(&ast.FragmentDefinition{ Name: name, TypeCondition: typeCondition, Directives: directives, SelectionSet: selectionSet, Loc: loc(parser, start), }), nil } /** * FragmentName : Name but not `on` */ func parseFragmentName(parser *Parser) (*ast.Name, error) { if parser.Token.Value == "on" { return nil, unexpected(parser, lexer.Token{}) } return parseName(parser) } /* Implements the parsing rules in the Values section. */ /** * Value[Const] : * - [~Const] Variable * - IntValue * - FloatValue * - StringValue * - BooleanValue * - EnumValue * - ListValue[?Const] * - ObjectValue[?Const] * * BooleanValue : one of `true` `false` * * EnumValue : Name but not `true`, `false` or `null` */ func parseValueLiteral(parser *Parser, isConst bool) (ast.Value, error) { token := parser.Token switch token.Kind { case lexer.BRACKET_L: return parseList(parser, isConst) case lexer.BRACE_L: return parseObject(parser, isConst) case lexer.INT: if err := advance(parser); err != nil { return nil, err } return ast.NewIntValue(&ast.IntValue{ Value: token.Value, Loc: loc(parser, token.Start), }), nil case lexer.FLOAT: if err := advance(parser); err != nil { return nil, err } return ast.NewFloatValue(&ast.FloatValue{ Value: token.Value, Loc: loc(parser, token.Start), }), nil case lexer.BLOCK_STRING, lexer.STRING: return parseStringLiteral(parser) case lexer.NAME: if token.Value == "true" || token.Value == "false" { if err := advance(parser); err != nil { return nil, err } value := true if token.Value == "false" { value = false } return ast.NewBooleanValue(&ast.BooleanValue{ Value: value, Loc: loc(parser, token.Start), }), nil } else if token.Value != "null" { if err := advance(parser); err != nil { return nil, err } return ast.NewEnumValue(&ast.EnumValue{ Value: token.Value, Loc: loc(parser, token.Start), }), nil } case lexer.DOLLAR: if !isConst { return parseVariable(parser) } } return nil, unexpected(parser, lexer.Token{}) } func parseConstValue(parser *Parser) (interface{}, error) { value, err := parseValueLiteral(parser, true) if err != nil { return value, err } return value, nil } func parseValueValue(parser *Parser) (interface{}, error) { return parseValueLiteral(parser, false) } /** * ListValue[Const] : * - [ ] * - [ Value[?Const]+ ] */ func parseList(parser *Parser, isConst bool) (*ast.ListValue, error) { start := parser.Token.Start var item parseFn = parseValueValue if isConst { item = parseConstValue } values := []ast.Value{} if iValues, err := reverse(parser, lexer.BRACKET_L, item, lexer.BRACKET_R, false, ); err != nil { return nil, err } else { for _, iValue := range iValues { values = append(values, iValue.(ast.Value)) } } return ast.NewListValue(&ast.ListValue{ Values: values, Loc: loc(parser, start), }), nil } /** * ObjectValue[Const] : * - { } * - { ObjectField[?Const]+ } */ func parseObject(parser *Parser, isConst bool) (*ast.ObjectValue, error) { start := parser.Token.Start if _, err := expect(parser, lexer.BRACE_L); err != nil { return nil, err } fields := []*ast.ObjectField{} for { if skp, err := skip(parser, lexer.BRACE_R); err != nil { return nil, err } else if skp { break } if field, err := parseObjectField(parser, isConst); err != nil { return nil, err } else { fields = append(fields, field) } } return ast.NewObjectValue(&ast.ObjectValue{ Fields: fields, Loc: loc(parser, start), }), nil } /** * ObjectField[Const] : Name : Value[?Const] */ func parseObjectField(parser *Parser, isConst bool) (*ast.ObjectField, error) { var ( name *ast.Name value ast.Value err error ) start := parser.Token.Start if name, err = parseName(parser); err != nil { return nil, err } if _, err = expect(parser, lexer.COLON); err != nil { return nil, err } if value, err = parseValueLiteral(parser, isConst); err != nil { return nil, err } return ast.NewObjectField(&ast.ObjectField{ Name: name, Value: value, Loc: loc(parser, start), }), nil } /* Implements the parsing rules in the Directives section. */ /** * Directives : Directive+ */ func parseDirectives(parser *Parser) ([]*ast.Directive, error) { directives := []*ast.Directive{} for peek(parser, lexer.AT) { if directive, err := parseDirective(parser); err != nil { return directives, err } else { directives = append(directives, directive) } } return directives, nil } /** * Directive : @ Name Arguments? */ func parseDirective(parser *Parser) (*ast.Directive, error) { var ( err error name *ast.Name args []*ast.Argument ) start := parser.Token.Start if _, err = expect(parser, lexer.AT); err != nil { return nil, err } if name, err = parseName(parser); err != nil { return nil, err } if args, err = parseArguments(parser); err != nil { return nil, err } return ast.NewDirective(&ast.Directive{ Name: name, Arguments: args, Loc: loc(parser, start), }), nil } /* Implements the parsing rules in the Types section. */ /** * Type : * - NamedType * - ListType * - NonNullType */ func parseType(parser *Parser) (ttype ast.Type, err error) { token := parser.Token // [ String! ]! switch token.Kind { case lexer.BRACKET_L: if err = advance(parser); err != nil { return nil, err } if ttype, err = parseType(parser); err != nil { return nil, err } fallthrough case lexer.BRACKET_R: if err = advance(parser); err != nil { return nil, err } ttype = ast.NewList(&ast.List{ Type: ttype, Loc: loc(parser, token.Start), }) case lexer.NAME: if ttype, err = parseNamed(parser); err != nil { return nil, err } } // BANG must be executed if skp, err := skip(parser, lexer.BANG); err != nil { return nil, err } else if skp { ttype = ast.NewNonNull(&ast.NonNull{ Type: ttype, Loc: loc(parser, token.Start), }) } return ttype, nil } /** * NamedType : Name */ func parseNamed(parser *Parser) (*ast.Named, error) { start := parser.Token.Start name, err := parseName(parser) if err != nil { return nil, err } return ast.NewNamed(&ast.Named{ Name: name, Loc: loc(parser, start), }), nil } /* Implements the parsing rules in the Type Definition section. */ /** * TypeSystemDefinition : * - SchemaDefinition * - TypeDefinition * - TypeExtension * - DirectiveDefinition * * TypeDefinition : * - ScalarTypeDefinition * - ObjectTypeDefinition * - InterfaceTypeDefinition * - UnionTypeDefinition * - EnumTypeDefinition * - InputObjectTypeDefinition */ func parseTypeSystemDefinition(parser *Parser) (ast.Node, error) { var ( item parseDefinitionFn err error ) // Many definitions begin with a description and require a lookahead. keywordToken := parser.Token if peekDescription(parser) { if keywordToken, err = lookahead(parser); err != nil { return nil, err } } if keywordToken.Kind != lexer.NAME { return nil, unexpected(parser, keywordToken) } var ok bool if item, ok = tokenDefinitionFn[keywordToken.Value]; !ok { return nil, unexpected(parser, keywordToken) } return item(parser) } /** * SchemaDefinition : schema Directives? { OperationTypeDefinition+ } * * OperationTypeDefinition : OperationType : NamedType */ func parseSchemaDefinition(parser *Parser) (ast.Node, error) { start := parser.Token.Start _, err := expectKeyWord(parser, "schema") if err != nil { return nil, err } directives, err := parseDirectives(parser) if err != nil { return nil, err } operationTypesI, err := reverse( parser, lexer.BRACE_L, parseOperationTypeDefinition, lexer.BRACE_R, true, ) if err != nil { return nil, err } operationTypes := []*ast.OperationTypeDefinition{} for _, op := range operationTypesI { if op, ok := op.(*ast.OperationTypeDefinition); ok { operationTypes = append(operationTypes, op) } } return ast.NewSchemaDefinition(&ast.SchemaDefinition{ OperationTypes: operationTypes, Directives: directives, Loc: loc(parser, start), }), nil } func parseOperationTypeDefinition(parser *Parser) (interface{}, error) { start := parser.Token.Start operation, err := parseOperationType(parser) if err != nil { return nil, err } _, err = expect(parser, lexer.COLON) if err != nil { return nil, err } ttype, err := parseNamed(parser) if err != nil { return nil, err } return ast.NewOperationTypeDefinition(&ast.OperationTypeDefinition{ Operation: operation, Type: ttype, Loc: loc(parser, start), }), nil } /** * ScalarTypeDefinition : Description? scalar Name Directives? */ func parseScalarTypeDefinition(parser *Parser) (ast.Node, error) { start := parser.Token.Start description, err := parseDescription(parser) if err != nil { return nil, err } _, err = expectKeyWord(parser, lexer.SCALAR) if err != nil { return nil, err } name, err := parseName(parser) if err != nil { return nil, err } directives, err := parseDirectives(parser) if err != nil { return nil, err } def := ast.NewScalarDefinition(&ast.ScalarDefinition{ Name: name, Description: description, Directives: directives, Loc: loc(parser, start), }) return def, nil } /** * ObjectTypeDefinition : * Description? * type Name ImplementsInterfaces? Directives? { FieldDefinition+ } */ func parseObjectTypeDefinition(parser *Parser) (ast.Node, error) { start := parser.Token.Start description, err := parseDescription(parser) if err != nil { return nil, err } _, err = expectKeyWord(parser, lexer.TYPE) if err != nil { return nil, err } name, err := parseName(parser) if err != nil { return nil, err } interfaces, err := parseImplementsInterfaces(parser) if err != nil { return nil, err } directives, err := parseDirectives(parser) if err != nil { return nil, err } iFields, err := reverse(parser, lexer.BRACE_L, parseFieldDefinition, lexer.BRACE_R, false, ) if err != nil { return nil, err } fields := []*ast.FieldDefinition{} for _, iField := range iFields { if iField != nil { fields = append(fields, iField.(*ast.FieldDefinition)) } } return ast.NewObjectDefinition(&ast.ObjectDefinition{ Name: name, Description: description, Loc: loc(parser, start), Interfaces: interfaces, Directives: directives, Fields: fields, }), nil } /** * ImplementsInterfaces : * - implements `&`? NamedType * - ImplementsInterfaces & NamedType */ func parseImplementsInterfaces(parser *Parser) ([]*ast.Named, error) { types := []*ast.Named{} if parser.Token.Value == "implements" { if err := advance(parser); err != nil { return nil, err } // optional leading ampersand skip(parser, lexer.AMP) for { ttype, err := parseNamed(parser) if err != nil { return types, err } types = append(types, ttype) if skipped, err := skip(parser, lexer.AMP); !skipped { break } else if err != nil { return types, err } } } return types, nil } /** * FieldDefinition : Description? Name ArgumentsDefinition? : Type Directives? */ func parseFieldDefinition(parser *Parser) (interface{}, error) { start := parser.Token.Start description, err := parseDescription(parser) if err != nil { return nil, err } name, err := parseName(parser) if err != nil { return nil, err } args, err := parseArgumentDefs(parser) if err != nil { return nil, err } _, err = expect(parser, lexer.COLON) if err != nil { return nil, err } ttype, err := parseType(parser) if err != nil { return nil, err } directives, err := parseDirectives(parser) if err != nil { return nil, err } return ast.NewFieldDefinition(&ast.FieldDefinition{ Name: name, Description: description, Arguments: args, Type: ttype, Directives: directives, Loc: loc(parser, start), }), nil } /** * ArgumentsDefinition : ( InputValueDefinition+ ) */ func parseArgumentDefs(parser *Parser) ([]*ast.InputValueDefinition, error) { inputValueDefinitions := []*ast.InputValueDefinition{} if !peek(parser, lexer.PAREN_L) { return inputValueDefinitions, nil } iInputValueDefinitions, err := reverse(parser, lexer.PAREN_L, parseInputValueDef, lexer.PAREN_R, true, ) if err != nil { return inputValueDefinitions, err } for _, iInputValueDefinition := range iInputValueDefinitions { if iInputValueDefinition != nil { inputValueDefinitions = append(inputValueDefinitions, iInputValueDefinition.(*ast.InputValueDefinition)) } } return inputValueDefinitions, err } /** * InputValueDefinition : Description? Name : Type DefaultValue? Directives? */ func parseInputValueDef(parser *Parser) (interface{}, error) { var ( description *ast.StringValue name *ast.Name ttype ast.Type directives []*ast.Directive err error ) start := parser.Token.Start if description, err = parseDescription(parser); err != nil { return nil, err } if name, err = parseName(parser); err != nil { return nil, err } if _, err = expect(parser, lexer.COLON); err != nil { return nil, err } if ttype, err = parseType(parser); err != nil { return nil, err } var defaultValue ast.Value if skp, err := skip(parser, lexer.EQUALS); err != nil { return nil, err } else if skp { val, err := parseConstValue(parser) if err != nil { return nil, err } if val, ok := val.(ast.Value); ok { defaultValue = val } } if directives, err = parseDirectives(parser); err != nil { return nil, err } return ast.NewInputValueDefinition(&ast.InputValueDefinition{ Name: name, Description: description, Type: ttype, DefaultValue: defaultValue, Directives: directives, Loc: loc(parser, start), }), nil } /** * InterfaceTypeDefinition : * Description? * interface Name Directives? { FieldDefinition+ } */ func parseInterfaceTypeDefinition(parser *Parser) (ast.Node, error) { start := parser.Token.Start description, err := parseDescription(parser) if err != nil { return nil, err } _, err = expectKeyWord(parser, lexer.INTERFACE) if err != nil { return nil, err } name, err := parseName(parser) if err != nil { return nil, err } directives, err := parseDirectives(parser) if err != nil { return nil, err } iFields, err := reverse(parser, lexer.BRACE_L, parseFieldDefinition, lexer.BRACE_R, false, ) if err != nil { return nil, err } fields := []*ast.FieldDefinition{} for _, iField := range iFields { if iField != nil { fields = append(fields, iField.(*ast.FieldDefinition)) } } return ast.NewInterfaceDefinition(&ast.InterfaceDefinition{ Name: name, Description: description, Directives: directives, Loc: loc(parser, start), Fields: fields, }), nil } /** * UnionTypeDefinition : Description? union Name Directives? = UnionMembers */ func parseUnionTypeDefinition(parser *Parser) (ast.Node, error) { start := parser.Token.Start description, err := parseDescription(parser) if err != nil { return nil, err } _, err = expectKeyWord(parser, lexer.UNION) if err != nil { return nil, err } name, err := parseName(parser) if err != nil { return nil, err } directives, err := parseDirectives(parser) if err != nil { return nil, err } _, err = expect(parser, lexer.EQUALS) if err != nil { return nil, err } types, err := parseUnionMembers(parser) if err != nil { return nil, err } return ast.NewUnionDefinition(&ast.UnionDefinition{ Name: name, Description: description, Directives: directives, Loc: loc(parser, start), Types: types, }), nil } /** * UnionMembers : * - NamedType * - UnionMembers | NamedType */ func parseUnionMembers(parser *Parser) ([]*ast.Named, error) { members := []*ast.Named{} for { member, err := parseNamed(parser) if err != nil { return members, err } members = append(members, member) if skp, err := skip(parser, lexer.PIPE); err != nil { return nil, err } else if !skp { break } } return members, nil } /** * EnumTypeDefinition : Description? enum Name Directives? { EnumValueDefinition+ } */ func parseEnumTypeDefinition(parser *Parser) (ast.Node, error) { start := parser.Token.Start description, err := parseDescription(parser) if err != nil { return nil, err } _, err = expectKeyWord(parser, lexer.ENUM) if err != nil { return nil, err } name, err := parseName(parser) if err != nil { return nil, err } directives, err := parseDirectives(parser) if err != nil { return nil, err } iEnumValueDefs, err := reverse(parser, lexer.BRACE_L, parseEnumValueDefinition, lexer.BRACE_R, false, ) if err != nil { return nil, err } values := []*ast.EnumValueDefinition{} for _, iEnumValueDef := range iEnumValueDefs { if iEnumValueDef != nil { values = append(values, iEnumValueDef.(*ast.EnumValueDefinition)) } } return ast.NewEnumDefinition(&ast.EnumDefinition{ Name: name, Description: description, Directives: directives, Loc: loc(parser, start), Values: values, }), nil } /** * EnumValueDefinition : Description? EnumValue Directives? * * EnumValue : Name */ func parseEnumValueDefinition(parser *Parser) (interface{}, error) { start := parser.Token.Start description, err := parseDescription(parser) if err != nil { return nil, err } name, err := parseName(parser) if err != nil { return nil, err } directives, err := parseDirectives(parser) if err != nil { return nil, err } return ast.NewEnumValueDefinition(&ast.EnumValueDefinition{ Name: name, Description: description, Directives: directives, Loc: loc(parser, start), }), nil } /** * InputObjectTypeDefinition : * - Description? input Name Directives? { InputValueDefinition+ } */ func parseInputObjectTypeDefinition(parser *Parser) (ast.Node, error) { start := parser.Token.Start description, err := parseDescription(parser) if err != nil { return nil, err } _, err = expectKeyWord(parser, lexer.INPUT) if err != nil { return nil, err } name, err := parseName(parser) if err != nil { return nil, err } directives, err := parseDirectives(parser) if err != nil { return nil, err } iInputValueDefinitions, err := reverse(parser, lexer.BRACE_L, parseInputValueDef, lexer.BRACE_R, false, ) if err != nil { return nil, err } fields := []*ast.InputValueDefinition{} for _, iInputValueDefinition := range iInputValueDefinitions { if iInputValueDefinition != nil { fields = append(fields, iInputValueDefinition.(*ast.InputValueDefinition)) } } return ast.NewInputObjectDefinition(&ast.InputObjectDefinition{ Name: name, Description: description, Directives: directives, Loc: loc(parser, start), Fields: fields, }), nil } /** * TypeExtensionDefinition : extend ObjectTypeDefinition */ func parseTypeExtensionDefinition(parser *Parser) (ast.Node, error) { start := parser.Token.Start _, err := expectKeyWord(parser, lexer.EXTEND) if err != nil { return nil, err } definition, err := parseObjectTypeDefinition(parser) if err != nil { return nil, err } return ast.NewTypeExtensionDefinition(&ast.TypeExtensionDefinition{ Loc: loc(parser, start), Definition: definition.(*ast.ObjectDefinition), }), nil } /** * DirectiveDefinition : * - directive @ Name ArgumentsDefinition? on DirectiveLocations */ func parseDirectiveDefinition(parser *Parser) (ast.Node, error) { var ( err error description *ast.StringValue name *ast.Name args []*ast.InputValueDefinition locations []*ast.Name ) start := parser.Token.Start if description, err = parseDescription(parser); err != nil { return nil, err } if _, err = expectKeyWord(parser, lexer.DIRECTIVE); err != nil { return nil, err } if _, err = expect(parser, lexer.AT); err != nil { return nil, err } if name, err = parseName(parser); err != nil { return nil, err } if args, err = parseArgumentDefs(parser); err != nil { return nil, err } if _, err = expectKeyWord(parser, "on"); err != nil { return nil, err } if locations, err = parseDirectiveLocations(parser); err != nil { return nil, err } return ast.NewDirectiveDefinition(&ast.DirectiveDefinition{ Loc: loc(parser, start), Name: name, Description: description, Arguments: args, Locations: locations, }), nil } /** * DirectiveLocations : * - Name * - DirectiveLocations | Name */ func parseDirectiveLocations(parser *Parser) ([]*ast.Name, error) { locations := []*ast.Name{} for { if name, err := parseName(parser); err != nil { return locations, err } else { locations = append(locations, name) } if hasPipe, err := skip(parser, lexer.PIPE); err != nil { return locations, err } else if !hasPipe { break } } return locations, nil } func parseStringLiteral(parser *Parser) (*ast.StringValue, error) { token := parser.Token if err := advance(parser); err != nil { return nil, err } return ast.NewStringValue(&ast.StringValue{ Value: token.Value, Loc: loc(parser, token.Start), }), nil } /** * Description : StringValue */ func parseDescription(parser *Parser) (*ast.StringValue, error) { if peekDescription(parser) { return parseStringLiteral(parser) } return nil, nil } /* Core parsing utility functions */ // Returns a location object, used to identify the place in // the source that created a given parsed object. func loc(parser *Parser, start int) *ast.Location { if parser.Options.NoLocation { return nil } if parser.Options.NoSource { return ast.NewLocation(&ast.Location{ Start: start, End: parser.PrevEnd, }) } return ast.NewLocation(&ast.Location{ Start: start, End: parser.PrevEnd, Source: parser.Source, }) } // Moves the internal parser object to the next lexed token. func advance(parser *Parser) error { parser.PrevEnd = parser.Token.End token, err := parser.LexToken(parser.PrevEnd) if err != nil { return err } parser.Token = token return nil } // lookahead retrieves the next token func lookahead(parser *Parser) (lexer.Token, error) { return parser.LexToken(parser.Token.End) } // Determines if the next token is of a given kind func peek(parser *Parser, Kind lexer.TokenKind) bool { return parser.Token.Kind == Kind } // peekDescription determines if the next token is a string value func peekDescription(parser *Parser) bool { return peek(parser, lexer.STRING) || peek(parser, lexer.BLOCK_STRING) } // If the next token is of the given kind, return true after advancing // the parser. Otherwise, do not change the parser state and return false. func skip(parser *Parser, Kind lexer.TokenKind) (bool, error) { if parser.Token.Kind == Kind { return true, advance(parser) } return false, nil } // If the next token is of the given kind, return that token after advancing // the parser. Otherwise, do not change the parser state and return error. func expect(parser *Parser, kind lexer.TokenKind) (lexer.Token, error) { token := parser.Token if token.Kind == kind { return token, advance(parser) } descp := fmt.Sprintf("Expected %s, found %s", kind, lexer.GetTokenDesc(token)) return token, gqlerrors.NewSyntaxError(parser.Source, token.Start, descp) } // If the next token is a keyword with the given value, return that token after // advancing the parser. Otherwise, do not change the parser state and return false. func expectKeyWord(parser *Parser, value string) (lexer.Token, error) { token := parser.Token if token.Kind == lexer.NAME && token.Value == value { return token, advance(parser) } descp := fmt.Sprintf("Expected \"%s\", found %s", value, lexer.GetTokenDesc(token)) return token, gqlerrors.NewSyntaxError(parser.Source, token.Start, descp) } // Helper function for creating an error when an unexpected lexed token // is encountered. func unexpected(parser *Parser, atToken lexer.Token) error { var token = atToken if (atToken == lexer.Token{}) { token = parser.Token } description := fmt.Sprintf("Unexpected %v", lexer.GetTokenDesc(token)) return gqlerrors.NewSyntaxError(parser.Source, token.Start, description) } func unexpectedEmpty(parser *Parser, beginLoc int, openKind, closeKind lexer.TokenKind) error { description := fmt.Sprintf("Unexpected empty IN %s%s", openKind, closeKind) return gqlerrors.NewSyntaxError(parser.Source, beginLoc, description) } // Returns list of parse nodes, determined by // the parseFn. This list begins with a lex token of openKind // and ends with a lex token of closeKind. Advances the parser // to the next lex token after the closing token. // if zinteger is true, len(nodes) > 0 func reverse(parser *Parser, openKind lexer.TokenKind, parseFn parseFn, closeKind lexer.TokenKind, zinteger bool) ([]interface{}, error) { token, err := expect(parser, openKind) if err != nil { return nil, err } var nodes []interface{} for { if skp, err := skip(parser, closeKind); err != nil { return nil, err } else if skp { break } node, err := parseFn(parser) if err != nil { return nodes, err } nodes = append(nodes, node) } if zinteger && len(nodes) == 0 { return nodes, unexpectedEmpty(parser, token.Start, openKind, closeKind) } return nodes, nil } ================================================ FILE: language/parser/parser_test.go ================================================ package parser import ( "fmt" "io/ioutil" "reflect" "strings" "testing" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/location" "github.com/graphql-go/graphql/language/printer" "github.com/graphql-go/graphql/language/source" ) func TestParser_BadToken(t *testing.T) { _, err := Parse(ParseParams{ Source: &source.Source{ Body: []byte("query _ {\n me {\n id`\n }\n}"), Name: "GraphQL", }, }) if err == nil { t.Fatal("expected a parse error") } } func TestParser_AcceptsOptionToNotIncludeSource(t *testing.T) { opts := ParseOptions{ NoSource: true, } params := ParseParams{ Source: "{ field }", Options: opts, } document, err := Parse(params) if err != nil { t.Fatalf("unexpected error: %v", err) } oDef := ast.OperationDefinition{ Kind: "OperationDefinition", Loc: &ast.Location{ Start: 0, End: 9, }, Operation: "query", Directives: []*ast.Directive{}, SelectionSet: &ast.SelectionSet{ Kind: "SelectionSet", Loc: &ast.Location{ Start: 0, End: 9, }, Selections: []ast.Selection{ &ast.Field{ Kind: "Field", Loc: &ast.Location{ Start: 2, End: 7, }, Name: &ast.Name{ Kind: "Name", Loc: &ast.Location{ Start: 2, End: 7, }, Value: "field", }, Arguments: []*ast.Argument{}, Directives: []*ast.Directive{}, }, }, }, } expectedDocument := ast.NewDocument(&ast.Document{ Loc: &ast.Location{ Start: 0, End: 9, }, Definitions: []ast.Node{&oDef}, }) if !reflect.DeepEqual(document, expectedDocument) { t.Fatalf("unexpected document, expected: %v, got: %v", expectedDocument, document) } } func TestParser_ParseProvidesUsefulErrors(t *testing.T) { opts := ParseOptions{ NoSource: true, } params := ParseParams{ Source: "{", Options: opts, } _, err := Parse(params) expectedError := &gqlerrors.Error{ Message: `Syntax Error GraphQL (1:2) Expected Name, found EOF 1: { ^ `, Positions: []int{1}, Locations: []location.SourceLocation{{Line: 1, Column: 2}}, } checkError(t, err, expectedError) testErrorMessagesTable := []errorMessageTest{ { `{ ...MissingOn } fragment MissingOn Type `, `Syntax Error GraphQL (2:20) Expected "on", found Name "Type"`, false, }, { `{ field: {} }`, `Syntax Error GraphQL (1:10) Expected Name, found {`, false, }, { `notanoperation Foo { field }`, `Syntax Error GraphQL (1:1) Unexpected Name "notanoperation"`, false, }, { "...", `Syntax Error GraphQL (1:1) Unexpected ...`, false, }, } for _, test := range testErrorMessagesTable { if test.skipped != false { t.Skipf("Skipped test: %v", test.source) } _, err := Parse(ParseParams{Source: test.source}) checkErrorMessage(t, err, test.expectedMessage) } } func TestParser_ParseProvidesUsefulErrorsWhenUsingSource(t *testing.T) { test := errorMessageTest{ source.NewSource(&source.Source{ Body: []byte("query"), Name: "MyQuery.graphql", }), `Syntax Error MyQuery.graphql (1:6) Expected {, found EOF`, false, } testErrorMessage(t, test) } func TestParser_ParsesVariableInlineValues(t *testing.T) { source := `{ field(complex: { a: { b: [ $var ] } }) }` // should not return error _, err := Parse(ParseParams{Source: source}) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestParser_ParsesConstantDefaultValues(t *testing.T) { test := errorMessageTest{ `query Foo($x: Complex = { a: { b: [ $var ] } }) { field }`, `Syntax Error GraphQL (1:37) Unexpected $`, false, } testErrorMessage(t, test) } func TestParser_DoesNotAcceptFragmentsNameOn(t *testing.T) { test := errorMessageTest{ `fragment on on on { on }`, `Syntax Error GraphQL (1:10) Unexpected Name "on"`, false, } testErrorMessage(t, test) } func TestParser_DoesNotAcceptFragmentsSpreadOfOn(t *testing.T) { test := errorMessageTest{ `{ ...on }'`, `Syntax Error GraphQL (1:9) Expected Name, found }`, false, } testErrorMessage(t, test) } func TestParser_DoesNotAllowNullAsValue(t *testing.T) { test := errorMessageTest{ `{ fieldWithNullableStringInput(input: null) }'`, `Syntax Error GraphQL (1:39) Unexpected Name "null"`, false, } testErrorMessage(t, test) } func TestParser_ParsesMultiByteCharacters_Unicode(t *testing.T) { doc := ` # This comment has a \u0A0A multi-byte character. { field(arg: "Has a \u0A0A multi-byte character.") } ` astDoc := parse(t, doc) expectedASTDoc := ast.NewDocument(&ast.Document{ Loc: ast.NewLocation(&ast.Location{ Start: 67, End: 121, }), Definitions: []ast.Node{ ast.NewOperationDefinition(&ast.OperationDefinition{ Loc: ast.NewLocation(&ast.Location{ Start: 67, End: 119, }), Operation: "query", SelectionSet: ast.NewSelectionSet(&ast.SelectionSet{ Loc: ast.NewLocation(&ast.Location{ Start: 67, End: 119, }), Selections: []ast.Selection{ ast.NewField(&ast.Field{ Loc: ast.NewLocation(&ast.Location{ Start: 67, End: 117, }), Name: ast.NewName(&ast.Name{ Loc: ast.NewLocation(&ast.Location{ Start: 69, End: 74, }), Value: "field", }), Arguments: []*ast.Argument{ ast.NewArgument(&ast.Argument{ Loc: ast.NewLocation(&ast.Location{ Start: 75, End: 116, }), Name: ast.NewName(&ast.Name{ Loc: ast.NewLocation(&ast.Location{ Start: 75, End: 78, }), Value: "arg", }), Value: ast.NewStringValue(&ast.StringValue{ Loc: ast.NewLocation(&ast.Location{ Start: 80, End: 116, }), Value: "Has a \u0A0A multi-byte character.", }), }), }, }), }, }), }), }, }) astDocQuery := printer.Print(astDoc) expectedASTDocQuery := printer.Print(expectedASTDoc) if !reflect.DeepEqual(astDocQuery, expectedASTDocQuery) { t.Fatalf("unexpected document, expected: %v, got: %v", astDocQuery, expectedASTDocQuery) } } func TestParser_ParsesMultiByteCharacters_UnicodeText(t *testing.T) { doc := ` # This comment has a фы世界 multi-byte character. { field(arg: "Has a фы世界 multi-byte character.") } ` astDoc := parse(t, doc) expectedASTDoc := ast.NewDocument(&ast.Document{ Loc: ast.NewLocation(&ast.Location{ Start: 67, End: 121, }), Definitions: []ast.Node{ ast.NewOperationDefinition(&ast.OperationDefinition{ Loc: ast.NewLocation(&ast.Location{ Start: 67, End: 119, }), Operation: "query", SelectionSet: ast.NewSelectionSet(&ast.SelectionSet{ Loc: ast.NewLocation(&ast.Location{ Start: 67, End: 119, }), Selections: []ast.Selection{ ast.NewField(&ast.Field{ Loc: ast.NewLocation(&ast.Location{ Start: 67, End: 117, }), Name: ast.NewName(&ast.Name{ Loc: ast.NewLocation(&ast.Location{ Start: 69, End: 74, }), Value: "field", }), Arguments: []*ast.Argument{ ast.NewArgument(&ast.Argument{ Loc: ast.NewLocation(&ast.Location{ Start: 75, End: 116, }), Name: ast.NewName(&ast.Name{ Loc: ast.NewLocation(&ast.Location{ Start: 75, End: 78, }), Value: "arg", }), Value: ast.NewStringValue(&ast.StringValue{ Loc: ast.NewLocation(&ast.Location{ Start: 80, End: 116, }), Value: "Has a фы世界 multi-byte character.", }), }), }, }), }, }), }), }, }) astDocQuery := printer.Print(astDoc) expectedASTDocQuery := printer.Print(expectedASTDoc) if !reflect.DeepEqual(astDocQuery, expectedASTDocQuery) { t.Fatalf("unexpected document, expected: %v, got: %v", astDocQuery, expectedASTDocQuery) } } func TestParser_ParsesKitchenSink(t *testing.T) { b, err := ioutil.ReadFile("../../kitchen-sink.graphql") if err != nil { t.Fatalf("unable to load kitchen-sink.graphql") } source := string(b) _, err = Parse(ParseParams{Source: source}) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestParser_AllowsNonKeywordsAnywhereNameIsAllowed(t *testing.T) { nonKeywords := []string{ "on", "fragment", "query", "mutation", "subscription", "true", "false", } for _, keyword := range nonKeywords { fragmentName := keyword // You can't define or reference a fragment named `on`. if keyword == "on" { fragmentName = "a" } source := fmt.Sprintf(`query %v { ... %v ... on %v { field } } fragment %v on Type { %v(%v: $%v) @%v(%v: $%v) } `, keyword, fragmentName, keyword, fragmentName, keyword, keyword, keyword, keyword, keyword, keyword) _, err := Parse(ParseParams{Source: source}) if err != nil { t.Fatalf("unexpected error: %v", err) } } } func TestParser_ParsesExperimentalSubscriptionFeature(t *testing.T) { source := ` subscription Foo { subscriptionField } ` _, err := Parse(ParseParams{Source: source}) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestParser_ParsesAnonymousMutationOperations(t *testing.T) { source := ` mutation { mutationField } ` _, err := Parse(ParseParams{Source: source}) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestParser_ParsesAnonymousSubscriptionOperations(t *testing.T) { source := ` subscription { subscriptionField } ` _, err := Parse(ParseParams{Source: source}) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestParser_ParsesNamedMutationOperations(t *testing.T) { source := ` mutation Foo { mutationField } ` _, err := Parse(ParseParams{Source: source}) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestParser_ParsesNamedSubscriptionOperations(t *testing.T) { source := ` subscription Foo { subscriptionField } ` _, err := Parse(ParseParams{Source: source}) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestParser_ParsesFieldDefinitionWithDescription(t *testing.T) { source := ` type Foo implements Bar { """ foo is quite the field. """ foo: String! } ` _, err := Parse(ParseParams{Source: source}) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestParser_ParsesInputValueDefinitionWithDescription(t *testing.T) { source := ` type Foo implements Bar { foo( """ input value comment """ bar: String! ): String! } ` _, err := Parse(ParseParams{Source: source}) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestParser_ParsesEnumValueDefinitionWithDescription(t *testing.T) { source := ` enum Site { "description 1" DESKTOP """ description 2 """ MOBILE } ` _, err := Parse(ParseParams{Source: source}) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestParser_DefinitionsWithDescriptions(t *testing.T) { testCases := []struct { name string source string expectedComment string }{ { name: "directives", source: ` "cool skip" directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT `, expectedComment: "cool skip", }, { name: "input", source: ` """ InputType is indeed a type """ input InputType { key: String! answer: Int = 42 } `, expectedComment: "InputType is indeed a type", }, { name: "enum", source: ` """ description 2 """ enum Site { DESKTOP MOBILE } `, expectedComment: "description 2", }, { name: "union", source: ` """ Cruft ... """ union Cruft = Foo | Bar `, expectedComment: "Cruft ...", }, { name: "interface", source: ` """ Bar is a symptom of the communist agenda """ interface Bar { foo: String! } `, expectedComment: "Bar is a symptom of the communist agenda", }, { name: "object", source: ` """ ★ Foo ★ """ type Foo implements Bar { foo: String! } `, expectedComment: "★ Foo ★", }, { name: "scalar", source: ` """ Returns RFC666; includes timezone offset. """ scalar TimeWithZone `, expectedComment: "Returns RFC666; includes timezone offset.", }, } for _, tc := range testCases { t.Run(fmt.Sprintf("%s may have comments", tc.name), func(t *testing.T) { doc, err := Parse(ParseParams{Source: tc.source}) if err != nil { t.Fatalf("unexpected error: %v", err) } if doc == nil { t.Fatal("no document was returned") } for _, def := range doc.Definitions { fmt.Printf("%#v\n", def) } if node, ok := doc.Definitions[0].(ast.DescribableNode); !ok { t.Fatalf("unexpected node received %#v", doc.Definitions[0]) } else if node.GetDescription().Value != tc.expectedComment { t.Fatalf( "parsed description '%s' does not match '%s'", node.GetDescription().Value, tc.expectedComment, ) } }) } } func TestParser_ParseCreatesAst(t *testing.T) { body := `{ node(id: 4) { id, name } } ` source := source.NewSource(&source.Source{ Body: []byte(body), }) document, err := Parse( ParseParams{ Source: source, Options: ParseOptions{ NoSource: true, }, }, ) if err != nil { t.Fatalf("unexpected error: %v", err) } oDef := ast.OperationDefinition{ Kind: "OperationDefinition", Loc: &ast.Location{ Start: 0, End: 40, }, Operation: "query", Directives: []*ast.Directive{}, SelectionSet: &ast.SelectionSet{ Kind: "SelectionSet", Loc: &ast.Location{ Start: 0, End: 40, }, Selections: []ast.Selection{ &ast.Field{ Kind: "Field", Loc: &ast.Location{ Start: 4, End: 38, }, Name: &ast.Name{ Kind: "Name", Loc: &ast.Location{ Start: 4, End: 8, }, Value: "node", }, Arguments: []*ast.Argument{ { Kind: "Argument", Name: &ast.Name{ Kind: "Name", Loc: &ast.Location{ Start: 9, End: 11, }, Value: "id", }, Value: &ast.IntValue{ Kind: "IntValue", Loc: &ast.Location{ Start: 13, End: 14, }, Value: "4", }, Loc: &ast.Location{ Start: 9, End: 14, }, }, }, Directives: []*ast.Directive{}, SelectionSet: &ast.SelectionSet{ Kind: "SelectionSet", Loc: &ast.Location{ Start: 16, End: 38, }, Selections: []ast.Selection{ &ast.Field{ Kind: "Field", Loc: &ast.Location{ Start: 22, End: 24, }, Name: &ast.Name{ Kind: "Name", Loc: &ast.Location{ Start: 22, End: 24, }, Value: "id", }, Arguments: []*ast.Argument{}, Directives: []*ast.Directive{}, SelectionSet: nil, }, &ast.Field{ Kind: "Field", Loc: &ast.Location{ Start: 30, End: 34, }, Name: &ast.Name{ Kind: "Name", Loc: &ast.Location{ Start: 30, End: 34, }, Value: "name", }, Arguments: []*ast.Argument{}, Directives: []*ast.Directive{}, SelectionSet: nil, }, }, }, }, }, }, } expectedDocument := ast.NewDocument(&ast.Document{ Loc: &ast.Location{ Start: 0, End: 41, }, Definitions: []ast.Node{&oDef}, }) if !reflect.DeepEqual(document, expectedDocument) { t.Fatalf("unexpected document, expected: %v, got: %v", expectedDocument, document.Definitions) } } func TestParser_DoesNotAcceptStringAsDefinition(t *testing.T) { test := errorMessageTest{ `String`, `Syntax Error GraphQL (1:1) Unexpected Name "String"`, false, } testErrorMessage(t, test) } type errorMessageTest struct { source interface{} expectedMessage string skipped bool } func testErrorMessage(t *testing.T, test errorMessageTest) { if test.skipped != false { t.Skipf("Skipped test: %v", test.source) } _, err := Parse(ParseParams{Source: test.source}) checkErrorMessage(t, err, test.expectedMessage) } func checkError(t *testing.T, err error, expectedError *gqlerrors.Error) { if expectedError == nil { if err != nil { t.Fatalf("unexpected error: %v", err) } return // ok } // else expectedError != nil if err == nil { t.Fatalf("unexpected nil error\nexpected:\n%v\n\ngot:\n%v", expectedError, err) } if err.Error() != expectedError.Message { t.Fatalf("unexpected error.\nexpected:\n%v\n\ngot:\n%v", expectedError, err.Error()) } gErr := toError(err) if gErr == nil { t.Fatalf("unexpected nil Error") } if len(expectedError.Positions) > 0 && !reflect.DeepEqual(gErr.Positions, expectedError.Positions) { t.Fatalf("unexpected Error.Positions.\nexpected:\n%v\n\ngot:\n%v", expectedError.Positions, gErr.Positions) } if len(expectedError.Locations) > 0 && !reflect.DeepEqual(gErr.Locations, expectedError.Locations) { t.Fatalf("unexpected Error.Locations.\nexpected:\n%v\n\ngot:\n%v", expectedError.Locations, gErr.Locations) } } func checkErrorMessage(t *testing.T, err error, expectedMessage string) { if err == nil { t.Fatalf("unexpected nil error\nexpected:\n%v\n\ngot:\n%v", expectedMessage, err) } if err.Error() != expectedMessage { // only check first line of error message lines := strings.Split(err.Error(), "\n") if lines[0] != expectedMessage { t.Fatalf("unexpected error.\nexpected:\n%v\n\ngot:\n%v", expectedMessage, lines[0]) } } } func toError(err error) *gqlerrors.Error { if err == nil { return nil } switch err := err.(type) { case *gqlerrors.Error: return err default: return nil } } ================================================ FILE: language/parser/schema_parser_test.go ================================================ package parser import ( "reflect" "testing" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/location" "github.com/graphql-go/graphql/language/source" ) func parse(t *testing.T, query string) *ast.Document { astDoc, err := Parse(ParseParams{ Source: query, Options: ParseOptions{ NoLocation: false, NoSource: true, }, }) if err != nil { t.Fatalf("Parse failed: %v", err) } return astDoc } func testLoc(start int, end int) *ast.Location { return &ast.Location{ Start: start, End: end, } } func TestSchemaParser_SimpleType(t *testing.T) { body := ` type Hello { world: String }` astDoc := parse(t, body) expected := ast.NewDocument(&ast.Document{ Loc: testLoc(1, 31), Definitions: []ast.Node{ ast.NewObjectDefinition(&ast.ObjectDefinition{ Loc: testLoc(1, 31), Name: ast.NewName(&ast.Name{ Value: "Hello", Loc: testLoc(6, 11), }), Directives: []*ast.Directive{}, Interfaces: []*ast.Named{}, Fields: []*ast.FieldDefinition{ ast.NewFieldDefinition(&ast.FieldDefinition{ Loc: testLoc(16, 29), Name: ast.NewName(&ast.Name{ Value: "world", Loc: testLoc(16, 21), }), Arguments: []*ast.InputValueDefinition{}, Directives: []*ast.Directive{}, Type: ast.NewNamed(&ast.Named{ Loc: testLoc(23, 29), Name: ast.NewName(&ast.Name{ Value: "String", Loc: testLoc(23, 29), }), }), }), }, }), }, }) if !reflect.DeepEqual(astDoc, expected) { t.Fatalf("unexpected document, expected: %v, got: %v", expected, astDoc) } } func TestSchemaParser_SimpleExtension(t *testing.T) { body := ` extend type Hello { world: String }` astDoc := parse(t, body) expected := ast.NewDocument(&ast.Document{ Loc: testLoc(1, 38), Definitions: []ast.Node{ ast.NewTypeExtensionDefinition(&ast.TypeExtensionDefinition{ Loc: testLoc(1, 38), Definition: ast.NewObjectDefinition(&ast.ObjectDefinition{ Loc: testLoc(8, 38), Name: ast.NewName(&ast.Name{ Value: "Hello", Loc: testLoc(13, 18), }), Directives: []*ast.Directive{}, Interfaces: []*ast.Named{}, Fields: []*ast.FieldDefinition{ ast.NewFieldDefinition(&ast.FieldDefinition{ Loc: testLoc(23, 36), Name: ast.NewName(&ast.Name{ Value: "world", Loc: testLoc(23, 28), }), Directives: []*ast.Directive{}, Arguments: []*ast.InputValueDefinition{}, Type: ast.NewNamed(&ast.Named{ Loc: testLoc(30, 36), Name: ast.NewName(&ast.Name{ Value: "String", Loc: testLoc(30, 36), }), }), }), }, }), }), }, }) if !reflect.DeepEqual(astDoc, expected) { t.Fatalf("unexpected document, expected: %v, got: %v", expected, astDoc) } } func TestSchemaParser_SimpleNonNullType(t *testing.T) { body := ` type Hello { world: String! }` astDoc := parse(t, body) expected := ast.NewDocument(&ast.Document{ Loc: testLoc(1, 32), Definitions: []ast.Node{ ast.NewObjectDefinition(&ast.ObjectDefinition{ Loc: testLoc(1, 32), Name: ast.NewName(&ast.Name{ Value: "Hello", Loc: testLoc(6, 11), }), Directives: []*ast.Directive{}, Interfaces: []*ast.Named{}, Fields: []*ast.FieldDefinition{ ast.NewFieldDefinition(&ast.FieldDefinition{ Loc: testLoc(16, 30), Name: ast.NewName(&ast.Name{ Value: "world", Loc: testLoc(16, 21), }), Directives: []*ast.Directive{}, Arguments: []*ast.InputValueDefinition{}, Type: ast.NewNonNull(&ast.NonNull{ Kind: "NonNullType", Loc: testLoc(23, 30), Type: ast.NewNamed(&ast.Named{ Loc: testLoc(23, 29), Name: ast.NewName(&ast.Name{ Value: "String", Loc: testLoc(23, 29), }), }), }), }), }, }), }, }) if !reflect.DeepEqual(astDoc, expected) { t.Fatalf("unexpected document, expected: %v, got: %v", expected, astDoc) } } func TestSchemaParser_SimpleTypeInheritingInterface(t *testing.T) { body := `type Hello implements World { }` astDoc := parse(t, body) expected := ast.NewDocument(&ast.Document{ Loc: testLoc(0, 31), Definitions: []ast.Node{ ast.NewObjectDefinition(&ast.ObjectDefinition{ Loc: testLoc(0, 31), Name: ast.NewName(&ast.Name{ Value: "Hello", Loc: testLoc(5, 10), }), Directives: []*ast.Directive{}, Interfaces: []*ast.Named{ ast.NewNamed(&ast.Named{ Name: ast.NewName(&ast.Name{ Value: "World", Loc: testLoc(22, 27), }), Loc: testLoc(22, 27), }), }, Fields: []*ast.FieldDefinition{}, }), }, }) if !reflect.DeepEqual(astDoc, expected) { t.Fatalf("unexpected document, expected: %v, got: %v", expected, astDoc) } } func TestSchemaParser_SimpleTypeInheritingMultipleInterfaces(t *testing.T) { body := `type Hello implements Wo & rld { }` astDoc := parse(t, body) expected := ast.NewDocument(&ast.Document{ Loc: testLoc(0, 34), Definitions: []ast.Node{ ast.NewObjectDefinition(&ast.ObjectDefinition{ Loc: testLoc(0, 34), Name: ast.NewName(&ast.Name{ Value: "Hello", Loc: testLoc(5, 10), }), Directives: []*ast.Directive{}, Interfaces: []*ast.Named{ ast.NewNamed(&ast.Named{ Name: ast.NewName(&ast.Name{ Value: "Wo", Loc: testLoc(22, 24), }), Loc: testLoc(22, 24), }), ast.NewNamed(&ast.Named{ Name: ast.NewName(&ast.Name{ Value: "rld", Loc: testLoc(27, 30), }), Loc: testLoc(27, 30), }), }, Fields: []*ast.FieldDefinition{}, }), }, }) if !reflect.DeepEqual(astDoc, expected) { t.Fatalf("unexpected document, expected: %v, got: %v", expected, astDoc) } } func TestSchemaParser_SimpleTypeInheritingMultipleInterfacesWithLeadingAmpersand(t *testing.T) { body := `type Hello implements & Wo & rld { }` astDoc := parse(t, body) expected := ast.NewDocument(&ast.Document{ Loc: testLoc(0, 36), Definitions: []ast.Node{ ast.NewObjectDefinition(&ast.ObjectDefinition{ Loc: testLoc(0, 36), Name: ast.NewName(&ast.Name{ Value: "Hello", Loc: testLoc(5, 10), }), Directives: []*ast.Directive{}, Interfaces: []*ast.Named{ ast.NewNamed(&ast.Named{ Name: ast.NewName(&ast.Name{ Value: "Wo", Loc: testLoc(24, 26), }), Loc: testLoc(24, 26), }), ast.NewNamed(&ast.Named{ Name: ast.NewName(&ast.Name{ Value: "rld", Loc: testLoc(29, 32), }), Loc: testLoc(29, 32), }), }, Fields: []*ast.FieldDefinition{}, }), }, }) if !reflect.DeepEqual(astDoc, expected) { t.Fatalf("unexpected document, expected: %v, got: %v", expected, astDoc) } } func TestSchemaParser_SingleValueEnum(t *testing.T) { body := `enum Hello { WORLD }` astDoc := parse(t, body) expected := ast.NewDocument(&ast.Document{ Loc: testLoc(0, 20), Definitions: []ast.Node{ ast.NewEnumDefinition(&ast.EnumDefinition{ Loc: testLoc(0, 20), Name: ast.NewName(&ast.Name{ Value: "Hello", Loc: testLoc(5, 10), }), Directives: []*ast.Directive{}, Values: []*ast.EnumValueDefinition{ ast.NewEnumValueDefinition(&ast.EnumValueDefinition{ Name: ast.NewName(&ast.Name{ Value: "WORLD", Loc: testLoc(13, 18), }), Directives: []*ast.Directive{}, Loc: testLoc(13, 18), }), }, }), }, }) if !reflect.DeepEqual(astDoc, expected) { t.Fatalf("unexpected document, expected: %v, got: %v", expected, astDoc) } } func TestSchemaParser_DoubleValueEnum(t *testing.T) { body := `enum Hello { WO, RLD }` astDoc := parse(t, body) expected := ast.NewDocument(&ast.Document{ Loc: testLoc(0, 22), Definitions: []ast.Node{ ast.NewEnumDefinition(&ast.EnumDefinition{ Loc: testLoc(0, 22), Name: ast.NewName(&ast.Name{ Value: "Hello", Loc: testLoc(5, 10), }), Directives: []*ast.Directive{}, Values: []*ast.EnumValueDefinition{ ast.NewEnumValueDefinition(&ast.EnumValueDefinition{ Name: ast.NewName(&ast.Name{ Value: "WO", Loc: testLoc(13, 15), }), Directives: []*ast.Directive{}, Loc: testLoc(13, 15), }), ast.NewEnumValueDefinition(&ast.EnumValueDefinition{ Name: ast.NewName(&ast.Name{ Value: "RLD", Loc: testLoc(17, 20), }), Directives: []*ast.Directive{}, Loc: testLoc(17, 20), }), }, }), }, }) if !reflect.DeepEqual(astDoc, expected) { t.Fatalf("unexpected document, expected: %v, got: %v", expected, astDoc) } } func TestSchemaParser_SimpleInterface(t *testing.T) { body := ` interface Hello { world: String }` astDoc := parse(t, body) expected := ast.NewDocument(&ast.Document{ Loc: testLoc(1, 36), Definitions: []ast.Node{ ast.NewInterfaceDefinition(&ast.InterfaceDefinition{ Loc: testLoc(1, 36), Name: ast.NewName(&ast.Name{ Value: "Hello", Loc: testLoc(11, 16), }), Directives: []*ast.Directive{}, Fields: []*ast.FieldDefinition{ ast.NewFieldDefinition(&ast.FieldDefinition{ Loc: testLoc(21, 34), Name: ast.NewName(&ast.Name{ Value: "world", Loc: testLoc(21, 26), }), Directives: []*ast.Directive{}, Arguments: []*ast.InputValueDefinition{}, Type: ast.NewNamed(&ast.Named{ Loc: testLoc(28, 34), Name: ast.NewName(&ast.Name{ Value: "String", Loc: testLoc(28, 34), }), }), }), }, }), }, }) if !reflect.DeepEqual(astDoc, expected) { t.Fatalf("unexpected document, expected: %v, got: %v", expected, astDoc) } } func TestSchemaParser_SimpleFieldWithArg(t *testing.T) { body := ` type Hello { world(flag: Boolean): String }` astDoc := parse(t, body) expected := ast.NewDocument(&ast.Document{ Loc: testLoc(1, 46), Definitions: []ast.Node{ ast.NewObjectDefinition(&ast.ObjectDefinition{ Loc: testLoc(1, 46), Name: ast.NewName(&ast.Name{ Value: "Hello", Loc: testLoc(6, 11), }), Directives: []*ast.Directive{}, Interfaces: []*ast.Named{}, Fields: []*ast.FieldDefinition{ ast.NewFieldDefinition(&ast.FieldDefinition{ Loc: testLoc(16, 44), Name: ast.NewName(&ast.Name{ Value: "world", Loc: testLoc(16, 21), }), Directives: []*ast.Directive{}, Arguments: []*ast.InputValueDefinition{ ast.NewInputValueDefinition(&ast.InputValueDefinition{ Loc: testLoc(22, 35), Name: ast.NewName(&ast.Name{ Value: "flag", Loc: testLoc(22, 26), }), Type: ast.NewNamed(&ast.Named{ Loc: testLoc(28, 35), Name: ast.NewName(&ast.Name{ Value: "Boolean", Loc: testLoc(28, 35), }), }), DefaultValue: nil, Directives: []*ast.Directive{}, }), }, Type: ast.NewNamed(&ast.Named{ Loc: testLoc(38, 44), Name: ast.NewName(&ast.Name{ Value: "String", Loc: testLoc(38, 44), }), }), }), }, }), }, }) if !reflect.DeepEqual(astDoc, expected) { t.Fatalf("unexpected document, expected: %v, got: %v", expected, astDoc) } } func TestSchemaParser_SimpleFieldWithArgWithDefaultValue(t *testing.T) { body := ` type Hello { world(flag: Boolean = true): String }` astDoc := parse(t, body) expected := ast.NewDocument(&ast.Document{ Loc: testLoc(1, 53), Definitions: []ast.Node{ ast.NewObjectDefinition(&ast.ObjectDefinition{ Loc: testLoc(1, 53), Name: ast.NewName(&ast.Name{ Value: "Hello", Loc: testLoc(6, 11), }), Directives: []*ast.Directive{}, Interfaces: []*ast.Named{}, Fields: []*ast.FieldDefinition{ ast.NewFieldDefinition(&ast.FieldDefinition{ Loc: testLoc(16, 51), Name: ast.NewName(&ast.Name{ Value: "world", Loc: testLoc(16, 21), }), Arguments: []*ast.InputValueDefinition{ ast.NewInputValueDefinition(&ast.InputValueDefinition{ Loc: testLoc(22, 42), Name: ast.NewName(&ast.Name{ Value: "flag", Loc: testLoc(22, 26), }), Type: ast.NewNamed(&ast.Named{ Loc: testLoc(28, 35), Name: ast.NewName(&ast.Name{ Value: "Boolean", Loc: testLoc(28, 35), }), }), DefaultValue: ast.NewBooleanValue(&ast.BooleanValue{ Value: true, Loc: testLoc(38, 42), }), Directives: []*ast.Directive{}, }), }, Directives: []*ast.Directive{}, Type: ast.NewNamed(&ast.Named{ Loc: testLoc(45, 51), Name: ast.NewName(&ast.Name{ Value: "String", Loc: testLoc(45, 51), }), }), }), }, }), }, }) if !reflect.DeepEqual(astDoc, expected) { t.Fatalf("unexpected document, expected: %v, got: %v", expected, astDoc) } } func TestSchemaParser_SimpleFieldWithListArg(t *testing.T) { body := ` type Hello { world(things: [String]): String }` astDoc := parse(t, body) expected := ast.NewDocument(&ast.Document{ Loc: testLoc(1, 49), Definitions: []ast.Node{ ast.NewObjectDefinition(&ast.ObjectDefinition{ Loc: testLoc(1, 49), Name: ast.NewName(&ast.Name{ Value: "Hello", Loc: testLoc(6, 11), }), Directives: []*ast.Directive{}, Interfaces: []*ast.Named{}, Fields: []*ast.FieldDefinition{ ast.NewFieldDefinition(&ast.FieldDefinition{ Loc: testLoc(16, 47), Name: ast.NewName(&ast.Name{ Value: "world", Loc: testLoc(16, 21), }), Directives: []*ast.Directive{}, Arguments: []*ast.InputValueDefinition{ ast.NewInputValueDefinition(&ast.InputValueDefinition{ Loc: testLoc(22, 38), Name: ast.NewName(&ast.Name{ Value: "things", Loc: testLoc(22, 28), }), Type: ast.NewList(&ast.List{ Loc: testLoc(30, 38), Type: ast.NewNamed(&ast.Named{ Loc: testLoc(31, 37), Name: ast.NewName(&ast.Name{ Value: "String", Loc: testLoc(31, 37), }), }), }), DefaultValue: nil, Directives: []*ast.Directive{}, }), }, Type: ast.NewNamed(&ast.Named{ Loc: testLoc(41, 47), Name: ast.NewName(&ast.Name{ Value: "String", Loc: testLoc(41, 47), }), }), }), }, }), }, }) if !reflect.DeepEqual(astDoc, expected) { t.Fatalf("unexpected document, expected: %v, got: %v", expected, astDoc) } } func TestSchemaParser_SimpleFieldWithTwoArg(t *testing.T) { body := ` type Hello { world(argOne: Boolean, argTwo: Int): String }` astDoc := parse(t, body) expected := ast.NewDocument(&ast.Document{ Loc: testLoc(1, 61), Definitions: []ast.Node{ ast.NewObjectDefinition(&ast.ObjectDefinition{ Loc: testLoc(1, 61), Name: ast.NewName(&ast.Name{ Value: "Hello", Loc: testLoc(6, 11), }), Directives: []*ast.Directive{}, Interfaces: []*ast.Named{}, Fields: []*ast.FieldDefinition{ ast.NewFieldDefinition(&ast.FieldDefinition{ Loc: testLoc(16, 59), Name: ast.NewName(&ast.Name{ Value: "world", Loc: testLoc(16, 21), }), Directives: []*ast.Directive{}, Arguments: []*ast.InputValueDefinition{ ast.NewInputValueDefinition(&ast.InputValueDefinition{ Loc: testLoc(22, 37), Name: ast.NewName(&ast.Name{ Value: "argOne", Loc: testLoc(22, 28), }), Type: ast.NewNamed(&ast.Named{ Loc: testLoc(30, 37), Name: ast.NewName(&ast.Name{ Value: "Boolean", Loc: testLoc(30, 37), }), }), DefaultValue: nil, Directives: []*ast.Directive{}, }), ast.NewInputValueDefinition(&ast.InputValueDefinition{ Loc: testLoc(39, 50), Name: ast.NewName(&ast.Name{ Value: "argTwo", Loc: testLoc(39, 45), }), Type: ast.NewNamed(&ast.Named{ Loc: testLoc(47, 50), Name: ast.NewName(&ast.Name{ Value: "Int", Loc: testLoc(47, 50), }), }), DefaultValue: nil, Directives: []*ast.Directive{}, }), }, Type: ast.NewNamed(&ast.Named{ Loc: testLoc(53, 59), Name: ast.NewName(&ast.Name{ Value: "String", Loc: testLoc(53, 59), }), }), }), }, }), }, }) if !reflect.DeepEqual(astDoc, expected) { t.Fatalf("unexpected document, expected: %v, got: %v", expected, astDoc) } } func TestSchemaParser_SimpleUnion(t *testing.T) { body := `union Hello = World` astDoc := parse(t, body) expected := ast.NewDocument(&ast.Document{ Loc: testLoc(0, 19), Definitions: []ast.Node{ ast.NewUnionDefinition(&ast.UnionDefinition{ Loc: testLoc(0, 19), Name: ast.NewName(&ast.Name{ Value: "Hello", Loc: testLoc(6, 11), }), Directives: []*ast.Directive{}, Types: []*ast.Named{ ast.NewNamed(&ast.Named{ Loc: testLoc(14, 19), Name: ast.NewName(&ast.Name{ Value: "World", Loc: testLoc(14, 19), }), }), }, }), }, }) if !reflect.DeepEqual(astDoc, expected) { t.Fatalf("unexpected document, expected: %v, got: %v", expected, astDoc) } } func TestSchemaParser_UnionWithTwoTypes(t *testing.T) { body := `union Hello = Wo | Rld` astDoc := parse(t, body) expected := ast.NewDocument(&ast.Document{ Loc: testLoc(0, 22), Definitions: []ast.Node{ ast.NewUnionDefinition(&ast.UnionDefinition{ Loc: testLoc(0, 22), Name: ast.NewName(&ast.Name{ Value: "Hello", Loc: testLoc(6, 11), }), Directives: []*ast.Directive{}, Types: []*ast.Named{ ast.NewNamed(&ast.Named{ Loc: testLoc(14, 16), Name: ast.NewName(&ast.Name{ Value: "Wo", Loc: testLoc(14, 16), }), }), ast.NewNamed(&ast.Named{ Loc: testLoc(19, 22), Name: ast.NewName(&ast.Name{ Value: "Rld", Loc: testLoc(19, 22), }), }), }, }), }, }) if !reflect.DeepEqual(astDoc, expected) { t.Fatalf("unexpected document, expected: %v, got: %v", expected, astDoc) } } func TestSchemaParser_Scalar(t *testing.T) { body := `scalar Hello` astDoc := parse(t, body) expected := ast.NewDocument(&ast.Document{ Loc: testLoc(0, 12), Definitions: []ast.Node{ ast.NewScalarDefinition(&ast.ScalarDefinition{ Loc: testLoc(0, 12), Name: ast.NewName(&ast.Name{ Value: "Hello", Loc: testLoc(7, 12), }), Directives: []*ast.Directive{}, }), }, }) if !reflect.DeepEqual(astDoc, expected) { t.Fatalf("unexpected document, expected: %v, got: %v", expected, astDoc) } } func TestSchemaParser_SimpleInputObject(t *testing.T) { body := ` input Hello { world: String }` astDoc := parse(t, body) expected := ast.NewDocument(&ast.Document{ Loc: testLoc(1, 32), Definitions: []ast.Node{ ast.NewInputObjectDefinition(&ast.InputObjectDefinition{ Loc: testLoc(1, 32), Name: ast.NewName(&ast.Name{ Value: "Hello", Loc: testLoc(7, 12), }), Directives: []*ast.Directive{}, Fields: []*ast.InputValueDefinition{ ast.NewInputValueDefinition(&ast.InputValueDefinition{ Loc: testLoc(17, 30), Name: ast.NewName(&ast.Name{ Value: "world", Loc: testLoc(17, 22), }), Type: ast.NewNamed(&ast.Named{ Loc: testLoc(24, 30), Name: ast.NewName(&ast.Name{ Value: "String", Loc: testLoc(24, 30), }), }), DefaultValue: nil, Directives: []*ast.Directive{}, }), }, }), }, }) if !reflect.DeepEqual(astDoc, expected) { t.Fatalf("unexpected document, expected: %v, got: %v", expected, astDoc) } } func TestSchemaParser_SimpleInputObjectWithArgsShouldFail(t *testing.T) { body := ` input Hello { world(foo: Int): String }` _, err := Parse(ParseParams{ Source: body, Options: ParseOptions{ NoLocation: false, NoSource: true, }, }) expectedError := &gqlerrors.Error{ Message: `Syntax Error GraphQL (3:8) Expected :, found ( 2: input Hello { 3: world(foo: Int): String ^ 4: } `, Stack: `Syntax Error GraphQL (3:8) Expected :, found ( 2: input Hello { 3: world(foo: Int): String ^ 4: } `, Nodes: []ast.Node{}, Source: &source.Source{ Body: []byte(` input Hello { world(foo: Int): String }`), Name: "GraphQL", }, Positions: []int{22}, Locations: []location.SourceLocation{ {Line: 3, Column: 8}, }, } if err == nil { t.Fatalf("expected error, expected: %v, got: %v", expectedError, nil) } if !reflect.DeepEqual(expectedError, err) { t.Fatalf("unexpected document, expected: %v, got: %v", expectedError, err) } } ================================================ FILE: language/printer/printer.go ================================================ package printer import ( "fmt" "strconv" "strings" "reflect" "github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/visitor" ) func getMapValue(m map[string]interface{}, key string) interface{} { tokens := strings.Split(key, ".") valMap := m for _, token := range tokens { v, ok := valMap[token] if !ok { return nil } switch v := v.(type) { case []interface{}: return v case map[string]interface{}: valMap = v continue default: return v } } return valMap } func getMapSliceValue(m map[string]interface{}, key string) []interface{} { tokens := strings.Split(key, ".") valMap := m for _, token := range tokens { v, ok := valMap[token] if !ok { return []interface{}{} } switch v := v.(type) { case []interface{}: return v default: return []interface{}{} } } return []interface{}{} } func getMapValueString(m map[string]interface{}, key string) string { tokens := strings.Split(key, ".") valMap := m for _, token := range tokens { v, ok := valMap[token] if !ok { return "" } if v == nil { return "" } switch v := v.(type) { case map[string]interface{}: valMap = v continue case string: return v default: return fmt.Sprintf("%v", v) } } return "" } func getDescription(raw interface{}) string { var desc string switch node := raw.(type) { case ast.DescribableNode: if sval := node.GetDescription(); sval != nil { desc = sval.Value } case map[string]interface{}: desc = getMapValueString(node, "Description.Value") } if desc != "" { sep := "" if strings.ContainsRune(desc, '\n') { sep = "\n" } desc = join([]string{`"""`, desc, `"""`}, sep) } return desc } func toSliceString(slice interface{}) []string { if slice == nil { return []string{} } res := []string{} switch reflect.TypeOf(slice).Kind() { case reflect.Slice: s := reflect.ValueOf(slice) for i := 0; i < s.Len(); i++ { elem := s.Index(i) elemInterface := elem.Interface() if elem, ok := elemInterface.(string); ok { res = append(res, elem) } } return res default: return res } } func join(str []string, sep string) string { ss := []string{} // filter out empty strings for _, s := range str { if s == "" { continue } ss = append(ss, s) } return strings.Join(ss, sep) } func wrap(start, maybeString, end string) string { if maybeString == "" { return maybeString } return start + maybeString + end } // Given array, print each item on its own line, wrapped in an indented "{ }" block. func block(maybeArray interface{}) string { s := toSliceString(maybeArray) if len(s) == 0 { return "{}" } return indent("{\n"+join(s, "\n")) + "\n}" } func indent(maybeString interface{}) string { if maybeString == nil { return "" } switch str := maybeString.(type) { case string: return strings.Replace(str, "\n", "\n ", -1) } return "" } var printDocASTReducer = map[string]visitor.VisitFunc{ "Name": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: return visitor.ActionUpdate, node.Value case map[string]interface{}: return visitor.ActionUpdate, getMapValue(node, "Value") } return visitor.ActionNoChange, nil }, "Variable": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Variable: return visitor.ActionUpdate, fmt.Sprintf("$%v", node.Name) case map[string]interface{}: return visitor.ActionUpdate, "$" + getMapValueString(node, "Name") } return visitor.ActionNoChange, nil }, // Document "Document": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Document: definitions := toSliceString(node.Definitions) return visitor.ActionUpdate, join(definitions, "\n\n") + "\n" case map[string]interface{}: definitions := toSliceString(getMapValue(node, "Definitions")) return visitor.ActionUpdate, join(definitions, "\n\n") + "\n" } return visitor.ActionNoChange, nil }, "OperationDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.OperationDefinition: op := string(node.Operation) name := fmt.Sprintf("%v", node.Name) varDefs := wrap("(", join(toSliceString(node.VariableDefinitions), ", "), ")") directives := join(toSliceString(node.Directives), " ") selectionSet := fmt.Sprintf("%v", node.SelectionSet) // Anonymous queries with no directives or variable definitions can use // the query short form. str := "" if name == "" && directives == "" && varDefs == "" && op == ast.OperationTypeQuery { str = selectionSet } else { str = join([]string{ op, join([]string{name, varDefs}, ""), directives, selectionSet, }, " ") } return visitor.ActionUpdate, str case map[string]interface{}: op := getMapValueString(node, "Operation") name := getMapValueString(node, "Name") varDefs := wrap("(", join(toSliceString(getMapValue(node, "VariableDefinitions")), ", "), ")") directives := join(toSliceString(getMapValue(node, "Directives")), " ") selectionSet := getMapValueString(node, "SelectionSet") str := "" if name == "" && directives == "" && varDefs == "" && op == ast.OperationTypeQuery { str = selectionSet } else { str = join([]string{ op, join([]string{name, varDefs}, ""), directives, selectionSet, }, " ") } return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "VariableDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.VariableDefinition: variable := fmt.Sprintf("%v", node.Variable) ttype := fmt.Sprintf("%v", node.Type) defaultValue := fmt.Sprintf("%v", node.DefaultValue) return visitor.ActionUpdate, variable + ": " + ttype + wrap(" = ", defaultValue, "") case map[string]interface{}: variable := getMapValueString(node, "Variable") ttype := getMapValueString(node, "Type") defaultValue := getMapValueString(node, "DefaultValue") return visitor.ActionUpdate, variable + ": " + ttype + wrap(" = ", defaultValue, "") } return visitor.ActionNoChange, nil }, "SelectionSet": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.SelectionSet: str := block(node.Selections) return visitor.ActionUpdate, str case map[string]interface{}: selections := getMapValue(node, "Selections") str := block(selections) return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "Field": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Argument: name := fmt.Sprintf("%v", node.Name) value := fmt.Sprintf("%v", node.Value) return visitor.ActionUpdate, name + ": " + value case map[string]interface{}: alias := getMapValueString(node, "Alias") name := getMapValueString(node, "Name") args := toSliceString(getMapValue(node, "Arguments")) directives := toSliceString(getMapValue(node, "Directives")) selectionSet := getMapValueString(node, "SelectionSet") str := join( []string{ wrap("", alias, ": ") + name + wrap("(", join(args, ", "), ")"), join(directives, " "), selectionSet, }, " ", ) return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "Argument": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.FragmentSpread: name := fmt.Sprintf("%v", node.Name) directives := toSliceString(node.Directives) return visitor.ActionUpdate, "..." + name + wrap(" ", join(directives, " "), "") case map[string]interface{}: name := getMapValueString(node, "Name") value := getMapValueString(node, "Value") return visitor.ActionUpdate, name + ": " + value } return visitor.ActionNoChange, nil }, // Fragments "FragmentSpread": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.InlineFragment: typeCondition := fmt.Sprintf("%v", node.TypeCondition) directives := toSliceString(node.Directives) selectionSet := fmt.Sprintf("%v", node.SelectionSet) return visitor.ActionUpdate, "... on " + typeCondition + " " + wrap("", join(directives, " "), " ") + selectionSet case map[string]interface{}: name := getMapValueString(node, "Name") directives := toSliceString(getMapValue(node, "Directives")) return visitor.ActionUpdate, "..." + name + wrap(" ", join(directives, " "), "") } return visitor.ActionNoChange, nil }, "InlineFragment": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case map[string]interface{}: typeCondition := getMapValueString(node, "TypeCondition") directives := toSliceString(getMapValue(node, "Directives")) selectionSet := getMapValueString(node, "SelectionSet") return visitor.ActionUpdate, join([]string{ "...", wrap("on ", typeCondition, ""), join(directives, " "), selectionSet, }, " ") } return visitor.ActionNoChange, nil }, "FragmentDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.FragmentDefinition: name := fmt.Sprintf("%v", node.Name) typeCondition := fmt.Sprintf("%v", node.TypeCondition) directives := toSliceString(node.Directives) selectionSet := fmt.Sprintf("%v", node.SelectionSet) return visitor.ActionUpdate, "fragment " + name + " on " + typeCondition + " " + wrap("", join(directives, " "), " ") + selectionSet case map[string]interface{}: name := getMapValueString(node, "Name") typeCondition := getMapValueString(node, "TypeCondition") directives := toSliceString(getMapValue(node, "Directives")) selectionSet := getMapValueString(node, "SelectionSet") return visitor.ActionUpdate, "fragment " + name + " on " + typeCondition + " " + wrap("", join(directives, " "), " ") + selectionSet } return visitor.ActionNoChange, nil }, // Value "IntValue": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.IntValue: return visitor.ActionUpdate, fmt.Sprintf("%v", node.Value) case map[string]interface{}: return visitor.ActionUpdate, getMapValueString(node, "Value") } return visitor.ActionNoChange, nil }, "FloatValue": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.FloatValue: return visitor.ActionUpdate, fmt.Sprintf("%v", node.Value) case map[string]interface{}: return visitor.ActionUpdate, getMapValueString(node, "Value") } return visitor.ActionNoChange, nil }, "StringValue": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.StringValue: return visitor.ActionUpdate, strconv.Quote(node.Value) case map[string]interface{}: return visitor.ActionUpdate, `"` + getMapValueString(node, "Value") + `"` } return visitor.ActionNoChange, nil }, "BooleanValue": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.BooleanValue: return visitor.ActionUpdate, fmt.Sprintf("%v", node.Value) case map[string]interface{}: return visitor.ActionUpdate, getMapValueString(node, "Value") } return visitor.ActionNoChange, nil }, "EnumValue": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.EnumValue: return visitor.ActionUpdate, fmt.Sprintf("%v", node.Value) case map[string]interface{}: return visitor.ActionUpdate, getMapValueString(node, "Value") } return visitor.ActionNoChange, nil }, "ListValue": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.ListValue: return visitor.ActionUpdate, "[" + join(toSliceString(node.Values), ", ") + "]" case map[string]interface{}: return visitor.ActionUpdate, "[" + join(toSliceString(getMapValue(node, "Values")), ", ") + "]" } return visitor.ActionNoChange, nil }, "ObjectValue": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.ObjectValue: return visitor.ActionUpdate, "{" + join(toSliceString(node.Fields), ", ") + "}" case map[string]interface{}: return visitor.ActionUpdate, "{" + join(toSliceString(getMapValue(node, "Fields")), ", ") + "}" } return visitor.ActionNoChange, nil }, "ObjectField": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.ObjectField: name := fmt.Sprintf("%v", node.Name) value := fmt.Sprintf("%v", node.Value) return visitor.ActionUpdate, name + ": " + value case map[string]interface{}: name := getMapValueString(node, "Name") value := getMapValueString(node, "Value") return visitor.ActionUpdate, name + ": " + value } return visitor.ActionNoChange, nil }, // Directive "Directive": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Directive: name := fmt.Sprintf("%v", node.Name) args := toSliceString(node.Arguments) return visitor.ActionUpdate, "@" + name + wrap("(", join(args, ", "), ")") case map[string]interface{}: name := getMapValueString(node, "Name") args := toSliceString(getMapValue(node, "Arguments")) return visitor.ActionUpdate, "@" + name + wrap("(", join(args, ", "), ")") } return visitor.ActionNoChange, nil }, // Type "Named": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Named: return visitor.ActionUpdate, fmt.Sprintf("%v", node.Name) case map[string]interface{}: return visitor.ActionUpdate, getMapValueString(node, "Name") } return visitor.ActionNoChange, nil }, "List": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.List: return visitor.ActionUpdate, "[" + fmt.Sprintf("%v", node.Type) + "]" case map[string]interface{}: return visitor.ActionUpdate, "[" + getMapValueString(node, "Type") + "]" } return visitor.ActionNoChange, nil }, "NonNull": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.NonNull: return visitor.ActionUpdate, fmt.Sprintf("%v", node.Type) + "!" case map[string]interface{}: return visitor.ActionUpdate, getMapValueString(node, "Type") + "!" } return visitor.ActionNoChange, nil }, // Type System Definitions "SchemaDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.SchemaDefinition: directives := []string{} for _, directive := range node.Directives { directives = append(directives, fmt.Sprintf("%v", directive.Name)) } str := join([]string{ "schema", join(directives, " "), block(node.OperationTypes), }, " ") return visitor.ActionUpdate, str case map[string]interface{}: operationTypes := toSliceString(getMapValue(node, "OperationTypes")) directives := []string{} for _, directive := range getMapSliceValue(node, "Directives") { directives = append(directives, fmt.Sprintf("%v", directive)) } str := join([]string{ "schema", join(directives, " "), block(operationTypes), }, " ") return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "OperationTypeDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.OperationTypeDefinition: str := fmt.Sprintf("%v: %v", node.Operation, node.Type) return visitor.ActionUpdate, str case map[string]interface{}: operation := getMapValueString(node, "Operation") ttype := getMapValueString(node, "Type") str := fmt.Sprintf("%v: %v", operation, ttype) return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "ScalarDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.ScalarDefinition: directives := []string{} for _, directive := range node.Directives { directives = append(directives, fmt.Sprintf("%v", directive.Name)) } str := join([]string{ "scalar", fmt.Sprintf("%v", node.Name), join(directives, " "), }, " ") if desc := getDescription(node); desc != "" { str = fmt.Sprintf("%s\n%s", desc, str) } return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") directives := []string{} for _, directive := range getMapSliceValue(node, "Directives") { directives = append(directives, fmt.Sprintf("%v", directive)) } str := join([]string{ "scalar", name, join(directives, " "), }, " ") if desc := getDescription(node); desc != "" { str = fmt.Sprintf("%s\n%s", desc, str) } return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "ObjectDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.ObjectDefinition: name := fmt.Sprintf("%v", node.Name) interfaces := toSliceString(node.Interfaces) fields := node.Fields directives := []string{} for _, directive := range node.Directives { directives = append(directives, fmt.Sprintf("%v", directive.Name)) } str := join([]string{ "type", name, wrap("implements ", join(interfaces, " & "), ""), join(directives, " "), block(fields), }, " ") if desc := getDescription(node); desc != "" { str = fmt.Sprintf("%s\n%s", desc, str) } return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") interfaces := toSliceString(getMapValue(node, "Interfaces")) fields := getMapValue(node, "Fields") directives := []string{} for _, directive := range getMapSliceValue(node, "Directives") { directives = append(directives, fmt.Sprintf("%v", directive)) } str := join([]string{ "type", name, wrap("implements ", join(interfaces, " & "), ""), join(directives, " "), block(fields), }, " ") if desc := getDescription(node); desc != "" { str = fmt.Sprintf("%s\n%s", desc, str) } return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "FieldDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.FieldDefinition: name := fmt.Sprintf("%v", node.Name) ttype := fmt.Sprintf("%v", node.Type) args := toSliceString(node.Arguments) directives := []string{} for _, directive := range node.Directives { directives = append(directives, fmt.Sprintf("%v", directive.Name)) } hasArgDesc := false for _, arg := range node.Arguments { if arg.Description != nil && arg.Description.Value != "" { hasArgDesc = true break } } var argsStr string if hasArgDesc { argsStr = wrap("(", indent("\n"+join(args, "\n")), "\n)") } else { argsStr = wrap("(", join(args, ", "), ")") } str := name + argsStr + ": " + ttype + wrap(" ", join(directives, " "), "") if desc := getDescription(node); desc != "" { str = fmt.Sprintf("\n%s\n%s", desc, str) } return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") ttype := getMapValueString(node, "Type") args := toSliceString(getMapValue(node, "Arguments")) directives := []string{} for _, directive := range getMapSliceValue(node, "Directives") { directives = append(directives, fmt.Sprintf("%v", directive)) } hasArgDesc := false for _, arg := range args { if strings.HasPrefix(strings.TrimSpace(arg), `"""`) { hasArgDesc = true break } } var argsStr string if hasArgDesc { argsStr = wrap("(", indent("\n"+join(args, "\n")), "\n)") } else { argsStr = wrap("(", join(args, ", "), ")") } str := name + argsStr + ": " + ttype + wrap(" ", join(directives, " "), "") if desc := getDescription(node); desc != "" { str = fmt.Sprintf("\n%s\n%s", desc, str) } return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "InputValueDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.InputValueDefinition: name := fmt.Sprintf("%v", node.Name) ttype := fmt.Sprintf("%v", node.Type) defaultValue := fmt.Sprintf("%v", node.DefaultValue) directives := []string{} for _, directive := range node.Directives { directives = append(directives, fmt.Sprintf("%v", directive.Name)) } str := join([]string{ name + ": " + ttype, wrap("= ", defaultValue, ""), join(directives, " "), }, " ") if desc := getDescription(node); desc != "" { str = fmt.Sprintf("\n%s\n%s", desc, str) } return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") ttype := getMapValueString(node, "Type") defaultValue := getMapValueString(node, "DefaultValue") directives := []string{} for _, directive := range getMapSliceValue(node, "Directives") { directives = append(directives, fmt.Sprintf("%v", directive)) } str := join([]string{ name + ": " + ttype, wrap("= ", defaultValue, ""), join(directives, " "), }, " ") if desc := getDescription(node); desc != "" { str = fmt.Sprintf("\n%s\n%s", desc, str) } return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "InterfaceDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.InterfaceDefinition: name := fmt.Sprintf("%v", node.Name) fields := node.Fields directives := []string{} for _, directive := range node.Directives { directives = append(directives, fmt.Sprintf("%v", directive.Name)) } str := join([]string{ "interface", name, join(directives, " "), block(fields), }, " ") if desc := getDescription(node); desc != "" { str = fmt.Sprintf("%s\n%s", desc, str) } return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") fields := getMapValue(node, "Fields") directives := []string{} for _, directive := range getMapSliceValue(node, "Directives") { directives = append(directives, fmt.Sprintf("%v", directive)) } str := join([]string{ "interface", name, join(directives, " "), block(fields), }, " ") if desc := getDescription(node); desc != "" { str = fmt.Sprintf("%s\n%s", desc, str) } return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "UnionDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.UnionDefinition: name := fmt.Sprintf("%v", node.Name) types := toSliceString(node.Types) directives := []string{} for _, directive := range node.Directives { directives = append(directives, fmt.Sprintf("%v", directive.Name)) } str := join([]string{ "union", name, join(directives, " "), "= " + join(types, " | "), }, " ") if desc := getDescription(node); desc != "" { str = fmt.Sprintf("%s\n%s", desc, str) } return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") types := toSliceString(getMapValue(node, "Types")) directives := []string{} for _, directive := range getMapSliceValue(node, "Directives") { directives = append(directives, fmt.Sprintf("%v", directive)) } str := join([]string{ "union", name, join(directives, " "), "= " + join(types, " | "), }, " ") if desc := getDescription(node); desc != "" { str = fmt.Sprintf("%s\n%s", desc, str) } return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "EnumDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.EnumDefinition: name := fmt.Sprintf("%v", node.Name) values := node.Values directives := []string{} for _, directive := range node.Directives { directives = append(directives, fmt.Sprintf("%v", directive.Name)) } str := join([]string{ "enum", name, join(directives, " "), block(values), }, " ") if desc := getDescription(node); desc != "" { str = fmt.Sprintf("%s\n%s", desc, str) } return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") values := getMapValue(node, "Values") directives := []string{} for _, directive := range getMapSliceValue(node, "Directives") { directives = append(directives, fmt.Sprintf("%v", directive)) } str := join([]string{ "enum", name, join(directives, " "), block(values), }, " ") if desc := getDescription(node); desc != "" { str = fmt.Sprintf("%s\n%s", desc, str) } return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "EnumValueDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.EnumValueDefinition: name := fmt.Sprintf("%v", node.Name) directives := []string{} for _, directive := range node.Directives { directives = append(directives, fmt.Sprintf("%v", directive.Name)) } str := join([]string{ name, join(directives, " "), }, " ") if desc := getDescription(node); desc != "" { str = fmt.Sprintf("\n%s\n%s", desc, str) } return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") directives := []string{} for _, directive := range getMapSliceValue(node, "Directives") { directives = append(directives, fmt.Sprintf("%v", directive)) } str := join([]string{ name, join(directives, " "), }, " ") if desc := getDescription(node); desc != "" { str = fmt.Sprintf("\n%s\n%s", desc, str) } return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "InputObjectDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.InputObjectDefinition: name := fmt.Sprintf("%v", node.Name) fields := node.Fields directives := []string{} for _, directive := range node.Directives { directives = append(directives, fmt.Sprintf("%v", directive.Name)) } str := join([]string{ "input", name, join(directives, " "), block(fields), }, " ") if desc := getDescription(node); desc != "" { str = fmt.Sprintf("%s\n%s", desc, str) } return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") fields := getMapValue(node, "Fields") directives := []string{} for _, directive := range getMapSliceValue(node, "Directives") { directives = append(directives, fmt.Sprintf("%v", directive)) } str := join([]string{ "input", name, join(directives, " "), block(fields), }, " ") if desc := getDescription(node); desc != "" { str = fmt.Sprintf("%s\n%s", desc, str) } return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "TypeExtensionDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.TypeExtensionDefinition: definition := fmt.Sprintf("%v", node.Definition) str := "extend " + definition return visitor.ActionUpdate, str case map[string]interface{}: definition := getMapValueString(node, "Definition") str := "extend " + definition return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "DirectiveDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.DirectiveDefinition: args := toSliceString(node.Arguments) hasArgDesc := false for _, arg := range node.Arguments { if arg.Description != nil && arg.Description.Value != "" { hasArgDesc = true break } } var argsStr string if hasArgDesc { argsStr = wrap("(", indent("\n"+join(args, "\n")), "\n)") } else { argsStr = wrap("(", join(args, ", "), ")") } str := fmt.Sprintf("directive @%v%v on %v", node.Name, argsStr, join(toSliceString(node.Locations), " | ")) if desc := getDescription(node); desc != "" { str = fmt.Sprintf("%s\n%s", desc, str) } return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") locations := toSliceString(getMapValue(node, "Locations")) args := toSliceString(getMapValue(node, "Arguments")) hasArgDesc := false for _, arg := range args { if strings.HasPrefix(strings.TrimSpace(arg), `"""`) { hasArgDesc = true break } } var argsStr string if hasArgDesc { argsStr = wrap("(", indent("\n"+join(args, "\n")), "\n)") } else { argsStr = wrap("(", join(args, ", "), ")") } str := fmt.Sprintf("directive @%v%v on %v", name, argsStr, join(locations, " | ")) if desc := getDescription(node); desc != "" { str = fmt.Sprintf("%s\n%s", desc, str) } return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, } func Print(astNode ast.Node) (printed interface{}) { defer func() interface{} { if r := recover(); r != nil { return fmt.Sprintf("%v", astNode) } return printed }() printed = visitor.Visit(astNode, &visitor.VisitorOptions{ LeaveKindMap: printDocASTReducer, }, nil) return printed } ================================================ FILE: language/printer/printer_test.go ================================================ package printer_test import ( "io/ioutil" "reflect" "testing" "github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/parser" "github.com/graphql-go/graphql/language/printer" "github.com/graphql-go/graphql/testutil" ) func parse(t *testing.T, query string) *ast.Document { astDoc, err := parser.Parse(parser.ParseParams{ Source: query, Options: parser.ParseOptions{ NoLocation: true, }, }) if err != nil { t.Fatalf("Parse failed: %v", err) } return astDoc } func TestPrinter_DoesNotAlterAST(t *testing.T) { b, err := ioutil.ReadFile("../../kitchen-sink.graphql") if err != nil { t.Fatalf("unable to load kitchen-sink.graphql") } query := string(b) astDoc := parse(t, query) astDocBefore := testutil.ASTToJSON(t, astDoc) _ = printer.Print(astDoc) astDocAfter := testutil.ASTToJSON(t, astDoc) _ = testutil.ASTToJSON(t, astDoc) if !reflect.DeepEqual(astDocAfter, astDocBefore) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(astDocBefore, astDocAfter)) } } func TestPrinter_PrintsMinimalAST(t *testing.T) { astDoc := ast.NewField(&ast.Field{ Name: ast.NewName(&ast.Name{ Value: "foo", }), }) results := printer.Print(astDoc) expected := "foo" if !reflect.DeepEqual(results, expected) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, results)) } } // TestPrinter_ProducesHelpfulErrorMessages // Skipped, can't figure out how to pass in an invalid astDoc, which is already strongly-typed func TestPrinter_CorrectlyPrintsNonQueryOperationsWithoutName(t *testing.T) { // Test #1 queryAstShorthanded := `query { id, name }` expected := `{ id name } ` astDoc := parse(t, queryAstShorthanded) results := printer.Print(astDoc) if !reflect.DeepEqual(expected, results) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, results)) } // Test #2 mutationAst := `mutation { id, name }` expected = `mutation { id name } ` astDoc = parse(t, mutationAst) results = printer.Print(astDoc) if !reflect.DeepEqual(expected, results) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, results)) } // Test #3 queryAstWithArtifacts := `query ($foo: TestType) @testDirective { id, name }` expected = `query ($foo: TestType) @testDirective { id name } ` astDoc = parse(t, queryAstWithArtifacts) results = printer.Print(astDoc) if !reflect.DeepEqual(expected, results) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, results)) } // Test #4 mutationAstWithArtifacts := `mutation ($foo: TestType) @testDirective { id, name }` expected = `mutation ($foo: TestType) @testDirective { id name } ` astDoc = parse(t, mutationAstWithArtifacts) results = printer.Print(astDoc) if !reflect.DeepEqual(expected, results) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, results)) } } func TestPrinter_PrintsKitchenSink(t *testing.T) { b, err := ioutil.ReadFile("../../kitchen-sink.graphql") if err != nil { t.Fatalf("unable to load kitchen-sink.graphql") } query := string(b) astDoc := parse(t, query) expected := `query namedQuery($foo: ComplexFooType, $bar: Bar = DefaultBarValue) { customUser: user(id: [987, 654]) { id ... on User @defer { field2 { id alias: field1(first: 10, after: $foo) @include(if: $foo) { id ...frag } } } ... @skip(unless: $foo) { id } ... { id } } } mutation favPost { fav(post: 123) @defer { post { id } } } subscription PostFavSubscription($input: StoryLikeSubscribeInput) { postFavSubscribe(input: $input) { post { favers { count } favSentence { text } } } } fragment frag on Follower { foo(size: $size, bar: $b, obj: {key: "value"}) } { unnamed(truthyVal: true, falseyVal: false) query } ` results := printer.Print(astDoc) if !reflect.DeepEqual(expected, results) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, results)) } } func TestPrinter_CorrectlyPrintsStringArgumentsWithProperQuoting(t *testing.T) { queryAst := `query { foo(jsonStr: "{\"foo\": \"bar\"}") }` expected := `{ foo(jsonStr: "{\"foo\": \"bar\"}") } ` astDoc := parse(t, queryAst) results := printer.Print(astDoc) if !reflect.DeepEqual(expected, results) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, results)) } } func BenchmarkPrint(b *testing.B) { q, err := ioutil.ReadFile("../../kitchen-sink.graphql") if err != nil { b.Fatalf("unable to load kitchen-sink.graphql") } query := string(q) astDoc, err := parser.Parse(parser.ParseParams{ Source: query, Options: parser.ParseOptions{ NoLocation: true, }, }) if err != nil { b.Fatalf("Parse failed: %v", err) } b.ResetTimer() for i := 0; i < b.N; i++ { _ = printer.Print(astDoc) } } ================================================ FILE: language/printer/schema_printer_test.go ================================================ package printer_test import ( "io/ioutil" "reflect" "testing" "github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/printer" "github.com/graphql-go/graphql/testutil" ) func TestSchemaPrinter_PrintsMinimalAST(t *testing.T) { astDoc := ast.NewScalarDefinition(&ast.ScalarDefinition{ Name: ast.NewName(&ast.Name{ Value: "foo", }), }) results := printer.Print(astDoc) expected := "scalar foo" if !reflect.DeepEqual(results, expected) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, results)) } } func TestSchemaPrinter_DoesNotAlterAST(t *testing.T) { b, err := ioutil.ReadFile("../../schema-kitchen-sink.graphql") if err != nil { t.Fatalf("unable to load schema-kitchen-sink.graphql") } query := string(b) astDoc := parse(t, query) astDocBefore := testutil.ASTToJSON(t, astDoc) _ = printer.Print(astDoc) astDocAfter := testutil.ASTToJSON(t, astDoc) _ = testutil.ASTToJSON(t, astDoc) if !reflect.DeepEqual(astDocAfter, astDocBefore) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(astDocAfter, astDocBefore)) } } func TestSchemaPrinter_PrintsKitchenSink(t *testing.T) { b, err := ioutil.ReadFile("../../schema-kitchen-sink.graphql") if err != nil { t.Fatalf("unable to load schema-kitchen-sink.graphql") } query := string(b) astDoc := parse(t, query) expected := `schema { query: QueryType mutation: MutationType } type Foo implements Bar & Baz { one: Type two(argument: InputType!): Type three(argument: InputType, other: String): Int four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): String six(argument: InputType = {key: "value"}): Type } type AnnotatedObject @onObject(arg: "value") { annotatedField(arg: Type = "default" @onArg): Type @onField } interface Bar { one: Type four(argument: String = "string"): String } interface AnnotatedInterface @onInterface { annotatedField(arg: Type @onArg): Type @onField } union Feed = Story | Article | Advert union AnnotatedUnion @onUnion = A | B scalar CustomScalar scalar AnnotatedScalar @onScalar enum Site { DESKTOP MOBILE } enum AnnotatedEnum @onEnum { ANNOTATED_VALUE @onEnumValue OTHER_VALUE } input InputType { key: String! answer: Int = 42 } input AnnotatedInput @onInputObjectType { annotatedField: Type @onField } extend type Foo { seven(argument: [String]): Type } extend type Foo @onType {} type NoFields {} directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT ` results := printer.Print(astDoc) if !reflect.DeepEqual(expected, results) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, results)) } } func TestSchemaPrinter_PrintsAllDescriptions(t *testing.T) { b, err := ioutil.ReadFile("../../schema-all-descriptions.graphql") if err != nil { t.Fatalf("unable to load schema-all-descriptions.graphql") } query := string(b) astDoc := parse(t, query) expected := `"""single line scalar description""" scalar ScalarSingleLine """ multi line scalar description """ scalar ScalarMultiLine """single line object description""" type ObjectSingleLine { no_description: ID """single line field description""" single_line(a: ID, b: ID, c: ID, d: ID): ID """ multi line field description """ multi_line( a: ID """single line argument description""" b: ID """ multi line field description """ c: ID d: ID ): ID } """ multi line object description """ type ObjectMultiLine { foo: ID } """single line interface description""" interface InterfaceSingleLine { no_description: ID """single line field description""" single_line(a: ID, b: ID, c: ID, d: ID): ID """ multi line field description """ multi_line( a: ID """single line argument description""" b: ID """ multi line argument description """ c: ID d: ID ): ID } """ multi line interface description """ interface InterfaceMultiLine { foo: ID } """single line union description""" union UnionSingleLine = String | Int | Float | ID """ multi line union description """ union UnionSingleLine = String | Int | Float | ID """single line enum description""" enum EnumSingleLine { no_description """single line enum description""" single_line """ multi line enum description """ multi_line again_no_description } """ multi line enum description """ enum EnumMultiLine { foo } """single line input description""" input InputSingleLine { a: ID """single line argument description""" b: ID """ multi line argument description """ c: ID d: ID } """ multi line input description """ input InputMultiLine { foo: ID } """single line directive description""" directive @DirectiveSingleLine( a: ID """single line argument description""" b: ID """ multi line argument description """ c: ID d: ID ) on SCALAR """ multi line directive description """ directive @DirectiveMultiLine on SCALAR ` results := printer.Print(astDoc) if !reflect.DeepEqual(expected, results) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, results)) } } ================================================ FILE: language/source/source.go ================================================ package source const ( name = "GraphQL" ) type Source struct { Body []byte Name string } func NewSource(s *Source) *Source { if s == nil { s = &Source{Name: name} } if s.Name == "" { s.Name = name } return s } ================================================ FILE: language/typeInfo/type_info.go ================================================ package typeInfo import ( "github.com/graphql-go/graphql/language/ast" ) // TypeInfoI defines the interface for TypeInfo Implementation type TypeInfoI interface { Enter(node ast.Node) Leave(node ast.Node) } ================================================ FILE: language/visitor/visitor.go ================================================ package visitor import ( "fmt" "github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/typeInfo" "reflect" ) const ( ActionNoChange = "" ActionBreak = "BREAK" ActionSkip = "SKIP" ActionUpdate = "UPDATE" ) type KeyMap map[string][]string // note that the keys are in Capital letters, equivalent to the ast.Node field Names var QueryDocumentKeys = KeyMap{ "Name": []string{}, "Document": []string{"Definitions"}, "OperationDefinition": []string{ "Name", "VariableDefinitions", "Directives", "SelectionSet", }, "VariableDefinition": []string{ "Variable", "Type", "DefaultValue", }, "Variable": []string{"Name"}, "SelectionSet": []string{"Selections"}, "Field": []string{ "Alias", "Name", "Arguments", "Directives", "SelectionSet", }, "Argument": []string{ "Name", "Value", }, "FragmentSpread": []string{ "Name", "Directives", }, "InlineFragment": []string{ "TypeCondition", "Directives", "SelectionSet", }, "FragmentDefinition": []string{ "Name", "TypeCondition", "Directives", "SelectionSet", }, "IntValue": []string{}, "FloatValue": []string{}, "StringValue": []string{}, "BooleanValue": []string{}, "EnumValue": []string{}, "ListValue": []string{"Values"}, "ObjectValue": []string{"Fields"}, "ObjectField": []string{ "Name", "Value", }, "Directive": []string{ "Name", "Arguments", }, "Named": []string{"Name"}, "List": []string{"Type"}, "NonNull": []string{"Type"}, "SchemaDefinition": []string{ "Directives", "OperationTypes", }, "OperationTypeDefinition": []string{"Type"}, "ScalarDefinition": []string{ "Name", "Directives", }, "ObjectDefinition": []string{ "Name", "Interfaces", "Directives", "Fields", }, "FieldDefinition": []string{ "Name", "Arguments", "Type", "Directives", }, "InputValueDefinition": []string{ "Name", "Type", "DefaultValue", "Directives", }, "InterfaceDefinition": []string{ "Name", "Directives", "Fields", }, "UnionDefinition": []string{ "Name", "Directives", "Types", }, "EnumDefinition": []string{ "Name", "Directives", "Values", }, "EnumValueDefinition": []string{ "Name", "Directives", }, "InputObjectDefinition": []string{ "Name", "Directives", "Fields", }, "TypeExtensionDefinition": []string{"Definition"}, "DirectiveDefinition": []string{"Name", "Arguments", "Locations"}, } type stack struct { Index int Keys []interface{} Edits []*edit inSlice bool Prev *stack } type edit struct { Key interface{} Value interface{} } type VisitFuncParams struct { Node interface{} Key interface{} Parent ast.Node Path []interface{} Ancestors []ast.Node } type VisitFunc func(p VisitFuncParams) (string, interface{}) type NamedVisitFuncs struct { Kind VisitFunc // 1) Named visitors triggered when entering a node a specific kind. Leave VisitFunc // 2) Named visitors that trigger upon entering and leaving a node of Enter VisitFunc // 2) Named visitors that trigger upon entering and leaving a node of } type VisitorOptions struct { KindFuncMap map[string]NamedVisitFuncs Enter VisitFunc // 3) Generic visitors that trigger upon entering and leaving any node. Leave VisitFunc // 3) Generic visitors that trigger upon entering and leaving any node. EnterKindMap map[string]VisitFunc // 4) Parallel visitors for entering and leaving nodes of a specific kind LeaveKindMap map[string]VisitFunc // 4) Parallel visitors for entering and leaving nodes of a specific kind } func Visit(root ast.Node, visitorOpts *VisitorOptions, keyMap KeyMap) interface{} { visitorKeys := keyMap if visitorKeys == nil { visitorKeys = QueryDocumentKeys } var ( result interface{} newRoot ast.Node = root sstack *stack parent interface{} parentSlice []interface{} inSlice = false prevInSlice = false keys = []interface{}{root} index = -1 edits = []*edit{} // key-value path = []interface{}{} ancestors = []interface{}{} ancestorsSlice = [][]interface{}{} ) // these algorithm must be simple!!! // abstract algorithm Loop: for { index++ isLeaving := (len(keys) == index) var ( key interface{} // string for structs or int for slices node interface{} // ast.Node or can be anything nodeSlice []interface{} ) isEdited := (isLeaving && len(edits) != 0) if isLeaving { key, path = pop(path) node = parent parent, ancestors = pop(ancestors) nodeSlice = parentSlice parentSlice, ancestorsSlice = popNodeSlice(ancestorsSlice) if isEdited { prevInSlice = inSlice editOffset := 0 for _, edit := range edits { if inSlice { if isNilNode(edit.Value) { nodeSlice = removeNodeByIndex(nodeSlice, edit.Key.(int)-editOffset) editOffset++ } else { nodeSlice[edit.Key.(int)-editOffset] = edit.Value } } else { var isConvertMap bool // check if edit.Value implements ast.Node or []ast.Node. if !isSlice(edit.Value) { if !isStructNode(edit.Value) { isConvertMap = true } } else { // check if edit.value slice is ast.nodes ev := reflect.ValueOf(edit.Value) for i := 0; i < ev.Len(); i++ { if !isStructNode(ev.Index(i).Interface()) { isConvertMap = true break } } } if !isConvertMap { node = updateNodeField(node, edit.Key.(string), edit.Value) } else { // non-node needs convert to map if todoNode, err := convertMap(node); err != nil { panic(err) } else { todoNode[edit.Key.(string)] = edit.Value node = todoNode } } } } } index, keys, edits, inSlice, sstack = sstack.Index, sstack.Keys, sstack.Edits, sstack.inSlice, sstack.Prev } else { // get key & value if inSlice { key = index } else if !isNilNode(parent) { key = getFieldValue(keys, index) } // get node var tmp interface{} if !isNilNode(parent) { tmp = parent } else if len(parentSlice) != 0 { tmp = parentSlice } else { node, nodeSlice = newRoot, []interface{}{} } if tmp != nil { fieldValue := getFieldValue(tmp, key) switch { case isNode(fieldValue): node = fieldValue.(ast.Node) case isSlice(fieldValue): nodeSlice = toSliceInterfaces(fieldValue) } } if isNilNode(node) && len(nodeSlice) == 0 { continue } if !inSlice { if !isNilNode(parent) { path = append(path, key) } } else { if len(parentSlice) != 0 { path = append(path, key) } } } // get result from visitFn for a node if set var result interface{} resultIsUndefined := true if !isNilNode(node) { // Note that since user can potentially return a non-ast.Node from visit functions. // if not exist map type for node, nodes implement ast.Node parentConcrete, _ := parent.(ast.Node) ancestorsConcrete := []ast.Node{} for _, ancestor := range ancestors { if ancestorConcrete, ok := ancestor.(ast.Node); ok { ancestorsConcrete = append(ancestorsConcrete, ancestorConcrete) } else { ancestorsConcrete = append(ancestorsConcrete, nil) // map for nil } } var kind string switch tmp := node.(type) { case map[string]interface{}: kind = tmp["Kind"].(string) case ast.Node: kind = tmp.GetKind() } visitFn := GetVisitFn(visitorOpts, kind, isLeaving) if visitFn != nil { p := VisitFuncParams{ Node: node, Key: key, Parent: parentConcrete, Path: path, Ancestors: ancestorsConcrete, } var action string switch action, result = visitFn(p); action { case ActionBreak: break Loop case ActionSkip: if !isLeaving { _, path = pop(path) continue } case ActionUpdate: resultIsUndefined = false edits = append(edits, &edit{ Key: key, Value: result, }) if !isLeaving { if isNode(result) { node = result } else { _, path = pop(path) continue } } } } } // collect back edits on the way out if resultIsUndefined && isEdited { if !prevInSlice { edits = append(edits, &edit{ Key: key, Value: node, }) } else { edits = append(edits, &edit{ Key: key, Value: nodeSlice, }) } } if !isLeaving { // add to stack prevStack := sstack sstack = &stack{ inSlice: inSlice, Index: index, Keys: keys, Edits: edits, Prev: prevStack, } // replace keys keys, index, edits = []interface{}{}, -1, []*edit{} if len(nodeSlice) > 0 { inSlice = true keys = append(keys, nodeSlice...) } else { inSlice = false if !isNilNode(node) { kind := node.(ast.Node).GetKind() for _, m := range visitorKeys[kind] { keys = append(keys, m) } } } ancestors = append(ancestors, parent) parent = node ancestorsSlice = append(ancestorsSlice, parentSlice) parentSlice = nodeSlice } // loop guard if sstack == nil { break Loop } } if len(edits) != 0 { result = edits[len(edits)-1].Value } return result } func pop(a []interface{}) (interface{}, []interface{}) { if len(a) == 0 { return nil, nil } return a[len(a)-1], a[:len(a)-1] } func popNodeSlice(a [][]interface{}) ([]interface{}, [][]interface{}) { if len(a) == 0 { return nil, nil } return a[len(a)-1], a[:len(a)-1] } func removeNodeByIndex(a []interface{}, pos int) []interface{} { if pos < 0 || pos >= len(a) { return a } return append(a[:pos], a[pos+1:]...) } func convertMap(src interface{}) (dest map[string]interface{}, err error) { if src == nil { return } // return if src is already a map dest, ok := src.(map[string]interface{}) if ok { return } outputMap := make(map[string]interface{}) val := reflect.ValueOf(src) // Dereference pointer if necessary if val.Kind() == reflect.Ptr { if val.IsNil() { return nil, fmt.Errorf("input is a nil pointer") } val = val.Elem() } if val.Kind() != reflect.Struct { return nil, fmt.Errorf("input is not a struct or pointer to struct") } typ := val.Type() for i := 0; i < val.NumField(); i++ { field := val.Field(i) fieldName := typ.Field(i).Name switch field.Kind() { case reflect.Ptr: if field.IsNil() { outputMap[fieldName] = nil } else { nestedMap, err := convertMap(field.Interface()) if err != nil { return nil, err } outputMap[fieldName] = nestedMap } case reflect.Struct: nestedMap, err := convertMap(field.Interface()) if err != nil { return nil, err } outputMap[fieldName] = nestedMap case reflect.Interface: if field.IsNil() { outputMap[fieldName] = nil } else { concreteValue := field.Elem() outputMap[fieldName], _ = convertMap(concreteValue.Interface()) } default: outputMap[fieldName] = field.Interface() } } return outputMap, nil } // get value by key from struct | slice | map | wrap(prev) // when obj type is struct, the key's type must be string // ... slice, ... int // ... map, ... any type. But the type satisfies map's key definition(feature: compare...) func getFieldValue(obj interface{}, key interface{}) interface{} { var value reflect.Value val := reflect.ValueOf(obj) if val.Kind() == reflect.Ptr { val = val.Elem() } switch val.Kind() { case reflect.Struct: value = val.FieldByName(key.(string)) case reflect.Map: value = val.MapIndex(reflect.ValueOf(key)) case reflect.Slice: if index, ok := key.(int); !ok { return nil } else if index >= 0 || val.Len() > index { value = val.Index(index) } } if !value.IsValid() { return nil } return value.Interface() } // currently only supports update struct field value func updateNodeField(src interface{}, targetName string, target interface{}) interface{} { var isPtr bool srcVal := reflect.ValueOf(src) // verify condition if srcVal.Kind() == reflect.Ptr { isPtr = true srcVal = srcVal.Elem() } targetVal := reflect.ValueOf(target) if srcVal.Kind() != reflect.Struct { return src } srcFieldValue := srcVal.FieldByName(targetName) if !srcFieldValue.IsValid() || srcFieldValue.Kind() != targetVal.Kind() { return src } if srcFieldValue.CanSet() { if srcFieldValue.Kind() == reflect.Slice { items := reflect.MakeSlice(srcFieldValue.Type(), targetVal.Len(), targetVal.Len()) for index := 0; index < items.Len(); index++ { tmp := targetVal.Index(index).Interface() items.Index(index).Set(reflect.ValueOf(tmp)) } srcFieldValue.Set(items) } else { srcFieldValue.Set(targetVal) } } if isPtr { return srcVal.Addr().Interface() } return srcVal.Interface() } func toSliceInterfaces(src interface{}) []interface{} { var list []interface{} value := reflect.ValueOf(src) if value.Kind() == reflect.Ptr { value = value.Elem() } if value.Kind() != reflect.Slice { return nil } for index := 0; index < value.Len(); index++ { list = append(list, value.Index(index).Interface()) } return list } func isSlice(value interface{}) bool { if value == nil { return false } typ := reflect.TypeOf(value) if typ.Kind() == reflect.Slice { return true } return false } func isStructNode(node interface{}) bool { if node == nil { return false } value := reflect.ValueOf(node) if value.Kind() == reflect.Ptr { value = value.Elem() } if value.Kind() == reflect.Struct { _, ok := node.(ast.Node) return ok } return false } // notice: type: Named, List or NonNull maybe map type // and it can't be asserted to ast.Node func isNode(node interface{}) bool { if node == nil { return false } val := reflect.ValueOf(node) if !val.IsValid() { return false } switch val.Kind() { case reflect.Map: return true case reflect.Ptr: val = val.Elem() } _, ok := node.(ast.Node) return ok } func isNilNode(node interface{}) bool { if node == nil { return true } val := reflect.ValueOf(node) if !val.IsValid() { return true } switch val.Kind() { case reflect.Ptr, reflect.Map, reflect.Slice: return val.IsNil() case reflect.Bool: return node.(bool) } return false } // VisitInParallel Creates a new visitor instance which delegates to many visitors to run in // parallel. Each visitor will be visited for each node before moving on. // // If a prior visitor edits a node, no following visitors will see that node. func VisitInParallel(visitorOptsSlice ...*VisitorOptions) *VisitorOptions { skipping := map[int]interface{}{} return &VisitorOptions{ Enter: func(p VisitFuncParams) (string, interface{}) { for i, visitorOpts := range visitorOptsSlice { if _, ok := skipping[i]; !ok { node, ok := p.Node.(ast.Node) if !ok { continue } kind := node.GetKind() fn := GetVisitFn(visitorOpts, kind, false) if fn != nil { action, result := fn(p) if action == ActionSkip { skipping[i] = node } else if action == ActionBreak { skipping[i] = ActionBreak } else if action == ActionUpdate { return ActionUpdate, result } } } } return ActionNoChange, nil }, Leave: func(p VisitFuncParams) (string, interface{}) { for i, visitorOpts := range visitorOptsSlice { skippedNode, ok := skipping[i] if !ok { if node, ok := p.Node.(ast.Node); ok { kind := node.GetKind() fn := GetVisitFn(visitorOpts, kind, true) if fn != nil { action, result := fn(p) if action == ActionBreak { skipping[i] = ActionBreak } else if action == ActionUpdate { return ActionUpdate, result } } } } else if skippedNode == p.Node { delete(skipping, i) } } return ActionNoChange, nil }, } } // VisitWithTypeInfo Creates a new visitor instance which maintains a provided TypeInfo instance // along with visiting visitor. func VisitWithTypeInfo(ttypeInfo typeInfo.TypeInfoI, visitorOpts *VisitorOptions) *VisitorOptions { return &VisitorOptions{ Enter: func(p VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(ast.Node); ok { ttypeInfo.Enter(node) fn := GetVisitFn(visitorOpts, node.GetKind(), false) if fn != nil { action, result := fn(p) if action == ActionUpdate { ttypeInfo.Leave(node) if isNode(result) { if result, ok := result.(ast.Node); ok { ttypeInfo.Enter(result) } } } return action, result } } return ActionNoChange, nil }, Leave: func(p VisitFuncParams) (string, interface{}) { action := ActionNoChange var result interface{} if node, ok := p.Node.(ast.Node); ok { fn := GetVisitFn(visitorOpts, node.GetKind(), true) if fn != nil { action, result = fn(p) } ttypeInfo.Leave(node) } return action, result }, } } // GetVisitFn Given a visitor instance, if it is leaving or not, and a node kind, return // the function the visitor runtime should call. // priority [high->low] in VisitorOptions: // KindFuncMap{Kind> {Leave, Enter}} > {Leave, Enter} > {EnterKindMap, LeaveKindMap} func GetVisitFn(visitorOpts *VisitorOptions, kind string, isLeaving bool) VisitFunc { if visitorOpts == nil { return nil } if kindVisitor, ok := visitorOpts.KindFuncMap[kind]; ok { if !isLeaving && kindVisitor.Kind != nil { // { Kind() {} } return kindVisitor.Kind } else if isLeaving { // { Kind: { leave() {} } } return kindVisitor.Leave } else { // { Kind: { enter() {} } } return kindVisitor.Enter } } if isLeaving { // { leave() {} } if genericVisitor := visitorOpts.Leave; genericVisitor != nil { return genericVisitor } if specificKindVisitor, ok := visitorOpts.LeaveKindMap[kind]; ok { // { leave: { Kind() {} } } return specificKindVisitor } } else { // { enter() {} } if genericVisitor := visitorOpts.Enter; genericVisitor != nil { return genericVisitor } if specificKindVisitor, ok := visitorOpts.EnterKindMap[kind]; ok { // { enter: { Kind() {} } } return specificKindVisitor } } return nil } ================================================ FILE: language/visitor/visitor_test.go ================================================ package visitor_test import ( "io/ioutil" "reflect" "testing" "fmt" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/kinds" "github.com/graphql-go/graphql/language/parser" "github.com/graphql-go/graphql/language/printer" "github.com/graphql-go/graphql/language/visitor" "github.com/graphql-go/graphql/testutil" ) func parse(t *testing.T, query string) *ast.Document { astDoc, err := parser.Parse(parser.ParseParams{ Source: query, Options: parser.ParseOptions{ NoLocation: true, }, }) if err != nil { t.Fatalf("Parse failed: %v", err) } return astDoc } func TestVisitor_AllowsEditingANodeBothOnEnterAndOnLeave(t *testing.T) { query := `{ a, b, c { a, b, c } }` astDoc := parse(t, query) var selectionSet *ast.SelectionSet expectedQuery := `{ a, b, c { a, b, c } }` expectedAST := parse(t, expectedQuery) visited := map[string]bool{ "didEnter": false, "didLeave": false, } expectedVisited := map[string]bool{ "didEnter": true, "didLeave": true, } v := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.OperationDefinition: { Enter: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.OperationDefinition); ok { selectionSet = node.SelectionSet visited["didEnter"] = true return visitor.ActionUpdate, ast.NewOperationDefinition(&ast.OperationDefinition{ Loc: node.Loc, Operation: node.Operation, Name: node.Name, VariableDefinitions: node.VariableDefinitions, Directives: node.Directives, SelectionSet: ast.NewSelectionSet(&ast.SelectionSet{ Selections: []ast.Selection{}, }), }) } return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.OperationDefinition); ok { visited["didLeave"] = true return visitor.ActionUpdate, ast.NewOperationDefinition(&ast.OperationDefinition{ Loc: node.Loc, Operation: node.Operation, Name: node.Name, VariableDefinitions: node.VariableDefinitions, Directives: node.Directives, SelectionSet: selectionSet, }) } return visitor.ActionNoChange, nil }, }, }, } editedAst := visitor.Visit(astDoc, v, nil) if !reflect.DeepEqual(expectedAST, editedAst) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedAST, editedAst)) } if !reflect.DeepEqual(visited, expectedVisited) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedVisited, visited)) } } func TestVisitor_AllowsEditingTheRootNodeOnEnterAndOnLeave(t *testing.T) { query := `{ a, b, c { a, b, c } }` astDoc := parse(t, query) definitions := astDoc.Definitions expectedQuery := `{ a, b, c { a, b, c } }` expectedAST := parse(t, expectedQuery) visited := map[string]bool{ "didEnter": false, "didLeave": false, } expectedVisited := map[string]bool{ "didEnter": true, "didLeave": true, } v := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.Document: { Enter: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.Document); ok { visited["didEnter"] = true return visitor.ActionUpdate, ast.NewDocument(&ast.Document{ Loc: node.Loc, Definitions: []ast.Node{}, }) } return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.Document); ok { visited["didLeave"] = true return visitor.ActionUpdate, ast.NewDocument(&ast.Document{ Loc: node.Loc, Definitions: definitions, }) } return visitor.ActionNoChange, nil }, }, }, } editedAst := visitor.Visit(astDoc, v, nil) if !reflect.DeepEqual(expectedAST, editedAst) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedAST, editedAst)) } if !reflect.DeepEqual(visited, expectedVisited) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedVisited, visited)) } } func TestVisitor_AllowsForEditingOnEnter(t *testing.T) { query := `{ a, b, c { a, b, c } }` astDoc := parse(t, query) expectedQuery := `{ a, c { a, c } }` expectedAST := parse(t, expectedQuery) v := &visitor.VisitorOptions{ Enter: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Field: if node.Name != nil && node.Name.Value == "b" { return visitor.ActionUpdate, nil } } return visitor.ActionNoChange, nil }, } editedAst := visitor.Visit(astDoc, v, nil) if !reflect.DeepEqual(expectedAST, editedAst) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedAST, editedAst)) } } func TestVisitor_AllowsForEditingOnLeave(t *testing.T) { query := `{ a, b, c { a, b, c } }` astDoc := parse(t, query) expectedQuery := `{ a, c { a, c } }` expectedAST := parse(t, expectedQuery) v := &visitor.VisitorOptions{ Leave: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Field: if node.Name != nil && node.Name.Value == "b" { return visitor.ActionUpdate, nil } } return visitor.ActionNoChange, nil }, } editedAst := visitor.Visit(astDoc, v, nil) if !reflect.DeepEqual(expectedAST, editedAst) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedAST, editedAst)) } } func TestVisitor_VisitsEditedNode(t *testing.T) { query := `{ a { x } }` astDoc := parse(t, query) addedField := &ast.Field{ Kind: "Field", Name: &ast.Name{ Kind: "Name", Value: "__typename", }, } didVisitAddedField := false v := &visitor.VisitorOptions{ Enter: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Field: if node.Name != nil && node.Name.Value == "a" { s := node.SelectionSet.Selections s = append(s, addedField) ss := node.SelectionSet ss.Selections = s return visitor.ActionUpdate, ast.NewField(&ast.Field{ Kind: "Field", SelectionSet: ss, }) } if reflect.DeepEqual(node, addedField) { didVisitAddedField = true } } return visitor.ActionNoChange, nil }, } _ = visitor.Visit(astDoc, v, nil) if didVisitAddedField == false { t.Fatalf("Unexpected result, expected didVisitAddedField == true") } } func TestVisitor_AllowsSkippingASubTree(t *testing.T) { query := `{ a, b { x }, c }` astDoc := parse(t, query) visited := []interface{}{} expectedVisited := []interface{}{ []interface{}{"enter", "Document", nil}, []interface{}{"enter", "OperationDefinition", nil}, []interface{}{"enter", "SelectionSet", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "a"}, []interface{}{"leave", "Name", "a"}, []interface{}{"leave", "Field", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "c"}, []interface{}{"leave", "Name", "c"}, []interface{}{"leave", "Field", nil}, []interface{}{"leave", "SelectionSet", nil}, []interface{}{"leave", "OperationDefinition", nil}, []interface{}{"leave", "Document", nil}, } v := &visitor.VisitorOptions{ Enter: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"enter", node.Kind, node.Value}) case *ast.Field: visited = append(visited, []interface{}{"enter", node.Kind, nil}) if node.Name != nil && node.Name.Value == "b" { return visitor.ActionSkip, nil } case ast.Node: visited = append(visited, []interface{}{"enter", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"enter", nil, nil}) } return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"leave", node.Kind, node.Value}) case ast.Node: visited = append(visited, []interface{}{"leave", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"leave", nil, nil}) } return visitor.ActionNoChange, nil }, } _ = visitor.Visit(astDoc, v, nil) if !reflect.DeepEqual(visited, expectedVisited) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedVisited, visited)) } } func TestVisitor_AllowsEarlyExitWhileVisiting(t *testing.T) { visited := []interface{}{} query := `{ a, b { x }, c }` astDoc := parse(t, query) expectedVisited := []interface{}{ []interface{}{"enter", "Document", nil}, []interface{}{"enter", "OperationDefinition", nil}, []interface{}{"enter", "SelectionSet", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "a"}, []interface{}{"leave", "Name", "a"}, []interface{}{"leave", "Field", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "b"}, []interface{}{"leave", "Name", "b"}, []interface{}{"enter", "SelectionSet", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "x"}, } v := &visitor.VisitorOptions{ Enter: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"enter", node.Kind, node.Value}) if node.Value == "x" { return visitor.ActionBreak, nil } case ast.Node: visited = append(visited, []interface{}{"enter", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"enter", nil, nil}) } return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"leave", node.Kind, node.Value}) case ast.Node: visited = append(visited, []interface{}{"leave", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"leave", nil, nil}) } return visitor.ActionNoChange, nil }, } _ = visitor.Visit(astDoc, v, nil) if !reflect.DeepEqual(visited, expectedVisited) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedVisited, visited)) } } func TestVisitor_AllowsEarlyExitWhileLeaving(t *testing.T) { visited := []interface{}{} query := `{ a, b { x }, c }` astDoc := parse(t, query) expectedVisited := []interface{}{ []interface{}{"enter", "Document", nil}, []interface{}{"enter", "OperationDefinition", nil}, []interface{}{"enter", "SelectionSet", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "a"}, []interface{}{"leave", "Name", "a"}, []interface{}{"leave", "Field", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "b"}, []interface{}{"leave", "Name", "b"}, []interface{}{"enter", "SelectionSet", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "x"}, []interface{}{"leave", "Name", "x"}, } v := &visitor.VisitorOptions{ Enter: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"enter", node.Kind, node.Value}) case ast.Node: visited = append(visited, []interface{}{"enter", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"enter", nil, nil}) } return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"leave", node.Kind, node.Value}) if node.Value == "x" { return visitor.ActionBreak, nil } case ast.Node: visited = append(visited, []interface{}{"leave", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"leave", nil, nil}) } return visitor.ActionNoChange, nil }, } _ = visitor.Visit(astDoc, v, nil) if !reflect.DeepEqual(visited, expectedVisited) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedVisited, visited)) } } func TestVisitor_AllowsANamedFunctionsVisitorAPI(t *testing.T) { query := `{ a, b { x }, c }` astDoc := parse(t, query) visited := []interface{}{} expectedVisited := []interface{}{ []interface{}{"enter", "SelectionSet", nil}, []interface{}{"enter", "Name", "a"}, []interface{}{"enter", "Name", "b"}, []interface{}{"enter", "SelectionSet", nil}, []interface{}{"enter", "Name", "x"}, []interface{}{"leave", "SelectionSet", nil}, []interface{}{"enter", "Name", "c"}, []interface{}{"leave", "SelectionSet", nil}, } v := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ "Name": { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"enter", node.Kind, node.Value}) } return visitor.ActionNoChange, nil }, }, "SelectionSet": { Enter: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.SelectionSet: visited = append(visited, []interface{}{"enter", node.Kind, nil}) } return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.SelectionSet: visited = append(visited, []interface{}{"leave", node.Kind, nil}) } return visitor.ActionNoChange, nil }, }, }, } _ = visitor.Visit(astDoc, v, nil) if !reflect.DeepEqual(visited, expectedVisited) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedVisited, visited)) } } func TestVisitor_VisitsKitchenSink(t *testing.T) { b, err := ioutil.ReadFile("../../kitchen-sink.graphql") if err != nil { t.Fatalf("unable to load kitchen-sink.graphql") } query := string(b) astDoc := parse(t, query) visited := []interface{}{} expectedVisited := []interface{}{ []interface{}{"enter", "Document", nil, nil}, []interface{}{"enter", "OperationDefinition", 0, nil}, []interface{}{"enter", "Name", "Name", "OperationDefinition"}, []interface{}{"leave", "Name", "Name", "OperationDefinition"}, []interface{}{"enter", "VariableDefinition", 0, nil}, []interface{}{"enter", "Variable", "Variable", "VariableDefinition"}, []interface{}{"enter", "Name", "Name", "Variable"}, []interface{}{"leave", "Name", "Name", "Variable"}, []interface{}{"leave", "Variable", "Variable", "VariableDefinition"}, []interface{}{"enter", "Named", "Type", "VariableDefinition"}, []interface{}{"enter", "Name", "Name", "Named"}, []interface{}{"leave", "Name", "Name", "Named"}, []interface{}{"leave", "Named", "Type", "VariableDefinition"}, []interface{}{"leave", "VariableDefinition", 0, nil}, []interface{}{"enter", "VariableDefinition", 1, nil}, []interface{}{"enter", "Variable", "Variable", "VariableDefinition"}, []interface{}{"enter", "Name", "Name", "Variable"}, []interface{}{"leave", "Name", "Name", "Variable"}, []interface{}{"leave", "Variable", "Variable", "VariableDefinition"}, []interface{}{"enter", "Named", "Type", "VariableDefinition"}, []interface{}{"enter", "Name", "Name", "Named"}, []interface{}{"leave", "Name", "Name", "Named"}, []interface{}{"leave", "Named", "Type", "VariableDefinition"}, []interface{}{"enter", "EnumValue", "DefaultValue", "VariableDefinition"}, []interface{}{"leave", "EnumValue", "DefaultValue", "VariableDefinition"}, []interface{}{"leave", "VariableDefinition", 1, nil}, []interface{}{"enter", "SelectionSet", "SelectionSet", "OperationDefinition"}, []interface{}{"enter", "Field", 0, nil}, []interface{}{"enter", "Name", "Alias", "Field"}, []interface{}{"leave", "Name", "Alias", "Field"}, []interface{}{"enter", "Name", "Name", "Field"}, []interface{}{"leave", "Name", "Name", "Field"}, []interface{}{"enter", "Argument", 0, nil}, []interface{}{"enter", "Name", "Name", "Argument"}, []interface{}{"leave", "Name", "Name", "Argument"}, []interface{}{"enter", "ListValue", "Value", "Argument"}, []interface{}{"enter", "IntValue", 0, nil}, []interface{}{"leave", "IntValue", 0, nil}, []interface{}{"enter", "IntValue", 1, nil}, []interface{}{"leave", "IntValue", 1, nil}, []interface{}{"leave", "ListValue", "Value", "Argument"}, []interface{}{"leave", "Argument", 0, nil}, []interface{}{"enter", "SelectionSet", "SelectionSet", "Field"}, []interface{}{"enter", "Field", 0, nil}, []interface{}{"enter", "Name", "Name", "Field"}, []interface{}{"leave", "Name", "Name", "Field"}, []interface{}{"leave", "Field", 0, nil}, []interface{}{"enter", "InlineFragment", 1, nil}, []interface{}{"enter", "Named", "TypeCondition", "InlineFragment"}, []interface{}{"enter", "Name", "Name", "Named"}, []interface{}{"leave", "Name", "Name", "Named"}, []interface{}{"leave", "Named", "TypeCondition", "InlineFragment"}, []interface{}{"enter", "Directive", 0, nil}, []interface{}{"enter", "Name", "Name", "Directive"}, []interface{}{"leave", "Name", "Name", "Directive"}, []interface{}{"leave", "Directive", 0, nil}, []interface{}{"enter", "SelectionSet", "SelectionSet", "InlineFragment"}, []interface{}{"enter", "Field", 0, nil}, []interface{}{"enter", "Name", "Name", "Field"}, []interface{}{"leave", "Name", "Name", "Field"}, []interface{}{"enter", "SelectionSet", "SelectionSet", "Field"}, []interface{}{"enter", "Field", 0, nil}, []interface{}{"enter", "Name", "Name", "Field"}, []interface{}{"leave", "Name", "Name", "Field"}, []interface{}{"leave", "Field", 0, nil}, []interface{}{"enter", "Field", 1, nil}, []interface{}{"enter", "Name", "Alias", "Field"}, []interface{}{"leave", "Name", "Alias", "Field"}, []interface{}{"enter", "Name", "Name", "Field"}, []interface{}{"leave", "Name", "Name", "Field"}, []interface{}{"enter", "Argument", 0, nil}, []interface{}{"enter", "Name", "Name", "Argument"}, []interface{}{"leave", "Name", "Name", "Argument"}, []interface{}{"enter", "IntValue", "Value", "Argument"}, []interface{}{"leave", "IntValue", "Value", "Argument"}, []interface{}{"leave", "Argument", 0, nil}, []interface{}{"enter", "Argument", 1, nil}, []interface{}{"enter", "Name", "Name", "Argument"}, []interface{}{"leave", "Name", "Name", "Argument"}, []interface{}{"enter", "Variable", "Value", "Argument"}, []interface{}{"enter", "Name", "Name", "Variable"}, []interface{}{"leave", "Name", "Name", "Variable"}, []interface{}{"leave", "Variable", "Value", "Argument"}, []interface{}{"leave", "Argument", 1, nil}, []interface{}{"enter", "Directive", 0, nil}, []interface{}{"enter", "Name", "Name", "Directive"}, []interface{}{"leave", "Name", "Name", "Directive"}, []interface{}{"enter", "Argument", 0, nil}, []interface{}{"enter", "Name", "Name", "Argument"}, []interface{}{"leave", "Name", "Name", "Argument"}, []interface{}{"enter", "Variable", "Value", "Argument"}, []interface{}{"enter", "Name", "Name", "Variable"}, []interface{}{"leave", "Name", "Name", "Variable"}, []interface{}{"leave", "Variable", "Value", "Argument"}, []interface{}{"leave", "Argument", 0, nil}, []interface{}{"leave", "Directive", 0, nil}, []interface{}{"enter", "SelectionSet", "SelectionSet", "Field"}, []interface{}{"enter", "Field", 0, nil}, []interface{}{"enter", "Name", "Name", "Field"}, []interface{}{"leave", "Name", "Name", "Field"}, []interface{}{"leave", "Field", 0, nil}, []interface{}{"enter", "FragmentSpread", 1, nil}, []interface{}{"enter", "Name", "Name", "FragmentSpread"}, []interface{}{"leave", "Name", "Name", "FragmentSpread"}, []interface{}{"leave", "FragmentSpread", 1, nil}, []interface{}{"leave", "SelectionSet", "SelectionSet", "Field"}, []interface{}{"leave", "Field", 1, nil}, []interface{}{"leave", "SelectionSet", "SelectionSet", "Field"}, []interface{}{"leave", "Field", 0, nil}, []interface{}{"leave", "SelectionSet", "SelectionSet", "InlineFragment"}, []interface{}{"leave", "InlineFragment", 1, nil}, []interface{}{"enter", "InlineFragment", 2, nil}, []interface{}{"enter", "Directive", 0, nil}, []interface{}{"enter", "Name", "Name", "Directive"}, []interface{}{"leave", "Name", "Name", "Directive"}, []interface{}{"enter", "Argument", 0, nil}, []interface{}{"enter", "Name", "Name", "Argument"}, []interface{}{"leave", "Name", "Name", "Argument"}, []interface{}{"enter", "Variable", "Value", "Argument"}, []interface{}{"enter", "Name", "Name", "Variable"}, []interface{}{"leave", "Name", "Name", "Variable"}, []interface{}{"leave", "Variable", "Value", "Argument"}, []interface{}{"leave", "Argument", 0, nil}, []interface{}{"leave", "Directive", 0, nil}, []interface{}{"enter", "SelectionSet", "SelectionSet", "InlineFragment"}, []interface{}{"enter", "Field", 0, nil}, []interface{}{"enter", "Name", "Name", "Field"}, []interface{}{"leave", "Name", "Name", "Field"}, []interface{}{"leave", "Field", 0, nil}, []interface{}{"leave", "SelectionSet", "SelectionSet", "InlineFragment"}, []interface{}{"leave", "InlineFragment", 2, nil}, []interface{}{"enter", "InlineFragment", 3, nil}, []interface{}{"enter", "SelectionSet", "SelectionSet", "InlineFragment"}, []interface{}{"enter", "Field", 0, nil}, []interface{}{"enter", "Name", "Name", "Field"}, []interface{}{"leave", "Name", "Name", "Field"}, []interface{}{"leave", "Field", 0, nil}, []interface{}{"leave", "SelectionSet", "SelectionSet", "InlineFragment"}, []interface{}{"leave", "InlineFragment", 3, nil}, []interface{}{"leave", "SelectionSet", "SelectionSet", "Field"}, []interface{}{"leave", "Field", 0, nil}, []interface{}{"leave", "SelectionSet", "SelectionSet", "OperationDefinition"}, []interface{}{"leave", "OperationDefinition", 0, nil}, []interface{}{"enter", "OperationDefinition", 1, nil}, []interface{}{"enter", "Name", "Name", "OperationDefinition"}, []interface{}{"leave", "Name", "Name", "OperationDefinition"}, []interface{}{"enter", "SelectionSet", "SelectionSet", "OperationDefinition"}, []interface{}{"enter", "Field", 0, nil}, []interface{}{"enter", "Name", "Name", "Field"}, []interface{}{"leave", "Name", "Name", "Field"}, []interface{}{"enter", "Argument", 0, nil}, []interface{}{"enter", "Name", "Name", "Argument"}, []interface{}{"leave", "Name", "Name", "Argument"}, []interface{}{"enter", "IntValue", "Value", "Argument"}, []interface{}{"leave", "IntValue", "Value", "Argument"}, []interface{}{"leave", "Argument", 0, nil}, []interface{}{"enter", "Directive", 0, nil}, []interface{}{"enter", "Name", "Name", "Directive"}, []interface{}{"leave", "Name", "Name", "Directive"}, []interface{}{"leave", "Directive", 0, nil}, []interface{}{"enter", "SelectionSet", "SelectionSet", "Field"}, []interface{}{"enter", "Field", 0, nil}, []interface{}{"enter", "Name", "Name", "Field"}, []interface{}{"leave", "Name", "Name", "Field"}, []interface{}{"enter", "SelectionSet", "SelectionSet", "Field"}, []interface{}{"enter", "Field", 0, nil}, []interface{}{"enter", "Name", "Name", "Field"}, []interface{}{"leave", "Name", "Name", "Field"}, []interface{}{"leave", "Field", 0, nil}, []interface{}{"leave", "SelectionSet", "SelectionSet", "Field"}, []interface{}{"leave", "Field", 0, nil}, []interface{}{"leave", "SelectionSet", "SelectionSet", "Field"}, []interface{}{"leave", "Field", 0, nil}, []interface{}{"leave", "SelectionSet", "SelectionSet", "OperationDefinition"}, []interface{}{"leave", "OperationDefinition", 1, nil}, []interface{}{"enter", "OperationDefinition", 2, nil}, []interface{}{"enter", "Name", "Name", "OperationDefinition"}, []interface{}{"leave", "Name", "Name", "OperationDefinition"}, []interface{}{"enter", "VariableDefinition", 0, nil}, []interface{}{"enter", "Variable", "Variable", "VariableDefinition"}, []interface{}{"enter", "Name", "Name", "Variable"}, []interface{}{"leave", "Name", "Name", "Variable"}, []interface{}{"leave", "Variable", "Variable", "VariableDefinition"}, []interface{}{"enter", "Named", "Type", "VariableDefinition"}, []interface{}{"enter", "Name", "Name", "Named"}, []interface{}{"leave", "Name", "Name", "Named"}, []interface{}{"leave", "Named", "Type", "VariableDefinition"}, []interface{}{"leave", "VariableDefinition", 0, nil}, []interface{}{"enter", "SelectionSet", "SelectionSet", "OperationDefinition"}, []interface{}{"enter", "Field", 0, nil}, []interface{}{"enter", "Name", "Name", "Field"}, []interface{}{"leave", "Name", "Name", "Field"}, []interface{}{"enter", "Argument", 0, nil}, []interface{}{"enter", "Name", "Name", "Argument"}, []interface{}{"leave", "Name", "Name", "Argument"}, []interface{}{"enter", "Variable", "Value", "Argument"}, []interface{}{"enter", "Name", "Name", "Variable"}, []interface{}{"leave", "Name", "Name", "Variable"}, []interface{}{"leave", "Variable", "Value", "Argument"}, []interface{}{"leave", "Argument", 0, nil}, []interface{}{"enter", "SelectionSet", "SelectionSet", "Field"}, []interface{}{"enter", "Field", 0, nil}, []interface{}{"enter", "Name", "Name", "Field"}, []interface{}{"leave", "Name", "Name", "Field"}, []interface{}{"enter", "SelectionSet", "SelectionSet", "Field"}, []interface{}{"enter", "Field", 0, nil}, []interface{}{"enter", "Name", "Name", "Field"}, []interface{}{"leave", "Name", "Name", "Field"}, []interface{}{"enter", "SelectionSet", "SelectionSet", "Field"}, []interface{}{"enter", "Field", 0, nil}, []interface{}{"enter", "Name", "Name", "Field"}, []interface{}{"leave", "Name", "Name", "Field"}, []interface{}{"leave", "Field", 0, nil}, []interface{}{"leave", "SelectionSet", "SelectionSet", "Field"}, []interface{}{"leave", "Field", 0, nil}, []interface{}{"enter", "Field", 1, nil}, []interface{}{"enter", "Name", "Name", "Field"}, []interface{}{"leave", "Name", "Name", "Field"}, []interface{}{"enter", "SelectionSet", "SelectionSet", "Field"}, []interface{}{"enter", "Field", 0, nil}, []interface{}{"enter", "Name", "Name", "Field"}, []interface{}{"leave", "Name", "Name", "Field"}, []interface{}{"leave", "Field", 0, nil}, []interface{}{"leave", "SelectionSet", "SelectionSet", "Field"}, []interface{}{"leave", "Field", 1, nil}, []interface{}{"leave", "SelectionSet", "SelectionSet", "Field"}, []interface{}{"leave", "Field", 0, nil}, []interface{}{"leave", "SelectionSet", "SelectionSet", "Field"}, []interface{}{"leave", "Field", 0, nil}, []interface{}{"leave", "SelectionSet", "SelectionSet", "OperationDefinition"}, []interface{}{"leave", "OperationDefinition", 2, nil}, []interface{}{"enter", "FragmentDefinition", 3, nil}, []interface{}{"enter", "Name", "Name", "FragmentDefinition"}, []interface{}{"leave", "Name", "Name", "FragmentDefinition"}, []interface{}{"enter", "Named", "TypeCondition", "FragmentDefinition"}, []interface{}{"enter", "Name", "Name", "Named"}, []interface{}{"leave", "Name", "Name", "Named"}, []interface{}{"leave", "Named", "TypeCondition", "FragmentDefinition"}, []interface{}{"enter", "SelectionSet", "SelectionSet", "FragmentDefinition"}, []interface{}{"enter", "Field", 0, nil}, []interface{}{"enter", "Name", "Name", "Field"}, []interface{}{"leave", "Name", "Name", "Field"}, []interface{}{"enter", "Argument", 0, nil}, []interface{}{"enter", "Name", "Name", "Argument"}, []interface{}{"leave", "Name", "Name", "Argument"}, []interface{}{"enter", "Variable", "Value", "Argument"}, []interface{}{"enter", "Name", "Name", "Variable"}, []interface{}{"leave", "Name", "Name", "Variable"}, []interface{}{"leave", "Variable", "Value", "Argument"}, []interface{}{"leave", "Argument", 0, nil}, []interface{}{"enter", "Argument", 1, nil}, []interface{}{"enter", "Name", "Name", "Argument"}, []interface{}{"leave", "Name", "Name", "Argument"}, []interface{}{"enter", "Variable", "Value", "Argument"}, []interface{}{"enter", "Name", "Name", "Variable"}, []interface{}{"leave", "Name", "Name", "Variable"}, []interface{}{"leave", "Variable", "Value", "Argument"}, []interface{}{"leave", "Argument", 1, nil}, []interface{}{"enter", "Argument", 2, nil}, []interface{}{"enter", "Name", "Name", "Argument"}, []interface{}{"leave", "Name", "Name", "Argument"}, []interface{}{"enter", "ObjectValue", "Value", "Argument"}, []interface{}{"enter", "ObjectField", 0, nil}, []interface{}{"enter", "Name", "Name", "ObjectField"}, []interface{}{"leave", "Name", "Name", "ObjectField"}, []interface{}{"enter", "StringValue", "Value", "ObjectField"}, []interface{}{"leave", "StringValue", "Value", "ObjectField"}, []interface{}{"leave", "ObjectField", 0, nil}, []interface{}{"leave", "ObjectValue", "Value", "Argument"}, []interface{}{"leave", "Argument", 2, nil}, []interface{}{"leave", "Field", 0, nil}, []interface{}{"leave", "SelectionSet", "SelectionSet", "FragmentDefinition"}, []interface{}{"leave", "FragmentDefinition", 3, nil}, []interface{}{"enter", "OperationDefinition", 4, nil}, []interface{}{"enter", "SelectionSet", "SelectionSet", "OperationDefinition"}, []interface{}{"enter", "Field", 0, nil}, []interface{}{"enter", "Name", "Name", "Field"}, []interface{}{"leave", "Name", "Name", "Field"}, []interface{}{"enter", "Argument", 0, nil}, []interface{}{"enter", "Name", "Name", "Argument"}, []interface{}{"leave", "Name", "Name", "Argument"}, []interface{}{"enter", "BooleanValue", "Value", "Argument"}, []interface{}{"leave", "BooleanValue", "Value", "Argument"}, []interface{}{"leave", "Argument", 0, nil}, []interface{}{"enter", "Argument", 1, nil}, []interface{}{"enter", "Name", "Name", "Argument"}, []interface{}{"leave", "Name", "Name", "Argument"}, []interface{}{"enter", "BooleanValue", "Value", "Argument"}, []interface{}{"leave", "BooleanValue", "Value", "Argument"}, []interface{}{"leave", "Argument", 1, nil}, []interface{}{"leave", "Field", 0, nil}, []interface{}{"enter", "Field", 1, nil}, []interface{}{"enter", "Name", "Name", "Field"}, []interface{}{"leave", "Name", "Name", "Field"}, []interface{}{"leave", "Field", 1, nil}, []interface{}{"leave", "SelectionSet", "SelectionSet", "OperationDefinition"}, []interface{}{"leave", "OperationDefinition", 4, nil}, []interface{}{"leave", "Document", nil, nil}, } v := &visitor.VisitorOptions{ Enter: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case ast.Node: if p.Parent != nil { visited = append(visited, []interface{}{"enter", node.GetKind(), p.Key, p.Parent.GetKind()}) } else { visited = append(visited, []interface{}{"enter", node.GetKind(), p.Key, nil}) } } return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case ast.Node: if p.Parent != nil { visited = append(visited, []interface{}{"leave", node.GetKind(), p.Key, p.Parent.GetKind()}) } else { visited = append(visited, []interface{}{"leave", node.GetKind(), p.Key, nil}) } } return visitor.ActionNoChange, nil }, } _ = visitor.Visit(astDoc, v, nil) if !reflect.DeepEqual(visited, expectedVisited) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedVisited, visited)) } } func TestVisitor_VisitInParallel_AllowsSkippingASubTree(t *testing.T) { // Note: nearly identical to the above test of the same test but // using visitInParallel. query := `{ a, b { x }, c }` astDoc := parse(t, query) visited := []interface{}{} expectedVisited := []interface{}{ []interface{}{"enter", "Document", nil}, []interface{}{"enter", "OperationDefinition", nil}, []interface{}{"enter", "SelectionSet", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "a"}, []interface{}{"leave", "Name", "a"}, []interface{}{"leave", "Field", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "c"}, []interface{}{"leave", "Name", "c"}, []interface{}{"leave", "Field", nil}, []interface{}{"leave", "SelectionSet", nil}, []interface{}{"leave", "OperationDefinition", nil}, []interface{}{"leave", "Document", nil}, } v := &visitor.VisitorOptions{ Enter: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"enter", node.Kind, node.Value}) case *ast.Field: visited = append(visited, []interface{}{"enter", node.Kind, nil}) if node.Name != nil && node.Name.Value == "b" { return visitor.ActionSkip, nil } case ast.Node: visited = append(visited, []interface{}{"enter", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"enter", nil, nil}) } return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"leave", node.Kind, node.Value}) case ast.Node: visited = append(visited, []interface{}{"leave", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"leave", nil, nil}) } return visitor.ActionNoChange, nil }, } _ = visitor.Visit(astDoc, visitor.VisitInParallel(v), nil) if !reflect.DeepEqual(visited, expectedVisited) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedVisited, visited)) } } func TestVisitor_VisitInParallel_AllowsSkippingDifferentSubTrees(t *testing.T) { query := `{ a { x }, b { y} }` astDoc := parse(t, query) visited := []interface{}{} expectedVisited := []interface{}{ []interface{}{"no-a", "enter", "Document", nil}, []interface{}{"no-b", "enter", "Document", nil}, []interface{}{"no-a", "enter", "OperationDefinition", nil}, []interface{}{"no-b", "enter", "OperationDefinition", nil}, []interface{}{"no-a", "enter", "SelectionSet", nil}, []interface{}{"no-b", "enter", "SelectionSet", nil}, []interface{}{"no-a", "enter", "Field", nil}, []interface{}{"no-b", "enter", "Field", nil}, []interface{}{"no-b", "enter", "Name", "a"}, []interface{}{"no-b", "leave", "Name", "a"}, []interface{}{"no-b", "enter", "SelectionSet", nil}, []interface{}{"no-b", "enter", "Field", nil}, []interface{}{"no-b", "enter", "Name", "x"}, []interface{}{"no-b", "leave", "Name", "x"}, []interface{}{"no-b", "leave", "Field", nil}, []interface{}{"no-b", "leave", "SelectionSet", nil}, []interface{}{"no-b", "leave", "Field", nil}, []interface{}{"no-a", "enter", "Field", nil}, []interface{}{"no-b", "enter", "Field", nil}, []interface{}{"no-a", "enter", "Name", "b"}, []interface{}{"no-a", "leave", "Name", "b"}, []interface{}{"no-a", "enter", "SelectionSet", nil}, []interface{}{"no-a", "enter", "Field", nil}, []interface{}{"no-a", "enter", "Name", "y"}, []interface{}{"no-a", "leave", "Name", "y"}, []interface{}{"no-a", "leave", "Field", nil}, []interface{}{"no-a", "leave", "SelectionSet", nil}, []interface{}{"no-a", "leave", "Field", nil}, []interface{}{"no-a", "leave", "SelectionSet", nil}, []interface{}{"no-b", "leave", "SelectionSet", nil}, []interface{}{"no-a", "leave", "OperationDefinition", nil}, []interface{}{"no-b", "leave", "OperationDefinition", nil}, []interface{}{"no-a", "leave", "Document", nil}, []interface{}{"no-b", "leave", "Document", nil}, } v := []*visitor.VisitorOptions{ { Enter: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"no-a", "enter", node.Kind, node.Value}) case *ast.Field: visited = append(visited, []interface{}{"no-a", "enter", node.Kind, nil}) if node.Name != nil && node.Name.Value == "a" { return visitor.ActionSkip, nil } case ast.Node: visited = append(visited, []interface{}{"no-a", "enter", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"no-a", "enter", nil, nil}) } return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"no-a", "leave", node.Kind, node.Value}) case ast.Node: visited = append(visited, []interface{}{"no-a", "leave", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"no-a", "leave", nil, nil}) } return visitor.ActionNoChange, nil }, }, { Enter: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"no-b", "enter", node.Kind, node.Value}) case *ast.Field: visited = append(visited, []interface{}{"no-b", "enter", node.Kind, nil}) if node.Name != nil && node.Name.Value == "b" { return visitor.ActionSkip, nil } case ast.Node: visited = append(visited, []interface{}{"no-b", "enter", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"no-b", "enter", nil, nil}) } return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"no-b", "leave", node.Kind, node.Value}) case ast.Node: visited = append(visited, []interface{}{"no-b", "leave", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"no-b", "leave", nil, nil}) } return visitor.ActionNoChange, nil }, }, } _ = visitor.Visit(astDoc, visitor.VisitInParallel(v...), nil) if !reflect.DeepEqual(visited, expectedVisited) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedVisited, visited)) } } func TestVisitor_VisitInParallel_AllowsEarlyExitWhileVisiting(t *testing.T) { // Note: nearly identical to the above test of the same test but // using visitInParallel. visited := []interface{}{} query := `{ a, b { x }, c }` astDoc := parse(t, query) expectedVisited := []interface{}{ []interface{}{"enter", "Document", nil}, []interface{}{"enter", "OperationDefinition", nil}, []interface{}{"enter", "SelectionSet", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "a"}, []interface{}{"leave", "Name", "a"}, []interface{}{"leave", "Field", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "b"}, []interface{}{"leave", "Name", "b"}, []interface{}{"enter", "SelectionSet", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "x"}, } v := &visitor.VisitorOptions{ Enter: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"enter", node.Kind, node.Value}) if node.Value == "x" { return visitor.ActionBreak, nil } case ast.Node: visited = append(visited, []interface{}{"enter", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"enter", nil, nil}) } return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"leave", node.Kind, node.Value}) case ast.Node: visited = append(visited, []interface{}{"leave", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"leave", nil, nil}) } return visitor.ActionNoChange, nil }, } _ = visitor.Visit(astDoc, visitor.VisitInParallel(v), nil) if !reflect.DeepEqual(visited, expectedVisited) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedVisited, visited)) } } func TestVisitor_VisitInParallel_AllowsEarlyExitFromDifferentPoints(t *testing.T) { visited := []interface{}{} query := `{ a { y }, b { x } }` astDoc := parse(t, query) expectedVisited := []interface{}{ []interface{}{"break-a", "enter", "Document", nil}, []interface{}{"break-b", "enter", "Document", nil}, []interface{}{"break-a", "enter", "OperationDefinition", nil}, []interface{}{"break-b", "enter", "OperationDefinition", nil}, []interface{}{"break-a", "enter", "SelectionSet", nil}, []interface{}{"break-b", "enter", "SelectionSet", nil}, []interface{}{"break-a", "enter", "Field", nil}, []interface{}{"break-b", "enter", "Field", nil}, []interface{}{"break-a", "enter", "Name", "a"}, []interface{}{"break-b", "enter", "Name", "a"}, []interface{}{"break-b", "leave", "Name", "a"}, []interface{}{"break-b", "enter", "SelectionSet", nil}, []interface{}{"break-b", "enter", "Field", nil}, []interface{}{"break-b", "enter", "Name", "y"}, []interface{}{"break-b", "leave", "Name", "y"}, []interface{}{"break-b", "leave", "Field", nil}, []interface{}{"break-b", "leave", "SelectionSet", nil}, []interface{}{"break-b", "leave", "Field", nil}, []interface{}{"break-b", "enter", "Field", nil}, []interface{}{"break-b", "enter", "Name", "b"}, } v := &visitor.VisitorOptions{ Enter: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"break-a", "enter", node.Kind, node.Value}) if node != nil && node.Value == "a" { return visitor.ActionBreak, nil } case ast.Node: visited = append(visited, []interface{}{"break-a", "enter", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"break-a", "enter", nil, nil}) } return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"break-a", "leave", node.Kind, node.Value}) case ast.Node: visited = append(visited, []interface{}{"break-a", "leave", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"break-a", "leave", nil, nil}) } return visitor.ActionNoChange, nil }, } v2 := &visitor.VisitorOptions{ Enter: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"break-b", "enter", node.Kind, node.Value}) if node != nil && node.Value == "b" { return visitor.ActionBreak, nil } case ast.Node: visited = append(visited, []interface{}{"break-b", "enter", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"break-b", "enter", nil, nil}) } return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"break-b", "leave", node.Kind, node.Value}) case ast.Node: visited = append(visited, []interface{}{"break-b", "leave", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"break-b", "leave", nil, nil}) } return visitor.ActionNoChange, nil }, } _ = visitor.Visit(astDoc, visitor.VisitInParallel(v, v2), nil) if !reflect.DeepEqual(visited, expectedVisited) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedVisited, visited)) } } func TestVisitor_VisitInParallel_AllowsEarlyExitWhileLeaving(t *testing.T) { visited := []interface{}{} query := `{ a, b { x }, c }` astDoc := parse(t, query) expectedVisited := []interface{}{ []interface{}{"enter", "Document", nil}, []interface{}{"enter", "OperationDefinition", nil}, []interface{}{"enter", "SelectionSet", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "a"}, []interface{}{"leave", "Name", "a"}, []interface{}{"leave", "Field", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "b"}, []interface{}{"leave", "Name", "b"}, []interface{}{"enter", "SelectionSet", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "x"}, []interface{}{"leave", "Name", "x"}, } v := &visitor.VisitorOptions{ Enter: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"enter", node.Kind, node.Value}) case ast.Node: visited = append(visited, []interface{}{"enter", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"enter", nil, nil}) } return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"leave", node.Kind, node.Value}) if node.Value == "x" { return visitor.ActionBreak, nil } case ast.Node: visited = append(visited, []interface{}{"leave", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"leave", nil, nil}) } return visitor.ActionNoChange, nil }, } _ = visitor.Visit(astDoc, visitor.VisitInParallel(v), nil) if !reflect.DeepEqual(visited, expectedVisited) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedVisited, visited)) } } func TestVisitor_VisitInParallel_AllowsEarlyExitFromLeavingDifferentPoints(t *testing.T) { visited := []interface{}{} query := `{ a { y }, b { x } }` astDoc := parse(t, query) expectedVisited := []interface{}{ []interface{}{"break-a", "enter", "Document", nil}, []interface{}{"break-b", "enter", "Document", nil}, []interface{}{"break-a", "enter", "OperationDefinition", nil}, []interface{}{"break-b", "enter", "OperationDefinition", nil}, []interface{}{"break-a", "enter", "SelectionSet", nil}, []interface{}{"break-b", "enter", "SelectionSet", nil}, []interface{}{"break-a", "enter", "Field", nil}, []interface{}{"break-b", "enter", "Field", nil}, []interface{}{"break-a", "enter", "Name", "a"}, []interface{}{"break-b", "enter", "Name", "a"}, []interface{}{"break-a", "leave", "Name", "a"}, []interface{}{"break-b", "leave", "Name", "a"}, []interface{}{"break-a", "enter", "SelectionSet", nil}, []interface{}{"break-b", "enter", "SelectionSet", nil}, []interface{}{"break-a", "enter", "Field", nil}, []interface{}{"break-b", "enter", "Field", nil}, []interface{}{"break-a", "enter", "Name", "y"}, []interface{}{"break-b", "enter", "Name", "y"}, []interface{}{"break-a", "leave", "Name", "y"}, []interface{}{"break-b", "leave", "Name", "y"}, []interface{}{"break-a", "leave", "Field", nil}, []interface{}{"break-b", "leave", "Field", nil}, []interface{}{"break-a", "leave", "SelectionSet", nil}, []interface{}{"break-b", "leave", "SelectionSet", nil}, []interface{}{"break-a", "leave", "Field", nil}, []interface{}{"break-b", "leave", "Field", nil}, []interface{}{"break-b", "enter", "Field", nil}, []interface{}{"break-b", "enter", "Name", "b"}, []interface{}{"break-b", "leave", "Name", "b"}, []interface{}{"break-b", "enter", "SelectionSet", nil}, []interface{}{"break-b", "enter", "Field", nil}, []interface{}{"break-b", "enter", "Name", "x"}, []interface{}{"break-b", "leave", "Name", "x"}, []interface{}{"break-b", "leave", "Field", nil}, []interface{}{"break-b", "leave", "SelectionSet", nil}, []interface{}{"break-b", "leave", "Field", nil}, } v := &visitor.VisitorOptions{ Enter: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"break-a", "enter", node.Kind, node.Value}) case ast.Node: visited = append(visited, []interface{}{"break-a", "enter", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"break-a", "enter", nil, nil}) } return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Field: visited = append(visited, []interface{}{"break-a", "leave", node.GetKind(), nil}) if node.Name != nil && node.Name.Value == "a" { return visitor.ActionBreak, nil } case *ast.Name: visited = append(visited, []interface{}{"break-a", "leave", node.Kind, node.Value}) case ast.Node: visited = append(visited, []interface{}{"break-a", "leave", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"break-a", "leave", nil, nil}) } return visitor.ActionNoChange, nil }, } v2 := &visitor.VisitorOptions{ Enter: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"break-b", "enter", node.Kind, node.Value}) case ast.Node: visited = append(visited, []interface{}{"break-b", "enter", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"break-b", "enter", nil, nil}) } return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Field: visited = append(visited, []interface{}{"break-b", "leave", node.GetKind(), nil}) if node.Name != nil && node.Name.Value == "b" { return visitor.ActionBreak, nil } case *ast.Name: visited = append(visited, []interface{}{"break-b", "leave", node.Kind, node.Value}) case ast.Node: visited = append(visited, []interface{}{"break-b", "leave", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"break-b", "leave", nil, nil}) } return visitor.ActionNoChange, nil }, } _ = visitor.Visit(astDoc, visitor.VisitInParallel(v, v2), nil) if !reflect.DeepEqual(visited, expectedVisited) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedVisited, visited)) } } func TestVisitor_VisitInParallel_AllowsForEditingOnEnter(t *testing.T) { visited := []interface{}{} query := `{ a, b, c { a, b, c } }` astDoc := parse(t, query) expectedVisited := []interface{}{ []interface{}{"enter", "Document", nil}, []interface{}{"enter", "OperationDefinition", nil}, []interface{}{"enter", "SelectionSet", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "a"}, []interface{}{"leave", "Name", "a"}, []interface{}{"leave", "Field", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "c"}, []interface{}{"leave", "Name", "c"}, []interface{}{"enter", "SelectionSet", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "a"}, []interface{}{"leave", "Name", "a"}, []interface{}{"leave", "Field", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "c"}, []interface{}{"leave", "Name", "c"}, []interface{}{"leave", "Field", nil}, []interface{}{"leave", "SelectionSet", nil}, []interface{}{"leave", "Field", nil}, []interface{}{"leave", "SelectionSet", nil}, []interface{}{"leave", "OperationDefinition", nil}, []interface{}{"leave", "Document", nil}, } v := &visitor.VisitorOptions{ Enter: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Field: if node != nil && node.Name != nil && node.Name.Value == "b" { return visitor.ActionUpdate, nil } } return visitor.ActionNoChange, nil }, } v2 := &visitor.VisitorOptions{ Enter: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"enter", node.Kind, node.Value}) case ast.Node: visited = append(visited, []interface{}{"enter", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"enter", nil, nil}) } return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"leave", node.Kind, node.Value}) case ast.Node: visited = append(visited, []interface{}{"leave", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"leave", nil, nil}) } return visitor.ActionNoChange, nil }, } _ = visitor.Visit(astDoc, visitor.VisitInParallel(v, v2), nil) if !reflect.DeepEqual(visited, expectedVisited) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedVisited, visited)) } } func TestVisitor_VisitInParallel_AllowsForEditingOnLeave(t *testing.T) { visited := []interface{}{} query := `{ a, b, c { a, b, c } }` astDoc := parse(t, query) expectedVisited := []interface{}{ []interface{}{"enter", "Document", nil}, []interface{}{"enter", "OperationDefinition", nil}, []interface{}{"enter", "SelectionSet", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "a"}, []interface{}{"leave", "Name", "a"}, []interface{}{"leave", "Field", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "b"}, []interface{}{"leave", "Name", "b"}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "c"}, []interface{}{"leave", "Name", "c"}, []interface{}{"enter", "SelectionSet", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "a"}, []interface{}{"leave", "Name", "a"}, []interface{}{"leave", "Field", nil}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "b"}, []interface{}{"leave", "Name", "b"}, []interface{}{"enter", "Field", nil}, []interface{}{"enter", "Name", "c"}, []interface{}{"leave", "Name", "c"}, []interface{}{"leave", "Field", nil}, []interface{}{"leave", "SelectionSet", nil}, []interface{}{"leave", "Field", nil}, []interface{}{"leave", "SelectionSet", nil}, []interface{}{"leave", "OperationDefinition", nil}, []interface{}{"leave", "Document", nil}, } v := &visitor.VisitorOptions{ Leave: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Field: if node != nil && node.Name != nil && node.Name.Value == "b" { return visitor.ActionUpdate, nil } } return visitor.ActionNoChange, nil }, } v2 := &visitor.VisitorOptions{ Enter: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"enter", node.Kind, node.Value}) case ast.Node: visited = append(visited, []interface{}{"enter", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"enter", nil, nil}) } return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"leave", node.Kind, node.Value}) case ast.Node: visited = append(visited, []interface{}{"leave", node.GetKind(), nil}) default: visited = append(visited, []interface{}{"leave", nil, nil}) } return visitor.ActionNoChange, nil }, } editedAST := visitor.Visit(astDoc, visitor.VisitInParallel(v, v2), nil) if !reflect.DeepEqual(visited, expectedVisited) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedVisited, visited)) } expectedEditedAST := parse(t, `{ a, c { a, c } }`) if !reflect.DeepEqual(editedAST, expectedEditedAST) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedEditedAST, editedAST)) } } func TestVisitor_VisitWithTypeInfo_MaintainsTypeInfoDuringVisit(t *testing.T) { visited := []interface{}{} typeInfo := graphql.NewTypeInfo(&graphql.TypeInfoConfig{ Schema: testutil.TestSchema, }) query := `{ human(id: 4) { name, pets { name }, unknown } }` astDoc := parse(t, query) expectedVisited := []interface{}{ []interface{}{"enter", "Document", nil, nil, nil, nil}, []interface{}{"enter", "OperationDefinition", nil, nil, "QueryRoot", nil}, []interface{}{"enter", "SelectionSet", nil, "QueryRoot", "QueryRoot", nil}, []interface{}{"enter", "Field", nil, "QueryRoot", "Human", nil}, []interface{}{"enter", "Name", "human", "QueryRoot", "Human", nil}, []interface{}{"leave", "Name", "human", "QueryRoot", "Human", nil}, []interface{}{"enter", "Argument", nil, "QueryRoot", "Human", "ID"}, []interface{}{"enter", "Name", "id", "QueryRoot", "Human", "ID"}, []interface{}{"leave", "Name", "id", "QueryRoot", "Human", "ID"}, []interface{}{"enter", "IntValue", nil, "QueryRoot", "Human", "ID"}, []interface{}{"leave", "IntValue", nil, "QueryRoot", "Human", "ID"}, []interface{}{"leave", "Argument", nil, "QueryRoot", "Human", "ID"}, []interface{}{"enter", "SelectionSet", nil, "Human", "Human", nil}, []interface{}{"enter", "Field", nil, "Human", "String", nil}, []interface{}{"enter", "Name", "name", "Human", "String", nil}, []interface{}{"leave", "Name", "name", "Human", "String", nil}, []interface{}{"leave", "Field", nil, "Human", "String", nil}, []interface{}{"enter", "Field", nil, "Human", "[Pet]", nil}, []interface{}{"enter", "Name", "pets", "Human", "[Pet]", nil}, []interface{}{"leave", "Name", "pets", "Human", "[Pet]", nil}, []interface{}{"enter", "SelectionSet", nil, "Pet", "[Pet]", nil}, []interface{}{"enter", "Field", nil, "Pet", "String", nil}, []interface{}{"enter", "Name", "name", "Pet", "String", nil}, []interface{}{"leave", "Name", "name", "Pet", "String", nil}, []interface{}{"leave", "Field", nil, "Pet", "String", nil}, []interface{}{"leave", "SelectionSet", nil, "Pet", "[Pet]", nil}, []interface{}{"leave", "Field", nil, "Human", "[Pet]", nil}, []interface{}{"enter", "Field", nil, "Human", nil, nil}, []interface{}{"enter", "Name", "unknown", "Human", nil, nil}, []interface{}{"leave", "Name", "unknown", "Human", nil, nil}, []interface{}{"leave", "Field", nil, "Human", nil, nil}, []interface{}{"leave", "SelectionSet", nil, "Human", "Human", nil}, []interface{}{"leave", "Field", nil, "QueryRoot", "Human", nil}, []interface{}{"leave", "SelectionSet", nil, "QueryRoot", "QueryRoot", nil}, []interface{}{"leave", "OperationDefinition", nil, nil, "QueryRoot", nil}, []interface{}{"leave", "Document", nil, nil, nil, nil}, } v := &visitor.VisitorOptions{ Enter: func(p visitor.VisitFuncParams) (string, interface{}) { var parentType interface{} var ttype interface{} var inputType interface{} if typeInfo.ParentType() != nil { parentType = fmt.Sprintf("%v", typeInfo.ParentType()) } if typeInfo.Type() != nil { ttype = fmt.Sprintf("%v", typeInfo.Type()) } if typeInfo.InputType() != nil { inputType = fmt.Sprintf("%v", typeInfo.InputType()) } switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"enter", node.Kind, node.Value, parentType, ttype, inputType}) case ast.Node: visited = append(visited, []interface{}{"enter", node.GetKind(), nil, parentType, ttype, inputType}) default: visited = append(visited, []interface{}{"enter", nil, nil, parentType, ttype, inputType}) } return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { var parentType interface{} var ttype interface{} var inputType interface{} if typeInfo.ParentType() != nil { parentType = fmt.Sprintf("%v", typeInfo.ParentType()) } if typeInfo.Type() != nil { ttype = fmt.Sprintf("%v", typeInfo.Type()) } if typeInfo.InputType() != nil { inputType = fmt.Sprintf("%v", typeInfo.InputType()) } switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"leave", node.Kind, node.Value, parentType, ttype, inputType}) case ast.Node: visited = append(visited, []interface{}{"leave", node.GetKind(), nil, parentType, ttype, inputType}) default: visited = append(visited, []interface{}{"leave", nil, nil, parentType, ttype, inputType}) } return visitor.ActionNoChange, nil }, } _ = visitor.Visit(astDoc, visitor.VisitWithTypeInfo(typeInfo, v), nil) if !reflect.DeepEqual(visited, expectedVisited) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedVisited, visited)) } } func TestVisitor_VisitWithTypeInfo_MaintainsTypeInfoDuringEdit(t *testing.T) { visited := []interface{}{} typeInfo := graphql.NewTypeInfo(&graphql.TypeInfoConfig{ Schema: testutil.TestSchema, }) astDoc := parse(t, `{ human(id: 4) { name, pets }, alien }`) expectedVisited := []interface{}{ []interface{}{"enter", "Document", nil, nil, nil, nil}, []interface{}{"enter", "OperationDefinition", nil, nil, "QueryRoot", nil}, []interface{}{"enter", "SelectionSet", nil, "QueryRoot", "QueryRoot", nil}, []interface{}{"enter", "Field", nil, "QueryRoot", "Human", nil}, []interface{}{"enter", "Name", "human", "QueryRoot", "Human", nil}, []interface{}{"leave", "Name", "human", "QueryRoot", "Human", nil}, []interface{}{"enter", "Argument", nil, "QueryRoot", "Human", "ID"}, []interface{}{"enter", "Name", "id", "QueryRoot", "Human", "ID"}, []interface{}{"leave", "Name", "id", "QueryRoot", "Human", "ID"}, []interface{}{"enter", "IntValue", nil, "QueryRoot", "Human", "ID"}, []interface{}{"leave", "IntValue", nil, "QueryRoot", "Human", "ID"}, []interface{}{"leave", "Argument", nil, "QueryRoot", "Human", "ID"}, []interface{}{"enter", "SelectionSet", nil, "Human", "Human", nil}, []interface{}{"enter", "Field", nil, "Human", "String", nil}, []interface{}{"enter", "Name", "name", "Human", "String", nil}, []interface{}{"leave", "Name", "name", "Human", "String", nil}, []interface{}{"leave", "Field", nil, "Human", "String", nil}, []interface{}{"enter", "Field", nil, "Human", "[Pet]", nil}, []interface{}{"enter", "Name", "pets", "Human", "[Pet]", nil}, []interface{}{"leave", "Name", "pets", "Human", "[Pet]", nil}, []interface{}{"enter", "SelectionSet", nil, "Pet", "[Pet]", nil}, []interface{}{"enter", "Field", nil, "Pet", "String!", nil}, []interface{}{"enter", "Name", "__typename", "Pet", "String!", nil}, []interface{}{"leave", "Name", "__typename", "Pet", "String!", nil}, []interface{}{"leave", "Field", nil, "Pet", "String!", nil}, []interface{}{"leave", "SelectionSet", nil, "Pet", "[Pet]", nil}, []interface{}{"leave", "Field", nil, "Human", "[Pet]", nil}, []interface{}{"leave", "SelectionSet", nil, "Human", "Human", nil}, []interface{}{"leave", "Field", nil, "QueryRoot", "Human", nil}, []interface{}{"enter", "Field", nil, "QueryRoot", "Alien", nil}, []interface{}{"enter", "Name", "alien", "QueryRoot", "Alien", nil}, []interface{}{"leave", "Name", "alien", "QueryRoot", "Alien", nil}, []interface{}{"enter", "SelectionSet", nil, "Alien", "Alien", nil}, []interface{}{"enter", "Field", nil, "Alien", "String!", nil}, []interface{}{"enter", "Name", "__typename", "Alien", "String!", nil}, []interface{}{"leave", "Name", "__typename", "Alien", "String!", nil}, []interface{}{"leave", "Field", nil, "Alien", "String!", nil}, []interface{}{"leave", "SelectionSet", nil, "Alien", "Alien", nil}, []interface{}{"leave", "Field", nil, "QueryRoot", "Alien", nil}, []interface{}{"leave", "SelectionSet", nil, "QueryRoot", "QueryRoot", nil}, []interface{}{"leave", "OperationDefinition", nil, nil, "QueryRoot", nil}, []interface{}{"leave", "Document", nil, nil, nil, nil}, } v := &visitor.VisitorOptions{ Enter: func(p visitor.VisitFuncParams) (string, interface{}) { var parentType interface{} var ttype interface{} var inputType interface{} if typeInfo.ParentType() != nil { parentType = fmt.Sprintf("%v", typeInfo.ParentType()) } if typeInfo.Type() != nil { ttype = fmt.Sprintf("%v", typeInfo.Type()) } if typeInfo.InputType() != nil { inputType = fmt.Sprintf("%v", typeInfo.InputType()) } switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"enter", node.Kind, node.Value, parentType, ttype, inputType}) case *ast.Field: visited = append(visited, []interface{}{"enter", node.GetKind(), nil, parentType, ttype, inputType}) // Make a query valid by adding missing selection sets. if node.SelectionSet == nil && graphql.IsCompositeType(graphql.GetNamed(typeInfo.Type())) { return visitor.ActionUpdate, ast.NewField(&ast.Field{ Alias: node.Alias, Name: node.Name, Arguments: node.Arguments, Directives: node.Directives, SelectionSet: ast.NewSelectionSet(&ast.SelectionSet{ Selections: []ast.Selection{ ast.NewField(&ast.Field{ Name: ast.NewName(&ast.Name{ Value: "__typename", }), }), }, }), }) } case ast.Node: visited = append(visited, []interface{}{"enter", node.GetKind(), nil, parentType, ttype, inputType}) default: visited = append(visited, []interface{}{"enter", nil, nil, parentType, ttype, inputType}) } return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { var parentType interface{} var ttype interface{} var inputType interface{} if typeInfo.ParentType() != nil { parentType = fmt.Sprintf("%v", typeInfo.ParentType()) } if typeInfo.Type() != nil { ttype = fmt.Sprintf("%v", typeInfo.Type()) } if typeInfo.InputType() != nil { inputType = fmt.Sprintf("%v", typeInfo.InputType()) } switch node := p.Node.(type) { case *ast.Name: visited = append(visited, []interface{}{"leave", node.Kind, node.Value, parentType, ttype, inputType}) case ast.Node: visited = append(visited, []interface{}{"leave", node.GetKind(), nil, parentType, ttype, inputType}) default: visited = append(visited, []interface{}{"leave", nil, nil, parentType, ttype, inputType}) } return visitor.ActionNoChange, nil }, } editedAST := visitor.Visit(astDoc, visitor.VisitWithTypeInfo(typeInfo, v), nil) editedASTQuery := printer.Print(editedAST.(ast.Node)) expectedEditedASTQuery := printer.Print(parse(t, `{ human(id: 4) { name, pets { __typename } }, alien { __typename } }`)) if !reflect.DeepEqual(editedASTQuery, expectedEditedASTQuery) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedEditedASTQuery, editedASTQuery)) } if !reflect.DeepEqual(visited, expectedVisited) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedVisited, visited)) } } ================================================ FILE: lists_test.go ================================================ package graphql_test import ( "reflect" "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/location" "github.com/graphql-go/graphql/testutil" ) func checkList(t *testing.T, testType graphql.Type, testData interface{}, expected *graphql.Result) { // TODO: uncomment t.Helper when support for go1.8 is dropped. //t.Helper() data := map[string]interface{}{ "test": testData, } dataType := graphql.NewObject(graphql.ObjectConfig{ Name: "DataType", Fields: graphql.Fields{ "test": &graphql.Field{ Type: testType, }, }, }) dataType.AddFieldConfig("nest", &graphql.Field{ Type: dataType, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return data, nil }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: dataType, }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) } // parse query ast := testutil.TestParse(t, `{ nest { test } }`) // execute ep := graphql.ExecuteParams{ Schema: schema, AST: ast, Root: data, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } // Describe [T] Array func TestLists_ListOfNullableObjects_ContainsValues(t *testing.T) { ttype := graphql.NewList(graphql.Int) data := []interface{}{ 1, 2, } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": []interface{}{ 1, 2, }, }, }, } checkList(t, ttype, data, expected) } func TestLists_ListOfNullableObjects_ContainsNull(t *testing.T) { ttype := graphql.NewList(graphql.Int) data := []interface{}{ 1, nil, 2, } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": []interface{}{ 1, nil, 2, }, }, }, } checkList(t, ttype, data, expected) } func TestLists_ListOfNullableObjects_ReturnsNull(t *testing.T) { ttype := graphql.NewList(graphql.Int) expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": nil, }, }, } checkList(t, ttype, nil, expected) } // Describe [T] Func()Array // equivalent to Promise> func TestLists_ListOfNullableFunc_ContainsValues(t *testing.T) { ttype := graphql.NewList(graphql.Int) // `data` is a function that return values // Note that its uses the expected signature `func() interface{} {...}` data := func() interface{} { return []interface{}{ 1, 2, } } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": []interface{}{ 1, 2, }, }, }, } checkList(t, ttype, data, expected) } func TestLists_ListOfNullableFunc_ContainsNull(t *testing.T) { ttype := graphql.NewList(graphql.Int) // `data` is a function that return values // Note that its uses the expected signature `func() interface{} {...}` data := func() interface{} { return []interface{}{ 1, nil, 2, } } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": []interface{}{ 1, nil, 2, }, }, }, } checkList(t, ttype, data, expected) } func TestLists_ListOfNullableFunc_ReturnsNull(t *testing.T) { ttype := graphql.NewList(graphql.Int) // `data` is a function that return values // Note that its uses the expected signature `func() interface{} {...}` data := func() interface{} { return nil } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": nil, }, }, } checkList(t, ttype, data, expected) } // Describe [T] Array> // equivalent to Array> func TestLists_ListOfNullableArrayOfFuncContainsValues(t *testing.T) { ttype := graphql.NewList(graphql.Int) // `data` is a slice of functions that return values // Note that its uses the expected signature `func() (interface{}, error) {...}` data := []interface{}{ func() (interface{}, error) { return 1, nil }, func() (interface{}, error) { return 2, nil }, } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": []interface{}{ 1, 2, }, }, }, } checkList(t, ttype, data, expected) } func TestLists_ListOfNullableArrayOfFuncContainsNulls(t *testing.T) { ttype := graphql.NewList(graphql.Int) // `data` is a slice of functions that return values // Note that its uses the expected signature `func() (interface{}, error) {...}` data := []interface{}{ func() (interface{}, error) { return 1, nil }, func() (interface{}, error) { return nil, nil }, func() (interface{}, error) { return 2, nil }, } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": []interface{}{ 1, nil, 2, }, }, }, } checkList(t, ttype, data, expected) } // Describe [T]! Array func TestLists_NonNullListOfNullableObjectsContainsValues(t *testing.T) { ttype := graphql.NewNonNull(graphql.NewList(graphql.Int)) data := []interface{}{ 1, 2, } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": []interface{}{ 1, 2, }, }, }, } checkList(t, ttype, data, expected) } func TestLists_NonNullListOfNullableObjectsContainsNull(t *testing.T) { ttype := graphql.NewNonNull(graphql.NewList(graphql.Int)) data := []interface{}{ 1, nil, 2, } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": []interface{}{ 1, nil, 2, }, }, }, } checkList(t, ttype, data, expected) } func TestLists_NonNullListOfNullableObjectsReturnsNull(t *testing.T) { ttype := graphql.NewNonNull(graphql.NewList(graphql.Int)) expected := &graphql.Result{ Data: map[string]interface{}{ "nest": nil, }, Errors: []gqlerrors.FormattedError{ { Message: "Cannot return null for non-nullable field DataType.test.", Locations: []location.SourceLocation{ { Line: 1, Column: 10, }, }, Path: []interface{}{ "nest", "test", }, }, }, } checkList(t, ttype, nil, expected) } // Describe [T]! Func()Array // equivalent to Promise> func TestLists_NonNullListOfNullableFunc_ContainsValues(t *testing.T) { ttype := graphql.NewNonNull(graphql.NewList(graphql.Int)) // `data` is a function that return values // Note that its uses the expected signature `func() interface{} {...}` data := func() interface{} { return []interface{}{ 1, 2, } } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": []interface{}{ 1, 2, }, }, }, } checkList(t, ttype, data, expected) } func TestLists_NonNullListOfNullableFunc_ContainsNull(t *testing.T) { ttype := graphql.NewNonNull(graphql.NewList(graphql.Int)) // `data` is a function that return values // Note that its uses the expected signature `func() interface{} {...}` data := func() interface{} { return []interface{}{ 1, nil, 2, } } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": []interface{}{ 1, nil, 2, }, }, }, } checkList(t, ttype, data, expected) } func TestLists_NonNullListOfNullableFunc_ReturnsNull(t *testing.T) { ttype := graphql.NewNonNull(graphql.NewList(graphql.Int)) // `data` is a function that return values // Note that its uses the expected signature `func() interface{} {...}` data := func() interface{} { return nil } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": nil, }, Errors: []gqlerrors.FormattedError{ { Message: "Cannot return null for non-nullable field DataType.test.", Locations: []location.SourceLocation{ { Line: 1, Column: 10, }, }, Path: []interface{}{ "nest", "test", }, }, }, } checkList(t, ttype, data, expected) } // Describe [T]! Array> // equivalent to Array> func TestLists_NonNullListOfNullableArrayOfFunc_ContainsValues(t *testing.T) { ttype := graphql.NewNonNull(graphql.NewList(graphql.Int)) // `data` is a slice of functions that return values // Note that its uses the expected signature `func() (interface{}, error) {...}` data := []interface{}{ func() (interface{}, error) { return 1, nil }, func() (interface{}, error) { return 2, nil }, } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": []interface{}{ 1, 2, }, }, }, } checkList(t, ttype, data, expected) } func TestLists_NonNullListOfNullableArrayOfFunc_ContainsNulls(t *testing.T) { ttype := graphql.NewNonNull(graphql.NewList(graphql.Int)) // `data` is a slice of functions that return values // Note that its uses the expected signature `func() (interface{}, error) {...}` data := []interface{}{ func() (interface{}, error) { return 1, nil }, func() (interface{}, error) { return nil, nil }, func() (interface{}, error) { return 2, nil }, } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": []interface{}{ 1, nil, 2, }, }, }, } checkList(t, ttype, data, expected) } // Describe [T!] Array func TestLists_NullableListOfNonNullObjects_ContainsValues(t *testing.T) { ttype := graphql.NewList(graphql.NewNonNull(graphql.Int)) data := []interface{}{ 1, 2, } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": []interface{}{ 1, 2, }, }, }, } checkList(t, ttype, data, expected) } func TestLists_NullableListOfNonNullObjects_ContainsNull(t *testing.T) { ttype := graphql.NewList(graphql.NewNonNull(graphql.Int)) data := []interface{}{ 1, nil, 2, } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": nil, }, }, Errors: []gqlerrors.FormattedError{ { Message: "Cannot return null for non-nullable field DataType.test.", Locations: []location.SourceLocation{ { Line: 1, Column: 10, }, }, Path: []interface{}{ "nest", "test", 1, }, }, }, } checkList(t, ttype, data, expected) } func TestLists_NullableListOfNonNullObjects_ReturnsNull(t *testing.T) { ttype := graphql.NewList(graphql.NewNonNull(graphql.Int)) expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": nil, }, }, } checkList(t, ttype, nil, expected) } // Describe [T!] Func()Array // equivalent to Promise> func TestLists_NullableListOfNonNullFunc_ContainsValues(t *testing.T) { ttype := graphql.NewList(graphql.NewNonNull(graphql.Int)) // `data` is a function that return values // Note that its uses the expected signature `func() interface{} {...}` data := func() interface{} { return []interface{}{ 1, 2, } } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": []interface{}{ 1, 2, }, }, }, } checkList(t, ttype, data, expected) } func TestLists_NullableListOfNonNullFunc_ContainsNull(t *testing.T) { ttype := graphql.NewList(graphql.NewNonNull(graphql.Int)) // `data` is a function that return values // Note that its uses the expected signature `func() interface{} {...}` data := func() interface{} { return []interface{}{ 1, nil, 2, } } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": nil, }, }, Errors: []gqlerrors.FormattedError{ { Message: "Cannot return null for non-nullable field DataType.test.", Locations: []location.SourceLocation{ { Line: 1, Column: 10, }, }, Path: []interface{}{ "nest", "test", 1, }, }, }, } checkList(t, ttype, data, expected) } func TestLists_NullableListOfNonNullFunc_ReturnsNull(t *testing.T) { ttype := graphql.NewList(graphql.NewNonNull(graphql.Int)) // `data` is a function that return values // Note that its uses the expected signature `func() interface{} {...}` data := func() interface{} { return nil } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": nil, }, }, } checkList(t, ttype, data, expected) } // Describe [T!] Array> // equivalent to Array> func TestLists_NullableListOfNonNullArrayOfFunc_ContainsValues(t *testing.T) { ttype := graphql.NewList(graphql.NewNonNull(graphql.Int)) // `data` is a slice of functions that return values // Note that its uses the expected signature `func() interface{} {...}` data := []interface{}{ func() (interface{}, error) { return 1, nil }, func() (interface{}, error) { return 2, nil }, } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": []interface{}{ 1, 2, }, }, }, } checkList(t, ttype, data, expected) } func TestLists_NullableListOfNonNullArrayOfFunc_ContainsNulls(t *testing.T) { ttype := graphql.NewList(graphql.NewNonNull(graphql.Int)) // `data` is a slice of functions that return values // Note that its uses the expected signature `func() (interface{}, error){...}` data := []interface{}{ func() (interface{}, error) { return 1, nil }, func() (interface{}, error) { return nil, nil }, func() (interface{}, error) { return 2, nil }, } expected := &graphql.Result{ /* // TODO: Because thunks are called after the result map has been assembled, // we are not able to traverse up the tree until we find a nullable type, // so in this case the entire data is nil. Will need some significant code // restructure to restore this. Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": nil, }, }, */ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: "Cannot return null for non-nullable field DataType.test.", Locations: []location.SourceLocation{ { Line: 1, Column: 10, }, }, Path: []interface{}{ "nest", "test", 1, }, }, }, } checkList(t, ttype, data, expected) } // Describe [T!]! Array func TestLists_NonNullListOfNonNullObjects_ContainsValues(t *testing.T) { ttype := graphql.NewNonNull(graphql.NewList(graphql.NewNonNull(graphql.Int))) data := []interface{}{ 1, 2, } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": []interface{}{ 1, 2, }, }, }, } checkList(t, ttype, data, expected) } func TestLists_NonNullListOfNonNullObjects_ContainsNull(t *testing.T) { ttype := graphql.NewNonNull(graphql.NewList(graphql.NewNonNull(graphql.Int))) data := []interface{}{ 1, nil, 2, } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": nil, }, Errors: []gqlerrors.FormattedError{ { Message: "Cannot return null for non-nullable field DataType.test.", Locations: []location.SourceLocation{ { Line: 1, Column: 10, }, }, Path: []interface{}{ "nest", "test", 1, }, }, }, } checkList(t, ttype, data, expected) } func TestLists_NonNullListOfNonNullObjects_ReturnsNull(t *testing.T) { ttype := graphql.NewNonNull(graphql.NewList(graphql.NewNonNull(graphql.Int))) expected := &graphql.Result{ Data: map[string]interface{}{ "nest": nil, }, Errors: []gqlerrors.FormattedError{ { Message: "Cannot return null for non-nullable field DataType.test.", Locations: []location.SourceLocation{ { Line: 1, Column: 10, }, }, Path: []interface{}{ "nest", "test", }, }, }, } checkList(t, ttype, nil, expected) } // Describe [T!]! Func()Array // equivalent to Promise> func TestLists_NonNullListOfNonNullFunc_ContainsValues(t *testing.T) { ttype := graphql.NewNonNull(graphql.NewList(graphql.NewNonNull(graphql.Int))) // `data` is a function that return values // Note that its uses the expected signature `func() interface{} {...}` data := func() interface{} { return []interface{}{ 1, 2, } } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": []interface{}{ 1, 2, }, }, }, } checkList(t, ttype, data, expected) } func TestLists_NonNullListOfNonNullFunc_ContainsNull(t *testing.T) { ttype := graphql.NewNonNull(graphql.NewList(graphql.NewNonNull(graphql.Int))) // `data` is a function that return values // Note that its uses the expected signature `func() interface{} {...}` data := func() interface{} { return []interface{}{ 1, nil, 2, } } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": nil, }, Errors: []gqlerrors.FormattedError{ { Message: "Cannot return null for non-nullable field DataType.test.", Locations: []location.SourceLocation{ { Line: 1, Column: 10, }, }, Path: []interface{}{ "nest", "test", 1, }, }, }, } checkList(t, ttype, data, expected) } func TestLists_NonNullListOfNonNullFunc_ReturnsNull(t *testing.T) { ttype := graphql.NewNonNull(graphql.NewList(graphql.NewNonNull(graphql.Int))) // `data` is a function that return values // Note that its uses the expected signature `func() interface{} {...}` data := func() interface{} { return nil } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": nil, }, Errors: []gqlerrors.FormattedError{ { Message: "Cannot return null for non-nullable field DataType.test.", Locations: []location.SourceLocation{ { Line: 1, Column: 10, }, }, Path: []interface{}{ "nest", "test", }, }, }, } checkList(t, ttype, data, expected) } // Describe [T!]! Array> // equivalent to Array> func TestLists_NonNullListOfNonNullArrayOfFunc_ContainsValues(t *testing.T) { ttype := graphql.NewNonNull(graphql.NewList(graphql.NewNonNull(graphql.Int))) // `data` is a slice of functions that return values // Note that its uses the expected signature `func() (interface{}, error) {...}` data := []interface{}{ func() (interface{}, error) { return 1, nil }, func() (interface{}, error) { return 2, nil }, } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": []interface{}{ 1, 2, }, }, }, } checkList(t, ttype, data, expected) } func TestLists_NonNullListOfNonNullArrayOfFunc_ContainsNulls(t *testing.T) { ttype := graphql.NewNonNull(graphql.NewList(graphql.NewNonNull(graphql.Int))) // `data` is a slice of functions that return values // Note that its uses the expected signature `func() (interface{}, error) {...}` data := []interface{}{ func() (interface{}, error) { return 1, nil }, func() (interface{}, error) { return nil, nil }, func() (interface{}, error) { return 2, nil }, } expected := &graphql.Result{ /* // TODO: Because thunks are called after the result map has been assembled, // we are not able to traverse up the tree until we find a nullable type, // so in this case the entire data is nil. Will need some significant code // restructure to restore this. Data: map[string]interface{}{ "nest": nil, }, */ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: "Cannot return null for non-nullable field DataType.test.", Locations: []location.SourceLocation{ { Line: 1, Column: 10, }, }, Path: []interface{}{ "nest", "test", 1, }, }, }, } checkList(t, ttype, data, expected) } func TestLists_UserErrorExpectIterableButDidNotGetOne(t *testing.T) { ttype := graphql.NewList(graphql.Int) data := "Not an iterable" expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": nil, }, }, Errors: []gqlerrors.FormattedError{ { Message: "User Error: expected iterable, but did not find one for field DataType.test.", Locations: []location.SourceLocation{ { Line: 1, Column: 10, }, }, Path: []interface{}{ "nest", "test", }, }, }, } checkList(t, ttype, data, expected) } func TestLists_ArrayOfNullableObjects_ContainsValues(t *testing.T) { ttype := graphql.NewList(graphql.Int) data := [2]interface{}{ 1, 2, } expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": []interface{}{ 1, 2, }, }, }, } checkList(t, ttype, data, expected) } func TestLists_ValueMayBeNilPointer(t *testing.T) { var listTestSchema, _ = graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "list": &graphql.Field{ Type: graphql.NewList(graphql.Int), Resolve: func(_ graphql.ResolveParams) (interface{}, error) { return []int(nil), nil }, }, }, }), }) query := "{ list }" expected := &graphql.Result{ Data: map[string]interface{}{ "list": []interface{}{}, }, } result := g(t, graphql.Params{ Schema: listTestSchema, RequestString: query, }) if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestLists_NullableListOfInt_ReturnsNull(t *testing.T) { ttype := graphql.NewList(graphql.Int) type dataType *[]int var data dataType expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "test": nil, }, }, } checkList(t, ttype, data, expected) } ================================================ FILE: located.go ================================================ package graphql import ( "errors" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/ast" ) func NewLocatedError(err interface{}, nodes []ast.Node) *gqlerrors.Error { return newLocatedError(err, nodes, nil) } func NewLocatedErrorWithPath(err interface{}, nodes []ast.Node, path []interface{}) *gqlerrors.Error { return newLocatedError(err, nodes, path) } func newLocatedError(err interface{}, nodes []ast.Node, path []interface{}) *gqlerrors.Error { if err, ok := err.(*gqlerrors.Error); ok { return err } var origError error message := "An unknown error occurred." if err, ok := err.(error); ok { message = err.Error() origError = err } if err, ok := err.(string); ok { message = err origError = errors.New(err) } stack := message return gqlerrors.NewErrorWithPath( message, nodes, stack, nil, []int{}, path, origError, ) } func FieldASTsToNodeASTs(fieldASTs []*ast.Field) []ast.Node { nodes := []ast.Node{} for _, fieldAST := range fieldASTs { nodes = append(nodes, fieldAST) } return nodes } ================================================ FILE: mutations_test.go ================================================ package graphql_test import ( "reflect" "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/location" "github.com/graphql-go/graphql/testutil" ) // testNumberHolder maps to numberHolderType type testNumberHolder struct { TheNumber int `json:"theNumber"` // map field to `theNumber` so it can be resolve by the default ResolveFn } type testRoot struct { NumberHolder *testNumberHolder } func newTestRoot(originalNumber int) *testRoot { return &testRoot{ NumberHolder: &testNumberHolder{originalNumber}, } } func (r *testRoot) ImmediatelyChangeTheNumber(newNumber int) *testNumberHolder { r.NumberHolder.TheNumber = newNumber return r.NumberHolder } func (r *testRoot) PromiseToChangeTheNumber(newNumber int) *testNumberHolder { return r.ImmediatelyChangeTheNumber(newNumber) } func (r *testRoot) FailToChangeTheNumber(newNumber int) *testNumberHolder { panic("Cannot change the number") } func (r *testRoot) PromiseAndFailToChangeTheNumber(newNumber int) *testNumberHolder { panic("Cannot change the number") } // numberHolderType creates a mapping to testNumberHolder var numberHolderType = graphql.NewObject(graphql.ObjectConfig{ Name: "NumberHolder", Fields: graphql.Fields{ "theNumber": &graphql.Field{ Type: graphql.Int, }, }, }) var mutationsTestSchema, _ = graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "numberHolder": &graphql.Field{ Type: numberHolderType, }, }, }), Mutation: graphql.NewObject(graphql.ObjectConfig{ Name: "Mutation", Fields: graphql.Fields{ "immediatelyChangeTheNumber": &graphql.Field{ Type: numberHolderType, Args: graphql.FieldConfigArgument{ "newNumber": &graphql.ArgumentConfig{ Type: graphql.Int, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { newNumber := 0 obj, _ := p.Source.(*testRoot) newNumber, _ = p.Args["newNumber"].(int) return obj.ImmediatelyChangeTheNumber(newNumber), nil }, }, "promiseToChangeTheNumber": &graphql.Field{ Type: numberHolderType, Args: graphql.FieldConfigArgument{ "newNumber": &graphql.ArgumentConfig{ Type: graphql.Int, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { newNumber := 0 obj, _ := p.Source.(*testRoot) newNumber, _ = p.Args["newNumber"].(int) return obj.PromiseToChangeTheNumber(newNumber), nil }, }, "failToChangeTheNumber": &graphql.Field{ Type: numberHolderType, Args: graphql.FieldConfigArgument{ "newNumber": &graphql.ArgumentConfig{ Type: graphql.Int, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { newNumber := 0 obj, _ := p.Source.(*testRoot) newNumber, _ = p.Args["newNumber"].(int) return obj.FailToChangeTheNumber(newNumber), nil }, }, "promiseAndFailToChangeTheNumber": &graphql.Field{ Type: numberHolderType, Args: graphql.FieldConfigArgument{ "newNumber": &graphql.ArgumentConfig{ Type: graphql.Int, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { newNumber := 0 obj, _ := p.Source.(*testRoot) newNumber, _ = p.Args["newNumber"].(int) return obj.PromiseAndFailToChangeTheNumber(newNumber), nil }, }, }, }), }) func TestMutations_ExecutionOrdering_EvaluatesMutationsSerially(t *testing.T) { root := newTestRoot(6) doc := `mutation M { first: immediatelyChangeTheNumber(newNumber: 1) { theNumber }, second: promiseToChangeTheNumber(newNumber: 2) { theNumber }, third: immediatelyChangeTheNumber(newNumber: 3) { theNumber } fourth: promiseToChangeTheNumber(newNumber: 4) { theNumber }, fifth: immediatelyChangeTheNumber(newNumber: 5) { theNumber } }` expected := &graphql.Result{ Data: map[string]interface{}{ "first": map[string]interface{}{ "theNumber": 1, }, "second": map[string]interface{}{ "theNumber": 2, }, "third": map[string]interface{}{ "theNumber": 3, }, "fourth": map[string]interface{}{ "theNumber": 4, }, "fifth": map[string]interface{}{ "theNumber": 5, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: mutationsTestSchema, AST: ast, Root: root, } result := testutil.TestExecute(t, ep) if len(result.Errors) != len(expected.Errors) { t.Fatalf("Unexpected errors, Diff: %v", testutil.Diff(expected.Errors, result.Errors)) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestMutations_EvaluatesMutationsCorrectlyInThePresenceOfAFailedMutation(t *testing.T) { root := newTestRoot(6) doc := `mutation M { first: immediatelyChangeTheNumber(newNumber: 1) { theNumber }, second: promiseToChangeTheNumber(newNumber: 2) { theNumber }, third: failToChangeTheNumber(newNumber: 3) { theNumber } fourth: promiseToChangeTheNumber(newNumber: 4) { theNumber }, fifth: immediatelyChangeTheNumber(newNumber: 5) { theNumber } sixth: promiseAndFailToChangeTheNumber(newNumber: 6) { theNumber } }` expected := &graphql.Result{ Data: map[string]interface{}{ "first": map[string]interface{}{ "theNumber": 1, }, "second": map[string]interface{}{ "theNumber": 2, }, "third": nil, "fourth": map[string]interface{}{ "theNumber": 4, }, "fifth": map[string]interface{}{ "theNumber": 5, }, "sixth": nil, }, Errors: []gqlerrors.FormattedError{ { Message: `Cannot change the number`, Locations: []location.SourceLocation{ {Line: 8, Column: 7}, }, }, { Message: `Cannot change the number`, Locations: []location.SourceLocation{ {Line: 17, Column: 7}, }, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: mutationsTestSchema, AST: ast, Root: root, } result := testutil.TestExecute(t, ep) if len(result.Errors) != len(expected.Errors) { t.Fatalf("Unexpected errors, Diff: %v", testutil.Diff(expected.Errors, result.Errors)) } t.Skipf("Testing equality for slice of errors in results") if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } ================================================ FILE: nonnull_test.go ================================================ package graphql_test import ( "sort" "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/location" "github.com/graphql-go/graphql/testutil" ) var syncError = "sync" var nonNullSyncError = "nonNullSync" var promiseError = "promise" var nonNullPromiseError = "nonNullPromise" var throwingData = map[string]interface{}{ "sync": func() interface{} { panic(syncError) }, "nonNullSync": func() interface{} { panic(nonNullSyncError) }, "promise": func() interface{} { panic(promiseError) }, "nonNullPromise": func() interface{} { panic(nonNullPromiseError) }, } var nullingData = map[string]interface{}{ "sync": func() interface{} { return nil }, "nonNullSync": func() interface{} { return nil }, "promise": func() interface{} { return nil }, "nonNullPromise": func() interface{} { return nil }, } var dataType = graphql.NewObject(graphql.ObjectConfig{ Name: "DataType", Fields: graphql.Fields{ "sync": &graphql.Field{ Type: graphql.String, }, "nonNullSync": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), }, "promise": &graphql.Field{ Type: graphql.String, }, "nonNullPromise": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), }, }, }) var nonNullTestSchema, _ = graphql.NewSchema(graphql.SchemaConfig{ Query: dataType, }) func init() { throwingData["nest"] = func() interface{} { return throwingData } throwingData["nonNullNest"] = func() interface{} { return throwingData } throwingData["promiseNest"] = func() interface{} { return throwingData } throwingData["nonNullPromiseNest"] = func() interface{} { return throwingData } nullingData["nest"] = func() interface{} { return nullingData } nullingData["nonNullNest"] = func() interface{} { return nullingData } nullingData["promiseNest"] = func() interface{} { return nullingData } nullingData["nonNullPromiseNest"] = func() interface{} { return nullingData } dataType.AddFieldConfig("nest", &graphql.Field{ Type: dataType, }) dataType.AddFieldConfig("nonNullNest", &graphql.Field{ Type: graphql.NewNonNull(dataType), }) dataType.AddFieldConfig("promiseNest", &graphql.Field{ Type: dataType, }) dataType.AddFieldConfig("nonNullPromiseNest", &graphql.Field{ Type: graphql.NewNonNull(dataType), }) } // nulls a nullable field that panics func TestNonNull_NullsANullableFieldThatThrowsSynchronously(t *testing.T) { doc := ` query Q { sync } ` expected := &graphql.Result{ Data: map[string]interface{}{ "sync": nil, }, Errors: []gqlerrors.FormattedError{ { Message: syncError, Locations: []location.SourceLocation{ { Line: 3, Column: 9, }, }, Path: []interface{}{ "sync", }, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, Root: throwingData, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestNonNull_NullsANullableFieldThatThrowsInAPromise(t *testing.T) { doc := ` query Q { promise } ` expected := &graphql.Result{ Data: map[string]interface{}{ "promise": nil, }, Errors: []gqlerrors.FormattedError{ { Message: promiseError, Locations: []location.SourceLocation{ { Line: 3, Column: 9, }, }, Path: []interface{}{ "promise", }, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, Root: throwingData, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestNonNull_NullsASynchronouslyReturnedObjectThatContainsANullableFieldThatThrowsSynchronously(t *testing.T) { doc := ` query Q { nest { nonNullSync, } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "nest": nil, }, Errors: []gqlerrors.FormattedError{ { Message: nonNullSyncError, Locations: []location.SourceLocation{ { Line: 4, Column: 11, }, }, Path: []interface{}{ "nest", "nonNullSync", }, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, Root: throwingData, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestNonNull_NullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatThrowsInAPromise(t *testing.T) { doc := ` query Q { nest { nonNullPromise, } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "nest": nil, }, Errors: []gqlerrors.FormattedError{ { Message: nonNullPromiseError, Locations: []location.SourceLocation{ { Line: 4, Column: 11, }, }, Path: []interface{}{ "nest", "nonNullPromise", }, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, Root: throwingData, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestNonNull_NullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatThrowsSynchronously(t *testing.T) { doc := ` query Q { promiseNest { nonNullSync, } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "promiseNest": nil, }, Errors: []gqlerrors.FormattedError{ { Message: nonNullSyncError, Locations: []location.SourceLocation{ { Line: 4, Column: 11, }, }, Path: []interface{}{ "promiseNest", "nonNullSync", }, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, Root: throwingData, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestNonNull_NullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatThrowsInAPromise(t *testing.T) { doc := ` query Q { promiseNest { nonNullPromise, } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "promiseNest": nil, }, Errors: []gqlerrors.FormattedError{ { Message: nonNullPromiseError, Locations: []location.SourceLocation{ { Line: 4, Column: 11, }, }, Path: []interface{}{ "promiseNest", "nonNullPromise", }, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, Root: throwingData, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestNonNull_NullsAComplexTreeOfNullableFieldsThatThrow(t *testing.T) { doc := ` query Q { nest { sync promise nest { sync promise } promiseNest { sync promise } } promiseNest { sync promise nest { sync promise } promiseNest { sync promise } } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "sync": nil, "promise": nil, "nest": map[string]interface{}{ "sync": nil, "promise": nil, }, "promiseNest": map[string]interface{}{ "sync": nil, "promise": nil, }, }, "promiseNest": map[string]interface{}{ "sync": nil, "promise": nil, "nest": map[string]interface{}{ "sync": nil, "promise": nil, }, "promiseNest": map[string]interface{}{ "sync": nil, "promise": nil, }, }, }, Errors: []gqlerrors.FormattedError{ gqlerrors.FormatError(gqlerrors.Error{ Message: syncError, Locations: []location.SourceLocation{ {Line: 4, Column: 11}, }, Path: []interface{}{ "nest", "sync", }, }), gqlerrors.FormatError(gqlerrors.Error{ Message: syncError, Locations: []location.SourceLocation{ {Line: 7, Column: 13}, }, Path: []interface{}{ "nest", "nest", "sync", }, }), gqlerrors.FormatError(gqlerrors.Error{ Message: syncError, Locations: []location.SourceLocation{ {Line: 11, Column: 13}, }, Path: []interface{}{ "nest", "promiseNest", "sync", }, }), gqlerrors.FormatError(gqlerrors.Error{ Message: syncError, Locations: []location.SourceLocation{ {Line: 16, Column: 11}, }, Path: []interface{}{ "promiseNest", "sync", }, }), gqlerrors.FormatError(gqlerrors.Error{ Message: syncError, Locations: []location.SourceLocation{ {Line: 19, Column: 13}, }, Path: []interface{}{ "promiseNest", "nest", "sync", }, }), gqlerrors.FormatError(gqlerrors.Error{ Message: syncError, Locations: []location.SourceLocation{ {Line: 23, Column: 13}, }, Path: []interface{}{ "promiseNest", "promiseNest", "sync", }, }), gqlerrors.FormatError(gqlerrors.Error{ Message: promiseError, Locations: []location.SourceLocation{ {Line: 5, Column: 11}, }, Path: []interface{}{ "nest", "promise", }, }), gqlerrors.FormatError(gqlerrors.Error{ Message: promiseError, Locations: []location.SourceLocation{ {Line: 8, Column: 13}, }, Path: []interface{}{ "nest", "nest", "promise", }, }), gqlerrors.FormatError(gqlerrors.Error{ Message: promiseError, Locations: []location.SourceLocation{ {Line: 12, Column: 13}, }, Path: []interface{}{ "nest", "promiseNest", "promise", }, }), gqlerrors.FormatError(gqlerrors.Error{ Message: promiseError, Locations: []location.SourceLocation{ {Line: 17, Column: 11}, }, Path: []interface{}{ "promiseNest", "promise", }, }), gqlerrors.FormatError(gqlerrors.Error{ Message: promiseError, Locations: []location.SourceLocation{ {Line: 20, Column: 13}, }, Path: []interface{}{ "promiseNest", "nest", "promise", }, }), gqlerrors.FormatError(gqlerrors.Error{ Message: promiseError, Locations: []location.SourceLocation{ {Line: 24, Column: 13}, }, Path: []interface{}{ "promiseNest", "promiseNest", "promise", }, }), }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, Root: throwingData, } result := testutil.TestExecute(t, ep) sort.Sort(gqlerrors.FormattedErrors(expected.Errors)) sort.Sort(gqlerrors.FormattedErrors(result.Errors)) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected.Errors, result.Errors)) } } func TestNonNull_NullsTheFirstNullableObjectAfterAFieldThrowsInALongChainOfFieldsThatAreNonNull(t *testing.T) { doc := ` query Q { nest { nonNullNest { nonNullPromiseNest { nonNullNest { nonNullPromiseNest { nonNullSync } } } } } promiseNest { nonNullNest { nonNullPromiseNest { nonNullNest { nonNullPromiseNest { nonNullSync } } } } } anotherNest: nest { nonNullNest { nonNullPromiseNest { nonNullNest { nonNullPromiseNest { nonNullPromise } } } } } anotherPromiseNest: promiseNest { nonNullNest { nonNullPromiseNest { nonNullNest { nonNullPromiseNest { nonNullPromise } } } } } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "nest": nil, "promiseNest": nil, "anotherNest": nil, "anotherPromiseNest": nil, }, Errors: []gqlerrors.FormattedError{ gqlerrors.FormatError(gqlerrors.Error{ Message: nonNullSyncError, Locations: []location.SourceLocation{ {Line: 8, Column: 19}, }, Path: []interface{}{ "nest", "nonNullNest", "nonNullPromiseNest", "nonNullNest", "nonNullPromiseNest", "nonNullSync", }, }), gqlerrors.FormatError(gqlerrors.Error{ Message: nonNullSyncError, Locations: []location.SourceLocation{ {Line: 19, Column: 19}, }, Path: []interface{}{ "promiseNest", "nonNullNest", "nonNullPromiseNest", "nonNullNest", "nonNullPromiseNest", "nonNullSync", }, }), gqlerrors.FormatError(gqlerrors.Error{ Message: nonNullPromiseError, Locations: []location.SourceLocation{ {Line: 30, Column: 19}, }, Path: []interface{}{ "anotherNest", "nonNullNest", "nonNullPromiseNest", "nonNullNest", "nonNullPromiseNest", "nonNullPromise", }, }), gqlerrors.FormatError(gqlerrors.Error{ Message: nonNullPromiseError, Locations: []location.SourceLocation{ {Line: 41, Column: 19}, }, Path: []interface{}{ "anotherPromiseNest", "nonNullNest", "nonNullPromiseNest", "nonNullNest", "nonNullPromiseNest", "nonNullPromise", }, }), }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, Root: throwingData, } result := testutil.TestExecute(t, ep) sort.Sort(gqlerrors.FormattedErrors(expected.Errors)) sort.Sort(gqlerrors.FormattedErrors(result.Errors)) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected.Errors, result.Errors)) } } func TestNonNull_NullsANullableFieldThatSynchronouslyReturnsNull(t *testing.T) { doc := ` query Q { sync } ` expected := &graphql.Result{ Data: map[string]interface{}{ "sync": nil, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, Root: nullingData, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestNonNull_NullsANullableFieldThatSynchronouslyReturnsNullInAPromise(t *testing.T) { doc := ` query Q { promise } ` expected := &graphql.Result{ Data: map[string]interface{}{ "promise": nil, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, Root: nullingData, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestNonNull_NullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatReturnsNullSynchronously(t *testing.T) { doc := ` query Q { nest { nonNullSync, } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "nest": nil, }, Errors: []gqlerrors.FormattedError{ { Message: `Cannot return null for non-nullable field DataType.nonNullSync.`, Locations: []location.SourceLocation{ {Line: 4, Column: 11}, }, Path: []interface{}{ "nest", "nonNullSync", }, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, Root: nullingData, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestNonNull_NullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatReturnsNullInAPromise(t *testing.T) { doc := ` query Q { nest { nonNullPromise, } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "nest": nil, }, Errors: []gqlerrors.FormattedError{ { Message: `Cannot return null for non-nullable field DataType.nonNullPromise.`, Locations: []location.SourceLocation{ {Line: 4, Column: 11}, }, Path: []interface{}{ "nest", "nonNullPromise", }, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, Root: nullingData, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestNonNull_NullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatReturnsNullSynchronously(t *testing.T) { doc := ` query Q { promiseNest { nonNullSync, } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "promiseNest": nil, }, Errors: []gqlerrors.FormattedError{ { Message: `Cannot return null for non-nullable field DataType.nonNullSync.`, Locations: []location.SourceLocation{ {Line: 4, Column: 11}, }, Path: []interface{}{ "promiseNest", "nonNullSync", }, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, Root: nullingData, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestNonNull_NullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatReturnsNullInAPromise(t *testing.T) { doc := ` query Q { promiseNest { nonNullPromise, } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "promiseNest": nil, }, Errors: []gqlerrors.FormattedError{ { Message: `Cannot return null for non-nullable field DataType.nonNullPromise.`, Locations: []location.SourceLocation{ {Line: 4, Column: 11}, }, Path: []interface{}{ "promiseNest", "nonNullPromise", }, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, Root: nullingData, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestNonNull_NullsAComplexTreeOfNullableFieldsThatReturnNull(t *testing.T) { doc := ` query Q { nest { sync promise nest { sync promise } promiseNest { sync promise } } promiseNest { sync promise nest { sync promise } promiseNest { sync promise } } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "nest": map[string]interface{}{ "sync": nil, "promise": nil, "nest": map[string]interface{}{ "sync": nil, "promise": nil, }, "promiseNest": map[string]interface{}{ "sync": nil, "promise": nil, }, }, "promiseNest": map[string]interface{}{ "sync": nil, "promise": nil, "nest": map[string]interface{}{ "sync": nil, "promise": nil, }, "promiseNest": map[string]interface{}{ "sync": nil, "promise": nil, }, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, Root: nullingData, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected.Data, result.Data)) } } func TestNonNull_NullsTheFirstNullableObjectAfterAFieldReturnsNullInALongChainOfFieldsThatAreNonNull(t *testing.T) { doc := ` query Q { nest { nonNullNest { nonNullPromiseNest { nonNullNest { nonNullPromiseNest { nonNullSync } } } } } promiseNest { nonNullNest { nonNullPromiseNest { nonNullNest { nonNullPromiseNest { nonNullSync } } } } } anotherNest: nest { nonNullNest { nonNullPromiseNest { nonNullNest { nonNullPromiseNest { nonNullPromise } } } } } anotherPromiseNest: promiseNest { nonNullNest { nonNullPromiseNest { nonNullNest { nonNullPromiseNest { nonNullPromise } } } } } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "nest": nil, "promiseNest": nil, "anotherNest": nil, "anotherPromiseNest": nil, }, Errors: []gqlerrors.FormattedError{ { Message: `Cannot return null for non-nullable field DataType.nonNullSync.`, Locations: []location.SourceLocation{ {Line: 8, Column: 19}, }, Path: []interface{}{ "nest", "nonNullNest", "nonNullPromiseNest", "nonNullNest", "nonNullPromiseNest", "nonNullSync", }, }, { Message: `Cannot return null for non-nullable field DataType.nonNullSync.`, Locations: []location.SourceLocation{ {Line: 19, Column: 19}, }, Path: []interface{}{ "promiseNest", "nonNullNest", "nonNullPromiseNest", "nonNullNest", "nonNullPromiseNest", "nonNullSync", }, }, { Message: `Cannot return null for non-nullable field DataType.nonNullPromise.`, Locations: []location.SourceLocation{ {Line: 30, Column: 19}, }, Path: []interface{}{ "anotherNest", "nonNullNest", "nonNullPromiseNest", "nonNullNest", "nonNullPromiseNest", "nonNullPromise", }, }, { Message: `Cannot return null for non-nullable field DataType.nonNullPromise.`, Locations: []location.SourceLocation{ {Line: 41, Column: 19}, }, Path: []interface{}{ "anotherPromiseNest", "nonNullNest", "nonNullPromiseNest", "nonNullNest", "nonNullPromiseNest", "nonNullPromise", }, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, Root: nullingData, } result := testutil.TestExecute(t, ep) sort.Sort(gqlerrors.FormattedErrors(expected.Errors)) sort.Sort(gqlerrors.FormattedErrors(result.Errors)) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestNonNull_NullsTheTopLevelIfSyncNonNullableFieldThrows(t *testing.T) { doc := ` query Q { nonNullSync } ` expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: nonNullSyncError, Locations: []location.SourceLocation{ {Line: 2, Column: 17}, }, Path: []interface{}{ "nonNullSync", }, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, Root: throwingData, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestNonNull_NullsTheTopLevelIfSyncNonNullableFieldErrors(t *testing.T) { doc := ` query Q { nonNullPromise } ` expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: nonNullPromiseError, Locations: []location.SourceLocation{ {Line: 2, Column: 17}, }, Path: []interface{}{ "nonNullPromise", }, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, Root: throwingData, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestNonNull_NullsTheTopLevelIfSyncNonNullableFieldReturnsNull(t *testing.T) { doc := ` query Q { nonNullSync } ` expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Cannot return null for non-nullable field DataType.nonNullSync.`, Locations: []location.SourceLocation{ {Line: 2, Column: 17}, }, Path: []interface{}{ "nonNullSync", }, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, Root: nullingData, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestNonNull_NullsTheTopLevelIfSyncNonNullableFieldResolvesNull(t *testing.T) { doc := ` query Q { nonNullPromise } ` expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Cannot return null for non-nullable field DataType.nonNullPromise.`, Locations: []location.SourceLocation{ {Line: 2, Column: 17}, }, Path: []interface{}{ "nonNullPromise", }, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, Root: nullingData, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } ================================================ FILE: quoted_or_list_internal_test.go ================================================ package graphql import ( "reflect" "testing" ) func TestQuotedOrList_DoesNoAcceptAnEmptyList(t *testing.T) { expected := "" result := quotedOrList([]string{}) if !reflect.DeepEqual(expected, result) { t.Fatalf("Expected %v, got: %v", expected, result) } } func TestQuotedOrList_ReturnsSingleQuotedItem(t *testing.T) { expected := `"A"` result := quotedOrList([]string{"A"}) if !reflect.DeepEqual(expected, result) { t.Fatalf("Expected %v, got: %v", expected, result) } } func TestQuotedOrList_ReturnsTwoItems(t *testing.T) { expected := `"A" or "B"` result := quotedOrList([]string{"A", "B"}) if !reflect.DeepEqual(expected, result) { t.Fatalf("Expected %v, got: %v", expected, result) } } func TestQuotedOrList_ReturnsCommaSeparatedManyItemList(t *testing.T) { expected := `"A", "B", "C", "D", or "E"` result := quotedOrList([]string{"A", "B", "C", "D", "E", "F"}) if !reflect.DeepEqual(expected, result) { t.Fatalf("Expected %v, got: %v", expected, result) } } ================================================ FILE: race_test.go ================================================ package graphql_test import ( "io/ioutil" "os" "os/exec" "path/filepath" "testing" ) func TestRace(t *testing.T) { tempdir, err := ioutil.TempDir("", "race") if err != nil { t.Fatal(err) } defer os.RemoveAll(tempdir) filename := filepath.Join(tempdir, "example.go") err = ioutil.WriteFile(filename, []byte(` package main import ( "runtime" "sync" "github.com/graphql-go/graphql" ) func main() { var wg sync.WaitGroup wg.Add(2) for i := 0; i < 2; i++ { go func() { defer wg.Done() schema, _ := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "RootQuery", Fields: graphql.Fields{ "hello": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return "world", nil }, }, }, }), }) runtime.KeepAlive(schema) }() } wg.Wait() } `), 0755) if err != nil { t.Fatal(err) } result, err := exec.Command("go", "run", "-race", filename).CombinedOutput() if err != nil || len(result) != 0 { t.Log(string(result)) t.Fatal(err) } } ================================================ FILE: rules.go ================================================ package graphql import ( "fmt" "math" "reflect" "sort" "strings" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/kinds" "github.com/graphql-go/graphql/language/printer" "github.com/graphql-go/graphql/language/visitor" ) // SpecifiedRules set includes all validation rules defined by the GraphQL spec. var SpecifiedRules = []ValidationRuleFn{ ArgumentsOfCorrectTypeRule, DefaultValuesOfCorrectTypeRule, FieldsOnCorrectTypeRule, FragmentsOnCompositeTypesRule, KnownArgumentNamesRule, KnownDirectivesRule, KnownFragmentNamesRule, KnownTypeNamesRule, LoneAnonymousOperationRule, NoFragmentCyclesRule, NoUndefinedVariablesRule, NoUnusedFragmentsRule, NoUnusedVariablesRule, OverlappingFieldsCanBeMergedRule, PossibleFragmentSpreadsRule, ProvidedNonNullArgumentsRule, ScalarLeafsRule, UniqueArgumentNamesRule, UniqueFragmentNamesRule, UniqueInputFieldNamesRule, UniqueOperationNamesRule, UniqueVariableNamesRule, VariablesAreInputTypesRule, VariablesInAllowedPositionRule, } type ValidationRuleInstance struct { VisitorOpts *visitor.VisitorOptions } type ValidationRuleFn func(context *ValidationContext) *ValidationRuleInstance func newValidationError(message string, nodes []ast.Node) *gqlerrors.Error { return gqlerrors.NewError( message, nodes, "", nil, []int{}, nil, // TODO: this is interim, until we port "better-error-messages-for-inputs" ) } func reportError(context *ValidationContext, message string, nodes []ast.Node) (string, interface{}) { context.ReportError(newValidationError(message, nodes)) return visitor.ActionNoChange, nil } // ArgumentsOfCorrectTypeRule Argument values of correct type // // A GraphQL document is only valid if all field argument literal values are // of the type expected by their position. func ArgumentsOfCorrectTypeRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.Argument: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if argAST, ok := p.Node.(*ast.Argument); ok { if argDef := context.Argument(); argDef != nil { if isValid, messages := isValidLiteralValue(argDef.Type, argAST.Value); !isValid { var messagesStr, argNameValue string if argAST.Name != nil { argNameValue = argAST.Name.Value } if len(messages) > 0 { messagesStr = "\n" + strings.Join(messages, "\n") } reportError( context, fmt.Sprintf(`Argument "%v" has invalid value %v.%v`, argNameValue, printer.Print(argAST.Value), messagesStr), []ast.Node{argAST.Value}, ) } } } return visitor.ActionSkip, nil }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } // DefaultValuesOfCorrectTypeRule Variable default values of correct type // // A GraphQL document is only valid if all variable default values are of the // type expected by their definition. func DefaultValuesOfCorrectTypeRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.VariableDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if varDefAST, ok := p.Node.(*ast.VariableDefinition); ok { var ( name string defaultValue = varDefAST.DefaultValue messagesStr string ) if varDefAST.Variable != nil && varDefAST.Variable.Name != nil { name = varDefAST.Variable.Name.Value } ttype := context.InputType() // when input variable value must be nonNull, and set default value is unnecessary if ttype, ok := ttype.(*NonNull); ok && defaultValue != nil { reportError( context, fmt.Sprintf(`Variable "$%v" of type "%v" is required and will not use the default value. Perhaps you meant to use type "%v".`, name, ttype, ttype.OfType), []ast.Node{defaultValue}, ) } if isValid, messages := isValidLiteralValue(ttype, defaultValue); !isValid && defaultValue != nil { if len(messages) > 0 { messagesStr = "\n" + strings.Join(messages, "\n") } reportError( context, fmt.Sprintf(`Variable "$%v" has invalid default value: %v.%v`, name, printer.Print(defaultValue), messagesStr), []ast.Node{defaultValue}, ) } } return visitor.ActionSkip, nil }, }, kinds.SelectionSet: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { return visitor.ActionSkip, nil }, }, kinds.FragmentDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { return visitor.ActionSkip, nil }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } func quoteStrings(slice []string) []string { quoted := []string{} for _, s := range slice { quoted = append(quoted, fmt.Sprintf(`"%v"`, s)) } return quoted } // quotedOrList Given [ A, B, C ] return '"A", "B", or "C"'. // Notice oxford comma func quotedOrList(slice []string) string { maxLength := 5 if len(slice) == 0 { return "" } quoted := quoteStrings(slice) if maxLength > len(quoted) { maxLength = len(quoted) } if maxLength > 2 { return fmt.Sprintf("%v, or %v", strings.Join(quoted[0:maxLength-1], ", "), quoted[maxLength-1]) } if maxLength > 1 { return fmt.Sprintf("%v or %v", strings.Join(quoted[0:maxLength-1], ", "), quoted[maxLength-1]) } return quoted[0] } func UndefinedFieldMessage(fieldName string, ttypeName string, suggestedTypeNames []string, suggestedFieldNames []string) string { message := fmt.Sprintf(`Cannot query field "%v" on type "%v".`, fieldName, ttypeName) if len(suggestedTypeNames) > 0 { message = fmt.Sprintf(`%v Did you mean to use an inline fragment on %v?`, message, quotedOrList(suggestedTypeNames)) } else if len(suggestedFieldNames) > 0 { message = fmt.Sprintf(`%v Did you mean %v?`, message, quotedOrList(suggestedFieldNames)) } return message } // FieldsOnCorrectTypeRule Fields on correct type // // A GraphQL document is only valid if all fields selected are defined by the // parent type, or are an allowed meta field such as __typenamme func FieldsOnCorrectTypeRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.Field: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { var action = visitor.ActionNoChange if node, ok := p.Node.(*ast.Field); ok { var ttype Composite if ttype = context.ParentType(); ttype == nil { return action, nil } switch ttype.(type) { case *Object, *Interface, *Union: if reflect.ValueOf(ttype).IsNil() { return action, nil } } fieldDef := context.FieldDef() if fieldDef == nil { // This field doesn't exist, lets look for suggestions. var nodeName string if node.Name != nil { nodeName = node.Name.Value } // First determine if there are any suggested types to condition on. suggestedTypeNames := getSuggestedTypeNames(context.Schema(), ttype, nodeName) // If there are no suggested types, then perhaps this was a typo? suggestedFieldNames := []string{} if len(suggestedTypeNames) == 0 { suggestedFieldNames = getSuggestedFieldNames(context.Schema(), ttype, nodeName) } reportError( context, UndefinedFieldMessage(nodeName, ttype.Name(), suggestedTypeNames, suggestedFieldNames), []ast.Node{node}, ) } } return action, nil }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } // getSuggestedTypeNames Go through all of the implementations of type, as well as the interfaces // that they implement. If any of those types include the provided field, // suggest them, sorted by how often the type is referenced, starting // with Interfaces. func getSuggestedTypeNames(schema *Schema, ttype Output, fieldName string) []string { var ( suggestedObjectTypes = []string{} suggestedInterfaces = []*suggestedInterface{} // stores a map of interface name => index in suggestedInterfaces suggestedInterfaceMap = map[string]int{} // stores a maps of object name => true to remove duplicates from results suggestedObjectMap = map[string]bool{} ) possibleTypes := schema.PossibleTypes(ttype) for _, possibleType := range possibleTypes { if field, ok := possibleType.Fields()[fieldName]; !ok || field == nil { continue } // This object type defines this field. suggestedObjectTypes = append(suggestedObjectTypes, possibleType.Name()) suggestedObjectMap[possibleType.Name()] = true for _, possibleInterface := range possibleType.Interfaces() { if field, ok := possibleInterface.Fields()[fieldName]; !ok || field == nil { continue } // This interface type defines this field. // - find the index of the suggestedInterface and retrieving the interface // - increase count index, ok := suggestedInterfaceMap[possibleInterface.Name()] if !ok { suggestedInterfaces = append(suggestedInterfaces, &suggestedInterface{ name: possibleInterface.Name(), count: 0, }) index = len(suggestedInterfaces) - 1 suggestedInterfaceMap[possibleInterface.Name()] = index } if index < len(suggestedInterfaces) { s := suggestedInterfaces[index] if s.name == possibleInterface.Name() { s.count++ } } } } // sort results (by count usage for interfaces, alphabetical order for objects) sort.Sort(suggestedInterfaceSortedSlice(suggestedInterfaces)) sort.Sort(sort.StringSlice(suggestedObjectTypes)) // return concatenated slices of both interface and object type names // and removing duplicates // ordered by: interface (sorted) and object (sorted) results := []string{} for _, s := range suggestedInterfaces { if _, ok := suggestedObjectMap[s.name]; !ok { results = append(results, s.name) } } results = append(results, suggestedObjectTypes...) return results } // getSuggestedFieldNames For the field name provided, determine if there are any similar field names // that may be the result of a typo. func getSuggestedFieldNames(schema *Schema, ttype Output, fieldName string) []string { fields := FieldDefinitionMap{} switch ttype := ttype.(type) { case *Object: fields = ttype.Fields() case *Interface: fields = ttype.Fields() default: return []string{} } possibleFieldNames := []string{} for possibleFieldName := range fields { possibleFieldNames = append(possibleFieldNames, possibleFieldName) } return suggestionList(fieldName, possibleFieldNames) } // suggestedInterface an internal struct to sort interface by usage count type suggestedInterface struct { name string count int } type suggestedInterfaceSortedSlice []*suggestedInterface func (s suggestedInterfaceSortedSlice) Len() int { return len(s) } func (s suggestedInterfaceSortedSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s suggestedInterfaceSortedSlice) Less(i, j int) bool { if s[i].count == s[j].count { return s[i].name < s[j].name } return s[i].count > s[j].count } // FragmentsOnCompositeTypesRule Fragments on composite type // // Fragments use a type condition to determine if they apply, since fragments // can only be spread into a composite type (object, interface, or union), the // type condition must also be a composite type. func FragmentsOnCompositeTypesRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.InlineFragment: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.InlineFragment); ok { ttype := context.Type() if node.TypeCondition != nil && ttype != nil && !IsCompositeType(ttype) { reportError( context, fmt.Sprintf(`Fragment cannot condition on non composite type "%v".`, ttype), []ast.Node{node.TypeCondition}, ) } } return visitor.ActionNoChange, nil }, }, kinds.FragmentDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.FragmentDefinition); ok { ttype := context.Type() if ttype != nil && !IsCompositeType(ttype) { nodeName := "" if node.Name != nil { nodeName = node.Name.Value } reportError( context, fmt.Sprintf(`Fragment "%v" cannot condition on non composite type "%v".`, nodeName, printer.Print(node.TypeCondition)), []ast.Node{node.TypeCondition}, ) } } return visitor.ActionNoChange, nil }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } func unknownArgMessage(argName string, fieldName string, parentTypeName string, suggestedArgs []string) string { message := fmt.Sprintf(`Unknown argument "%v" on field "%v" of type "%v".`, argName, fieldName, parentTypeName) if len(suggestedArgs) > 0 { message = fmt.Sprintf(`%v Did you mean %v?`, message, quotedOrList(suggestedArgs)) } return message } func unknownDirectiveArgMessage(argName string, directiveName string, suggestedArgs []string) string { message := fmt.Sprintf(`Unknown argument "%v" on directive "@%v".`, argName, directiveName) if len(suggestedArgs) > 0 { message = fmt.Sprintf(`%v Did you mean %v?`, message, quotedOrList(suggestedArgs)) } return message } // KnownArgumentNamesRule Known argument names // // A GraphQL field is only valid if all supplied arguments are defined by // that field. func KnownArgumentNamesRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.Argument: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { var action = visitor.ActionNoChange if node, ok := p.Node.(*ast.Argument); ok { var argumentOf ast.Node if len(p.Ancestors) > 0 { argumentOf = p.Ancestors[len(p.Ancestors)-1] } if argumentOf == nil { return action, nil } // verify node, if the node's name exists in Arguments{Field, Directive} var ( fieldArgDef *Argument fieldDef = context.FieldDef() directive = context.Directive() argNames []string parentTypeName string ) switch argumentOf.GetKind() { case kinds.Field: // get field definition if fieldDef == nil { return action, nil } for _, arg := range fieldDef.Args { if arg.Name() == node.Name.Value { fieldArgDef = arg break } argNames = append(argNames, arg.Name()) } if fieldArgDef == nil { parentType := context.ParentType() if parentType != nil { parentTypeName = parentType.Name() } reportError( context, unknownArgMessage( node.Name.Value, fieldDef.Name, parentTypeName, suggestionList(node.Name.Value, argNames), ), []ast.Node{node}, ) } case kinds.Directive: if directive = context.Directive(); directive == nil { return action, nil } for _, arg := range directive.Args { if arg.Name() == node.Name.Value { fieldArgDef = arg break } argNames = append(argNames, arg.Name()) } if fieldArgDef == nil { reportError( context, unknownDirectiveArgMessage( node.Name.Value, directive.Name, suggestionList(node.Name.Value, argNames), ), []ast.Node{node}, ) } } } return action, nil }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } func MisplaceDirectiveMessage(directiveName string, location string) string { return fmt.Sprintf(`Directive "%v" may not be used on %v.`, directiveName, location) } // KnownDirectivesRule Known directives // // A GraphQL document is only valid if all `@directives` are known by the // schema and legally positioned. func KnownDirectivesRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.Directive: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { var action = visitor.ActionNoChange var result interface{} if node, ok := p.Node.(*ast.Directive); ok { nodeName := "" if node.Name != nil { nodeName = node.Name.Value } var directiveDef *Directive for _, def := range context.Schema().Directives() { if def.Name == nodeName { directiveDef = def } } if directiveDef == nil { return reportError( context, fmt.Sprintf(`Unknown directive "%v".`, nodeName), []ast.Node{node}, ) } candidateLocation := getDirectiveLocationForASTPath(p.Ancestors) directiveHasLocation := false for _, loc := range directiveDef.Locations { if loc == candidateLocation { directiveHasLocation = true break } } if candidateLocation == "" { reportError( context, MisplaceDirectiveMessage(nodeName, node.GetKind()), []ast.Node{node}, ) } else if !directiveHasLocation { reportError( context, MisplaceDirectiveMessage(nodeName, candidateLocation), []ast.Node{node}, ) } } return action, result }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } func getDirectiveLocationForASTPath(ancestors []ast.Node) string { var appliedTo ast.Node if len(ancestors) > 0 { appliedTo = ancestors[len(ancestors)-1] } if appliedTo == nil { return "" } kind := appliedTo.GetKind() if kind == kinds.OperationDefinition { appliedTo, _ := appliedTo.(*ast.OperationDefinition) if appliedTo.Operation == ast.OperationTypeQuery { return DirectiveLocationQuery } if appliedTo.Operation == ast.OperationTypeMutation { return DirectiveLocationMutation } if appliedTo.Operation == ast.OperationTypeSubscription { return DirectiveLocationSubscription } } if kind == kinds.Field { return DirectiveLocationField } if kind == kinds.FragmentSpread { return DirectiveLocationFragmentSpread } if kind == kinds.InlineFragment { return DirectiveLocationInlineFragment } if kind == kinds.FragmentDefinition { return DirectiveLocationFragmentDefinition } if kind == kinds.SchemaDefinition { return DirectiveLocationSchema } if kind == kinds.ScalarDefinition { return DirectiveLocationScalar } if kind == kinds.ObjectDefinition { return DirectiveLocationObject } if kind == kinds.FieldDefinition { return DirectiveLocationFieldDefinition } if kind == kinds.InterfaceDefinition { return DirectiveLocationInterface } if kind == kinds.UnionDefinition { return DirectiveLocationUnion } if kind == kinds.EnumDefinition { return DirectiveLocationEnum } if kind == kinds.EnumValueDefinition { return DirectiveLocationEnumValue } if kind == kinds.InputObjectDefinition { return DirectiveLocationInputObject } if kind == kinds.InputValueDefinition { var parentNode ast.Node if len(ancestors) >= 3 { parentNode = ancestors[len(ancestors)-3] } if parentNode.GetKind() == kinds.InputObjectDefinition { return DirectiveLocationInputFieldDefinition } else { return DirectiveLocationArgumentDefinition } } return "" } // KnownFragmentNamesRule Known fragment names // // A GraphQL document is only valid if all `...Fragment` fragment spreads refer // to fragments defined in the same document. func KnownFragmentNamesRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.FragmentSpread: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { var action = visitor.ActionNoChange var result interface{} if node, ok := p.Node.(*ast.FragmentSpread); ok { fragmentName := "" if node.Name != nil { fragmentName = node.Name.Value } fragment := context.Fragment(fragmentName) if fragment == nil { reportError( context, fmt.Sprintf(`Unknown fragment "%v".`, fragmentName), []ast.Node{node.Name}, ) } } return action, result }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } func unknownTypeMessage(typeName string, suggestedTypes []string) string { message := fmt.Sprintf(`Unknown type "%v".`, typeName) if len(suggestedTypes) > 0 { message = fmt.Sprintf(`%v Did you mean %v?`, message, quotedOrList(suggestedTypes)) } return message } // KnownTypeNamesRule Known type names // // A GraphQL document is only valid if referenced types (specifically // variable definitions and fragment conditions) are defined by the type schema. func KnownTypeNamesRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.ObjectDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { return visitor.ActionSkip, nil }, }, kinds.InterfaceDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { return visitor.ActionSkip, nil }, }, kinds.UnionDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { return visitor.ActionSkip, nil }, }, kinds.InputObjectDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { return visitor.ActionSkip, nil }, }, kinds.Named: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.Named); ok { typeNameValue := "" typeName := node.Name if typeName != nil { typeNameValue = typeName.Value } ttype := context.Schema().Type(typeNameValue) if ttype == nil { suggestedTypes := []string{} for key := range context.Schema().TypeMap() { suggestedTypes = append(suggestedTypes, key) } reportError( context, unknownTypeMessage(typeNameValue, suggestionList(typeNameValue, suggestedTypes)), []ast.Node{node}, ) } } return visitor.ActionNoChange, nil }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } // LoneAnonymousOperationRule Lone anonymous operation // // A GraphQL document is only valid if when it contains an anonymous operation // (the query short-hand) that it contains only that one operation definition. func LoneAnonymousOperationRule(context *ValidationContext) *ValidationRuleInstance { var operationCount = 0 visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.Document: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.Document); ok { operationCount = 0 for _, definition := range node.Definitions { if definition.GetKind() == kinds.OperationDefinition { operationCount++ } } } return visitor.ActionNoChange, nil }, }, kinds.OperationDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.OperationDefinition); ok { if node.Name == nil && operationCount > 1 { reportError( context, `This anonymous operation must be the only defined operation.`, []ast.Node{node}, ) } } return visitor.ActionNoChange, nil }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } func CycleErrorMessage(fragName string, spreadNames []string) string { via := "" if len(spreadNames) > 0 { via = " via " + strings.Join(spreadNames, ", ") } return fmt.Sprintf(`Cannot spread fragment "%v" within itself%v.`, fragName, via) } // NoFragmentCyclesRule No fragment cycles func NoFragmentCyclesRule(context *ValidationContext) *ValidationRuleInstance { // Tracks already visited fragments to maintain O(N) and to ensure that cycles // are not redundantly reported. visitedFrags := map[string]bool{} // Array of AST nodes used to produce meaningful errors spreadPath := []*ast.FragmentSpread{} // Position in the spread path spreadPathIndexByName := map[string]int{} // This does a straight-forward DFS to find cycles. // It does not terminate when a cycle was found but continues to explore // the graph to find all possible cycles. var detectCycleRecursive func(fragment *ast.FragmentDefinition) detectCycleRecursive = func(fragment *ast.FragmentDefinition) { fragmentName := "" if fragment.Name != nil { fragmentName = fragment.Name.Value } visitedFrags[fragmentName] = true spreadNodes := context.FragmentSpreads(fragment.SelectionSet) if len(spreadNodes) == 0 { return } spreadPathIndexByName[fragmentName] = len(spreadPath) for _, spreadNode := range spreadNodes { spreadName := "" if spreadNode.Name != nil { spreadName = spreadNode.Name.Value } cycleIndex, ok := spreadPathIndexByName[spreadName] if !ok { spreadPath = append(spreadPath, spreadNode) if visited, ok := visitedFrags[spreadName]; !ok || !visited { spreadFragment := context.Fragment(spreadName) if spreadFragment != nil { detectCycleRecursive(spreadFragment) } } spreadPath = spreadPath[:len(spreadPath)-1] } else { cyclePath := spreadPath[cycleIndex:] spreadNames := []string{} for _, s := range cyclePath { name := "" if s.Name != nil { name = s.Name.Value } spreadNames = append(spreadNames, name) } nodes := []ast.Node{} for _, c := range cyclePath { nodes = append(nodes, c) } nodes = append(nodes, spreadNode) reportError( context, CycleErrorMessage(spreadName, spreadNames), nodes, ) } } delete(spreadPathIndexByName, fragmentName) } visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.OperationDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { return visitor.ActionSkip, nil }, }, kinds.FragmentDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.FragmentDefinition); ok && node != nil { nodeName := "" if node.Name != nil { nodeName = node.Name.Value } if _, ok := visitedFrags[nodeName]; !ok { detectCycleRecursive(node) } } return visitor.ActionSkip, nil }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } func UndefinedVarMessage(varName string, opName string) string { if opName != "" { return fmt.Sprintf(`Variable "$%v" is not defined by operation "%v".`, varName, opName) } return fmt.Sprintf(`Variable "$%v" is not defined.`, varName) } // NoUndefinedVariablesRule No undefined variables // // A GraphQL operation is only valid if all variables encountered, both directly // and via fragment spreads, are defined by that operation. func NoUndefinedVariablesRule(context *ValidationContext) *ValidationRuleInstance { var variableNameDefined = map[string]bool{} visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.OperationDefinition: { Enter: func(p visitor.VisitFuncParams) (string, interface{}) { variableNameDefined = map[string]bool{} return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { if operation, ok := p.Node.(*ast.OperationDefinition); ok && operation != nil { usages := context.RecursiveVariableUsages(operation) for _, usage := range usages { if usage == nil { continue } if usage.Node == nil { continue } varName := "" if usage.Node.Name != nil { varName = usage.Node.Name.Value } opName := "" if operation.Name != nil { opName = operation.Name.Value } if res, ok := variableNameDefined[varName]; !ok || !res { reportError( context, UndefinedVarMessage(varName, opName), []ast.Node{usage.Node, operation}, ) } } } return visitor.ActionNoChange, nil }, }, kinds.VariableDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.VariableDefinition); ok && node != nil { variableName := "" if node.Variable != nil && node.Variable.Name != nil { variableName = node.Variable.Name.Value } variableNameDefined[variableName] = true } return visitor.ActionNoChange, nil }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } // NoUnusedFragmentsRule No unused fragments // // A GraphQL document is only valid if all fragment definitions are spread // within operations, or spread within other fragments spread within operations. func NoUnusedFragmentsRule(context *ValidationContext) *ValidationRuleInstance { var fragmentDefs = []*ast.FragmentDefinition{} var operationDefs = []*ast.OperationDefinition{} visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.OperationDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.OperationDefinition); ok && node != nil { operationDefs = append(operationDefs, node) } return visitor.ActionSkip, nil }, }, kinds.FragmentDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.FragmentDefinition); ok && node != nil { fragmentDefs = append(fragmentDefs, node) } return visitor.ActionSkip, nil }, }, kinds.Document: { Leave: func(p visitor.VisitFuncParams) (string, interface{}) { fragmentNameUsed := map[string]bool{} for _, operation := range operationDefs { fragments := context.RecursivelyReferencedFragments(operation) for _, fragment := range fragments { fragName := "" if fragment.Name != nil { fragName = fragment.Name.Value } fragmentNameUsed[fragName] = true } } for _, def := range fragmentDefs { defName := "" if def.Name != nil { defName = def.Name.Value } isFragNameUsed, ok := fragmentNameUsed[defName] if !ok || isFragNameUsed != true { reportError( context, fmt.Sprintf(`Fragment "%v" is never used.`, defName), []ast.Node{def}, ) } } return visitor.ActionNoChange, nil }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } func UnusedVariableMessage(varName string, opName string) string { if opName != "" { return fmt.Sprintf(`Variable "$%v" is never used in operation "%v".`, varName, opName) } return fmt.Sprintf(`Variable "$%v" is never used.`, varName) } // NoUnusedVariablesRule No unused variables // // A GraphQL operation is only valid if all variables defined by an operation // are used, either directly or within a spread fragment. func NoUnusedVariablesRule(context *ValidationContext) *ValidationRuleInstance { var variableDefs = []*ast.VariableDefinition{} visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.OperationDefinition: { Enter: func(p visitor.VisitFuncParams) (string, interface{}) { variableDefs = []*ast.VariableDefinition{} return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { if operation, ok := p.Node.(*ast.OperationDefinition); ok && operation != nil { variableNameUsed := map[string]bool{} usages := context.RecursiveVariableUsages(operation) for _, usage := range usages { varName := "" if usage != nil && usage.Node != nil && usage.Node.Name != nil { varName = usage.Node.Name.Value } if varName != "" { variableNameUsed[varName] = true } } for _, variableDef := range variableDefs { variableName := "" if variableDef != nil && variableDef.Variable != nil && variableDef.Variable.Name != nil { variableName = variableDef.Variable.Name.Value } opName := "" if operation.Name != nil { opName = operation.Name.Value } if res, ok := variableNameUsed[variableName]; !ok || !res { reportError( context, UnusedVariableMessage(variableName, opName), []ast.Node{variableDef}, ) } } } return visitor.ActionNoChange, nil }, }, kinds.VariableDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if def, ok := p.Node.(*ast.VariableDefinition); ok && def != nil { variableDefs = append(variableDefs, def) } return visitor.ActionNoChange, nil }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } func getFragmentType(context *ValidationContext, name string) Type { frag := context.Fragment(name) if frag == nil { return nil } ttype, _ := typeFromAST(*context.Schema(), frag.TypeCondition) return ttype } func doTypesOverlap(schema *Schema, t1 Type, t2 Type) bool { if t1 == t2 { return true } if _, ok := t1.(*Object); ok { if _, ok := t2.(*Object); ok { return false } if t2, ok := t2.(Abstract); ok { for _, ttype := range schema.PossibleTypes(t2) { if ttype == t1 { return true } } return false } } if t1, ok := t1.(Abstract); ok { if _, ok := t2.(*Object); ok { for _, ttype := range schema.PossibleTypes(t1) { if ttype == t2 { return true } } return false } t1TypeNames := map[string]bool{} for _, ttype := range schema.PossibleTypes(t1) { t1TypeNames[ttype.Name()] = true } if t2, ok := t2.(Abstract); ok { for _, ttype := range schema.PossibleTypes(t2) { if hasT1TypeName, _ := t1TypeNames[ttype.Name()]; hasT1TypeName { return true } } return false } } return false } // PossibleFragmentSpreadsRule Possible fragment spread // // A fragment spread is only valid if the type condition could ever possibly // be true: if there is a non-empty intersection of the possible parent types, // and possible types which pass the type condition. func PossibleFragmentSpreadsRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.InlineFragment: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.InlineFragment); ok && node != nil { fragType := context.Type() parentType, _ := context.ParentType().(Type) if fragType != nil && parentType != nil && !doTypesOverlap(context.Schema(), fragType, parentType) { reportError( context, fmt.Sprintf(`Fragment cannot be spread here as objects of `+ `type "%v" can never be of type "%v".`, parentType, fragType), []ast.Node{node}, ) } } return visitor.ActionNoChange, nil }, }, kinds.FragmentSpread: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.FragmentSpread); ok && node != nil { fragName := "" if node.Name != nil { fragName = node.Name.Value } fragType := getFragmentType(context, fragName) parentType, _ := context.ParentType().(Type) if fragType != nil && parentType != nil && !doTypesOverlap(context.Schema(), fragType, parentType) { reportError( context, fmt.Sprintf(`Fragment "%v" cannot be spread here as objects of `+ `type "%v" can never be of type "%v".`, fragName, parentType, fragType), []ast.Node{node}, ) } } return visitor.ActionNoChange, nil }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } // ProvidedNonNullArgumentsRule Provided required arguments // // A field or directive is only valid if all required (non-null) field arguments // have been provided. func ProvidedNonNullArgumentsRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.Field: { Leave: func(p visitor.VisitFuncParams) (string, interface{}) { // Validate on leave to allow for deeper errors to appear first. if fieldAST, ok := p.Node.(*ast.Field); ok && fieldAST != nil { fieldDef := context.FieldDef() if fieldDef == nil { return visitor.ActionSkip, nil } argASTs := fieldAST.Arguments argASTMap := map[string]*ast.Argument{} for _, arg := range argASTs { name := "" if arg.Name != nil { name = arg.Name.Value } argASTMap[name] = arg } for _, argDef := range fieldDef.Args { argAST, _ := argASTMap[argDef.Name()] if argAST == nil { if argDefType, ok := argDef.Type.(*NonNull); ok { fieldName := "" if fieldAST.Name != nil { fieldName = fieldAST.Name.Value } reportError( context, fmt.Sprintf(`Field "%v" argument "%v" of type "%v" `+ `is required but not provided.`, fieldName, argDef.Name(), argDefType), []ast.Node{fieldAST}, ) } } } } return visitor.ActionNoChange, nil }, }, kinds.Directive: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { // Validate on leave to allow for deeper errors to appear first. if directiveAST, ok := p.Node.(*ast.Directive); ok && directiveAST != nil { directiveDef := context.Directive() if directiveDef == nil { return visitor.ActionSkip, nil } argASTs := directiveAST.Arguments argASTMap := map[string]*ast.Argument{} for _, arg := range argASTs { name := "" if arg.Name != nil { name = arg.Name.Value } argASTMap[name] = arg } for _, argDef := range directiveDef.Args { argAST, _ := argASTMap[argDef.Name()] if argAST == nil { if argDefType, ok := argDef.Type.(*NonNull); ok { directiveName := "" if directiveAST.Name != nil { directiveName = directiveAST.Name.Value } reportError( context, fmt.Sprintf(`Directive "@%v" argument "%v" of type `+ `"%v" is required but not provided.`, directiveName, argDef.Name(), argDefType), []ast.Node{directiveAST}, ) } } } } return visitor.ActionNoChange, nil }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } // ScalarLeafsRule Scalar leafs // // A GraphQL document is valid only if all leaf fields (fields without // sub selections) are of scalar or enum types. func ScalarLeafsRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.Field: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.Field); ok && node != nil { nodeName := "" if node.Name != nil { nodeName = node.Name.Value } ttype := context.Type() if ttype != nil { if IsLeafType(ttype) { if node.SelectionSet != nil { reportError( context, fmt.Sprintf(`Field "%v" of type "%v" must not have a sub selection.`, nodeName, ttype), []ast.Node{node.SelectionSet}, ) } } else if node.SelectionSet == nil { reportError( context, fmt.Sprintf(`Field "%v" of type "%v" must have a sub selection.`, nodeName, ttype), []ast.Node{node}, ) } } } return visitor.ActionNoChange, nil }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } // UniqueArgumentNamesRule Unique argument names // // A GraphQL field or directive is only valid if all supplied arguments are // uniquely named. func UniqueArgumentNamesRule(context *ValidationContext) *ValidationRuleInstance { knownArgNames := map[string]*ast.Name{} visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.Field: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { knownArgNames = map[string]*ast.Name{} return visitor.ActionNoChange, nil }, }, kinds.Directive: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { knownArgNames = map[string]*ast.Name{} return visitor.ActionNoChange, nil }, }, kinds.Argument: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.Argument); ok { argName := "" if node.Name != nil { argName = node.Name.Value } if nameAST, ok := knownArgNames[argName]; ok { reportError( context, fmt.Sprintf(`There can be only one argument named "%v".`, argName), []ast.Node{nameAST, node.Name}, ) } else { knownArgNames[argName] = node.Name } } return visitor.ActionSkip, nil }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } // UniqueFragmentNamesRule Unique fragment names // // A GraphQL document is only valid if all defined fragments have unique names. func UniqueFragmentNamesRule(context *ValidationContext) *ValidationRuleInstance { knownFragmentNames := map[string]*ast.Name{} visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.OperationDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { return visitor.ActionSkip, nil }, }, kinds.FragmentDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.FragmentDefinition); ok && node != nil { fragmentName := "" if node.Name != nil { fragmentName = node.Name.Value } if nameAST, ok := knownFragmentNames[fragmentName]; ok { reportError( context, fmt.Sprintf(`There can only be one fragment named "%v".`, fragmentName), []ast.Node{nameAST, node.Name}, ) } else { knownFragmentNames[fragmentName] = node.Name } } return visitor.ActionSkip, nil }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } // UniqueInputFieldNamesRule Unique input field names // // A GraphQL input object value is only valid if all supplied fields are // uniquely named. func UniqueInputFieldNamesRule(context *ValidationContext) *ValidationRuleInstance { knownNameStack := []map[string]*ast.Name{} knownNames := map[string]*ast.Name{} visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.ObjectValue: { Enter: func(p visitor.VisitFuncParams) (string, interface{}) { knownNameStack = append(knownNameStack, knownNames) knownNames = map[string]*ast.Name{} return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { // pop knownNames, knownNameStack = knownNameStack[len(knownNameStack)-1], knownNameStack[:len(knownNameStack)-1] return visitor.ActionNoChange, nil }, }, kinds.ObjectField: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.ObjectField); ok { fieldName := "" if node.Name != nil { fieldName = node.Name.Value } if knownNameAST, ok := knownNames[fieldName]; ok { reportError( context, fmt.Sprintf(`There can be only one input field named "%v".`, fieldName), []ast.Node{knownNameAST, node.Name}, ) } else { knownNames[fieldName] = node.Name } } return visitor.ActionSkip, nil }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } // UniqueOperationNamesRule Unique operation names // // A GraphQL document is only valid if all defined operations have unique names. func UniqueOperationNamesRule(context *ValidationContext) *ValidationRuleInstance { knownOperationNames := make(map[string]ast.Node) visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.OperationDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.OperationDefinition); ok && node != nil { operationName := "" if node.Name != nil { operationName = node.Name.Value } var errNode ast.Node = node if node.Name != nil { errNode = node.Name } if nameAST, ok := knownOperationNames[operationName]; ok { reportError( context, fmt.Sprintf(`There can only be one operation named "%v".`, operationName), []ast.Node{nameAST, errNode}, ) } else { knownOperationNames[operationName] = errNode } } return visitor.ActionSkip, nil }, }, kinds.FragmentDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { return visitor.ActionSkip, nil }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } // UniqueVariableNamesRule Unique variable names // // A GraphQL operation is only valid if all its variables are uniquely named. func UniqueVariableNamesRule(context *ValidationContext) *ValidationRuleInstance { knownVariableNames := map[string]*ast.Name{} visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.OperationDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.OperationDefinition); ok && node != nil { knownVariableNames = map[string]*ast.Name{} } return visitor.ActionNoChange, nil }, }, kinds.VariableDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.VariableDefinition); ok && node != nil { variableName := "" var variableNameAST *ast.Name if node.Variable != nil && node.Variable.Name != nil { variableNameAST = node.Variable.Name variableName = node.Variable.Name.Value } if nameAST, ok := knownVariableNames[variableName]; ok { reportError( context, fmt.Sprintf(`There can only be one variable named "%v".`, variableName), []ast.Node{nameAST, variableNameAST}, ) } else { knownVariableNames[variableName] = variableNameAST } } return visitor.ActionNoChange, nil }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } // VariablesAreInputTypesRule Variables are input types // // A GraphQL operation is only valid if all the variables it defines are of // input types (scalar, enum, or input object). func VariablesAreInputTypesRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.VariableDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.VariableDefinition); ok && node != nil { ttype, _ := typeFromAST(*context.Schema(), node.Type) // If the variable type is not an input type, return an error. if ttype != nil && !IsInputType(ttype) { variableName := "" if node.Variable != nil && node.Variable.Name != nil { variableName = node.Variable.Name.Value } reportError( context, fmt.Sprintf(`Variable "$%v" cannot be non-input type "%v".`, variableName, printer.Print(node.Type)), []ast.Node{node.Type}, ) } } return visitor.ActionNoChange, nil }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } // If a variable definition has a default value, it's effectively non-null. func effectiveType(varType Type, varDef *ast.VariableDefinition) Type { if varDef.DefaultValue == nil { return varType } if _, ok := varType.(*NonNull); ok { return varType } return NewNonNull(varType) } // VariablesInAllowedPositionRule Variables passed to field arguments conform to type func VariablesInAllowedPositionRule(context *ValidationContext) *ValidationRuleInstance { varDefMap := map[string]*ast.VariableDefinition{} visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.OperationDefinition: { Enter: func(p visitor.VisitFuncParams) (string, interface{}) { varDefMap = map[string]*ast.VariableDefinition{} return visitor.ActionNoChange, nil }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { if operation, ok := p.Node.(*ast.OperationDefinition); ok { usages := context.RecursiveVariableUsages(operation) for _, usage := range usages { varName := "" if usage != nil && usage.Node != nil && usage.Node.Name != nil { varName = usage.Node.Name.Value } varDef, _ := varDefMap[varName] if varDef != nil && usage.Type != nil { varType, err := typeFromAST(*context.Schema(), varDef.Type) if err != nil { varType = nil } if varType != nil && !isTypeSubTypeOf(context.Schema(), effectiveType(varType, varDef), usage.Type) { reportError( context, fmt.Sprintf(`Variable "$%v" of type "%v" used in position `+ `expecting type "%v".`, varName, varType, usage.Type), []ast.Node{varDef, usage.Node}, ) } } } } return visitor.ActionNoChange, nil }, }, kinds.VariableDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if varDefAST, ok := p.Node.(*ast.VariableDefinition); ok { defName := "" if varDefAST.Variable != nil && varDefAST.Variable.Name != nil { defName = varDefAST.Variable.Name.Value } if defName != "" { varDefMap[defName] = varDefAST } } return visitor.ActionNoChange, nil }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } // Utility for validators which determines if a value literal AST is valid given // an input type. // // Note that this only validates literal values, variables are assumed to // provide values of the correct type. func isValidLiteralValue(ttype Input, valueAST ast.Value) (bool, []string) { if _, ok := ttype.(*NonNull); !ok { if valueAST == nil { return true, nil } // This function only tests literals, and assumes variables will provide // values of the correct type. if valueAST.GetKind() == kinds.Variable { return true, nil } } switch ttype := ttype.(type) { case *NonNull: // A value must be provided if the type is non-null. if e := ttype.Error(); e != nil { return false, []string{e.Error()} } if valueAST == nil { if ttype.OfType.Name() != "" { return false, []string{fmt.Sprintf(`Expected "%v!", found null.`, ttype.OfType.Name())} } return false, []string{"Expected non-null value, found null."} } ofType, _ := ttype.OfType.(Input) return isValidLiteralValue(ofType, valueAST) case *List: // Lists accept a non-list value as a list of one. itemType, _ := ttype.OfType.(Input) if valueAST, ok := valueAST.(*ast.ListValue); ok { messagesReduce := []string{} for _, value := range valueAST.Values { _, messages := isValidLiteralValue(itemType, value) for idx, message := range messages { messagesReduce = append(messagesReduce, fmt.Sprintf(`In element #%v: %v`, idx+1, message)) } } return (len(messagesReduce) == 0), messagesReduce } return isValidLiteralValue(itemType, valueAST) case *InputObject: // Input objects check each defined field and look for undefined fields. valueAST, ok := valueAST.(*ast.ObjectValue) if !ok { return false, []string{fmt.Sprintf(`Expected "%v", found not an object.`, ttype.Name())} } fields := ttype.Fields() messagesReduce := []string{} // Ensure every provided field is defined. fieldASTs := valueAST.Fields fieldASTMap := map[string]*ast.ObjectField{} for _, fieldAST := range fieldASTs { fieldASTMap[fieldAST.Name.Value] = fieldAST field, ok := fields[fieldAST.Name.Value] if !ok || field == nil { messagesReduce = append(messagesReduce, fmt.Sprintf(`In field "%v": Unknown field.`, fieldAST.Name.Value)) } } // Ensure every defined field is valid. for fieldName, field := range fields { var fieldASTValue ast.Value if fieldAST := fieldASTMap[fieldName]; fieldAST != nil { fieldASTValue = fieldAST.Value } if isValid, messages := isValidLiteralValue(field.Type, fieldASTValue); !isValid { for _, message := range messages { messagesReduce = append(messagesReduce, fmt.Sprintf("In field \"%v\": %v", fieldName, message)) } } } return (len(messagesReduce) == 0), messagesReduce case *Scalar: if isNullish(ttype.ParseLiteral(valueAST)) { return false, []string{fmt.Sprintf(`Expected type "%v", found %v.`, ttype.Name(), printer.Print(valueAST))} } case *Enum: if isNullish(ttype.ParseLiteral(valueAST)) { return false, []string{fmt.Sprintf(`Expected type "%v", found %v.`, ttype.Name(), printer.Print(valueAST))} } } return true, nil } // Internal struct to sort results from suggestionList() type suggestionListResult struct { Options []string Distances []float64 } func (s suggestionListResult) Len() int { return len(s.Options) } func (s suggestionListResult) Swap(i, j int) { s.Options[i], s.Options[j] = s.Options[j], s.Options[i] } func (s suggestionListResult) Less(i, j int) bool { return s.Distances[i] < s.Distances[j] } // suggestionList Given an invalid input string and a list of valid options, returns a filtered // list of valid options sorted based on their similarity with the input. func suggestionList(input string, options []string) []string { dists := []float64{} filteredOpts := []string{} inputThreshold := float64(len(input) / 2) for _, opt := range options { dist := lexicalDistance(input, opt) threshold := math.Max(inputThreshold, float64(len(opt)/2)) threshold = math.Max(threshold, 1) if dist <= threshold { filteredOpts = append(filteredOpts, opt) dists = append(dists, dist) } } //sort results suggested := suggestionListResult{filteredOpts, dists} sort.Sort(suggested) return suggested.Options } // lexicalDistance Computes the lexical distance between strings A and B. // The "distance" between two strings is given by counting the minimum number // of edits needed to transform string A into string B. An edit can be an // insertion, deletion, or substitution of a single character, or a swap of two // adjacent characters. // This distance can be useful for detecting typos in input or sorting func lexicalDistance(a, b string) float64 { d := [][]float64{} aLen := len(a) bLen := len(b) for i := 0; i <= aLen; i++ { d = append(d, []float64{float64(i)}) } for k := 1; k <= bLen; k++ { d[0] = append(d[0], float64(k)) } for i := 1; i <= aLen; i++ { for k := 1; k <= bLen; k++ { cost := 1.0 if a[i-1] == b[k-1] { cost = 0.0 } minCostFloat := math.Min( d[i-1][k]+1.0, d[i][k-1]+1.0, ) minCostFloat = math.Min( minCostFloat, d[i-1][k-1]+cost, ) d[i] = append(d[i], minCostFloat) if i > 1 && k < 1 && a[i-1] == b[k-2] && a[i-2] == b[k-1] { d[i][k] = math.Min(d[i][k], d[i-2][k-2]+cost) } } } return d[aLen][bLen] } ================================================ FILE: rules_arguments_of_correct_type_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_ArgValuesOfCorrectType_ValidValue_GoodIntValue(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { intArgField(intArg: 2) } } `) } func TestValidate_ArgValuesOfCorrectType_ValidValue_GoodBooleanValue(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { booleanArgField(booleanArg: true) } } `) } func TestValidate_ArgValuesOfCorrectType_ValidValue_GoodStringValue(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { stringArgField(stringArg: "foo") } } `) } func TestValidate_ArgValuesOfCorrectType_ValidValue_GoodFloatValue(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { floatArgField(floatArg: 1.1) } } `) } func TestValidate_ArgValuesOfCorrectType_ValidValue_IntIntoFloat(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { floatArgField(floatArg: 1) } } `) } func TestValidate_ArgValuesOfCorrectType_ValidValue_IntIntoID(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { idArgField(idArg: 1) } } `) } func TestValidate_ArgValuesOfCorrectType_ValidValue_StringIntoID(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { idArgField(idArg: "someIdString") } } `) } func TestValidate_ArgValuesOfCorrectType_ValidValue_GoodEnumValue(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { dog { doesKnowCommand(dogCommand: SIT) } } `) } func TestValidate_ArgValuesOfCorrectType_InvalidStringValues_IntIntoString(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { stringArgField(stringArg: 1) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"stringArg\" has invalid value 1.\nExpected type \"String\", found 1.", 4, 39, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidStringValues_FloatIntoString(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { stringArgField(stringArg: 1.0) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"stringArg\" has invalid value 1.0.\nExpected type \"String\", found 1.0.", 4, 39, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidStringValues_BooleanIntoString(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { stringArgField(stringArg: true) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"stringArg\" has invalid value true.\nExpected type \"String\", found true.", 4, 39, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidStringValues_UnquotedStringIntoString(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { stringArgField(stringArg: BAR) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"stringArg\" has invalid value BAR.\nExpected type \"String\", found BAR.", 4, 39, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidIntValues_StringIntoInt(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { intArgField(intArg: "3") } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"intArg\" has invalid value \"3\".\nExpected type \"Int\", found \"3\".", 4, 33, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidIntValues_BigIntIntoInt(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { intArgField(intArg: 829384293849283498239482938) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"intArg\" has invalid value 829384293849283498239482938.\nExpected type \"Int\", found 829384293849283498239482938.", 4, 33, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidIntValues_UnquotedStringIntoInt(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { intArgField(intArg: FOO) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"intArg\" has invalid value FOO.\nExpected type \"Int\", found FOO.", 4, 33, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidIntValues_SimpleFloatIntoInt(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { intArgField(intArg: 3.0) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"intArg\" has invalid value 3.0.\nExpected type \"Int\", found 3.0.", 4, 33, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidIntValues_FloatIntoInt(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { intArgField(intArg: 3.333) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"intArg\" has invalid value 3.333.\nExpected type \"Int\", found 3.333.", 4, 33, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidFloatValues_StringIntoFloat(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { floatArgField(floatArg: "3.333") } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"floatArg\" has invalid value \"3.333\".\nExpected type \"Float\", found \"3.333\".", 4, 37, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidFloatValues_BooleanIntoFloat(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { floatArgField(floatArg: true) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"floatArg\" has invalid value true.\nExpected type \"Float\", found true.", 4, 37, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidFloatValues_UnquotedIntoFloat(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { floatArgField(floatArg: FOO) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"floatArg\" has invalid value FOO.\nExpected type \"Float\", found FOO.", 4, 37, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidBooleanValues_IntIntoBoolean(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { booleanArgField(booleanArg: 2) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"booleanArg\" has invalid value 2.\nExpected type \"Boolean\", found 2.", 4, 41, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidBooleanValues_FloatIntoBoolean(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { booleanArgField(booleanArg: 1.0) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"booleanArg\" has invalid value 1.0.\nExpected type \"Boolean\", found 1.0.", 4, 41, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidBooleanValues_StringIntoBoolean(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { booleanArgField(booleanArg: "true") } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"booleanArg\" has invalid value \"true\".\nExpected type \"Boolean\", found \"true\".", 4, 41, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidBooleanValues_UnquotedStringIntoBoolean(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { booleanArgField(booleanArg: TRUE) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"booleanArg\" has invalid value TRUE.\nExpected type \"Boolean\", found TRUE.", 4, 41, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidIDValue_FloatIntoID(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { idArgField(idArg: 1.0) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"idArg\" has invalid value 1.0.\nExpected type \"ID\", found 1.0.", 4, 31, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidIDValue_BooleanIntoID(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { idArgField(idArg: true) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"idArg\" has invalid value true.\nExpected type \"ID\", found true.", 4, 31, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidIDValue_UnquotedIntoID(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { idArgField(idArg: SOMETHING) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"idArg\" has invalid value SOMETHING.\nExpected type \"ID\", found SOMETHING.", 4, 31, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidEnumValue_IntIntoEnum(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { dog { doesKnowCommand(dogCommand: 2) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"dogCommand\" has invalid value 2.\nExpected type \"DogCommand\", found 2.", 4, 41, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidEnumValue_FloatIntoEnum(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { dog { doesKnowCommand(dogCommand: 1.0) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"dogCommand\" has invalid value 1.0.\nExpected type \"DogCommand\", found 1.0.", 4, 41, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidEnumValue_StringIntoEnum(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { dog { doesKnowCommand(dogCommand: "SIT") } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"dogCommand\" has invalid value \"SIT\".\nExpected type \"DogCommand\", found \"SIT\".", 4, 41, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidEnumValue_BooleanIntoEnum(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { dog { doesKnowCommand(dogCommand: true) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"dogCommand\" has invalid value true.\nExpected type \"DogCommand\", found true.", 4, 41, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidEnumValue_UnknownEnumValueIntoEnum(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { dog { doesKnowCommand(dogCommand: JUGGLE) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"dogCommand\" has invalid value JUGGLE.\nExpected type \"DogCommand\", found JUGGLE.", 4, 41, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidEnumValue_DifferentCaseEnumValueIntoEnum(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { dog { doesKnowCommand(dogCommand: sit) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"dogCommand\" has invalid value sit.\nExpected type \"DogCommand\", found sit.", 4, 41, ), }) } func TestValidate_ArgValuesOfCorrectType_ValidListValue_GoodListValue(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { stringListArgField(stringListArg: ["one", "two"]) } } `) } func TestValidate_ArgValuesOfCorrectType_ValidListValue_EmptyListValue(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { stringListArgField(stringListArg: []) } } `) } func TestValidate_ArgValuesOfCorrectType_ValidListValue_SingleValueIntoList(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { stringListArgField(stringListArg: "one") } } `) } func TestValidate_ArgValuesOfCorrectType_InvalidListValue_IncorrectItemType(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { stringListArgField(stringListArg: ["one", 2]) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"stringListArg\" has invalid value [\"one\", 2].\nIn element #1: Expected type \"String\", found 2.", 4, 47, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidListValue_SingleValueOfIncorrentType(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { stringListArgField(stringListArg: 1) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"stringListArg\" has invalid value 1.\nExpected type \"String\", found 1.", 4, 47, ), }) } func TestValidate_ArgValuesOfCorrectType_ValidNonNullableValue_ArgOnOptionalArg(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { dog { isHousetrained(atOtherHomes: true) } } `) } func TestValidate_ArgValuesOfCorrectType_ValidNonNullableValue_NoArgOnOptionalArg(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { dog { isHousetrained } } `) } func TestValidate_ArgValuesOfCorrectType_ValidNonNullableValue_MultipleArgs(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { multipleReqs(req1: 1, req2: 2) } } `) } func TestValidate_ArgValuesOfCorrectType_ValidNonNullableValue_MultipleArgsReverseOrder(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { multipleReqs(req2: 2, req1: 1) } } `) } func TestValidate_ArgValuesOfCorrectType_ValidNonNullableValue_NoArgsOnMultipleOptional(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { multipleOpts } } `) } func TestValidate_ArgValuesOfCorrectType_ValidNonNullableValue_OneArgOnMultipleOptional(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { multipleOpts(opt1: 1) } } `) } func TestValidate_ArgValuesOfCorrectType_ValidNonNullableValue_SecondArgOnMultipleOptional(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { multipleOpts(opt2: 1) } } `) } func TestValidate_ArgValuesOfCorrectType_ValidNonNullableValue_MultipleRequiredsOnMixedList(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { multipleOptAndReq(req1: 3, req2: 4) } } `) } func TestValidate_ArgValuesOfCorrectType_ValidNonNullableValue_MultipleRequiredsAndOptionalOnMixedList(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { multipleOptAndReq(req1: 3, req2: 4, opt1: 5) } } `) } func TestValidate_ArgValuesOfCorrectType_ValidNonNullableValue_AllRequiredsAndOptionalOnMixedList(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { multipleOptAndReq(req1: 3, req2: 4, opt1: 5, opt2: 6) } } `) } func TestValidate_ArgValuesOfCorrectType_InvalidNonNullableValue_IncorrectValueType(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { multipleReqs(req2: "two", req1: "one") } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"req2\" has invalid value \"two\".\nExpected type \"Int\", found \"two\".", 4, 32, ), testutil.RuleError( "Argument \"req1\" has invalid value \"one\".\nExpected type \"Int\", found \"one\".", 4, 45, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidNonNullableValue_IncorrectValueAndMissingArgument(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { multipleReqs(req1: "one") } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"req1\" has invalid value \"one\".\nExpected type \"Int\", found \"one\".", 4, 32, ), }) } func TestValidate_ArgValuesOfCorrectType_ValidInputObjectValue_OptionalArg_DespiteRequiredFieldInType(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { complexArgField } } `) } func TestValidate_ArgValuesOfCorrectType_ValidInputObjectValue_PartialObject_OnlyRequired(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { complexArgField(complexArg: { requiredField: true }) } } `) } func TestValidate_ArgValuesOfCorrectType_ValidInputObjectValue_PartialObject_RequiredFieldCanBeFalsey(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { complexArgField(complexArg: { requiredField: false }) } } `) } func TestValidate_ArgValuesOfCorrectType_ValidInputObjectValue_PartialObject_IncludingRequired(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { complexArgField(complexArg: { requiredField: false, intField: 4 }) } } `) } func TestValidate_ArgValuesOfCorrectType_ValidInputObjectValue_FullObject(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { complexArgField(complexArg: { requiredField: true, intField: 4, stringField: "foo", booleanField: false, stringListField: ["one", "two"] }) } } `) } func TestValidate_ArgValuesOfCorrectType_ValidInputObjectValue_FullObject_WithFieldsInDifferentOrder(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { complexArgField(complexArg: { stringListField: ["one", "two"], booleanField: false, requiredField: true, stringField: "foo", intField: 4, }) } } `) } func TestValidate_ArgValuesOfCorrectType_InvalidInputObjectValue_PartialObject_MissingRequired(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { complexArgField(complexArg: { intField: 4 }) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"complexArg\" has invalid value {intField: 4}.\nIn field \"requiredField\": Expected \"Boolean!\", found null.", 4, 41, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidInputObjectValue_PartialObject_InvalidFieldType(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { complexArgField(complexArg: { stringListField: ["one", 2], requiredField: true, }) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"complexArg\" has invalid value {stringListField: [\"one\", 2], requiredField: true}.\nIn field \"stringListField\": In element #1: Expected type \"String\", found 2.", 4, 41, ), }) } func TestValidate_ArgValuesOfCorrectType_InvalidInputObjectValue_PartialObject_UnknownFieldArg(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { complicatedArgs { complexArgField(complexArg: { requiredField: true, unknownField: "value" }) } } `, []gqlerrors.FormattedError{ testutil.RuleError( "Argument \"complexArg\" has invalid value {requiredField: true, unknownField: \"value\"}.\nIn field \"unknownField\": Unknown field.", 4, 41, ), }) } func TestValidate_ArgValuesOfCorrectType_DirectiveArguments_WithDirectivesOfValidType(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { dog @include(if: true) { name } human @skip(if: false) { name } } `) } func TestValidate_ArgValuesOfCorrectType_DirectiveArguments_WithDirectivesWithIncorrectTypes(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ArgumentsOfCorrectTypeRule, ` { dog @include(if: "yes") { name @skip(if: ENUM) } } `, []gqlerrors.FormattedError{ testutil.RuleError( `Argument "if" has invalid value "yes".`+ "\nExpected type \"Boolean\", found \"yes\".", 3, 28, ), testutil.RuleError( `Argument "if" has invalid value ENUM.`+ "\nExpected type \"Boolean\", found ENUM.", 4, 28, ), }) } ================================================ FILE: rules_default_values_of_correct_type_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_VariableDefaultValuesOfCorrectType_VariablesWithNoDefaultValues(t *testing.T) { testutil.ExpectPassesRule(t, graphql.DefaultValuesOfCorrectTypeRule, ` query NullableValues($a: Int, $b: String, $c: ComplexInput) { dog { name } } `) } func TestValidate_VariableDefaultValuesOfCorrectType_RequiredVariablesWithoutDefaultValues(t *testing.T) { testutil.ExpectPassesRule(t, graphql.DefaultValuesOfCorrectTypeRule, ` query RequiredValues($a: Int!, $b: String!) { dog { name } } `) } func TestValidate_VariableDefaultValuesOfCorrectType_VariablesWithValidDefaultValues(t *testing.T) { testutil.ExpectPassesRule(t, graphql.DefaultValuesOfCorrectTypeRule, ` query WithDefaultValues( $a: Int = 1, $b: String = "ok", $c: ComplexInput = { requiredField: true, intField: 3 } ) { dog { name } } `) } func TestValidate_VariableDefaultValuesOfCorrectType_NoRequiredVariablesWithDefaultValues(t *testing.T) { testutil.ExpectFailsRule(t, graphql.DefaultValuesOfCorrectTypeRule, ` query UnreachableDefaultValues($a: Int! = 3, $b: String! = "default") { dog { name } } `, []gqlerrors.FormattedError{ testutil.RuleError( `Variable "$a" of type "Int!" is required and will not `+ `use the default value. Perhaps you meant to use type "Int".`, 2, 49, ), testutil.RuleError( `Variable "$b" of type "String!" is required and will not `+ `use the default value. Perhaps you meant to use type "String".`, 2, 66, ), }) } func TestValidate_VariableDefaultValuesOfCorrectType_VariablesWithInvalidDefaultValues(t *testing.T) { testutil.ExpectFailsRule(t, graphql.DefaultValuesOfCorrectTypeRule, ` query InvalidDefaultValues( $a: Int = "one", $b: String = 4, $c: ComplexInput = "notverycomplex" ) { dog { name } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$a" has invalid default value: "one".`+ "\nExpected type \"Int\", found \"one\".", 3, 19), testutil.RuleError(`Variable "$b" has invalid default value: 4.`+ "\nExpected type \"String\", found 4.", 4, 22), testutil.RuleError( `Variable "$c" has invalid default value: "notverycomplex".`+ "\nExpected \"ComplexInput\", found not an object.", 5, 28), }) } func TestValidate_VariableDefaultValuesOfCorrectType_ComplexVariablesMissingRequiredField(t *testing.T) { testutil.ExpectFailsRule(t, graphql.DefaultValuesOfCorrectTypeRule, ` query MissingRequiredField($a: ComplexInput = {intField: 3}) { dog { name } } `, []gqlerrors.FormattedError{ testutil.RuleError( `Variable "$a" has invalid default value: {intField: 3}.`+ "\nIn field \"requiredField\": Expected \"Boolean!\", found null.", 2, 53), }) } func TestValidate_VariableDefaultValuesOfCorrectType_ListVariablesWithInvalidItem(t *testing.T) { testutil.ExpectFailsRule(t, graphql.DefaultValuesOfCorrectTypeRule, ` query InvalidItem($a: [String] = ["one", 2]) { dog { name } } `, []gqlerrors.FormattedError{ testutil.RuleError( `Variable "$a" has invalid default value: ["one", 2].`+ "\nIn element #1: Expected type \"String\", found 2.", 2, 40), }) } func TestValidate_VariableDefaultValuesOfCorrectType_InvalidNonNull(t *testing.T) { testutil.ExpectPassesRule(t, graphql.DefaultValuesOfCorrectTypeRule, `query($g:e!){a}`) } ================================================ FILE: rules_fields_on_correct_type_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_FieldsOnCorrectType_ObjectFieldSelection(t *testing.T) { testutil.ExpectPassesRule(t, graphql.FieldsOnCorrectTypeRule, ` fragment objectFieldSelection on Dog { __typename name } `) } func TestValidate_FieldsOnCorrectType_AliasedObjectFieldSelection(t *testing.T) { testutil.ExpectPassesRule(t, graphql.FieldsOnCorrectTypeRule, ` fragment aliasedObjectFieldSelection on Dog { tn : __typename otherName : name } `) } func TestValidate_FieldsOnCorrectType_InterfaceFieldSelection(t *testing.T) { testutil.ExpectPassesRule(t, graphql.FieldsOnCorrectTypeRule, ` fragment interfaceFieldSelection on Pet { __typename name } `) } func TestValidate_FieldsOnCorrectType_AliasedInterfaceFieldSelection(t *testing.T) { testutil.ExpectPassesRule(t, graphql.FieldsOnCorrectTypeRule, ` fragment interfaceFieldSelection on Pet { otherName : name } `) } func TestValidate_FieldsOnCorrectType_LyingAliasSelection(t *testing.T) { testutil.ExpectPassesRule(t, graphql.FieldsOnCorrectTypeRule, ` fragment lyingAliasSelection on Dog { name : nickname } `) } func TestValidate_FieldsOnCorrectType_IgnoresFieldsOnUnknownType(t *testing.T) { testutil.ExpectPassesRule(t, graphql.FieldsOnCorrectTypeRule, ` fragment unknownSelection on UnknownType { unknownField } `) } func TestValidate_FieldsOnCorrectType_ReportErrorsWhenTheTypeIsKnownAgain(t *testing.T) { testutil.ExpectFailsRule(t, graphql.FieldsOnCorrectTypeRule, ` fragment typeKnownAgain on Pet { unknown_pet_field { ... on Cat { unknown_cat_field } } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Cannot query field "unknown_pet_field" on type "Pet".`, 3, 9), testutil.RuleError(`Cannot query field "unknown_cat_field" on type "Cat".`, 5, 13), }) } func TestValidate_FieldsOnCorrectType_FieldNotDefinedOnFragment(t *testing.T) { testutil.ExpectFailsRule(t, graphql.FieldsOnCorrectTypeRule, ` fragment fieldNotDefined on Dog { meowVolume } `, []gqlerrors.FormattedError{ testutil.RuleError(`Cannot query field "meowVolume" on type "Dog". Did you mean "barkVolume"?`, 3, 9), }) } func TestValidate_FieldsOnCorrectType_IgnoreDeeplyUnknownField(t *testing.T) { testutil.ExpectFailsRule(t, graphql.FieldsOnCorrectTypeRule, ` fragment deepFieldNotDefined on Dog { unknown_field { deeper_unknown_field } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Cannot query field "unknown_field" on type "Dog".`, 3, 9), }) } func TestValidate_FieldsOnCorrectType_SubFieldNotDefined(t *testing.T) { testutil.ExpectFailsRule(t, graphql.FieldsOnCorrectTypeRule, ` fragment subFieldNotDefined on Human { pets { unknown_field } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Cannot query field "unknown_field" on type "Pet".`, 4, 11), }) } func TestValidate_FieldsOnCorrectType_FieldNotDefinedOnInlineFragment(t *testing.T) { testutil.ExpectFailsRule(t, graphql.FieldsOnCorrectTypeRule, ` fragment fieldNotDefined on Pet { ... on Dog { meowVolume } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Cannot query field "meowVolume" on type "Dog". Did you mean "barkVolume"?`, 4, 11), }) } func TestValidate_FieldsOnCorrectType_AliasedFieldTargetNotDefined(t *testing.T) { testutil.ExpectFailsRule(t, graphql.FieldsOnCorrectTypeRule, ` fragment aliasedFieldTargetNotDefined on Dog { volume : mooVolume } `, []gqlerrors.FormattedError{ testutil.RuleError(`Cannot query field "mooVolume" on type "Dog". Did you mean "barkVolume"?`, 3, 9), }) } func TestValidate_FieldsOnCorrectType_AliasedLyingFieldTargetNotDefined(t *testing.T) { testutil.ExpectFailsRule(t, graphql.FieldsOnCorrectTypeRule, ` fragment aliasedLyingFieldTargetNotDefined on Dog { barkVolume : kawVolume } `, []gqlerrors.FormattedError{ testutil.RuleError(`Cannot query field "kawVolume" on type "Dog". Did you mean "barkVolume"?`, 3, 9), }) } func TestValidate_FieldsOnCorrectType_NotDefinedOnInterface(t *testing.T) { testutil.ExpectFailsRule(t, graphql.FieldsOnCorrectTypeRule, ` fragment notDefinedOnInterface on Pet { tailLength } `, []gqlerrors.FormattedError{ testutil.RuleError(`Cannot query field "tailLength" on type "Pet".`, 3, 9), }) } func TestValidate_FieldsOnCorrectType_DefinedOnImplementorsButNotOnInterface(t *testing.T) { testutil.ExpectFailsRule(t, graphql.FieldsOnCorrectTypeRule, ` fragment definedOnImplementorsButNotInterface on Pet { nickname } `, []gqlerrors.FormattedError{ testutil.RuleError(`Cannot query field "nickname" on type "Pet". Did you mean to use an inline fragment on "Cat" or "Dog"?`, 3, 9), }) } func TestValidate_FieldsOnCorrectType_MetaFieldSelectionOnUnion(t *testing.T) { testutil.ExpectPassesRule(t, graphql.FieldsOnCorrectTypeRule, ` fragment directFieldSelectionOnUnion on CatOrDog { __typename } `) } func TestValidate_FieldsOnCorrectType_DirectFieldSelectionOnUnion(t *testing.T) { testutil.ExpectFailsRule(t, graphql.FieldsOnCorrectTypeRule, ` fragment directFieldSelectionOnUnion on CatOrDog { directField } `, []gqlerrors.FormattedError{ testutil.RuleError(`Cannot query field "directField" on type "CatOrDog".`, 3, 9), }) } func TestValidate_FieldsOnCorrectType_DefinedImplementorsQueriedOnUnion(t *testing.T) { testutil.ExpectFailsRule(t, graphql.FieldsOnCorrectTypeRule, ` fragment definedOnImplementorsQueriedOnUnion on CatOrDog { name } `, []gqlerrors.FormattedError{ testutil.RuleError(`Cannot query field "name" on type "CatOrDog". Did you mean to use an inline fragment on "Being", "Pet", "Canine", "Cat", or "Dog"?`, 3, 9), }) } func TestValidate_FieldsOnCorrectType_ValidFieldInInlineFragment(t *testing.T) { testutil.ExpectPassesRule(t, graphql.FieldsOnCorrectTypeRule, ` fragment objectFieldSelection on Pet { ... on Dog { name } ... { name } } `) } func TestValidate_FieldsOnCorrectTypeErrorMessage_WorksWithNoSuggestions(t *testing.T) { message := graphql.UndefinedFieldMessage("f", "T", []string{}, []string{}) expected := `Cannot query field "f" on type "T".` if message != expected { t.Fatalf("Unexpected message, expected: %v, got %v", expected, message) } } func TestValidate_FieldsOnCorrectTypeErrorMessage_WorksWithNoSmallNumbersOfTypeSuggestions(t *testing.T) { message := graphql.UndefinedFieldMessage("f", "T", []string{"A", "B"}, []string{}) expected := `Cannot query field "f" on type "T". ` + `Did you mean to use an inline fragment on "A" or "B"?` if message != expected { t.Fatalf("Unexpected message, expected: %v, got %v", expected, message) } } func TestValidate_FieldsOnCorrectTypeErrorMessage_WorksWithNoSmallNumbersOfFieldSuggestions(t *testing.T) { message := graphql.UndefinedFieldMessage("f", "T", []string{}, []string{"z", "y"}) expected := `Cannot query field "f" on type "T". ` + `Did you mean "z" or "y"?` if message != expected { t.Fatalf("Unexpected message, expected: %v, got %v", expected, message) } } func TestValidate_FieldsOnCorrectTypeErrorMessage_OnlyShowsOneSetOfSuggestionsAtATimePreferringTypes(t *testing.T) { message := graphql.UndefinedFieldMessage("f", "T", []string{"A", "B"}, []string{"z", "y"}) expected := `Cannot query field "f" on type "T". ` + `Did you mean to use an inline fragment on "A" or "B"?` if message != expected { t.Fatalf("Unexpected message, expected: %v, got %v", expected, message) } } func TestValidate_FieldsOnCorrectTypeErrorMessage_LimitLotsOfTypeSuggestions(t *testing.T) { message := graphql.UndefinedFieldMessage("f", "T", []string{"A", "B", "C", "D", "E", "F"}, []string{}) expected := `Cannot query field "f" on type "T". ` + `Did you mean to use an inline fragment on "A", "B", "C", "D", or "E"?` if message != expected { t.Fatalf("Unexpected message, expected: %v, got %v", expected, message) } } func TestValidate_FieldsOnCorrectTypeErrorMessage_LimitLotsOfFieldSuggestions(t *testing.T) { message := graphql.UndefinedFieldMessage( "f", "T", []string{}, []string{"z", "y", "x", "w", "v", "u"}, ) expected := `Cannot query field "f" on type "T". ` + `Did you mean "z", "y", "x", "w", or "v"?` if message != expected { t.Fatalf("Unexpected message, expected: %v, got %v", expected, message) } } func TestValidate_FieldsOnCorrectType_NilCrash(t *testing.T) { testutil.ExpectPassesRule(t, graphql.FieldsOnCorrectTypeRule, `mutation{o}`) } ================================================ FILE: rules_fragments_on_composite_types_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_FragmentsOnCompositeTypes_ObjectIsValidFragmentType(t *testing.T) { testutil.ExpectPassesRule(t, graphql.FragmentsOnCompositeTypesRule, ` fragment validFragment on Dog { barks } `) } func TestValidate_FragmentsOnCompositeTypes_InterfaceIsValidFragmentType(t *testing.T) { testutil.ExpectPassesRule(t, graphql.FragmentsOnCompositeTypesRule, ` fragment validFragment on Pet { name } `) } func TestValidate_FragmentsOnCompositeTypes_ObjectIsValidInlineFragmentType(t *testing.T) { testutil.ExpectPassesRule(t, graphql.FragmentsOnCompositeTypesRule, ` fragment validFragment on Pet { ... on Dog { barks } } `) } func TestValidate_FragmentsOnCompositeTypes_InlineFragmentWithoutTypeIsValid(t *testing.T) { testutil.ExpectPassesRule(t, graphql.FragmentsOnCompositeTypesRule, ` fragment validFragment on Pet { ... { name } } `) } func TestValidate_FragmentsOnCompositeTypes_UnionIsValidFragmentType(t *testing.T) { testutil.ExpectPassesRule(t, graphql.FragmentsOnCompositeTypesRule, ` fragment validFragment on CatOrDog { __typename } `) } func TestValidate_FragmentsOnCompositeTypes_ScalarIsInvalidFragmentType(t *testing.T) { testutil.ExpectFailsRule(t, graphql.FragmentsOnCompositeTypesRule, ` fragment scalarFragment on Boolean { bad } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fragment "scalarFragment" cannot condition on non composite type "Boolean".`, 2, 34), }) } func TestValidate_FragmentsOnCompositeTypes_EnumIsInvalidFragmentType(t *testing.T) { testutil.ExpectFailsRule(t, graphql.FragmentsOnCompositeTypesRule, ` fragment scalarFragment on FurColor { bad } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fragment "scalarFragment" cannot condition on non composite type "FurColor".`, 2, 34), }) } func TestValidate_FragmentsOnCompositeTypes_InputObjectIsInvalidFragmentType(t *testing.T) { testutil.ExpectFailsRule(t, graphql.FragmentsOnCompositeTypesRule, ` fragment inputFragment on ComplexInput { stringField } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fragment "inputFragment" cannot condition on non composite type "ComplexInput".`, 2, 33), }) } func TestValidate_FragmentsOnCompositeTypes_ScalarIsInvalidInlineFragmentType(t *testing.T) { testutil.ExpectFailsRule(t, graphql.FragmentsOnCompositeTypesRule, ` fragment invalidFragment on Pet { ... on String { barks } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fragment cannot condition on non composite type "String".`, 3, 16), }) } ================================================ FILE: rules_known_argument_names_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_KnownArgumentNames_SingleArgIsKnown(t *testing.T) { testutil.ExpectPassesRule(t, graphql.KnownArgumentNamesRule, ` fragment argOnRequiredArg on Dog { doesKnowCommand(dogCommand: SIT) } `) } func TestValidate_KnownArgumentNames_MultipleArgsAreKnown(t *testing.T) { testutil.ExpectPassesRule(t, graphql.KnownArgumentNamesRule, ` fragment multipleArgs on ComplicatedArgs { multipleReqs(req1: 1, req2: 2) } `) } func TestValidate_KnownArgumentNames_IgnoresArgsOfUnknownFields(t *testing.T) { testutil.ExpectPassesRule(t, graphql.KnownArgumentNamesRule, ` fragment argOnUnknownField on Dog { unknownField(unknownArg: SIT) } `) } func TestValidate_KnownArgumentNames_MultipleArgsInReverseOrderAreKnown(t *testing.T) { testutil.ExpectPassesRule(t, graphql.KnownArgumentNamesRule, ` fragment multipleArgsReverseOrder on ComplicatedArgs { multipleReqs(req2: 2, req1: 1) } `) } func TestValidate_KnownArgumentNames_NoArgsOnOptionalArg(t *testing.T) { testutil.ExpectPassesRule(t, graphql.KnownArgumentNamesRule, ` fragment noArgOnOptionalArg on Dog { isHousetrained } `) } func TestValidate_KnownArgumentNames_ArgsAreKnownDeeply(t *testing.T) { testutil.ExpectPassesRule(t, graphql.KnownArgumentNamesRule, ` { dog { doesKnowCommand(dogCommand: SIT) } human { pet { ... on Dog { doesKnowCommand(dogCommand: SIT) } } } } `) } func TestValidate_KnownArgumentNames_DirectiveArgsAreKnown(t *testing.T) { testutil.ExpectPassesRule(t, graphql.KnownArgumentNamesRule, ` { dog @skip(if: true) } `) } func TestValidate_KnownArgumentNames_UndirectiveArgsAreInvalid(t *testing.T) { testutil.ExpectFailsRule(t, graphql.KnownArgumentNamesRule, ` { dog @skip(unless: true) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Unknown argument "unless" on directive "@skip".`, 3, 19), }) } func TestValidate_KnownArgumentNames_UndirectiveArgsAreInvalidWithSuggestion(t *testing.T) { testutil.ExpectFailsRule(t, graphql.KnownArgumentNamesRule, ` { dog @skip(of: true) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Unknown argument "of" on directive "@skip". `+ `Did you mean "if"?`, 3, 19), }) } func TestValidate_KnownArgumentNames_InvalidArgName(t *testing.T) { testutil.ExpectFailsRule(t, graphql.KnownArgumentNamesRule, ` fragment invalidArgName on Dog { doesKnowCommand(unknown: true) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Unknown argument "unknown" on field "doesKnowCommand" of type "Dog".`, 3, 25), }) } func TestValidate_KnownArgumentNames_UnknownArgsAmongstKnownArgs(t *testing.T) { testutil.ExpectFailsRule(t, graphql.KnownArgumentNamesRule, ` fragment oneGoodArgOneInvalidArg on Dog { doesKnowCommand(whoknows: 1, dogCommand: SIT, unknown: true) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Unknown argument "whoknows" on field "doesKnowCommand" of type "Dog".`, 3, 25), testutil.RuleError(`Unknown argument "unknown" on field "doesKnowCommand" of type "Dog".`, 3, 55), }) } func TestValidate_KnownArgumentNames_UnknownArgsAmongstKnownArgsWithSuggestions(t *testing.T) { testutil.ExpectFailsRule(t, graphql.KnownArgumentNamesRule, ` fragment oneGoodArgOneInvalidArg on Dog { doesKnowCommand(ddogCommand: SIT,) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Unknown argument "ddogCommand" on field "doesKnowCommand" of type "Dog". `+ `Did you mean "dogCommand" or "nextDogCommand"?`, 3, 25), }) } func TestValidate_KnownArgumentNames_UnknownArgsDeeply(t *testing.T) { testutil.ExpectFailsRule(t, graphql.KnownArgumentNamesRule, ` { dog { doesKnowCommand(unknown: true) } human { pet { ... on Dog { doesKnowCommand(unknown: true) } } } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Unknown argument "unknown" on field "doesKnowCommand" of type "Dog".`, 4, 27), testutil.RuleError(`Unknown argument "unknown" on field "doesKnowCommand" of type "Dog".`, 9, 31), }) } ================================================ FILE: rules_known_directives_rule_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_KnownDirectives_WithNoDirectives(t *testing.T) { testutil.ExpectPassesRule(t, graphql.KnownDirectivesRule, ` query Foo { name ...Frag } fragment Frag on Dog { name } `) } func TestValidate_KnownDirectives_WithKnownDirective(t *testing.T) { testutil.ExpectPassesRule(t, graphql.KnownDirectivesRule, ` { dog @include(if: true) { name } human @skip(if: false) { name } } `) } func TestValidate_KnownDirectives_WithUnknownDirective(t *testing.T) { testutil.ExpectFailsRule(t, graphql.KnownDirectivesRule, ` { dog @unknown(directive: "value") { name } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Unknown directive "unknown".`, 3, 13), }) } func TestValidate_KnownDirectives_WithManyUnknownDirectives(t *testing.T) { testutil.ExpectFailsRule(t, graphql.KnownDirectivesRule, ` { dog @unknown(directive: "value") { name } human @unknown(directive: "value") { name pets @unknown(directive: "value") { name } } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Unknown directive "unknown".`, 3, 13), testutil.RuleError(`Unknown directive "unknown".`, 6, 15), testutil.RuleError(`Unknown directive "unknown".`, 8, 16), }) } func TestValidate_KnownDirectives_WithWellPlacedDirectives(t *testing.T) { testutil.ExpectPassesRule(t, graphql.KnownDirectivesRule, ` query Foo @onQuery { name @include(if: true) ...Frag @include(if: true) skippedField @skip(if: true) ...SkippedFrag @skip(if: true) } mutation Bar @onMutation { someField } `) } func TestValidate_KnownDirectives_WithMisplacedDirectives(t *testing.T) { testutil.ExpectFailsRule(t, graphql.KnownDirectivesRule, ` query Foo @include(if: true) { name @onQuery ...Frag @onQuery } mutation Bar @onQuery { someField } `, []gqlerrors.FormattedError{ testutil.RuleError(`Directive "include" may not be used on QUERY.`, 2, 17), testutil.RuleError(`Directive "onQuery" may not be used on FIELD.`, 3, 14), testutil.RuleError(`Directive "onQuery" may not be used on FRAGMENT_SPREAD.`, 4, 17), testutil.RuleError(`Directive "onQuery" may not be used on MUTATION.`, 7, 20), }) } func TestValidate_KnownDirectives_WithinSchemaLanguage_WithWellPlacedDirectives(t *testing.T) { testutil.ExpectPassesRule(t, graphql.KnownDirectivesRule, ` type MyObj implements MyInterface @onObject { myField(myArg: Int @onArgumentDefinition): String @onFieldDefinition } scalar MyScalar @onScalar interface MyInterface @onInterface { myField(myArg: Int @onArgumentDefinition): String @onFieldDefinition } union MyUnion @onUnion = MyObj | Other enum MyEnum @onEnum { MY_VALUE @onEnumValue } input MyInput @onInputObject { myField: Int @onInputFieldDefinition } schema @onSchema { query: MyQuery } `) } func TestValidate_KnownDirectives_WithinSchemaLanguage_WithMisplacedDirectives(t *testing.T) { testutil.ExpectFailsRule(t, graphql.KnownDirectivesRule, ` type MyObj implements MyInterface @onInterface { myField(myArg: Int @onInputFieldDefinition): String @onInputFieldDefinition } scalar MyScalar @onEnum interface MyInterface @onObject { myField(myArg: Int @onInputFieldDefinition): String @onInputFieldDefinition } union MyUnion @onEnumValue = MyObj | Other enum MyEnum @onScalar { MY_VALUE @onUnion } input MyInput @onEnum { myField: Int @onArgumentDefinition } schema @onObject { query: MyQuery } `, []gqlerrors.FormattedError{ testutil.RuleError(`Directive "onInterface" may not be used on OBJECT.`, 2, 43), testutil.RuleError(`Directive "onInputFieldDefinition" may not be used on ARGUMENT_DEFINITION.`, 3, 30), testutil.RuleError(`Directive "onInputFieldDefinition" may not be used on FIELD_DEFINITION.`, 3, 63), testutil.RuleError(`Directive "onEnum" may not be used on SCALAR.`, 6, 25), testutil.RuleError(`Directive "onObject" may not be used on INTERFACE.`, 8, 31), testutil.RuleError(`Directive "onInputFieldDefinition" may not be used on ARGUMENT_DEFINITION.`, 9, 30), testutil.RuleError(`Directive "onInputFieldDefinition" may not be used on FIELD_DEFINITION.`, 9, 63), testutil.RuleError(`Directive "onEnumValue" may not be used on UNION.`, 12, 23), testutil.RuleError(`Directive "onScalar" may not be used on ENUM.`, 14, 21), testutil.RuleError(`Directive "onUnion" may not be used on ENUM_VALUE.`, 15, 20), testutil.RuleError(`Directive "onEnum" may not be used on INPUT_OBJECT.`, 18, 23), testutil.RuleError(`Directive "onArgumentDefinition" may not be used on INPUT_FIELD_DEFINITION.`, 19, 24), testutil.RuleError(`Directive "onObject" may not be used on SCHEMA.`, 22, 16), }) } ================================================ FILE: rules_known_fragment_names_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_KnownFragmentNames_KnownFragmentNamesAreValid(t *testing.T) { testutil.ExpectPassesRule(t, graphql.KnownFragmentNamesRule, ` { human(id: 4) { ...HumanFields1 ... on Human { ...HumanFields2 } ... { name } } } fragment HumanFields1 on Human { name ...HumanFields3 } fragment HumanFields2 on Human { name } fragment HumanFields3 on Human { name } `) } func TestValidate_KnownFragmentNames_UnknownFragmentNamesAreInvalid(t *testing.T) { testutil.ExpectFailsRule(t, graphql.KnownFragmentNamesRule, ` { human(id: 4) { ...UnknownFragment1 ... on Human { ...UnknownFragment2 } } } fragment HumanFields on Human { name ...UnknownFragment3 } `, []gqlerrors.FormattedError{ testutil.RuleError(`Unknown fragment "UnknownFragment1".`, 4, 14), testutil.RuleError(`Unknown fragment "UnknownFragment2".`, 6, 16), testutil.RuleError(`Unknown fragment "UnknownFragment3".`, 12, 12), }) } ================================================ FILE: rules_known_type_names_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_KnownTypeNames_KnownTypeNamesAreValid(t *testing.T) { testutil.ExpectPassesRule(t, graphql.KnownTypeNamesRule, ` query Foo($var: String, $required: [String!]!) { user(id: 4) { pets { ... on Pet { name }, ...PetFields, ... { name } } } } fragment PetFields on Pet { name } `) } func TestValidate_KnownTypeNames_UnknownTypeNamesAreInValid(t *testing.T) { testutil.ExpectFailsRule(t, graphql.KnownTypeNamesRule, ` query Foo($var: JumbledUpLetters) { user(id: 4) { name pets { ... on Badger { name }, ...PetFields } } } fragment PetFields on Peettt { name } `, []gqlerrors.FormattedError{ testutil.RuleError(`Unknown type "JumbledUpLetters".`, 2, 23), testutil.RuleError(`Unknown type "Badger".`, 5, 25), testutil.RuleError(`Unknown type "Peettt". Did you mean "Pet"?`, 8, 29), }) } func TestValidate_KnownTypeNames_IgnoresTypeDefinitions(t *testing.T) { testutil.ExpectFailsRule(t, graphql.KnownTypeNamesRule, ` type NotInTheSchema { field: FooBar } interface FooBar { field: NotInTheSchema } union U = A | B input Blob { field: UnknownType } query Foo($var: NotInTheSchema) { user(id: $var) { id } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Unknown type "NotInTheSchema".`, 12, 23), }) } ================================================ FILE: rules_lone_anonymous_operation_rule_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_AnonymousOperationMustBeAlone_NoOperations(t *testing.T) { testutil.ExpectPassesRule(t, graphql.LoneAnonymousOperationRule, ` fragment fragA on Type { field } `) } func TestValidate_AnonymousOperationMustBeAlone_OneAnonOperation(t *testing.T) { testutil.ExpectPassesRule(t, graphql.LoneAnonymousOperationRule, ` { field } `) } func TestValidate_AnonymousOperationMustBeAlone_MultipleNamedOperations(t *testing.T) { testutil.ExpectPassesRule(t, graphql.LoneAnonymousOperationRule, ` query Foo { field } query Bar { field } `) } func TestValidate_AnonymousOperationMustBeAlone_AnonOperationWithFragment(t *testing.T) { testutil.ExpectPassesRule(t, graphql.LoneAnonymousOperationRule, ` { ...Foo } fragment Foo on Type { field } `) } func TestValidate_AnonymousOperationMustBeAlone_MultipleAnonOperations(t *testing.T) { testutil.ExpectFailsRule(t, graphql.LoneAnonymousOperationRule, ` { fieldA } { fieldB } `, []gqlerrors.FormattedError{ testutil.RuleError(`This anonymous operation must be the only defined operation.`, 2, 7), testutil.RuleError(`This anonymous operation must be the only defined operation.`, 5, 7), }) } func TestValidate_AnonymousOperationMustBeAlone_AnonOperationWithAMutation(t *testing.T) { testutil.ExpectFailsRule(t, graphql.LoneAnonymousOperationRule, ` { fieldA } mutation Foo { fieldB } `, []gqlerrors.FormattedError{ testutil.RuleError(`This anonymous operation must be the only defined operation.`, 2, 7), }) } func TestValidate_AnonymousOperationMustBeAlone_AnonOperationWithASubscription(t *testing.T) { testutil.ExpectFailsRule(t, graphql.LoneAnonymousOperationRule, ` { fieldA } mutation Foo { fieldB } `, []gqlerrors.FormattedError{ testutil.RuleError(`This anonymous operation must be the only defined operation.`, 2, 7), }) } ================================================ FILE: rules_no_fragment_cycles_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_NoCircularFragmentSpreads_SingleReferenceIsValid(t *testing.T) { testutil.ExpectPassesRule(t, graphql.NoFragmentCyclesRule, ` fragment fragA on Dog { ...fragB } fragment fragB on Dog { name } `) } func TestValidate_NoCircularFragmentSpreads_SpreadingTwiceIsNotCircular(t *testing.T) { testutil.ExpectPassesRule(t, graphql.NoFragmentCyclesRule, ` fragment fragA on Dog { ...fragB, ...fragB } fragment fragB on Dog { name } `) } func TestValidate_NoCircularFragmentSpreads_SpreadingTwiceIndirectlyIsNotCircular(t *testing.T) { testutil.ExpectPassesRule(t, graphql.NoFragmentCyclesRule, ` fragment fragA on Dog { ...fragB, ...fragC } fragment fragB on Dog { ...fragC } fragment fragC on Dog { name } `) } func TestValidate_NoCircularFragmentSpreads_DoubleSpreadWithinAbstractTypes(t *testing.T) { testutil.ExpectPassesRule(t, graphql.NoFragmentCyclesRule, ` fragment nameFragment on Pet { ... on Dog { name } ... on Cat { name } } fragment spreadsInAnon on Pet { ... on Dog { ...nameFragment } ... on Cat { ...nameFragment } } `) } func TestValidate_NoCircularFragmentSpreads_DoesNotFalsePositiveOnUnknownFragment(t *testing.T) { testutil.ExpectPassesRule(t, graphql.NoFragmentCyclesRule, ` fragment nameFragment on Pet { ...UnknownFragment } `) } func TestValidate_NoCircularFragmentSpreads_SpreadingRecursivelyWithinFieldFails(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoFragmentCyclesRule, ` fragment fragA on Human { relatives { ...fragA } }, `, []gqlerrors.FormattedError{ testutil.RuleError(`Cannot spread fragment "fragA" within itself.`, 2, 45), }) } func TestValidate_NoCircularFragmentSpreads_NoSpreadingItselfDirectly(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoFragmentCyclesRule, ` fragment fragA on Dog { ...fragA } `, []gqlerrors.FormattedError{ testutil.RuleError(`Cannot spread fragment "fragA" within itself.`, 2, 31), }) } func TestValidate_NoCircularFragmentSpreads_NoSpreadingItselfDirectlyWithinInlineFragment(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoFragmentCyclesRule, ` fragment fragA on Pet { ... on Dog { ...fragA } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Cannot spread fragment "fragA" within itself.`, 4, 11), }) } func TestValidate_NoCircularFragmentSpreads_NoSpreadingItselfIndirectly(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoFragmentCyclesRule, ` fragment fragA on Dog { ...fragB } fragment fragB on Dog { ...fragA } `, []gqlerrors.FormattedError{ testutil.RuleError(`Cannot spread fragment "fragA" within itself via fragB.`, 2, 31, 3, 31), }) } func TestValidate_NoCircularFragmentSpreads_NoSpreadingItselfIndirectlyReportsOppositeOrder(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoFragmentCyclesRule, ` fragment fragB on Dog { ...fragA } fragment fragA on Dog { ...fragB } `, []gqlerrors.FormattedError{ testutil.RuleError(`Cannot spread fragment "fragB" within itself via fragA.`, 2, 31, 3, 31), }) } func TestValidate_NoCircularFragmentSpreads_NoSpreadingItselfIndirectlyWithinInlineFragment(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoFragmentCyclesRule, ` fragment fragA on Pet { ... on Dog { ...fragB } } fragment fragB on Pet { ... on Dog { ...fragA } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Cannot spread fragment "fragA" within itself via fragB.`, 4, 11, 9, 11), }) } func TestValidate_NoCircularFragmentSpreads_NoSpreadingItselfDeeply(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoFragmentCyclesRule, ` fragment fragA on Dog { ...fragB } fragment fragB on Dog { ...fragC } fragment fragC on Dog { ...fragO } fragment fragX on Dog { ...fragY } fragment fragY on Dog { ...fragZ } fragment fragZ on Dog { ...fragO } fragment fragO on Dog { ...fragP } fragment fragP on Dog { ...fragA, ...fragX } `, []gqlerrors.FormattedError{ testutil.RuleError(`Cannot spread fragment "fragA" within itself via fragB, fragC, fragO, fragP.`, 2, 31, 3, 31, 4, 31, 8, 31, 9, 31), testutil.RuleError(`Cannot spread fragment "fragO" within itself via fragP, fragX, fragY, fragZ.`, 8, 31, 9, 41, 5, 31, 6, 31, 7, 31), }) } func TestValidate_NoCircularFragmentSpreads_NoSpreadingItselfDeeplyTwoPaths(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoFragmentCyclesRule, ` fragment fragA on Dog { ...fragB, ...fragC } fragment fragB on Dog { ...fragA } fragment fragC on Dog { ...fragA } `, []gqlerrors.FormattedError{ testutil.RuleError(`Cannot spread fragment "fragA" within itself via fragB.`, 2, 31, 3, 31), testutil.RuleError(`Cannot spread fragment "fragA" within itself via fragC.`, 2, 41, 4, 31), }) } func TestValidate_NoCircularFragmentSpreads_NoSpreadingItselfDeeplyTwoPaths_AltTraverseOrder(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoFragmentCyclesRule, ` fragment fragA on Dog { ...fragC } fragment fragB on Dog { ...fragC } fragment fragC on Dog { ...fragA, ...fragB } `, []gqlerrors.FormattedError{ testutil.RuleError(`Cannot spread fragment "fragA" within itself via fragC.`, 2, 31, 4, 31), testutil.RuleError(`Cannot spread fragment "fragC" within itself via fragB.`, 4, 41, 3, 31), }) } func TestValidate_NoCircularFragmentSpreads_NoSpreadingItselfDeeplyAndImmediately(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoFragmentCyclesRule, ` fragment fragA on Dog { ...fragB } fragment fragB on Dog { ...fragB, ...fragC } fragment fragC on Dog { ...fragA, ...fragB } `, []gqlerrors.FormattedError{ testutil.RuleError(`Cannot spread fragment "fragB" within itself.`, 3, 31), testutil.RuleError(`Cannot spread fragment "fragA" within itself via fragB, fragC.`, 2, 31, 3, 41, 4, 31), testutil.RuleError(`Cannot spread fragment "fragB" within itself via fragC.`, 3, 41, 4, 41), }) } ================================================ FILE: rules_no_undefined_variables_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_NoUndefinedVariables_AllVariablesDefined(t *testing.T) { testutil.ExpectPassesRule(t, graphql.NoUndefinedVariablesRule, ` query Foo($a: String, $b: String, $c: String) { field(a: $a, b: $b, c: $c) } `) } func TestValidate_NoUndefinedVariables_AllVariablesDeeplyDefined(t *testing.T) { testutil.ExpectPassesRule(t, graphql.NoUndefinedVariablesRule, ` query Foo($a: String, $b: String, $c: String) { field(a: $a) { field(b: $b) { field(c: $c) } } } `) } func TestValidate_NoUndefinedVariables_AllVariablesDeeplyDefinedInInlineFragmentsDefined(t *testing.T) { testutil.ExpectPassesRule(t, graphql.NoUndefinedVariablesRule, ` query Foo($a: String, $b: String, $c: String) { ... on Type { field(a: $a) { field(b: $b) { ... on Type { field(c: $c) } } } } } `) } func TestValidate_NoUndefinedVariables_AllVariablesInFragmentsDeeplyDefined(t *testing.T) { testutil.ExpectPassesRule(t, graphql.NoUndefinedVariablesRule, ` query Foo($a: String, $b: String, $c: String) { ...FragA } fragment FragA on Type { field(a: $a) { ...FragB } } fragment FragB on Type { field(b: $b) { ...FragC } } fragment FragC on Type { field(c: $c) } `) } func TestValidate_NoUndefinedVariables_VariablesWithinSingleFragmentDefinedInMultipleOperations(t *testing.T) { testutil.ExpectPassesRule(t, graphql.NoUndefinedVariablesRule, ` query Foo($a: String) { ...FragA } query Bar($a: String) { ...FragA } fragment FragA on Type { field(a: $a) } `) } func TestValidate_NoUndefinedVariables_VariableWithinFragmentsDefinedInOperations(t *testing.T) { testutil.ExpectPassesRule(t, graphql.NoUndefinedVariablesRule, ` query Foo($a: String) { ...FragA } query Bar($b: String) { ...FragB } fragment FragA on Type { field(a: $a) } fragment FragB on Type { field(b: $b) } `) } func TestValidate_NoUndefinedVariables_VariableWithinRecursiveFragmentDefined(t *testing.T) { testutil.ExpectPassesRule(t, graphql.NoUndefinedVariablesRule, ` query Foo($a: String) { ...FragA } fragment FragA on Type { field(a: $a) { ...FragA } } `) } func TestValidate_NoUndefinedVariables_VariableNotDefined(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoUndefinedVariablesRule, ` query Foo($a: String, $b: String, $c: String) { field(a: $a, b: $b, c: $c, d: $d) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$d" is not defined by operation "Foo".`, 3, 39, 2, 7), }) } func TestValidate_NoUndefinedVariables_VariableNotDefinedByUnnamedQuery(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoUndefinedVariablesRule, ` { field(a: $a) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$a" is not defined.`, 3, 18, 2, 7), }) } func TestValidate_NoUndefinedVariables_MultipleVariablesNotDefined(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoUndefinedVariablesRule, ` query Foo($b: String) { field(a: $a, b: $b, c: $c) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$a" is not defined by operation "Foo".`, 3, 18, 2, 7), testutil.RuleError(`Variable "$c" is not defined by operation "Foo".`, 3, 32, 2, 7), }) } func TestValidate_NoUndefinedVariables_VariableInFragmentNotDefinedByUnnamedQuery(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoUndefinedVariablesRule, ` { ...FragA } fragment FragA on Type { field(a: $a) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$a" is not defined.`, 6, 18, 2, 7), }) } func TestValidate_NoUndefinedVariables_VariableInFragmentNotDefinedByOperation(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoUndefinedVariablesRule, ` query Foo($a: String, $b: String) { ...FragA } fragment FragA on Type { field(a: $a) { ...FragB } } fragment FragB on Type { field(b: $b) { ...FragC } } fragment FragC on Type { field(c: $c) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$c" is not defined by operation "Foo".`, 16, 18, 2, 7), }) } func TestValidate_NoUndefinedVariables_MultipleVariablesInFragmentsNotDefined(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoUndefinedVariablesRule, ` query Foo($b: String) { ...FragA } fragment FragA on Type { field(a: $a) { ...FragB } } fragment FragB on Type { field(b: $b) { ...FragC } } fragment FragC on Type { field(c: $c) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$a" is not defined by operation "Foo".`, 6, 18, 2, 7), testutil.RuleError(`Variable "$c" is not defined by operation "Foo".`, 16, 18, 2, 7), }) } func TestValidate_NoUndefinedVariables_SingleVariableInFragmentNotDefinedByMultipleOperations(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoUndefinedVariablesRule, ` query Foo($a: String) { ...FragAB } query Bar($a: String) { ...FragAB } fragment FragAB on Type { field(a: $a, b: $b) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$b" is not defined by operation "Foo".`, 9, 25, 2, 7), testutil.RuleError(`Variable "$b" is not defined by operation "Bar".`, 9, 25, 5, 7), }) } func TestValidate_NoUndefinedVariables_VariablesInFragmentNotDefinedByMultipleOperations(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoUndefinedVariablesRule, ` query Foo($b: String) { ...FragAB } query Bar($a: String) { ...FragAB } fragment FragAB on Type { field(a: $a, b: $b) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$a" is not defined by operation "Foo".`, 9, 18, 2, 7), testutil.RuleError(`Variable "$b" is not defined by operation "Bar".`, 9, 25, 5, 7), }) } func TestValidate_NoUndefinedVariables_VariableInFragmentUsedByOtherOperation(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoUndefinedVariablesRule, ` query Foo($b: String) { ...FragA } query Bar($a: String) { ...FragB } fragment FragA on Type { field(a: $a) } fragment FragB on Type { field(b: $b) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$a" is not defined by operation "Foo".`, 9, 18, 2, 7), testutil.RuleError(`Variable "$b" is not defined by operation "Bar".`, 12, 18, 5, 7), }) } func TestValidate_NoUndefinedVariables_VaMultipleUndefinedVariablesProduceMultipleErrors(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoUndefinedVariablesRule, ` query Foo($b: String) { ...FragAB } query Bar($a: String) { ...FragAB } fragment FragAB on Type { field1(a: $a, b: $b) ...FragC field3(a: $a, b: $b) } fragment FragC on Type { field2(c: $c) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$a" is not defined by operation "Foo".`, 9, 19, 2, 7), testutil.RuleError(`Variable "$c" is not defined by operation "Foo".`, 14, 19, 2, 7), testutil.RuleError(`Variable "$a" is not defined by operation "Foo".`, 11, 19, 2, 7), testutil.RuleError(`Variable "$b" is not defined by operation "Bar".`, 9, 26, 5, 7), testutil.RuleError(`Variable "$c" is not defined by operation "Bar".`, 14, 19, 5, 7), testutil.RuleError(`Variable "$b" is not defined by operation "Bar".`, 11, 26, 5, 7), }) } ================================================ FILE: rules_no_unused_fragments_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_NoUnusedFragments_AllFragmentNamesAreUsed(t *testing.T) { testutil.ExpectPassesRule(t, graphql.NoUnusedFragmentsRule, ` { human(id: 4) { ...HumanFields1 ... on Human { ...HumanFields2 } } } fragment HumanFields1 on Human { name ...HumanFields3 } fragment HumanFields2 on Human { name } fragment HumanFields3 on Human { name } `) } func TestValidate_NoUnusedFragments_AllFragmentNamesAreUsedByMultipleOperations(t *testing.T) { testutil.ExpectPassesRule(t, graphql.NoUnusedFragmentsRule, ` query Foo { human(id: 4) { ...HumanFields1 } } query Bar { human(id: 4) { ...HumanFields2 } } fragment HumanFields1 on Human { name ...HumanFields3 } fragment HumanFields2 on Human { name } fragment HumanFields3 on Human { name } `) } func TestValidate_NoUnusedFragments_ContainsUnknownFragments(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoUnusedFragmentsRule, ` query Foo { human(id: 4) { ...HumanFields1 } } query Bar { human(id: 4) { ...HumanFields2 } } fragment HumanFields1 on Human { name ...HumanFields3 } fragment HumanFields2 on Human { name } fragment HumanFields3 on Human { name } fragment Unused1 on Human { name } fragment Unused2 on Human { name } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fragment "Unused1" is never used.`, 22, 7), testutil.RuleError(`Fragment "Unused2" is never used.`, 25, 7), }) } func TestValidate_NoUnusedFragments_ContainsUnknownFragmentsWithRefCycle(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoUnusedFragmentsRule, ` query Foo { human(id: 4) { ...HumanFields1 } } query Bar { human(id: 4) { ...HumanFields2 } } fragment HumanFields1 on Human { name ...HumanFields3 } fragment HumanFields2 on Human { name } fragment HumanFields3 on Human { name } fragment Unused1 on Human { name ...Unused2 } fragment Unused2 on Human { name ...Unused1 } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fragment "Unused1" is never used.`, 22, 7), testutil.RuleError(`Fragment "Unused2" is never used.`, 26, 7), }) } func TestValidate_NoUnusedFragments_ContainsUnknownAndUndefFragments(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoUnusedFragmentsRule, ` query Foo { human(id: 4) { ...bar } } fragment foo on Human { name } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fragment "foo" is never used.`, 7, 7), }) } ================================================ FILE: rules_no_unused_variables_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_NoUnusedVariables_UsesAllVariables(t *testing.T) { testutil.ExpectPassesRule(t, graphql.NoUnusedVariablesRule, ` query ($a: String, $b: String, $c: String) { field(a: $a, b: $b, c: $c) } `) } func TestValidate_NoUnusedVariables_UsesAllVariablesDeeply(t *testing.T) { testutil.ExpectPassesRule(t, graphql.NoUnusedVariablesRule, ` query Foo($a: String, $b: String, $c: String) { field(a: $a) { field(b: $b) { field(c: $c) } } } `) } func TestValidate_NoUnusedVariables_UsesAllVariablesDeeplyInInlineFragments(t *testing.T) { testutil.ExpectPassesRule(t, graphql.NoUnusedVariablesRule, ` query Foo($a: String, $b: String, $c: String) { ... on Type { field(a: $a) { field(b: $b) { ... on Type { field(c: $c) } } } } } `) } func TestValidate_NoUnusedVariables_UsesAllVariablesInFragments(t *testing.T) { testutil.ExpectPassesRule(t, graphql.NoUnusedVariablesRule, ` query Foo($a: String, $b: String, $c: String) { ...FragA } fragment FragA on Type { field(a: $a) { ...FragB } } fragment FragB on Type { field(b: $b) { ...FragC } } fragment FragC on Type { field(c: $c) } `) } func TestValidate_NoUnusedVariables_VariableUsedByFragmentInMultipleOperations(t *testing.T) { testutil.ExpectPassesRule(t, graphql.NoUnusedVariablesRule, ` query Foo($a: String) { ...FragA } query Bar($b: String) { ...FragB } fragment FragA on Type { field(a: $a) } fragment FragB on Type { field(b: $b) } `) } func TestValidate_NoUnusedVariables_VariableUsedByRecursiveFragment(t *testing.T) { testutil.ExpectPassesRule(t, graphql.NoUnusedVariablesRule, ` query Foo($a: String) { ...FragA } fragment FragA on Type { field(a: $a) { ...FragA } } `) } func TestValidate_NoUnusedVariables_VariableNotUsed(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoUnusedVariablesRule, ` query ($a: String, $b: String, $c: String) { field(a: $a, b: $b) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$c" is never used.`, 2, 38), }) } func TestValidate_NoUnusedVariables_MultipleVariablesNotUsed(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoUnusedVariablesRule, ` query Foo($a: String, $b: String, $c: String) { field(b: $b) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$a" is never used in operation "Foo".`, 2, 17), testutil.RuleError(`Variable "$c" is never used in operation "Foo".`, 2, 41), }) } func TestValidate_NoUnusedVariables_VariableNotUsedInFragments(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoUnusedVariablesRule, ` query Foo($a: String, $b: String, $c: String) { ...FragA } fragment FragA on Type { field(a: $a) { ...FragB } } fragment FragB on Type { field(b: $b) { ...FragC } } fragment FragC on Type { field } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$c" is never used in operation "Foo".`, 2, 41), }) } func TestValidate_NoUnusedVariables_MultipleVariablesNotUsed2(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoUnusedVariablesRule, ` query Foo($a: String, $b: String, $c: String) { ...FragA } fragment FragA on Type { field { ...FragB } } fragment FragB on Type { field(b: $b) { ...FragC } } fragment FragC on Type { field } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$a" is never used in operation "Foo".`, 2, 17), testutil.RuleError(`Variable "$c" is never used in operation "Foo".`, 2, 41), }) } func TestValidate_NoUnusedVariables_VariableNotUsedByUnreferencedFragment(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoUnusedVariablesRule, ` query Foo($b: String) { ...FragA } fragment FragA on Type { field(a: $a) } fragment FragB on Type { field(b: $b) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$b" is never used in operation "Foo".`, 2, 17), }) } func TestValidate_NoUnusedVariables_VariableNotUsedByFragmentUsedByOtherOperation(t *testing.T) { testutil.ExpectFailsRule(t, graphql.NoUnusedVariablesRule, ` query Foo($b: String) { ...FragA } query Bar($a: String) { ...FragB } fragment FragA on Type { field(a: $a) } fragment FragB on Type { field(b: $b) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$b" is never used in operation "Foo".`, 2, 17), testutil.RuleError(`Variable "$a" is never used in operation "Bar".`, 5, 17), }) } ================================================ FILE: rules_overlapping_fields_can_be_merged.go ================================================ package graphql import ( "fmt" "strings" "github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/kinds" "github.com/graphql-go/graphql/language/printer" "github.com/graphql-go/graphql/language/visitor" ) func fieldsConflictMessage(responseName string, reason conflictReason) string { return fmt.Sprintf(`Fields "%v" conflict because %v. `+ `Use different aliases on the fields to fetch both if this was intentional.`, responseName, fieldsConflictReasonMessage(reason), ) } func fieldsConflictReasonMessage(message interface{}) string { switch reason := message.(type) { case string: return reason case conflictReason: return fieldsConflictReasonMessage(reason.Message) case []conflictReason: messages := []string{} for _, r := range reason { messages = append(messages, fmt.Sprintf( `subfields "%v" conflict because %v`, r.Name, fieldsConflictReasonMessage(r.Message), )) } return strings.Join(messages, " and ") } return "" } // OverlappingFieldsCanBeMergedRule Overlapping fields can be merged // // A selection set is only valid if all fields (including spreading any // fragments) either correspond to distinct response names or can be merged // without ambiguity. func OverlappingFieldsCanBeMergedRule(context *ValidationContext) *ValidationRuleInstance { // A memoization for when two fragments are compared "between" each other for // conflicts. Two fragments may be compared many times, so memoizing this can // dramatically improve the performance of this validator. comparedSet := newPairSet() // A cache for the "field map" and list of fragment names found in any given // selection set. Selection sets may be asked for this information multiple // times, so this improves the performance of this validator. cacheMap := map[*ast.SelectionSet]*fieldsAndFragmentNames{} visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.SelectionSet: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if selectionSet, ok := p.Node.(*ast.SelectionSet); ok && selectionSet != nil { parentType, _ := context.ParentType().(Named) rule := &overlappingFieldsCanBeMergedRule{ context: context, comparedSet: comparedSet, cacheMap: cacheMap, } conflicts := rule.findConflictsWithinSelectionSet(parentType, selectionSet) if len(conflicts) > 0 { for _, c := range conflicts { responseName := c.Reason.Name reason := c.Reason reportError( context, fieldsConflictMessage(responseName, reason), append(c.FieldsLeft, c.FieldsRight...), ) } return visitor.ActionNoChange, nil } } return visitor.ActionNoChange, nil }, }, }, } return &ValidationRuleInstance{ VisitorOpts: visitorOpts, } } /** * Algorithm: * * Conflicts occur when two fields exist in a query which will produce the same * response name, but represent differing values, thus creating a conflict. * The algorithm below finds all conflicts via making a series of comparisons * between fields. In order to compare as few fields as possible, this makes * a series of comparisons "within" sets of fields and "between" sets of fields. * * Given any selection set, a collection produces both a set of fields by * also including all inline fragments, as well as a list of fragments * referenced by fragment spreads. * * A) Each selection set represented in the document first compares "within" its * collected set of fields, finding any conflicts between every pair of * overlapping fields. * Note: This is the *only time* that a the fields "within" a set are compared * to each other. After this only fields "between" sets are compared. * * B) Also, if any fragment is referenced in a selection set, then a * comparison is made "between" the original set of fields and the * referenced fragment. * * C) Also, if multiple fragments are referenced, then comparisons * are made "between" each referenced fragment. * * D) When comparing "between" a set of fields and a referenced fragment, first * a comparison is made between each field in the original set of fields and * each field in the the referenced set of fields. * * E) Also, if any fragment is referenced in the referenced selection set, * then a comparison is made "between" the original set of fields and the * referenced fragment (recursively referring to step D). * * F) When comparing "between" two fragments, first a comparison is made between * each field in the first referenced set of fields and each field in the the * second referenced set of fields. * * G) Also, any fragments referenced by the first must be compared to the * second, and any fragments referenced by the second must be compared to the * first (recursively referring to step F). * * H) When comparing two fields, if both have selection sets, then a comparison * is made "between" both selection sets, first comparing the set of fields in * the first selection set with the set of fields in the second. * * I) Also, if any fragment is referenced in either selection set, then a * comparison is made "between" the other set of fields and the * referenced fragment. * * J) Also, if two fragments are referenced in both selection sets, then a * comparison is made "between" the two fragments. * */ type overlappingFieldsCanBeMergedRule struct { context *ValidationContext // A memoization for when two fragments are compared "between" each other for // conflicts. Two fragments may be compared many times, so memoizing this can // dramatically improve the performance of this validator. comparedSet *pairSet // A cache for the "field map" and list of fragment names found in any given // selection set. Selection sets may be asked for this information multiple // times, so this improves the performance of this validator. cacheMap map[*ast.SelectionSet]*fieldsAndFragmentNames } // Find all conflicts found "within" a selection set, including those found // via spreading in fragments. Called when visiting each SelectionSet in the // GraphQL Document. func (rule *overlappingFieldsCanBeMergedRule) findConflictsWithinSelectionSet(parentType Named, selectionSet *ast.SelectionSet) []conflict { conflicts := []conflict{} fieldsInfo := rule.getFieldsAndFragmentNames(parentType, selectionSet) // (A) Find find all conflicts "within" the fields of this selection set. // Note: this is the *only place* `collectConflictsWithin` is called. conflicts = rule.collectConflictsWithin(conflicts, fieldsInfo) // (B) Then collect conflicts between these fields and those represented by // each spread fragment name found. for i := 0; i < len(fieldsInfo.fragmentNames); i++ { conflicts = rule.collectConflictsBetweenFieldsAndFragment(conflicts, false, fieldsInfo, fieldsInfo.fragmentNames[i]) // (C) Then compare this fragment with all other fragments found in this // selection set to collect conflicts between fragments spread together. // This compares each item in the list of fragment names to every other item // in that same list (except for itself). for k := i + 1; k < len(fieldsInfo.fragmentNames); k++ { conflicts = rule.collectConflictsBetweenFragments(conflicts, false, fieldsInfo.fragmentNames[i], fieldsInfo.fragmentNames[k]) } } return conflicts } // Collect all conflicts found between a set of fields and a fragment reference // including via spreading in any nested fragments. func (rule *overlappingFieldsCanBeMergedRule) collectConflictsBetweenFieldsAndFragment(conflicts []conflict, areMutuallyExclusive bool, fieldsInfo *fieldsAndFragmentNames, fragmentName string) []conflict { fragment := rule.context.Fragment(fragmentName) if fragment == nil { return conflicts } fieldsInfo2 := rule.getReferencedFieldsAndFragmentNames(fragment) // (D) First collect any conflicts between the provided collection of fields // and the collection of fields represented by the given fragment. conflicts = rule.collectConflictsBetween(conflicts, areMutuallyExclusive, fieldsInfo, fieldsInfo2) // (E) Then collect any conflicts between the provided collection of fields // and any fragment names found in the given fragment. for _, fragmentName2 := range fieldsInfo2.fragmentNames { conflicts = rule.collectConflictsBetweenFieldsAndFragment(conflicts, areMutuallyExclusive, fieldsInfo2, fragmentName2) } return conflicts } // Collect all conflicts found between two fragments, including via spreading in // any nested fragments. func (rule *overlappingFieldsCanBeMergedRule) collectConflictsBetweenFragments(conflicts []conflict, areMutuallyExclusive bool, fragmentName1 string, fragmentName2 string) []conflict { fragment1 := rule.context.Fragment(fragmentName1) fragment2 := rule.context.Fragment(fragmentName2) if fragment1 == nil || fragment2 == nil { return conflicts } // No need to compare a fragment to itself. if fragment1 == fragment2 { return conflicts } // Memoize so two fragments are not compared for conflicts more than once. if rule.comparedSet.Has(fragmentName1, fragmentName2, areMutuallyExclusive) { return conflicts } rule.comparedSet.Add(fragmentName1, fragmentName2, areMutuallyExclusive) fieldsInfo1 := rule.getReferencedFieldsAndFragmentNames(fragment1) fieldsInfo2 := rule.getReferencedFieldsAndFragmentNames(fragment2) // (F) First, collect all conflicts between these two collections of fields // (not including any nested fragments). conflicts = rule.collectConflictsBetween(conflicts, areMutuallyExclusive, fieldsInfo1, fieldsInfo2) // (G) Then collect conflicts between the first fragment and any nested // fragments spread in the second fragment. for _, innerFragmentName2 := range fieldsInfo2.fragmentNames { conflicts = rule.collectConflictsBetweenFragments(conflicts, areMutuallyExclusive, fragmentName1, innerFragmentName2) } // (G) Then collect conflicts between the second fragment and any nested // fragments spread in the first fragment. for _, innerFragmentName1 := range fieldsInfo1.fragmentNames { conflicts = rule.collectConflictsBetweenFragments(conflicts, areMutuallyExclusive, innerFragmentName1, fragmentName2) } return conflicts } // Find all conflicts found between two selection sets, including those found // via spreading in fragments. Called when determining if conflicts exist // between the sub-fields of two overlapping fields. func (rule *overlappingFieldsCanBeMergedRule) findConflictsBetweenSubSelectionSets(areMutuallyExclusive bool, parentType1 Named, selectionSet1 *ast.SelectionSet, parentType2 Named, selectionSet2 *ast.SelectionSet) []conflict { conflicts := []conflict{} fieldsInfo1 := rule.getFieldsAndFragmentNames(parentType1, selectionSet1) fieldsInfo2 := rule.getFieldsAndFragmentNames(parentType2, selectionSet2) // (H) First, collect all conflicts between these two collections of field. conflicts = rule.collectConflictsBetween(conflicts, areMutuallyExclusive, fieldsInfo1, fieldsInfo2) // (I) Then collect conflicts between the first collection of fields and // those referenced by each fragment name associated with the second. for _, fragmentName2 := range fieldsInfo2.fragmentNames { conflicts = rule.collectConflictsBetweenFieldsAndFragment(conflicts, areMutuallyExclusive, fieldsInfo1, fragmentName2) } // (I) Then collect conflicts between the second collection of fields and // those referenced by each fragment name associated with the first. for _, fragmentName1 := range fieldsInfo1.fragmentNames { conflicts = rule.collectConflictsBetweenFieldsAndFragment(conflicts, areMutuallyExclusive, fieldsInfo2, fragmentName1) } // (J) Also collect conflicts between any fragment names by the first and // fragment names by the second. This compares each item in the first set of // names to each item in the second set of names. for _, fragmentName1 := range fieldsInfo1.fragmentNames { for _, fragmentName2 := range fieldsInfo2.fragmentNames { conflicts = rule.collectConflictsBetweenFragments(conflicts, areMutuallyExclusive, fragmentName1, fragmentName2) } } return conflicts } // Collect all Conflicts "within" one collection of fields. func (rule *overlappingFieldsCanBeMergedRule) collectConflictsWithin(conflicts []conflict, fieldsInfo *fieldsAndFragmentNames) []conflict { // A field map is a keyed collection, where each key represents a response // name and the value at that key is a list of all fields which provide that // response name. For every response name, if there are multiple fields, they // must be compared to find a potential conflict. for _, responseName := range fieldsInfo.fieldsOrder { fields, ok := fieldsInfo.fieldMap[responseName] if !ok { continue } // This compares every field in the list to every other field in this list // (except to itself). If the list only has one item, nothing needs to // be compared. if len(fields) <= 1 { continue } for i := 0; i < len(fields); i++ { for k := i + 1; k < len(fields); k++ { // within one collection is never mutually exclusive isMutuallyExclusive := false conflict := rule.findConflict(isMutuallyExclusive, responseName, fields[i], fields[k]) if conflict != nil { conflicts = append(conflicts, *conflict) } } } } return conflicts } // Collect all Conflicts between two collections of fields. This is similar to, // but different from the `collectConflictsWithin` function above. This check // assumes that `collectConflictsWithin` has already been called on each // provided collection of fields. This is true because this validator traverses // each individual selection set. func (rule *overlappingFieldsCanBeMergedRule) collectConflictsBetween(conflicts []conflict, parentFieldsAreMutuallyExclusive bool, fieldsInfo1 *fieldsAndFragmentNames, fieldsInfo2 *fieldsAndFragmentNames) []conflict { // A field map is a keyed collection, where each key represents a response // name and the value at that key is a list of all fields which provide that // response name. For any response name which appears in both provided field // maps, each field from the first field map must be compared to every field // in the second field map to find potential conflicts. for _, responseName := range fieldsInfo1.fieldsOrder { fields1, ok1 := fieldsInfo1.fieldMap[responseName] fields2, ok2 := fieldsInfo2.fieldMap[responseName] if !ok1 || !ok2 { continue } for i := 0; i < len(fields1); i++ { for k := 0; k < len(fields2); k++ { conflict := rule.findConflict(parentFieldsAreMutuallyExclusive, responseName, fields1[i], fields2[k]) if conflict != nil { conflicts = append(conflicts, *conflict) } } } } return conflicts } // findConflict Determines if there is a conflict between two particular fields. func (rule *overlappingFieldsCanBeMergedRule) findConflict(parentFieldsAreMutuallyExclusive bool, responseName string, field *fieldDefPair, field2 *fieldDefPair) *conflict { parentType1 := field.ParentType ast1 := field.Field def1 := field.FieldDef parentType2 := field2.ParentType ast2 := field2.Field def2 := field2.FieldDef // If it is known that two fields could not possibly apply at the same // time, due to the parent types, then it is safe to permit them to diverge // in aliased field or arguments used as they will not present any ambiguity // by differing. // It is known that two parent types could never overlap if they are // different Object types. Interface or Union types might overlap - if not // in the current state of the schema, then perhaps in some future version, // thus may not safely diverge. _, isParentType1Object := parentType1.(*Object) _, isParentType2Object := parentType2.(*Object) areMutuallyExclusive := parentFieldsAreMutuallyExclusive || parentType1 != parentType2 && isParentType1Object && isParentType2Object // The return type for each field. var type1 Type var type2 Type if def1 != nil { type1 = def1.Type } if def2 != nil { type2 = def2.Type } if !areMutuallyExclusive { // Two aliases must refer to the same field. name1 := "" name2 := "" if ast1.Name != nil { name1 = ast1.Name.Value } if ast2.Name != nil { name2 = ast2.Name.Value } if name1 != name2 { return &conflict{ Reason: conflictReason{ Name: responseName, Message: fmt.Sprintf(`%v and %v are different fields`, name1, name2), }, FieldsLeft: []ast.Node{ast1}, FieldsRight: []ast.Node{ast2}, } } // Two field calls must have the same arguments. if !sameArguments(ast1.Arguments, ast2.Arguments) { return &conflict{ Reason: conflictReason{ Name: responseName, Message: `they have differing arguments`, }, FieldsLeft: []ast.Node{ast1}, FieldsRight: []ast.Node{ast2}, } } } if type1 != nil && type2 != nil && doTypesConflict(type1, type2) { return &conflict{ Reason: conflictReason{ Name: responseName, Message: fmt.Sprintf(`they return conflicting types %v and %v`, type1, type2), }, FieldsLeft: []ast.Node{ast1}, FieldsRight: []ast.Node{ast2}, } } // Collect and compare sub-fields. Use the same "visited fragment names" list // for both collections so fields in a fragment reference are never // compared to themselves. selectionSet1 := ast1.SelectionSet selectionSet2 := ast2.SelectionSet if selectionSet1 != nil && selectionSet2 != nil { conflicts := rule.findConflictsBetweenSubSelectionSets(areMutuallyExclusive, GetNamed(type1), selectionSet1, GetNamed(type2), selectionSet2) return subfieldConflicts(conflicts, responseName, ast1, ast2) } return nil } // Given a selection set, return the collection of fields (a mapping of response // name to field ASTs and definitions) as well as a list of fragment names // referenced via fragment spreads. func (rule *overlappingFieldsCanBeMergedRule) getFieldsAndFragmentNames(parentType Named, selectionSet *ast.SelectionSet) *fieldsAndFragmentNames { if cached, ok := rule.cacheMap[selectionSet]; ok && cached != nil { return cached } astAndDefs := astAndDefCollection{} fieldsOrder := []string{} fragmentNames := []string{} fragmentNamesMap := map[string]bool{} var collectFieldsAndFragmentNames func(parentType Named, selectionSet *ast.SelectionSet) collectFieldsAndFragmentNames = func(parentType Named, selectionSet *ast.SelectionSet) { for _, selection := range selectionSet.Selections { switch selection := selection.(type) { case *ast.Field: fieldName := "" if selection.Name != nil { fieldName = selection.Name.Value } var fieldDef *FieldDefinition if parentType, ok := parentType.(*Object); ok && parentType != nil { fieldDef, _ = parentType.Fields()[fieldName] } if parentType, ok := parentType.(*Interface); ok && parentType != nil { fieldDef, _ = parentType.Fields()[fieldName] } responseName := fieldName if selection.Alias != nil { responseName = selection.Alias.Value } fieldDefPairs, ok := astAndDefs[responseName] if !ok || fieldDefPairs == nil { fieldDefPairs = []*fieldDefPair{} fieldsOrder = append(fieldsOrder, responseName) } fieldDefPairs = append(fieldDefPairs, &fieldDefPair{ ParentType: parentType, Field: selection, FieldDef: fieldDef, }) astAndDefs[responseName] = fieldDefPairs case *ast.FragmentSpread: fieldName := "" if selection.Name != nil { fieldName = selection.Name.Value } if val, ok := fragmentNamesMap[fieldName]; !ok || !val { fragmentNamesMap[fieldName] = true fragmentNames = append(fragmentNames, fieldName) } case *ast.InlineFragment: typeCondition := selection.TypeCondition inlineFragmentType := parentType if typeCondition != nil { ttype, err := typeFromAST(*(rule.context.Schema()), typeCondition) if err == nil { inlineFragmentType, _ = ttype.(Named) } } collectFieldsAndFragmentNames(inlineFragmentType, selection.SelectionSet) } } } collectFieldsAndFragmentNames(parentType, selectionSet) cached := &fieldsAndFragmentNames{ fieldMap: astAndDefs, fieldsOrder: fieldsOrder, fragmentNames: fragmentNames, } rule.cacheMap[selectionSet] = cached return cached } func (rule *overlappingFieldsCanBeMergedRule) getReferencedFieldsAndFragmentNames(fragment *ast.FragmentDefinition) *fieldsAndFragmentNames { // Short-circuit building a type from the AST if possible. if cached, ok := rule.cacheMap[fragment.SelectionSet]; ok && cached != nil { return cached } fragmentType, err := typeFromAST(*(rule.context.Schema()), fragment.TypeCondition) if err != nil { return nil } return rule.getFieldsAndFragmentNames(fragmentType, fragment.SelectionSet) } type conflictReason struct { Name string Message interface{} // conflictReason || []conflictReason } type conflict struct { Reason conflictReason FieldsLeft []ast.Node FieldsRight []ast.Node } // a.k.a AstAndDef type fieldDefPair struct { ParentType Named Field *ast.Field FieldDef *FieldDefinition } type astAndDefCollection map[string][]*fieldDefPair // cache struct for fields, its order and fragments names type fieldsAndFragmentNames struct { fieldMap astAndDefCollection fieldsOrder []string // stores the order of field names in fieldMap fragmentNames []string } // pairSet A way to keep track of pairs of things when the ordering of the pair does // not matter. We do this by maintaining a sort of double adjacency sets. type pairSet struct { data map[string]map[string]bool } func newPairSet() *pairSet { return &pairSet{ data: map[string]map[string]bool{}, } } func (pair *pairSet) Has(a string, b string, areMutuallyExclusive bool) bool { first, ok := pair.data[a] if !ok || first == nil { return false } res, ok := first[b] if !ok { return false } // areMutuallyExclusive being false is a superset of being true, // hence if we want to know if this PairSet "has" these two with no // exclusivity, we have to ensure it was added as such. if !areMutuallyExclusive { return res == false } return true } func (pair *pairSet) Add(a string, b string, areMutuallyExclusive bool) { pair.data = pairSetAdd(pair.data, a, b, areMutuallyExclusive) pair.data = pairSetAdd(pair.data, b, a, areMutuallyExclusive) } func pairSetAdd(data map[string]map[string]bool, a, b string, areMutuallyExclusive bool) map[string]map[string]bool { set, ok := data[a] if !ok || set == nil { set = map[string]bool{} } set[b] = areMutuallyExclusive data[a] = set return data } func sameArguments(args1 []*ast.Argument, args2 []*ast.Argument) bool { if len(args1) != len(args2) { return false } for _, arg1 := range args1 { arg1Name := "" if arg1.Name != nil { arg1Name = arg1.Name.Value } var foundArgs2 *ast.Argument for _, arg2 := range args2 { arg2Name := "" if arg2.Name != nil { arg2Name = arg2.Name.Value } if arg1Name == arg2Name { foundArgs2 = arg2 break } } if foundArgs2 == nil { return false } if sameValue(arg1.Value, foundArgs2.Value) == false { return false } } return true } func sameValue(value1 ast.Value, value2 ast.Value) bool { if value1 == nil && value2 == nil { return true } val1 := printer.Print(value1) val2 := printer.Print(value2) return val1 == val2 } // Two types conflict if both types could not apply to a value simultaneously. // Composite types are ignored as their individual field types will be compared // later recursively. However List and Non-Null types must match. func doTypesConflict(type1 Output, type2 Output) bool { if type1, ok := type1.(*List); ok { if type2, ok := type2.(*List); ok { return doTypesConflict(type1.OfType, type2.OfType) } return true } if type2, ok := type2.(*List); ok { if type1, ok := type1.(*List); ok { return doTypesConflict(type1.OfType, type2.OfType) } return true } if type1, ok := type1.(*NonNull); ok { if type2, ok := type2.(*NonNull); ok { return doTypesConflict(type1.OfType, type2.OfType) } return true } if type2, ok := type2.(*NonNull); ok { if type1, ok := type1.(*NonNull); ok { return doTypesConflict(type1.OfType, type2.OfType) } return true } if IsLeafType(type1) || IsLeafType(type2) { return type1 != type2 } return false } // subfieldConflicts Given a series of Conflicts which occurred between two sub-fields, generate a single Conflict. func subfieldConflicts(conflicts []conflict, responseName string, ast1 *ast.Field, ast2 *ast.Field) *conflict { if len(conflicts) > 0 { conflictReasons := []conflictReason{} conflictFieldsLeft := []ast.Node{ast1} conflictFieldsRight := []ast.Node{ast2} for _, c := range conflicts { conflictReasons = append(conflictReasons, c.Reason) conflictFieldsLeft = append(conflictFieldsLeft, c.FieldsLeft...) conflictFieldsRight = append(conflictFieldsRight, c.FieldsRight...) } return &conflict{ Reason: conflictReason{ Name: responseName, Message: conflictReasons, }, FieldsLeft: conflictFieldsLeft, FieldsRight: conflictFieldsRight, } } return nil } ================================================ FILE: rules_overlapping_fields_can_be_merged_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_OverlappingFieldsCanBeMerged_UniqueFields(t *testing.T) { testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` fragment uniqueFields on Dog { name nickname } `) } func TestValidate_OverlappingFieldsCanBeMerged_IdenticalFields(t *testing.T) { testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` fragment mergeIdenticalFields on Dog { name name } `) } func TestValidate_OverlappingFieldsCanBeMerged_IdenticalFieldsWithIdenticalArgs(t *testing.T) { testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` fragment mergeIdenticalFieldsWithIdenticalArgs on Dog { doesKnowCommand(dogCommand: SIT) doesKnowCommand(dogCommand: SIT) } `) } func TestValidate_OverlappingFieldsCanBeMerged_IdenticalFieldsWithMultipleIdenticalArgs(t *testing.T) { testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` fragment mergeIdenticalFieldsWithIdenticalArgs on Dog { doesKnowCommand(dogCommand: SIT nextDogCommand: DOWN) doesKnowCommand(dogCommand: SIT nextDogCommand: DOWN) } `) } func TestValidate_OverlappingFieldsCanBeMerged_IdenticalFieldsWithIdenticalDirectives(t *testing.T) { testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` fragment mergeSameFieldsWithSameDirectives on Dog { name @include(if: true) name @include(if: true) } `) } func TestValidate_OverlappingFieldsCanBeMerged_DifferentArgsWithDifferentAliases(t *testing.T) { testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` fragment differentArgsWithDifferentAliases on Dog { knowsSit: doesKnowCommand(dogCommand: SIT) knowsDown: doesKnowCommand(dogCommand: DOWN) } `) } func TestValidate_OverlappingFieldsCanBeMerged_DifferentDirectivesWithDifferentAliases(t *testing.T) { testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` fragment differentDirectivesWithDifferentAliases on Dog { nameIfTrue: name @include(if: true) nameIfFalse: name @include(if: false) } `) } func TestValidate_OverlappingFieldsCanBeMerged_DifferentSkipIncludeDirectivesAccepted(t *testing.T) { // Note: Differing skip/include directives don't create an ambiguous return // value and are acceptable in conditions where differing runtime values // may have the same desired effect of including or skipping a field. testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` fragment differentDirectivesWithDifferentAliases on Dog { name @include(if: true) name @include(if: false) } `) } func TestValidate_OverlappingFieldsCanBeMerged_SameAliasesWithDifferentFieldTargets(t *testing.T) { testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` fragment sameAliasesWithDifferentFieldTargets on Dog { fido: name fido: nickname } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fields "fido" conflict because name and nickname are different fields. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 3, 9, 4, 9), }) } func TestValidate_OverlappingFieldsCanBeMerged_SameAliasesAllowedOnNonOverlappingFields(t *testing.T) { testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` fragment sameAliasesWithDifferentFieldTargets on Pet { ... on Dog { name } ... on Cat { name: nickname } } `) } func TestValidate_OverlappingFieldsCanBeMerged_AliasMaskingDirectFieldAccess(t *testing.T) { testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` fragment aliasMaskingDirectFieldAccess on Dog { name: nickname name } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fields "name" conflict because nickname and name are different fields. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 3, 9, 4, 9), }) } func TestValidate_OverlappingFieldsCanBeMerged_DifferentArgs_SecondAddsAnArgument(t *testing.T) { testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` fragment conflictingArgs on Dog { doesKnowCommand doesKnowCommand(dogCommand: HEEL) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fields "doesKnowCommand" conflict because they have differing arguments. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 3, 9, 4, 9), }) } func TestValidate_OverlappingFieldsCanBeMerged_DifferentArgs_SecondMissingAnArgument(t *testing.T) { testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` fragment conflictingArgs on Dog { doesKnowCommand(dogCommand: SIT) doesKnowCommand } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fields "doesKnowCommand" conflict because they have differing arguments. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 3, 9, 4, 9), }) } func TestValidate_OverlappingFieldsCanBeMerged_ConflictingArgs(t *testing.T) { testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` fragment conflictingArgs on Dog { doesKnowCommand(dogCommand: SIT) doesKnowCommand(dogCommand: HEEL) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fields "doesKnowCommand" conflict because they have differing arguments. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 3, 9, 4, 9), }) } func TestValidate_OverlappingFieldsCanBeMerged_AllowDifferentArgsWhereNoConflictIsPossible(t *testing.T) { // This is valid since no object can be both a "Dog" and a "Cat", thus // these fields can never overlap. testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` fragment conflictingArgs on Pet { ... on Dog { name(surname: true) } ... on Cat { name } } `) } func TestValidate_OverlappingFieldsCanBeMerged_EncountersConflictInFragments(t *testing.T) { testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` { ...A ...B } fragment A on Type { x: a } fragment B on Type { x: b } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fields "x" conflict because a and b are different fields. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 7, 9, 10, 9), }) } func TestValidate_OverlappingFieldsCanBeMerged_ReportsEachConflictOnce(t *testing.T) { testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` { f1 { ...A ...B } f2 { ...B ...A } f3 { ...A ...B x: c } } fragment A on Type { x: a } fragment B on Type { x: b } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fields "x" conflict because a and b are different fields. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 18, 9, 21, 9), testutil.RuleError(`Fields "x" conflict because c and a are different fields. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 14, 11, 18, 9), testutil.RuleError(`Fields "x" conflict because c and b are different fields. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 14, 11, 21, 9), }) } func TestValidate_OverlappingFieldsCanBeMerged_DeepConflict(t *testing.T) { testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` { field { x: a }, field { x: b } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fields "field" conflict because subfields "x" conflict because a and b are different fields. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 3, 9, 4, 11, 6, 9, 7, 11), }) } func TestValidate_OverlappingFieldsCanBeMerged_DeepConflictWithMultipleIssues(t *testing.T) { testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` { field { x: a y: c }, field { x: b y: d } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fields "field" conflict because subfields "x" conflict because a and b are different fields and `+ `subfields "y" conflict because c and d are different fields. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 3, 9, 4, 11, 5, 11, 7, 9, 8, 11, 9, 11), }) } func TestValidate_OverlappingFieldsCanBeMerged_VeryDeepConflict(t *testing.T) { testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` { field { deepField { x: a } }, field { deepField { x: b } } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fields "field" conflict because subfields "deepField" conflict because subfields "x" conflict because `+ `a and b are different fields. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 3, 9, 4, 11, 5, 13, 8, 9, 9, 11, 10, 13), }) } func TestValidate_OverlappingFieldsCanBeMerged_ReportsDeepConflictToNearestCommonAncestor(t *testing.T) { testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` { field { deepField { x: a } deepField { x: b } }, field { deepField { y } } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fields "deepField" conflict because subfields "x" conflict because `+ `a and b are different fields. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 4, 11, 5, 13, 7, 11, 8, 13), }) } func TestValidate_OverlappingFieldsCanBeMerged_ReportsDeepConflictToNearestCommonAncestorInFragments(t *testing.T) { testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` { field { ...F } field { ...F } } fragment F on T { deepField { deeperField { x: a } deeperField { x: b } }, deepField { deeperField { y } } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fields "deeperField" conflict because subfields "x" conflict because `+ `a and b are different fields. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 12, 11, 13, 13, 15, 11, 16, 13), }) } func TestValidate_OverlappingFieldsCanBeMerged_ReportsDeepConflictInNestedFragments(t *testing.T) { testutil.ExpectFailsRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` { field { ...F } field { ...I } } fragment F on T { x: a ...G } fragment G on T { y: c } fragment I on T { y: d ...J } fragment J on T { x: b } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fields "field" conflict because `+ `subfields "x" conflict because a and b are different fields and `+ `subfields "y" conflict because c and d are different fields. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 3, 9, 11, 9, 15, 9, 6, 9, 22, 9, 18, 9), }) } func TestValidate_OverlappingFieldsCanBeMerged_IgnoresUnknownFragments(t *testing.T) { testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, ` { field ...Unknown ...Known } fragment Known on T { field ...OtherUnknown } `) } var someBoxInterface *graphql.Interface var stringBoxObject *graphql.Object var intBoxObject *graphql.Object var schema graphql.Schema func init() { someBoxInterface = graphql.NewInterface(graphql.InterfaceConfig{ Name: "SomeBox", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return stringBoxObject }, Fields: graphql.FieldsThunk(func() graphql.Fields { return graphql.Fields{ "deepBox": &graphql.Field{ Type: someBoxInterface, }, "unrelatedField": &graphql.Field{ Type: graphql.String, }, } }), }) stringBoxObject = graphql.NewObject(graphql.ObjectConfig{ Name: "StringBox", Interfaces: (graphql.InterfacesThunk)(func() []*graphql.Interface { return []*graphql.Interface{someBoxInterface} }), Fields: graphql.FieldsThunk(func() graphql.Fields { return graphql.Fields{ "scalar": &graphql.Field{ Type: graphql.String, }, "deepBox": &graphql.Field{ Type: stringBoxObject, }, "unrelatedField": &graphql.Field{ Type: graphql.String, }, "listStringBox": &graphql.Field{ Type: graphql.NewList(stringBoxObject), }, "stringBox": &graphql.Field{ Type: stringBoxObject, }, "intBox": &graphql.Field{ Type: intBoxObject, }, } }), }) intBoxObject = graphql.NewObject(graphql.ObjectConfig{ Name: "IntBox", Interfaces: (graphql.InterfacesThunk)(func() []*graphql.Interface { return []*graphql.Interface{someBoxInterface} }), Fields: graphql.FieldsThunk(func() graphql.Fields { return graphql.Fields{ "scalar": &graphql.Field{ Type: graphql.Int, }, "deepBox": &graphql.Field{ Type: someBoxInterface, }, "unrelatedField": &graphql.Field{ Type: graphql.String, }, "listStringBox": &graphql.Field{ Type: graphql.NewList(stringBoxObject), }, "stringBox": &graphql.Field{ Type: stringBoxObject, }, "intBox": &graphql.Field{ Type: intBoxObject, }, } }), }) var nonNullStringBox1Interface = graphql.NewInterface(graphql.InterfaceConfig{ Name: "NonNullStringBox1", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return stringBoxObject }, Fields: graphql.Fields{ "scalar": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), }, }, }) NonNullStringBox1Impl := graphql.NewObject(graphql.ObjectConfig{ Name: "NonNullStringBox1Impl", Interfaces: (graphql.InterfacesThunk)(func() []*graphql.Interface { return []*graphql.Interface{someBoxInterface, nonNullStringBox1Interface} }), Fields: graphql.Fields{ "scalar": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), }, "unrelatedField": &graphql.Field{ Type: graphql.String, }, "deepBox": &graphql.Field{ Type: someBoxInterface, }, }, }) var nonNullStringBox2Interface = graphql.NewInterface(graphql.InterfaceConfig{ Name: "NonNullStringBox2", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return stringBoxObject }, Fields: graphql.Fields{ "scalar": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), }, }, }) NonNullStringBox2Impl := graphql.NewObject(graphql.ObjectConfig{ Name: "NonNullStringBox2Impl", Interfaces: (graphql.InterfacesThunk)(func() []*graphql.Interface { return []*graphql.Interface{someBoxInterface, nonNullStringBox2Interface} }), Fields: graphql.Fields{ "scalar": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), }, "unrelatedField": &graphql.Field{ Type: graphql.String, }, "deepBox": &graphql.Field{ Type: someBoxInterface, }, }, }) var connectionObject = graphql.NewObject(graphql.ObjectConfig{ Name: "Connection", Fields: graphql.Fields{ "edges": &graphql.Field{ Type: graphql.NewList(graphql.NewObject(graphql.ObjectConfig{ Name: "Edge", Fields: graphql.Fields{ "node": &graphql.Field{ Type: graphql.NewObject(graphql.ObjectConfig{ Name: "Node", Fields: graphql.Fields{ "id": &graphql.Field{ Type: graphql.ID, }, "name": &graphql.Field{ Type: graphql.String, }, }, }), }, }, })), }, }, }) var err error schema, err = graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "QueryRoot", Fields: graphql.Fields{ "someBox": &graphql.Field{ Type: someBoxInterface, }, "connection": &graphql.Field{ Type: connectionObject, }, }, }), Types: []graphql.Type{ intBoxObject, stringBoxObject, NonNullStringBox1Impl, NonNullStringBox2Impl, }, }) if err != nil { panic(err) } } func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_ConflictingReturnTypesWhichPotentiallyOverlap(t *testing.T) { // This is invalid since an object could potentially be both the Object // type IntBox and the interface type NonNullStringBox1. While that // condition does not exist in the current schema, the schema could // expand in the future to allow this. Thus it is invalid. testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, ` { someBox { ...on IntBox { scalar } ...on NonNullStringBox1 { scalar } } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fields "scalar" conflict because they return conflicting types Int and String!. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 5, 15, 8, 15), }) } func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_CompatibleReturnShapesOnDifferentReturnTypes(t *testing.T) { // In this case `deepBox` returns `SomeBox` in the first usage, and // `StringBox` in the second usage. These return types are not the same! // however this is valid because the return *shapes* are compatible. testutil.ExpectPassesRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, ` { someBox { ... on SomeBox { deepBox { unrelatedField } } ... on StringBox { deepBox { unrelatedField } } } } `) } func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_DisallowsDifferingReturnTypesDespiteNoOverlap(t *testing.T) { testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, ` { someBox { ... on IntBox { scalar } ... on StringBox { scalar } } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fields "scalar" conflict because they return conflicting types Int and String. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 5, 15, 8, 15), }) } func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_ReportsCorrectlyWhenANonExclusiveFollosAnExclusive(t *testing.T) { testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, ` { someBox { ... on IntBox { deepBox { ...X } } } someBox { ... on StringBox { deepBox { ...Y } } } memoed: someBox { ... on IntBox { deepBox { ...X } } } memoed: someBox { ... on StringBox { deepBox { ...Y } } } other: someBox { ...X } other: someBox { ...Y } } fragment X on SomeBox { scalar } fragment Y on SomeBox { scalar: unrelatedField } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fields "other" conflict because subfields "scalar" conflict `+ `because scalar and unrelatedField are different fields. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 31, 11, 39, 11, 34, 11, 42, 11), }) } func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_DisallowsDifferingReturnTypeNullabilityDespiteNoOverlap(t *testing.T) { testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, ` { someBox { ... on NonNullStringBox1 { scalar } ... on StringBox { scalar } } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fields "scalar" conflict because they return conflicting types String! and String. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 5, 15, 8, 15), }) } func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_DisallowsDifferingReturnTypeListDespiteNoOverlap(t *testing.T) { testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, ` { someBox { ... on IntBox { box: listStringBox { scalar } } ... on StringBox { box: stringBox { scalar } } } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fields "box" conflict because they return conflicting types [StringBox] and StringBox. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 5, 15, 10, 15), }) testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, ` { someBox { ... on IntBox { box: stringBox { scalar } } ... on StringBox { box: listStringBox { scalar } } } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fields "box" conflict because they return conflicting types StringBox and [StringBox]. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 5, 15, 10, 15), }) } func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_DisallowsDifferingSubfields(t *testing.T) { testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, ` { someBox { ... on IntBox { box: stringBox { val: scalar val: unrelatedField } } ... on StringBox { box: stringBox { val: scalar } } } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fields "val" conflict because scalar and unrelatedField are different fields. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 6, 17, 7, 17), }) } func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_DisallowsDifferingDeepReturnTypesDespiteNoOverlap(t *testing.T) { testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, ` { someBox { ... on IntBox { box: stringBox { scalar } } ... on StringBox { box: intBox { scalar } } } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fields "box" conflict because subfields "scalar" conflict because they return conflicting types String and Int. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 5, 15, 6, 17, 10, 15, 11, 17), }) } func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_AllowsNonConflictingOverlappingTypes(t *testing.T) { testutil.ExpectPassesRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, ` { someBox { ... on IntBox { scalar: unrelatedField } ... on StringBox { scalar } } } `) } func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_SameWrappedScalarReturnTypes(t *testing.T) { testutil.ExpectPassesRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, ` { someBox { ...on NonNullStringBox1 { scalar } ...on NonNullStringBox2 { scalar } } } `) } func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_AllowsInlineTypelessFragments(t *testing.T) { testutil.ExpectPassesRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, ` { a ... { a } } `) } func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_ComparesDeepTypesIncludingList(t *testing.T) { testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, ` { connection { ...edgeID edges { node { id: name } } } } fragment edgeID on Connection { edges { node { id } } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fields "edges" conflict because subfields "node" conflict because subfields "id" conflict because `+ `name and id are different fields. `+ `Use different aliases on the fields to fetch both if this was intentional.`, 5, 13, 6, 15, 7, 17, 14, 11, 15, 13, 16, 15), }) } func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_IgnoresUnknownTypes(t *testing.T) { testutil.ExpectPassesRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, ` { someBox { ...on UnknownType { scalar } ...on NonNullStringBox2 { scalar } } } `) } func TestValidate_OverlappingFieldsCanBeMerged_NilCrash(t *testing.T) { testutil.ExpectPassesRule(t, graphql.OverlappingFieldsCanBeMergedRule, `subscription {e}`) } ================================================ FILE: rules_possible_fragment_spreads_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_PossibleFragmentSpreads_OfTheSameObject(t *testing.T) { testutil.ExpectPassesRule(t, graphql.PossibleFragmentSpreadsRule, ` fragment objectWithinObject on Dog { ...dogFragment } fragment dogFragment on Dog { barkVolume } `) } func TestValidate_PossibleFragmentSpreads_OfTheSameObjectWithInlineFragment(t *testing.T) { testutil.ExpectPassesRule(t, graphql.PossibleFragmentSpreadsRule, ` fragment objectWithinObjectAnon on Dog { ... on Dog { barkVolume } } `) } func TestValidate_PossibleFragmentSpreads_ObjectIntoAnImplementedInterface(t *testing.T) { testutil.ExpectPassesRule(t, graphql.PossibleFragmentSpreadsRule, ` fragment objectWithinInterface on Pet { ...dogFragment } fragment dogFragment on Dog { barkVolume } `) } func TestValidate_PossibleFragmentSpreads_ObjectIntoContainingUnion(t *testing.T) { testutil.ExpectPassesRule(t, graphql.PossibleFragmentSpreadsRule, ` fragment objectWithinUnion on CatOrDog { ...dogFragment } fragment dogFragment on Dog { barkVolume } `) } func TestValidate_PossibleFragmentSpreads_UnionIntoContainedObject(t *testing.T) { testutil.ExpectPassesRule(t, graphql.PossibleFragmentSpreadsRule, ` fragment unionWithinObject on Dog { ...catOrDogFragment } fragment catOrDogFragment on CatOrDog { __typename } `) } func TestValidate_PossibleFragmentSpreads_UnionIntoOverlappingInterface(t *testing.T) { testutil.ExpectPassesRule(t, graphql.PossibleFragmentSpreadsRule, ` fragment unionWithinInterface on Pet { ...catOrDogFragment } fragment catOrDogFragment on CatOrDog { __typename } `) } func TestValidate_PossibleFragmentSpreads_UnionIntoOverlappingUnion(t *testing.T) { testutil.ExpectPassesRule(t, graphql.PossibleFragmentSpreadsRule, ` fragment unionWithinUnion on DogOrHuman { ...catOrDogFragment } fragment catOrDogFragment on CatOrDog { __typename } `) } func TestValidate_PossibleFragmentSpreads_InterfaceIntoImplementedObject(t *testing.T) { testutil.ExpectPassesRule(t, graphql.PossibleFragmentSpreadsRule, ` fragment interfaceWithinObject on Dog { ...petFragment } fragment petFragment on Pet { name } `) } func TestValidate_PossibleFragmentSpreads_InterfaceIntoOverlappingInterface(t *testing.T) { testutil.ExpectPassesRule(t, graphql.PossibleFragmentSpreadsRule, ` fragment interfaceWithinInterface on Pet { ...beingFragment } fragment beingFragment on Being { name } `) } func TestValidate_PossibleFragmentSpreads_InterfaceIntoOverlappingInterfaceInInlineFragment(t *testing.T) { testutil.ExpectPassesRule(t, graphql.PossibleFragmentSpreadsRule, ` fragment interfaceWithinInterface on Pet { ... on Being { name } } `) } func TestValidate_PossibleFragmentSpreads_InterfaceIntoOverlappingUnion(t *testing.T) { testutil.ExpectPassesRule(t, graphql.PossibleFragmentSpreadsRule, ` fragment interfaceWithinUnion on CatOrDog { ...petFragment } fragment petFragment on Pet { name } `) } func TestValidate_PossibleFragmentSpreads_DifferentObjectIntoObject(t *testing.T) { testutil.ExpectFailsRule(t, graphql.PossibleFragmentSpreadsRule, ` fragment invalidObjectWithinObject on Cat { ...dogFragment } fragment dogFragment on Dog { barkVolume } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fragment "dogFragment" cannot be spread here as objects of `+ `type "Cat" can never be of type "Dog".`, 2, 51), }) } func TestValidate_PossibleFragmentSpreads_DifferentObjectIntoObjectInInlineFragment(t *testing.T) { testutil.ExpectFailsRule(t, graphql.PossibleFragmentSpreadsRule, ` fragment invalidObjectWithinObjectAnon on Cat { ... on Dog { barkVolume } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fragment cannot be spread here as objects of `+ `type "Cat" can never be of type "Dog".`, 3, 9), }) } func TestValidate_PossibleFragmentSpreads_ObjectIntoNotImplementingInterface(t *testing.T) { testutil.ExpectFailsRule(t, graphql.PossibleFragmentSpreadsRule, ` fragment invalidObjectWithinInterface on Pet { ...humanFragment } fragment humanFragment on Human { pets { name } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fragment "humanFragment" cannot be spread here as objects of `+ `type "Pet" can never be of type "Human".`, 2, 54), }) } func TestValidate_PossibleFragmentSpreads_ObjectIntoNotContainingUnion(t *testing.T) { testutil.ExpectFailsRule(t, graphql.PossibleFragmentSpreadsRule, ` fragment invalidObjectWithinUnion on CatOrDog { ...humanFragment } fragment humanFragment on Human { pets { name } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fragment "humanFragment" cannot be spread here as objects of `+ `type "CatOrDog" can never be of type "Human".`, 2, 55), }) } func TestValidate_PossibleFragmentSpreads_UnionIntoNotContainedObject(t *testing.T) { testutil.ExpectFailsRule(t, graphql.PossibleFragmentSpreadsRule, ` fragment invalidUnionWithinObject on Human { ...catOrDogFragment } fragment catOrDogFragment on CatOrDog { __typename } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fragment "catOrDogFragment" cannot be spread here as objects of `+ `type "Human" can never be of type "CatOrDog".`, 2, 52), }) } func TestValidate_PossibleFragmentSpreads_UnionIntoNonOverlappingInterface(t *testing.T) { testutil.ExpectFailsRule(t, graphql.PossibleFragmentSpreadsRule, ` fragment invalidUnionWithinInterface on Pet { ...humanOrAlienFragment } fragment humanOrAlienFragment on HumanOrAlien { __typename } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fragment "humanOrAlienFragment" cannot be spread here as objects of `+ `type "Pet" can never be of type "HumanOrAlien".`, 2, 53), }) } func TestValidate_PossibleFragmentSpreads_UnionIntoNonOverlappingUnion(t *testing.T) { testutil.ExpectFailsRule(t, graphql.PossibleFragmentSpreadsRule, ` fragment invalidUnionWithinUnion on CatOrDog { ...humanOrAlienFragment } fragment humanOrAlienFragment on HumanOrAlien { __typename } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fragment "humanOrAlienFragment" cannot be spread here as objects of `+ `type "CatOrDog" can never be of type "HumanOrAlien".`, 2, 54), }) } func TestValidate_PossibleFragmentSpreads_InterfaceIntoNonImplementingObject(t *testing.T) { testutil.ExpectFailsRule(t, graphql.PossibleFragmentSpreadsRule, ` fragment invalidInterfaceWithinObject on Cat { ...intelligentFragment } fragment intelligentFragment on Intelligent { iq } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fragment "intelligentFragment" cannot be spread here as objects of `+ `type "Cat" can never be of type "Intelligent".`, 2, 54), }) } func TestValidate_PossibleFragmentSpreads_InterfaceIntoNonOverlappingInterface(t *testing.T) { testutil.ExpectFailsRule(t, graphql.PossibleFragmentSpreadsRule, ` fragment invalidInterfaceWithinInterface on Pet { ...intelligentFragment } fragment intelligentFragment on Intelligent { iq } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fragment "intelligentFragment" cannot be spread here as objects of `+ `type "Pet" can never be of type "Intelligent".`, 3, 9), }) } func TestValidate_PossibleFragmentSpreads_InterfaceIntoNonOverlappingInterfaceInInlineFragment(t *testing.T) { testutil.ExpectFailsRule(t, graphql.PossibleFragmentSpreadsRule, ` fragment invalidInterfaceWithinInterfaceAnon on Pet { ...on Intelligent { iq } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fragment cannot be spread here as objects of `+ `type "Pet" can never be of type "Intelligent".`, 3, 9), }) } func TestValidate_PossibleFragmentSpreads_InterfaceIntoNonOverlappingUnion(t *testing.T) { testutil.ExpectFailsRule(t, graphql.PossibleFragmentSpreadsRule, ` fragment invalidInterfaceWithinUnion on HumanOrAlien { ...petFragment } fragment petFragment on Pet { name } `, []gqlerrors.FormattedError{ testutil.RuleError(`Fragment "petFragment" cannot be spread here as objects of `+ `type "HumanOrAlien" can never be of type "Pet".`, 2, 62), }) } ================================================ FILE: rules_provided_non_null_arguments_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_ProvidedNonNullArguments_IgnoresUnknownArguments(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` { dog { isHousetrained(unknownArgument: true) } } `) } func TestValidate_ProvidedNonNullArguments_ValidNonNullableValue_ArgOnOptionalArg(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` { dog { isHousetrained(atOtherHomes: true) } } `) } func TestValidate_ProvidedNonNullArguments_ValidNonNullableValue_NoArgOnOptionalArg(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` { dog { isHousetrained } } `) } func TestValidate_ProvidedNonNullArguments_ValidNonNullableValue_MultipleArgs(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` { complicatedArgs { multipleReqs(req1: 1, req2: 2) } } `) } func TestValidate_ProvidedNonNullArguments_ValidNonNullableValue_MultipleArgsReverseOrder(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` { complicatedArgs { multipleReqs(req2: 2, req1: 1) } } `) } func TestValidate_ProvidedNonNullArguments_ValidNonNullableValue_NoArgsOnMultipleOptional(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` { complicatedArgs { multipleOpts } } `) } func TestValidate_ProvidedNonNullArguments_ValidNonNullableValue_OneArgOnMultipleOptional(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` { complicatedArgs { multipleOpts(opt1: 1) } } `) } func TestValidate_ProvidedNonNullArguments_ValidNonNullableValue_SecondArgOnMultipleOptional(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` { complicatedArgs { multipleOpts(opt2: 1) } } `) } func TestValidate_ProvidedNonNullArguments_ValidNonNullableValue_MultipleReqsOnMixedList(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` { complicatedArgs { multipleOptAndReq(req1: 3, req2: 4) } } `) } func TestValidate_ProvidedNonNullArguments_ValidNonNullableValue_MultipleReqsAndOneOptOnMixedList(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` { complicatedArgs { multipleOptAndReq(req1: 3, req2: 4, opt1: 5) } } `) } func TestValidate_ProvidedNonNullArguments_ValidNonNullableValue_AllReqsAndOptsOnMixedList(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` { complicatedArgs { multipleOptAndReq(req1: 3, req2: 4, opt1: 5, opt2: 6) } } `) } func TestValidate_ProvidedNonNullArguments_InvalidNonNullableValue_MissingOneNonNullableArgument(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ProvidedNonNullArgumentsRule, ` { complicatedArgs { multipleReqs(req2: 2) } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Field "multipleReqs" argument "req1" of type "Int!" is required but not provided.`, 4, 13), }) } func TestValidate_ProvidedNonNullArguments_InvalidNonNullableValue_MissingMultipleNonNullableArguments(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ProvidedNonNullArgumentsRule, ` { complicatedArgs { multipleReqs } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Field "multipleReqs" argument "req1" of type "Int!" is required but not provided.`, 4, 13), testutil.RuleError(`Field "multipleReqs" argument "req2" of type "Int!" is required but not provided.`, 4, 13), }) } func TestValidate_ProvidedNonNullArguments_InvalidNonNullableValue_IncorrectValueAndMissingArgument(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ProvidedNonNullArgumentsRule, ` { complicatedArgs { multipleReqs(req1: "one") } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Field "multipleReqs" argument "req2" of type "Int!" is required but not provided.`, 4, 13), }) } func TestValidate_ProvidedNonNullArguments_DirectiveArguments_IgnoresUnknownDirectives(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` { dog @unknown } `) } func TestValidate_ProvidedNonNullArguments_DirectiveArguments_WithDirectivesOfValidTypes(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` { dog @include(if: true) { name } human @skip(if: false) { name } } `) } func TestValidate_ProvidedNonNullArguments_DirectiveArguments_WithDirectiveWithMissingTypes(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ProvidedNonNullArgumentsRule, ` { dog @include { name @skip } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Directive "@include" argument "if" of type "Boolean!" is required but not provided.`, 3, 15), testutil.RuleError(`Directive "@skip" argument "if" of type "Boolean!" is required but not provided.`, 4, 18), }) } ================================================ FILE: rules_scalar_leafs_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_ScalarLeafs_ValidScalarSelection(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ScalarLeafsRule, ` fragment scalarSelection on Dog { barks } `) } func TestValidate_ScalarLeafs_ObjectTypeMissingSelection(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ScalarLeafsRule, ` query directQueryOnObjectWithoutSubFields { human } `, []gqlerrors.FormattedError{ testutil.RuleError(`Field "human" of type "Human" must have a sub selection.`, 3, 9), }) } func TestValidate_ScalarLeafs_InterfaceTypeMissingSelection(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ScalarLeafsRule, ` { human { pets } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Field "pets" of type "[Pet]" must have a sub selection.`, 3, 17), }) } func TestValidate_ScalarLeafs_ValidScalarSelectionWithArgs(t *testing.T) { testutil.ExpectPassesRule(t, graphql.ScalarLeafsRule, ` fragment scalarSelectionWithArgs on Dog { doesKnowCommand(dogCommand: SIT) } `) } func TestValidate_ScalarLeafs_ScalarSelectionNotAllowedOnBoolean(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ScalarLeafsRule, ` fragment scalarSelectionsNotAllowedOnBoolean on Dog { barks { sinceWhen } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Field "barks" of type "Boolean" must not have a sub selection.`, 3, 15), }) } func TestValidate_ScalarLeafs_ScalarSelectionNotAllowedOnEnum(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ScalarLeafsRule, ` fragment scalarSelectionsNotAllowedOnEnum on Cat { furColor { inHexdec } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Field "furColor" of type "FurColor" must not have a sub selection.`, 3, 18), }) } func TestValidate_ScalarLeafs_ScalarSelectionNotAllowedWithArgs(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ScalarLeafsRule, ` fragment scalarSelectionsNotAllowedWithArgs on Dog { doesKnowCommand(dogCommand: SIT) { sinceWhen } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Field "doesKnowCommand" of type "Boolean" must not have a sub selection.`, 3, 42), }) } func TestValidate_ScalarLeafs_ScalarSelectionNotAllowedWithDirectives(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ScalarLeafsRule, ` fragment scalarSelectionsNotAllowedWithDirectives on Dog { name @include(if: true) { isAlsoHumanName } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Field "name" of type "String" must not have a sub selection.`, 3, 33), }) } func TestValidate_ScalarLeafs_ScalarSelectionNotAllowedWithDirectivesAndArgs(t *testing.T) { testutil.ExpectFailsRule(t, graphql.ScalarLeafsRule, ` fragment scalarSelectionsNotAllowedWithDirectivesAndArgs on Dog { doesKnowCommand(dogCommand: SIT) @include(if: true) { sinceWhen } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Field "doesKnowCommand" of type "Boolean" must not have a sub selection.`, 3, 61), }) } ================================================ FILE: rules_unique_argument_names_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_UniqueArgumentNames_NoArgumentsOnField(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueArgumentNamesRule, ` { field } `) } func TestValidate_UniqueArgumentNames_NoArgumentsOnDirective(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueArgumentNamesRule, ` { field @directive } `) } func TestValidate_UniqueArgumentNames_ArgumentOnField(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueArgumentNamesRule, ` { field(arg: "value") } `) } func TestValidate_UniqueArgumentNames_ArgumentOnDirective(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueArgumentNamesRule, ` { field @directive(arg: "value") } `) } func TestValidate_UniqueArgumentNames_SameArgumentOnTwoFields(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueArgumentNamesRule, ` { one: field(arg: "value") two: field(arg: "value") } `) } func TestValidate_UniqueArgumentNames_SameArgumentOnFieldAndDirective(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueArgumentNamesRule, ` { field(arg: "value") @directive(arg: "value") } `) } func TestValidate_UniqueArgumentNames_SameArgumentOnTwoDirectives(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueArgumentNamesRule, ` { field @directive1(arg: "value") @directive2(arg: "value") } `) } func TestValidate_UniqueArgumentNames_MultipleFieldArguments(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueArgumentNamesRule, ` { field(arg1: "value", arg2: "value", arg3: "value") } `) } func TestValidate_UniqueArgumentNames_MultipleDirectiveArguments(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueArgumentNamesRule, ` { field @directive(arg1: "value", arg2: "value", arg3: "value") } `) } func TestValidate_UniqueArgumentNames_DuplicateFieldArguments(t *testing.T) { testutil.ExpectFailsRule(t, graphql.UniqueArgumentNamesRule, ` { field(arg1: "value", arg1: "value") } `, []gqlerrors.FormattedError{ testutil.RuleError(`There can be only one argument named "arg1".`, 3, 15, 3, 30), }) } func TestValidate_UniqueArgumentNames_ManyDuplicateFieldArguments(t *testing.T) { testutil.ExpectFailsRule(t, graphql.UniqueArgumentNamesRule, ` { field(arg1: "value", arg1: "value", arg1: "value") } `, []gqlerrors.FormattedError{ testutil.RuleError(`There can be only one argument named "arg1".`, 3, 15, 3, 30), testutil.RuleError(`There can be only one argument named "arg1".`, 3, 15, 3, 45), }) } func TestValidate_UniqueArgumentNames_DuplicateDirectiveArguments(t *testing.T) { testutil.ExpectFailsRule(t, graphql.UniqueArgumentNamesRule, ` { field @directive(arg1: "value", arg1: "value") } `, []gqlerrors.FormattedError{ testutil.RuleError(`There can be only one argument named "arg1".`, 3, 26, 3, 41), }) } func TestValidate_UniqueArgumentNames_ManyDuplicateDirectiveArguments(t *testing.T) { testutil.ExpectFailsRule(t, graphql.UniqueArgumentNamesRule, ` { field @directive(arg1: "value", arg1: "value", arg1: "value") } `, []gqlerrors.FormattedError{ testutil.RuleError(`There can be only one argument named "arg1".`, 3, 26, 3, 41), testutil.RuleError(`There can be only one argument named "arg1".`, 3, 26, 3, 56), }) } ================================================ FILE: rules_unique_fragment_names_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_UniqueFragmentNames_NoFragments(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueFragmentNamesRule, ` { field } `) } func TestValidate_UniqueFragmentNames_OneFragment(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueFragmentNamesRule, ` { ...fragA } fragment fragA on Type { field } `) } func TestValidate_UniqueFragmentNames_ManyFragments(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueFragmentNamesRule, ` { ...fragA ...fragB ...fragC } fragment fragA on Type { fieldA } fragment fragB on Type { fieldB } fragment fragC on Type { fieldC } `) } func TestValidate_UniqueFragmentNames_InlineFragmentsAreAlwaysUnique(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueFragmentNamesRule, ` { ...on Type { fieldA } ...on Type { fieldB } } `) } func TestValidate_UniqueFragmentNames_FragmentAndOperationNamedTheSame(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueFragmentNamesRule, ` query Foo { ...Foo } fragment Foo on Type { field } `) } func TestValidate_UniqueFragmentNames_FragmentsNamedTheSame(t *testing.T) { testutil.ExpectFailsRule(t, graphql.UniqueFragmentNamesRule, ` { ...fragA } fragment fragA on Type { fieldA } fragment fragA on Type { fieldB } `, []gqlerrors.FormattedError{ testutil.RuleError(`There can only be one fragment named "fragA".`, 5, 16, 8, 16), }) } func TestValidate_UniqueFragmentNames_FragmentsNamedTheSameWithoutBeingReferenced(t *testing.T) { testutil.ExpectFailsRule(t, graphql.UniqueFragmentNamesRule, ` fragment fragA on Type { fieldA } fragment fragA on Type { fieldB } `, []gqlerrors.FormattedError{ testutil.RuleError(`There can only be one fragment named "fragA".`, 2, 16, 5, 16), }) } ================================================ FILE: rules_unique_input_field_names_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_UniqueInputFieldNames_InputObjectWithFields(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueInputFieldNamesRule, ` { field(arg: { f: true }) } `) } func TestValidate_UniqueInputFieldNames_SameInputObjectWithinTwoArgs(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueInputFieldNamesRule, ` { field(arg1: { f: true }, arg2: { f: true }) } `) } func TestValidate_UniqueInputFieldNames_MultipleInputObjectFields(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueInputFieldNamesRule, ` { field(arg: { f1: "value", f2: "value", f3: "value" }) } `) } func TestValidate_UniqueInputFieldNames_AllowsForNestedInputObjectsWithSimilarFields(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueInputFieldNamesRule, ` { field(arg: { deep: { deep: { id: 1 } id: 1 } id: 1 }) } `) } func TestValidate_UniqueInputFieldNames_DuplicateInputObjectFields(t *testing.T) { testutil.ExpectFailsRule(t, graphql.UniqueInputFieldNamesRule, ` { field(arg: { f1: "value", f1: "value" }) } `, []gqlerrors.FormattedError{ testutil.RuleError(`There can be only one input field named "f1".`, 3, 22, 3, 35), }) } func TestValidate_UniqueInputFieldNames_ManyDuplicateInputObjectFields(t *testing.T) { testutil.ExpectFailsRule(t, graphql.UniqueInputFieldNamesRule, ` { field(arg: { f1: "value", f1: "value", f1: "value" }) } `, []gqlerrors.FormattedError{ testutil.RuleError(`There can be only one input field named "f1".`, 3, 22, 3, 35), testutil.RuleError(`There can be only one input field named "f1".`, 3, 22, 3, 48), }) } ================================================ FILE: rules_unique_operation_names_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_UniqueOperationNames_NoOperations(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueOperationNamesRule, ` fragment fragA on Type { field } `) } func TestValidate_UniqueOperationNames_OneAnonOperation(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueOperationNamesRule, ` { field } `) } func TestValidate_UniqueOperationNames_OneNamedOperation(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueOperationNamesRule, ` query Foo { field } `) } func TestValidate_UniqueOperationNames_MultipleOperations(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueOperationNamesRule, ` query Foo { field } query Bar { field } `) } func TestValidate_UniqueOperationNames_MultipleOperationsOfDifferentTypes(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueOperationNamesRule, ` query Foo { field } mutation Bar { field } subscription Baz { field } `) } func TestValidate_UniqueOperationNames_FragmentAndOperationNamedTheSame(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueOperationNamesRule, ` query Foo { ...Foo } fragment Foo on Type { field } `) } func TestValidate_UniqueOperationNames_MultipleOperationsOfSameName(t *testing.T) { testutil.ExpectFailsRule(t, graphql.UniqueOperationNamesRule, ` query Foo { fieldA } query Foo { fieldB } `, []gqlerrors.FormattedError{ testutil.RuleError(`There can only be one operation named "Foo".`, 2, 13, 5, 13), }) } func TestValidate_UniqueOperationNames_MultipleOperationsOfSameNameOfDifferentTypes_Mutation(t *testing.T) { testutil.ExpectFailsRule(t, graphql.UniqueOperationNamesRule, ` query Foo { fieldA } mutation Foo { fieldB } `, []gqlerrors.FormattedError{ testutil.RuleError(`There can only be one operation named "Foo".`, 2, 13, 5, 16), }) } func TestValidate_UniqueOperationNames_MultipleOperationsOfSameNameOfDifferentTypes_Subscription(t *testing.T) { testutil.ExpectFailsRule(t, graphql.UniqueOperationNamesRule, ` query Foo { fieldA } subscription Foo { fieldB } `, []gqlerrors.FormattedError{ testutil.RuleError(`There can only be one operation named "Foo".`, 2, 13, 5, 20), }) } func TestValidate_UniqueOperationNames_MultipleAnonymousOperations(t *testing.T) { testutil.ExpectFailsRule(t, graphql.UniqueOperationNamesRule, `{a}{b}`, []gqlerrors.FormattedError{ testutil.RuleError(`There can only be one operation named "".`, 1, 1, 1, 4), }) } ================================================ FILE: rules_unique_variable_names_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_UniqueVariableNames_UniqueVariableNames(t *testing.T) { testutil.ExpectPassesRule(t, graphql.UniqueVariableNamesRule, ` query A($x: Int, $y: String) { __typename } query B($x: String, $y: Int) { __typename } `) } func TestValidate_UniqueVariableNames_DuplicateVariableNames(t *testing.T) { testutil.ExpectFailsRule(t, graphql.UniqueVariableNamesRule, ` query A($x: Int, $x: Int, $x: String) { __typename } query B($x: String, $x: Int) { __typename } query C($x: Int, $x: Int) { __typename } `, []gqlerrors.FormattedError{ testutil.RuleError(`There can only be one variable named "x".`, 2, 16, 2, 25), testutil.RuleError(`There can only be one variable named "x".`, 2, 16, 2, 34), testutil.RuleError(`There can only be one variable named "x".`, 3, 16, 3, 28), testutil.RuleError(`There can only be one variable named "x".`, 4, 16, 4, 25), }) } ================================================ FILE: rules_variables_are_input_types_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_VariablesAreInputTypes_(t *testing.T) { testutil.ExpectPassesRule(t, graphql.VariablesAreInputTypesRule, ` query Foo($a: String, $b: [Boolean!]!, $c: ComplexInput) { field(a: $a, b: $b, c: $c) } `) } func TestValidate_VariablesAreInputTypes_1(t *testing.T) { testutil.ExpectFailsRule(t, graphql.VariablesAreInputTypesRule, ` query Foo($a: Dog, $b: [[CatOrDog!]]!, $c: Pet) { field(a: $a, b: $b, c: $c) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$a" cannot be non-input type "Dog".`, 2, 21), testutil.RuleError(`Variable "$b" cannot be non-input type "[[CatOrDog!]]!".`, 2, 30), testutil.RuleError(`Variable "$c" cannot be non-input type "Pet".`, 2, 50), }) } ================================================ FILE: rules_variables_in_allowed_position_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/testutil" ) func TestValidate_VariablesInAllowedPosition_BooleanToBoolean(t *testing.T) { testutil.ExpectPassesRule(t, graphql.VariablesInAllowedPositionRule, ` query Query($booleanArg: Boolean) { complicatedArgs { booleanArgField(booleanArg: $booleanArg) } } `) } func TestValidate_VariablesInAllowedPosition_BooleanToBooleanWithinFragment(t *testing.T) { testutil.ExpectPassesRule(t, graphql.VariablesInAllowedPositionRule, ` fragment booleanArgFrag on ComplicatedArgs { booleanArgField(booleanArg: $booleanArg) } query Query($booleanArg: Boolean) { complicatedArgs { ...booleanArgFrag } } `) testutil.ExpectPassesRule(t, graphql.VariablesInAllowedPositionRule, ` query Query($booleanArg: Boolean) { complicatedArgs { ...booleanArgFrag } } fragment booleanArgFrag on ComplicatedArgs { booleanArgField(booleanArg: $booleanArg) } `) } func TestValidate_VariablesInAllowedPosition_NonNullableBooleanToBoolean(t *testing.T) { testutil.ExpectPassesRule(t, graphql.VariablesInAllowedPositionRule, ` query Query($nonNullBooleanArg: Boolean!) { complicatedArgs { booleanArgField(booleanArg: $nonNullBooleanArg) } } `) } func TestValidate_VariablesInAllowedPosition_NonNullableBooleanToBooleanWithinFragment(t *testing.T) { testutil.ExpectPassesRule(t, graphql.VariablesInAllowedPositionRule, ` fragment booleanArgFrag on ComplicatedArgs { booleanArgField(booleanArg: $nonNullBooleanArg) } query Query($nonNullBooleanArg: Boolean!) { complicatedArgs { ...booleanArgFrag } } `) } func TestValidate_VariablesInAllowedPosition_IntToNonNullableIntWithDefault(t *testing.T) { testutil.ExpectPassesRule(t, graphql.VariablesInAllowedPositionRule, ` query Query($intArg: Int = 1) { complicatedArgs { nonNullIntArgField(nonNullIntArg: $intArg) } } `) } func TestValidate_VariablesInAllowedPosition_ListOfStringToListOfString(t *testing.T) { testutil.ExpectPassesRule(t, graphql.VariablesInAllowedPositionRule, ` query Query($stringListVar: [String]) { complicatedArgs { stringListArgField(stringListArg: $stringListVar) } } `) } func TestValidate_VariablesInAllowedPosition_ListOfNonNullableStringToListOfString(t *testing.T) { testutil.ExpectPassesRule(t, graphql.VariablesInAllowedPositionRule, ` query Query($stringListVar: [String!]) { complicatedArgs { stringListArgField(stringListArg: $stringListVar) } } `) } func TestValidate_VariablesInAllowedPosition_StringToListOfStringInItemPosition(t *testing.T) { testutil.ExpectPassesRule(t, graphql.VariablesInAllowedPositionRule, ` query Query($stringVar: String) { complicatedArgs { stringListArgField(stringListArg: [$stringVar]) } } `) } func TestValidate_VariablesInAllowedPosition_NonNullableStringToListOfStringInItemPosition(t *testing.T) { testutil.ExpectPassesRule(t, graphql.VariablesInAllowedPositionRule, ` query Query($stringVar: String!) { complicatedArgs { stringListArgField(stringListArg: [$stringVar]) } } `) } func TestValidate_VariablesInAllowedPosition_ComplexInputToComplexInput(t *testing.T) { testutil.ExpectPassesRule(t, graphql.VariablesInAllowedPositionRule, ` query Query($complexVar: ComplexInput) { complicatedArgs { complexArgField(complexArg: $complexVar) } } `) } func TestValidate_VariablesInAllowedPosition_ComplexInputToComplexInputInFieldPosition(t *testing.T) { testutil.ExpectPassesRule(t, graphql.VariablesInAllowedPositionRule, ` query Query($boolVar: Boolean = false) { complicatedArgs { complexArgField(complexArg: {requiredArg: $boolVar}) } } `) } func TestValidate_VariablesInAllowedPosition_NonNullableBooleanToNonNullableBooleanInDirective(t *testing.T) { testutil.ExpectPassesRule(t, graphql.VariablesInAllowedPositionRule, ` query Query($boolVar: Boolean!) { dog @include(if: $boolVar) } `) } func TestValidate_VariablesInAllowedPosition_NonNullableBooleanToNonNullableBooleanInDirectiveInDirectiveWithDefault(t *testing.T) { testutil.ExpectPassesRule(t, graphql.VariablesInAllowedPositionRule, ` query Query($boolVar: Boolean = false) { dog @include(if: $boolVar) } `) } func TestValidate_VariablesInAllowedPosition_IntToNonNullableInt(t *testing.T) { testutil.ExpectFailsRule(t, graphql.VariablesInAllowedPositionRule, ` query Query($intArg: Int) { complicatedArgs { nonNullIntArgField(nonNullIntArg: $intArg) } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$intArg" of type "Int" used in position `+ `expecting type "Int!".`, 2, 19, 4, 45), }) } func TestValidate_VariablesInAllowedPosition_IntToNonNullableIntWithinFragment(t *testing.T) { testutil.ExpectFailsRule(t, graphql.VariablesInAllowedPositionRule, ` fragment nonNullIntArgFieldFrag on ComplicatedArgs { nonNullIntArgField(nonNullIntArg: $intArg) } query Query($intArg: Int) { complicatedArgs { ...nonNullIntArgFieldFrag } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$intArg" of type "Int" used in position `+ `expecting type "Int!".`, 6, 19, 3, 43), }) } func TestValidate_VariablesInAllowedPosition_IntToNonNullableIntWithinNestedFragment(t *testing.T) { testutil.ExpectFailsRule(t, graphql.VariablesInAllowedPositionRule, ` fragment outerFrag on ComplicatedArgs { ...nonNullIntArgFieldFrag } fragment nonNullIntArgFieldFrag on ComplicatedArgs { nonNullIntArgField(nonNullIntArg: $intArg) } query Query($intArg: Int) { complicatedArgs { ...outerFrag } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$intArg" of type "Int" used in position `+ `expecting type "Int!".`, 10, 19, 7, 43), }) } func TestValidate_VariablesInAllowedPosition_StringOverBoolean(t *testing.T) { testutil.ExpectFailsRule(t, graphql.VariablesInAllowedPositionRule, ` query Query($stringVar: String) { complicatedArgs { booleanArgField(booleanArg: $stringVar) } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$stringVar" of type "String" used in position `+ `expecting type "Boolean".`, 2, 19, 4, 39), }) } func TestValidate_VariablesInAllowedPosition_StringToListOfString(t *testing.T) { testutil.ExpectFailsRule(t, graphql.VariablesInAllowedPositionRule, ` query Query($stringVar: String) { complicatedArgs { stringListArgField(stringListArg: $stringVar) } } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$stringVar" of type "String" used in position `+ `expecting type "[String]".`, 2, 19, 4, 45), }) } func TestValidate_VariablesInAllowedPosition_BooleanToNonNullableBooleanInDirective(t *testing.T) { testutil.ExpectFailsRule(t, graphql.VariablesInAllowedPositionRule, ` query Query($boolVar: Boolean) { dog @include(if: $boolVar) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$boolVar" of type "Boolean" used in position `+ `expecting type "Boolean!".`, 2, 19, 3, 26), }) } func TestValidate_VariablesInAllowedPosition_StringToNonNullableBooleanInDirective(t *testing.T) { testutil.ExpectFailsRule(t, graphql.VariablesInAllowedPositionRule, ` query Query($stringVar: String) { dog @include(if: $stringVar) } `, []gqlerrors.FormattedError{ testutil.RuleError(`Variable "$stringVar" of type "String" used in position `+ `expecting type "Boolean!".`, 2, 19, 3, 26), }) } ================================================ FILE: scalars.go ================================================ package graphql import ( "fmt" "math" "strconv" "time" "github.com/graphql-go/graphql/language/ast" ) // As per the GraphQL Spec, Integers are only treated as valid when a valid // 32-bit signed integer, providing the broadest support across platforms. // // n.b. JavaScript's integers are safe between -(2^53 - 1) and 2^53 - 1 because // they are internally represented as IEEE 754 doubles. func coerceInt(value interface{}) interface{} { switch value := value.(type) { case bool: if value == true { return 1 } return 0 case *bool: if value == nil { return nil } return coerceInt(*value) case int: if value < int(math.MinInt32) || value > int(math.MaxInt32) { return nil } return value case *int: if value == nil { return nil } return coerceInt(*value) case int8: return int(value) case *int8: if value == nil { return nil } return int(*value) case int16: return int(value) case *int16: if value == nil { return nil } return int(*value) case int32: return int(value) case *int32: if value == nil { return nil } return int(*value) case int64: if value < int64(math.MinInt32) || value > int64(math.MaxInt32) { return nil } return int(value) case *int64: if value == nil { return nil } return coerceInt(*value) case uint: if value > math.MaxInt32 { return nil } return int(value) case *uint: if value == nil { return nil } return coerceInt(*value) case uint8: return int(value) case *uint8: if value == nil { return nil } return int(*value) case uint16: return int(value) case *uint16: if value == nil { return nil } return int(*value) case uint32: if value > uint32(math.MaxInt32) { return nil } return int(value) case *uint32: if value == nil { return nil } return coerceInt(*value) case uint64: if value > uint64(math.MaxInt32) { return nil } return int(value) case *uint64: if value == nil { return nil } return coerceInt(*value) case float32: if value < float32(math.MinInt32) || value > float32(math.MaxInt32) { return nil } return int(value) case *float32: if value == nil { return nil } return coerceInt(*value) case float64: if value < float64(math.MinInt32) || value > float64(math.MaxInt32) { return nil } return int(value) case *float64: if value == nil { return nil } return coerceInt(*value) case string: val, err := strconv.ParseFloat(value, 0) if err != nil { return nil } return coerceInt(val) case *string: if value == nil { return nil } return coerceInt(*value) } // If the value cannot be transformed into an int, return nil instead of '0' // to denote 'no integer found' return nil } // Int is the GraphQL Integer type definition. var Int = NewScalar(ScalarConfig{ Name: "Int", Description: "The `Int` scalar type represents non-fractional signed whole numeric " + "values. Int can represent values between -(2^31) and 2^31 - 1. ", Serialize: coerceInt, ParseValue: coerceInt, ParseLiteral: func(valueAST ast.Value) interface{} { switch valueAST := valueAST.(type) { case *ast.IntValue: if intValue, err := strconv.Atoi(valueAST.Value); err == nil { return intValue } } return nil }, }) func coerceFloat(value interface{}) interface{} { switch value := value.(type) { case bool: if value == true { return 1.0 } return 0.0 case *bool: if value == nil { return nil } return coerceFloat(*value) case int: return float64(value) case *int: if value == nil { return nil } return coerceFloat(*value) case int8: return float64(value) case *int8: if value == nil { return nil } return coerceFloat(*value) case int16: return float64(value) case *int16: if value == nil { return nil } return coerceFloat(*value) case int32: return float64(value) case *int32: if value == nil { return nil } return coerceFloat(*value) case int64: return float64(value) case *int64: if value == nil { return nil } return coerceFloat(*value) case uint: return float64(value) case *uint: if value == nil { return nil } return coerceFloat(*value) case uint8: return float64(value) case *uint8: if value == nil { return nil } return coerceFloat(*value) case uint16: return float64(value) case *uint16: if value == nil { return nil } return coerceFloat(*value) case uint32: return float64(value) case *uint32: if value == nil { return nil } return coerceFloat(*value) case uint64: return float64(value) case *uint64: if value == nil { return nil } return coerceFloat(*value) case float32: return value case *float32: if value == nil { return nil } return coerceFloat(*value) case float64: return value case *float64: if value == nil { return nil } return coerceFloat(*value) case string: val, err := strconv.ParseFloat(value, 0) if err != nil { return nil } return val case *string: if value == nil { return nil } return coerceFloat(*value) } // If the value cannot be transformed into an float, return nil instead of '0.0' // to denote 'no float found' return nil } // Float is the GraphQL float type definition. var Float = NewScalar(ScalarConfig{ Name: "Float", Description: "The `Float` scalar type represents signed double-precision fractional " + "values as specified by " + "[IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point). ", Serialize: coerceFloat, ParseValue: coerceFloat, ParseLiteral: func(valueAST ast.Value) interface{} { switch valueAST := valueAST.(type) { case *ast.FloatValue: if floatValue, err := strconv.ParseFloat(valueAST.Value, 64); err == nil { return floatValue } case *ast.IntValue: if floatValue, err := strconv.ParseFloat(valueAST.Value, 64); err == nil { return floatValue } } return nil }, }) func coerceString(value interface{}) interface{} { if v, ok := value.(*string); ok { if v == nil { return nil } return *v } return fmt.Sprintf("%v", value) } // String is the GraphQL string type definition var String = NewScalar(ScalarConfig{ Name: "String", Description: "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.", Serialize: coerceString, ParseValue: coerceString, ParseLiteral: func(valueAST ast.Value) interface{} { switch valueAST := valueAST.(type) { case *ast.StringValue: return valueAST.Value } return nil }, }) func coerceBool(value interface{}) interface{} { switch value := value.(type) { case bool: return value case *bool: if value == nil { return nil } return *value case string: switch value { case "", "false": return false } return true case *string: if value == nil { return nil } return coerceBool(*value) case float64: if value != 0 { return true } return false case *float64: if value == nil { return nil } return coerceBool(*value) case float32: if value != 0 { return true } return false case *float32: if value == nil { return nil } return coerceBool(*value) case int: if value != 0 { return true } return false case *int: if value == nil { return nil } return coerceBool(*value) case int8: if value != 0 { return true } return false case *int8: if value == nil { return nil } return coerceBool(*value) case int16: if value != 0 { return true } return false case *int16: if value == nil { return nil } return coerceBool(*value) case int32: if value != 0 { return true } return false case *int32: if value == nil { return nil } return coerceBool(*value) case int64: if value != 0 { return true } return false case *int64: if value == nil { return nil } return coerceBool(*value) case uint: if value != 0 { return true } return false case *uint: if value == nil { return nil } return coerceBool(*value) case uint8: if value != 0 { return true } return false case *uint8: if value == nil { return nil } return coerceBool(*value) case uint16: if value != 0 { return true } return false case *uint16: if value == nil { return nil } return coerceBool(*value) case uint32: if value != 0 { return true } return false case *uint32: if value == nil { return nil } return coerceBool(*value) case uint64: if value != 0 { return true } return false case *uint64: if value == nil { return nil } return coerceBool(*value) } return false } // Boolean is the GraphQL boolean type definition var Boolean = NewScalar(ScalarConfig{ Name: "Boolean", Description: "The `Boolean` scalar type represents `true` or `false`.", Serialize: coerceBool, ParseValue: coerceBool, ParseLiteral: func(valueAST ast.Value) interface{} { switch valueAST := valueAST.(type) { case *ast.BooleanValue: return valueAST.Value } return nil }, }) // ID is the GraphQL id type definition var ID = NewScalar(ScalarConfig{ Name: "ID", Description: "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.", Serialize: coerceString, ParseValue: coerceString, ParseLiteral: func(valueAST ast.Value) interface{} { switch valueAST := valueAST.(type) { case *ast.IntValue: return valueAST.Value case *ast.StringValue: return valueAST.Value } return nil }, }) func serializeDateTime(value interface{}) interface{} { switch value := value.(type) { case time.Time: buff, err := value.MarshalText() if err != nil { return nil } return string(buff) case *time.Time: if value == nil { return nil } return serializeDateTime(*value) default: return nil } } func unserializeDateTime(value interface{}) interface{} { switch value := value.(type) { case []byte: t := time.Time{} err := t.UnmarshalText(value) if err != nil { return nil } return t case string: return unserializeDateTime([]byte(value)) case *string: if value == nil { return nil } return unserializeDateTime([]byte(*value)) case time.Time: return value default: return nil } } var DateTime = NewScalar(ScalarConfig{ Name: "DateTime", Description: "The `DateTime` scalar type represents a DateTime." + " The DateTime is serialized as an RFC 3339 quoted string", Serialize: serializeDateTime, ParseValue: unserializeDateTime, ParseLiteral: func(valueAST ast.Value) interface{} { switch valueAST := valueAST.(type) { case *ast.StringValue: return unserializeDateTime(valueAST.Value) } return nil }, }) ================================================ FILE: scalars_parse_test.go ================================================ package graphql_test import ( "reflect" "testing" "time" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/language/ast" ) func TestTypeSystem_Scalar_ParseValueOutputDateTime(t *testing.T) { t1, _ := time.Parse(time.RFC3339, "2017-07-23T03:46:56.647Z") tests := []dateTimeSerializationTest{ {nil, nil}, {"", nil}, {(*string)(nil), nil}, {"2017-07-23", nil}, {"2017-07-23T03:46:56.647Z", t1}, } for _, test := range tests { val := graphql.DateTime.ParseValue(test.Value) if val != test.Expected { reflectedValue := reflect.ValueOf(test.Value) t.Fatalf("failed DateTime.ParseValue(%v(%v)), expected: %v, got %v", reflectedValue.Type(), test.Value, test.Expected, val) } } } func TestTypeSystem_Scalar_ParseLiteralOutputDateTime(t *testing.T) { t1, _ := time.Parse(time.RFC3339, "2017-07-23T03:46:56.647Z") for name, testCase := range map[string]struct { Literal ast.Value Expected interface{} }{ "String": { Literal: &ast.StringValue{ Value: "2017-07-23T03:46:56.647Z", }, Expected: t1, }, "NotAString": { Literal: &ast.IntValue{}, Expected: nil, }, } { t.Run(name, func(t *testing.T) { parsed := graphql.DateTime.ParseLiteral(testCase.Literal) if parsed != testCase.Expected { t.Fatalf("failed DateTime.ParseLiteral(%T(%v)), expected: %v, got %v", testCase.Literal, testCase.Literal, parsed, testCase.Expected) } }) } } ================================================ FILE: scalars_serialization_test.go ================================================ package graphql_test import ( "math" "reflect" "testing" "time" "github.com/graphql-go/graphql" ) type intSerializationTest struct { Value interface{} Expected interface{} } type float64SerializationTest struct { Value interface{} Expected interface{} } type stringSerializationTest struct { Value interface{} Expected string } type dateTimeSerializationTest struct { Value interface{} Expected interface{} } type boolSerializationTest struct { Value interface{} Expected bool } func TestTypeSystem_Scalar_SerializesOutputInt(t *testing.T) { tests := []intSerializationTest{ {1, 1}, {0, 0}, {-1, -1}, {float32(0.1), 0}, {float32(1.1), 1}, {float32(-1.1), -1}, {float32(1e5), 100000}, {float32(math.MaxFloat32), nil}, {float64(0.1), 0}, {float64(1.1), 1}, {float64(-1.1), -1}, {float64(1e5), 100000}, {float64(math.MaxFloat32), nil}, {float64(math.MaxFloat64), nil}, // Maybe a safe Go/Javascript `int`, but bigger than 2^32, so not // representable as a GraphQL Int {9876504321, nil}, {-9876504321, nil}, // Too big to represent as an Int in Go, JavaScript or GraphQL {float64(1e100), nil}, {float64(-1e100), nil}, {"-1.1", -1}, {"one", nil}, {false, 0}, {true, 1}, {int8(1), 1}, {int16(1), 1}, {int32(1), 1}, {int64(1), 1}, {uint(1), 1}, // Maybe a safe Go `uint`, but bigger than 2^32, so not // representable as a GraphQL Int {uint(math.MaxInt32 + 1), nil}, {uint8(1), 1}, {uint16(1), 1}, {uint32(1), 1}, {uint32(math.MaxUint32), nil}, {uint64(1), 1}, {uint64(math.MaxInt32), math.MaxInt32}, {int64(math.MaxInt32) + int64(1), nil}, {int64(math.MinInt32) - int64(1), nil}, {uint64(math.MaxInt64) + uint64(1), nil}, {byte(127), 127}, {'世', int('世')}, // testing types that don't match a value in the array. {[]int{}, nil}, } for i, test := range tests { val := graphql.Int.Serialize(test.Value) if val != test.Expected { reflectedTestValue := reflect.ValueOf(test.Value) reflectedExpectedValue := reflect.ValueOf(test.Expected) reflectedValue := reflect.ValueOf(val) t.Fatalf("Failed test #%d - Int.Serialize(%v(%v)), expected: %v(%v), got %v(%v)", i, reflectedTestValue.Type(), test.Value, reflectedExpectedValue.Type(), test.Expected, reflectedValue.Type(), val, ) } } } func TestTypeSystem_Scalar_SerializesOutputFloat(t *testing.T) { tests := []float64SerializationTest{ {int(1), 1.0}, {int(0), 0.0}, {int(-1), -1.0}, {float32(0.1), float32(0.1)}, {float32(1.1), float32(1.1)}, {float32(-1.1), float32(-1.1)}, {float64(0.1), float64(0.1)}, {float64(1.1), float64(1.1)}, {float64(-1.1), float64(-1.1)}, {"-1.1", -1.1}, {"one", nil}, {false, 0.0}, {true, 1.0}, } for i, test := range tests { val := graphql.Float.Serialize(test.Value) if val != test.Expected { reflectedTestValue := reflect.ValueOf(test.Value) reflectedExpectedValue := reflect.ValueOf(test.Expected) reflectedValue := reflect.ValueOf(val) t.Fatalf("Failed test #%d - Float.Serialize(%v(%v)), expected: %v(%v), got %v(%v)", i, reflectedTestValue.Type(), test.Value, reflectedExpectedValue.Type(), test.Expected, reflectedValue.Type(), val, ) } } } func TestTypeSystem_Scalar_SerializesOutputStrings(t *testing.T) { tests := []stringSerializationTest{ {"string", "string"}, {int(1), "1"}, {float32(-1.1), "-1.1"}, {float64(-1.1), "-1.1"}, {true, "true"}, {false, "false"}, } for _, test := range tests { val := graphql.String.Serialize(test.Value) if val != test.Expected { reflectedValue := reflect.ValueOf(test.Value) t.Fatalf("Failed String.Serialize(%v(%v)), expected: %v, got %v", reflectedValue.Type(), test.Value, test.Expected, val) } } } func TestTypeSystem_Scalar_SerializesOutputBoolean(t *testing.T) { tests := []boolSerializationTest{ {"true", true}, {"false", false}, {"string", true}, {"", false}, {int(1), true}, {int(0), false}, {true, true}, {false, false}, } for _, test := range tests { val := graphql.Boolean.Serialize(test.Value) if val != test.Expected { reflectedValue := reflect.ValueOf(test.Value) t.Fatalf("Failed String.Boolean(%v(%v)), expected: %v, got %v", reflectedValue.Type(), test.Value, test.Expected, val) } } } func TestTypeSystem_Scalar_SerializeOutputDateTime(t *testing.T) { now := time.Now() nowString, err := now.MarshalText() if err != nil { t.Fatal(err) } tests := []dateTimeSerializationTest{ {"string", nil}, {int(1), nil}, {float32(-1.1), nil}, {float64(-1.1), nil}, {true, nil}, {false, nil}, {now, string(nowString)}, {&now, string(nowString)}, } for _, test := range tests { val := graphql.DateTime.Serialize(test.Value) if val != test.Expected { reflectedValue := reflect.ValueOf(test.Value) t.Fatalf("Failed DateTime.Serialize(%v(%v)), expected: %v, got %v", reflectedValue.Type(), test.Value, test.Expected, val) } } } ================================================ FILE: scalars_test.go ================================================ package graphql import ( "math" "testing" ) func TestCoerceInt(t *testing.T) { tests := []struct { in interface{} want interface{} }{ { in: false, want: 0, }, { in: true, want: 1, }, { in: boolPtr(false), want: 0, }, { in: boolPtr(true), want: 1, }, { in: (*bool)(nil), want: nil, }, { in: int(math.MinInt32) - 1, want: nil, }, { in: int(math.MaxInt32) + 1, want: nil, }, { in: uint(math.MaxInt32) + 1, want: nil, }, { in: uint32(math.MaxInt32) + 1, want: nil, }, { in: int64(math.MinInt32) - 1, want: nil, }, { in: int64(math.MaxInt32) + 1, want: nil, }, { in: uint64(math.MaxInt32) + 1, want: nil, }, { // need to subtract more than one because of float32 precision in: float32(math.MinInt32) - 1000, want: nil, }, { // need to add more than one because of float32 precision in: float32(math.MaxInt32) + 1000, want: nil, }, { in: float64(math.MinInt32) - 1, want: nil, }, { in: float64(math.MaxInt32) + 1, want: nil, }, { in: int(math.MinInt32), want: int(math.MinInt32), }, { in: int(math.MaxInt32), want: int(math.MaxInt32), }, { in: intPtr(12), want: 12, }, { in: (*int)(nil), want: nil, }, { in: int8(13), want: int(13), }, { in: int8Ptr(14), want: int(14), }, { in: (*int8)(nil), want: nil, }, { in: int16(15), want: int(15), }, { in: int16Ptr(16), want: int(16), }, { in: (*int16)(nil), want: nil, }, { in: int32(17), want: int(17), }, { in: int32Ptr(18), want: int(18), }, { in: (*int32)(nil), want: nil, }, { in: int64(19), want: int(19), }, { in: int64Ptr(20), want: int(20), }, { in: (*int64)(nil), want: nil, }, { in: uint8(21), want: int(21), }, { in: uint8Ptr(22), want: int(22), }, { in: (*uint8)(nil), want: nil, }, { in: uint16(23), want: int(23), }, { in: uint16Ptr(24), want: int(24), }, { in: (*uint16)(nil), want: nil, }, { in: uint32(25), want: int(25), }, { in: uint32Ptr(26), want: int(26), }, { in: (*uint32)(nil), want: nil, }, { in: uint64(27), want: int(27), }, { in: uint64Ptr(28), want: int(28), }, { in: (*uint64)(nil), want: nil, }, { in: uintPtr(29), want: int(29), }, { in: (*uint)(nil), want: nil, }, { in: float32(30.1), want: int(30), }, { in: float32Ptr(31.2), want: int(31), }, { in: (*float32)(nil), want: nil, }, { in: float64(32), want: int(32), }, { in: float64Ptr(33.1), want: int(33), }, { in: (*float64)(nil), want: nil, }, { in: "34", want: int(34), }, { in: stringPtr("35"), want: int(35), }, { in: (*string)(nil), want: nil, }, { in: "I'm not a number", want: nil, }, { in: make(map[string]interface{}), want: nil, }, } for i, tt := range tests { if got, want := coerceInt(tt.in), tt.want; got != want { t.Errorf("%d: in=%v, got=%v, want=%v", i, tt.in, got, want) } } } func TestCoerceFloat(t *testing.T) { tests := []struct { in interface{} want interface{} }{ { in: false, want: 0.0, }, { in: true, want: 1.0, }, { in: boolPtr(false), want: 0.0, }, { in: boolPtr(true), want: 1.0, }, { in: (*bool)(nil), want: nil, }, { in: int(math.MinInt32), want: float64(math.MinInt32), }, { in: int(math.MaxInt32), want: float64(math.MaxInt32), }, { in: intPtr(12), want: float64(12), }, { in: (*int)(nil), want: nil, }, { in: int8(13), want: float64(13), }, { in: int8Ptr(14), want: float64(14), }, { in: (*int8)(nil), want: nil, }, { in: int16(15), want: float64(15), }, { in: int16Ptr(16), want: float64(16), }, { in: (*int16)(nil), want: nil, }, { in: int32(17), want: float64(17), }, { in: int32Ptr(18), want: float64(18), }, { in: (*int32)(nil), want: nil, }, { in: int64(19), want: float64(19), }, { in: int64Ptr(20), want: float64(20), }, { in: (*int64)(nil), want: nil, }, { in: uint8(21), want: float64(21), }, { in: uint8Ptr(22), want: float64(22), }, { in: (*uint8)(nil), want: nil, }, { in: uint16(23), want: float64(23), }, { in: uint16Ptr(24), want: float64(24), }, { in: (*uint16)(nil), want: nil, }, { in: uint32(25), want: float64(25), }, { in: uint32Ptr(26), want: float64(26), }, { in: (*uint32)(nil), want: nil, }, { in: uint64(27), want: float64(27), }, { in: uint64Ptr(28), want: float64(28), }, { in: (*uint64)(nil), want: nil, }, { in: uintPtr(29), want: float64(29), }, { in: (*uint)(nil), want: nil, }, { in: float32(30), want: float32(30), }, { in: float32Ptr(31), want: float32(31), }, { in: (*float32)(nil), want: nil, }, { in: float64(32), want: float64(32), }, { in: float64Ptr(33.2), want: float64(33.2), }, { in: (*float64)(nil), want: nil, }, { in: "34", want: float64(34), }, { in: stringPtr("35.2"), want: float64(35.2), }, { in: (*string)(nil), want: nil, }, { in: "I'm not a number", want: nil, }, { in: make(map[string]interface{}), want: nil, }, } for i, tt := range tests { if got, want := coerceFloat(tt.in), tt.want; got != want { t.Errorf("%d: in=%v, got=%v, want=%v", i, tt.in, got, want) } } } func TestCoerceBool(t *testing.T) { tests := []struct { in interface{} want interface{} }{ { in: false, want: false, }, { in: true, want: true, }, { in: boolPtr(false), want: false, }, { in: boolPtr(true), want: true, }, { in: (*bool)(nil), want: nil, }, { in: int(math.MinInt32), want: true, }, { in: int(math.MaxInt32), want: true, }, { in: int(0), want: false, }, { in: intPtr(12), want: true, }, { in: intPtr(0), want: false, }, { in: (*int)(nil), want: nil, }, { in: int8(13), want: true, }, { in: int8(0), want: false, }, { in: int8Ptr(14), want: true, }, { in: int8Ptr(0), want: false, }, { in: (*int8)(nil), want: nil, }, { in: int16(15), want: true, }, { in: int16(0), want: false, }, { in: int16Ptr(16), want: true, }, { in: int16Ptr(0), want: false, }, { in: (*int16)(nil), want: nil, }, { in: int32(17), want: true, }, { in: int32(0), want: false, }, { in: int32Ptr(18), want: true, }, { in: int32Ptr(0), want: false, }, { in: (*int32)(nil), want: nil, }, { in: int64(19), want: true, }, { in: int64(0), want: false, }, { in: int64Ptr(20), want: true, }, { in: int64Ptr(0), want: false, }, { in: (*int64)(nil), want: nil, }, { in: uint8(21), want: true, }, { in: uint8(0), want: false, }, { in: uint8Ptr(22), want: true, }, { in: uint8Ptr(0), want: false, }, { in: (*uint8)(nil), want: nil, }, { in: uint16(23), want: true, }, { in: uint16(0), want: false, }, { in: uint16Ptr(24), want: true, }, { in: uint16Ptr(0), want: false, }, { in: (*uint16)(nil), want: nil, }, { in: uint32(25), want: true, }, { in: uint32(0), want: false, }, { in: uint32Ptr(26), want: true, }, { in: uint32Ptr(0), want: false, }, { in: (*uint32)(nil), want: nil, }, { in: uint64(27), want: true, }, { in: uint64(0), want: false, }, { in: uint64Ptr(28), want: true, }, { in: uint64Ptr(0), want: false, }, { in: (*uint64)(nil), want: nil, }, { in: uintPtr(29), want: true, }, { in: uintPtr(0), want: false, }, { in: float32(30), want: true, }, { in: float32(0), want: false, }, { in: float32Ptr(31), want: true, }, { in: float32Ptr(0), want: false, }, { in: (*float32)(nil), want: nil, }, { in: float64(32), want: true, }, { in: float64(0), want: false, }, { in: float64Ptr(33.2), want: true, }, { in: float64Ptr(0), want: false, }, { in: (*float64)(nil), want: nil, }, { in: "34", want: true, }, { in: "false", want: false, }, { in: stringPtr("true"), want: true, }, { in: stringPtr("false"), want: false, }, { in: (*string)(nil), want: nil, }, { in: "I'm some random string", want: true, }, { in: "", want: false, }, { in: int8(0), want: false, }, { in: make(map[string]interface{}), want: false, }, } for i, tt := range tests { if got, want := coerceBool(tt.in), tt.want; got != want { t.Errorf("%d: in=%v, got=%v, want=%v", i, tt.in, got, want) } } } func boolPtr(b bool) *bool { return &b } func intPtr(n int) *int { return &n } func int8Ptr(n int8) *int8 { return &n } func int16Ptr(n int16) *int16 { return &n } func int32Ptr(n int32) *int32 { return &n } func int64Ptr(n int64) *int64 { return &n } func uintPtr(n uint) *uint { return &n } func uint8Ptr(n uint8) *uint8 { return &n } func uint16Ptr(n uint16) *uint16 { return &n } func uint32Ptr(n uint32) *uint32 { return &n } func uint64Ptr(n uint64) *uint64 { return &n } func float32Ptr(n float32) *float32 { return &n } func float64Ptr(n float64) *float64 { return &n } func stringPtr(s string) *string { return &s } ================================================ FILE: schema-all-descriptions.graphql ================================================ # File: schema-all-descriptions.graphql """single line scalar description""" scalar ScalarSingleLine """ multi line scalar description """ scalar ScalarMultiLine """single line object description""" type ObjectSingleLine { no_description: ID """single line field description""" single_line(a: ID, b: ID, c: ID, d: ID): ID """ multi line field description """ multi_line( a: ID """single line argument description""" b: ID """ multi line field description """ c: ID d: ID ): ID } """ multi line object description """ type ObjectMultiLine { foo: ID } """single line interface description""" interface InterfaceSingleLine { no_description: ID """single line field description""" single_line(a: ID, b: ID, c: ID, d: ID): ID """ multi line field description """ multi_line( a: ID """single line argument description""" b: ID """ multi line argument description """ c: ID d: ID ): ID } """ multi line interface description """ interface InterfaceMultiLine { foo: ID } """single line union description""" union UnionSingleLine = String | Int | Float | ID """ multi line union description """ union UnionSingleLine = String | Int | Float | ID """single line enum description""" enum EnumSingleLine { no_description """single line enum description""" single_line """ multi line enum description """ multi_line again_no_description } """ multi line enum description """ enum EnumMultiLine { foo } """single line input description""" input InputSingleLine { a: ID """single line argument description""" b: ID """ multi line argument description """ c: ID d: ID } """ multi line input description """ input InputMultiLine { foo: ID } """single line directive description""" directive @DirectiveSingleLine( a: ID """single line argument description""" b: ID """ multi line argument description """ c: ID d: ID ) on SCALAR """ multi line directive description """ directive @DirectiveMultiLine on SCALAR ================================================ FILE: schema-kitchen-sink.graphql ================================================ # Filename: schema-kitchen-sink.graphql schema { query: QueryType mutation: MutationType } type Foo implements Bar & Baz { one: Type two(argument: InputType!): Type three(argument: InputType, other: String): Int four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): String six(argument: InputType = {key: "value"}): Type } type AnnotatedObject @onObject(arg: "value") { annotatedField(arg: Type = "default" @onArg): Type @onField } interface Bar { one: Type four(argument: String = "string"): String } interface AnnotatedInterface @onInterface { annotatedField(arg: Type @onArg): Type @onField } union Feed = Story | Article | Advert union AnnotatedUnion @onUnion = A | B scalar CustomScalar scalar AnnotatedScalar @onScalar enum Site { DESKTOP MOBILE } enum AnnotatedEnum @onEnum { ANNOTATED_VALUE @onEnumValue OTHER_VALUE } input InputType { key: String! answer: Int = 42 } input AnnotatedInput @onInputObjectType { annotatedField: Type @onField } extend type Foo { seven(argument: [String]): Type } extend type Foo @onType {} type NoFields {} directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT ================================================ FILE: schema.go ================================================ package graphql type SchemaConfig struct { Query *Object Mutation *Object Subscription *Object Types []Type Directives []*Directive Extensions []Extension } type TypeMap map[string]Type // Schema Definition // A Schema is created by supplying the root types of each type of operation, // query, mutation (optional) and subscription (optional). A schema definition is then supplied to the // validator and executor. // Example: // myAppSchema, err := NewSchema(SchemaConfig({ // Query: MyAppQueryRootType, // Mutation: MyAppMutationRootType, // Subscription: MyAppSubscriptionRootType, // }); // Note: If an array of `directives` are provided to GraphQLSchema, that will be // the exact list of directives represented and allowed. If `directives` is not // provided then a default set of the specified directives (e.g. @include and // @skip) will be used. If you wish to provide *additional* directives to these // specified directives, you must explicitly declare them. Example: // // const MyAppSchema = new GraphQLSchema({ // ... // directives: specifiedDirectives.concat([ myCustomDirective ]), // }) type Schema struct { typeMap TypeMap directives []*Directive queryType *Object mutationType *Object subscriptionType *Object implementations map[string][]*Object possibleTypeMap map[string]map[string]bool extensions []Extension } func NewSchema(config SchemaConfig) (Schema, error) { var err error schema := Schema{} if err = invariant(config.Query != nil, "Schema query must be Object Type but got: nil."); err != nil { return schema, err } // if schema config contains error at creation time, return those errors if config.Query != nil && config.Query.err != nil { return schema, config.Query.err } if config.Mutation != nil && config.Mutation.err != nil { return schema, config.Mutation.err } schema.queryType = config.Query schema.mutationType = config.Mutation schema.subscriptionType = config.Subscription // Provide specified directives (e.g. @include and @skip) by default. schema.directives = config.Directives if len(schema.directives) == 0 { schema.directives = SpecifiedDirectives } // Ensure directive definitions are error-free for _, dir := range schema.directives { if dir.err != nil { return schema, dir.err } } // Build type map now to detect any errors within this schema. typeMap := TypeMap{} initialTypes := []Type{} if schema.QueryType() != nil { initialTypes = append(initialTypes, schema.QueryType()) } if schema.MutationType() != nil { initialTypes = append(initialTypes, schema.MutationType()) } if schema.SubscriptionType() != nil { initialTypes = append(initialTypes, schema.SubscriptionType()) } if SchemaType != nil { initialTypes = append(initialTypes, SchemaType) } // assume that user will never add a nil object to config initialTypes = append(initialTypes, config.Types...) for _, ttype := range initialTypes { if ttype.Error() != nil { return schema, ttype.Error() } if typeMap, err = typeMapReducer(&schema, typeMap, ttype); err != nil { return schema, err } } schema.typeMap = typeMap // Keep track of all implementations by interface name. if schema.implementations == nil { schema.implementations = map[string][]*Object{} } for _, ttype := range schema.typeMap { if ttype, ok := ttype.(*Object); ok { for _, iface := range ttype.Interfaces() { impls, ok := schema.implementations[iface.Name()] if impls == nil || !ok { impls = []*Object{} } impls = append(impls, ttype) schema.implementations[iface.Name()] = impls } } } // Enforce correct interface implementations for _, ttype := range schema.typeMap { if ttype, ok := ttype.(*Object); ok { for _, iface := range ttype.Interfaces() { err := assertObjectImplementsInterface(&schema, ttype, iface) if err != nil { return schema, err } } } } // Add extensions from config if len(config.Extensions) != 0 { schema.extensions = config.Extensions } return schema, nil } //Added Check implementation of interfaces at runtime.. //Add Implementations at Runtime.. func (gq *Schema) AddImplementation() error { // Keep track of all implementations by interface name. if gq.implementations == nil { gq.implementations = map[string][]*Object{} } for _, ttype := range gq.typeMap { if ttype, ok := ttype.(*Object); ok { for _, iface := range ttype.Interfaces() { impls, ok := gq.implementations[iface.Name()] if impls == nil || !ok { impls = []*Object{} } impls = append(impls, ttype) gq.implementations[iface.Name()] = impls } } } // Enforce correct interface implementations for _, ttype := range gq.typeMap { if ttype, ok := ttype.(*Object); ok { for _, iface := range ttype.Interfaces() { err := assertObjectImplementsInterface(gq, ttype, iface) if err != nil { return err } } } } return nil } //Edited. To check add Types at RunTime.. //Append Runtime schema to typeMap func (gq *Schema) AppendType(objectType Type) error { if objectType.Error() != nil { return objectType.Error() } var err error gq.typeMap, err = typeMapReducer(gq, gq.typeMap, objectType) if err != nil { return err } //Now Add interface implementation.. return gq.AddImplementation() } func (gq *Schema) QueryType() *Object { return gq.queryType } func (gq *Schema) MutationType() *Object { return gq.mutationType } func (gq *Schema) SubscriptionType() *Object { return gq.subscriptionType } func (gq *Schema) Directives() []*Directive { return gq.directives } func (gq *Schema) Directive(name string) *Directive { for _, directive := range gq.Directives() { if directive.Name == name { return directive } } return nil } func (gq *Schema) TypeMap() TypeMap { return gq.typeMap } func (gq *Schema) Type(name string) Type { return gq.TypeMap()[name] } func (gq *Schema) PossibleTypes(abstractType Abstract) []*Object { switch abstractType := abstractType.(type) { case *Union: return abstractType.Types() case *Interface: if impls, ok := gq.implementations[abstractType.Name()]; ok { return impls } } return []*Object{} } func (gq *Schema) IsPossibleType(abstractType Abstract, possibleType *Object) bool { possibleTypeMap := gq.possibleTypeMap if possibleTypeMap == nil { possibleTypeMap = map[string]map[string]bool{} } if typeMap, ok := possibleTypeMap[abstractType.Name()]; !ok { typeMap = map[string]bool{} for _, possibleType := range gq.PossibleTypes(abstractType) { typeMap[possibleType.Name()] = true } possibleTypeMap[abstractType.Name()] = typeMap } gq.possibleTypeMap = possibleTypeMap if typeMap, ok := possibleTypeMap[abstractType.Name()]; ok { isPossible, _ := typeMap[possibleType.Name()] return isPossible } return false } // AddExtensions can be used to add additional extensions to the schema func (gq *Schema) AddExtensions(e ...Extension) { gq.extensions = append(gq.extensions, e...) } // map-reduce func typeMapReducer(schema *Schema, typeMap TypeMap, objectType Type) (TypeMap, error) { var err error if objectType == nil || objectType.Name() == "" { return typeMap, nil } // first: switch objectType := objectType.(type) { case *List: if objectType.OfType != nil { return typeMapReducer(schema, typeMap, objectType.OfType) } case *NonNull: if objectType.OfType != nil { return typeMapReducer(schema, typeMap, objectType.OfType) } case *Object: if objectType.err != nil { return typeMap, objectType.err } } if mappedObjectType, ok := typeMap[objectType.Name()]; ok { err = invariantf( mappedObjectType == objectType, `Schema must contain unique named types but contains multiple types named "%v".`, objectType.Name()) return typeMap, err } typeMap[objectType.Name()] = objectType // second: switch objectType := objectType.(type) { case *Union, *Interface: types := schema.PossibleTypes(objectType) if objectType.Error() != nil { return typeMap, objectType.Error() } for _, innerObjectType := range types { if innerObjectType.err != nil { return typeMap, innerObjectType.err } if typeMap, err = typeMapReducer(schema, typeMap, innerObjectType); err != nil { return typeMap, err } } case *Object: interfaces := objectType.Interfaces() if objectType.err != nil { return typeMap, objectType.err } for _, innerObjectType := range interfaces { if innerObjectType.err != nil { return typeMap, innerObjectType.err } if typeMap, err = typeMapReducer(schema, typeMap, innerObjectType); err != nil { return typeMap, err } } } switch objectType := objectType.(type) { case *Object: fieldMap := objectType.Fields() if objectType.err != nil { return typeMap, objectType.err } for _, field := range fieldMap { for _, arg := range field.Args { typeMap, err = typeMapReducer(schema, typeMap, arg.Type) if err != nil { return typeMap, err } } typeMap, err = typeMapReducer(schema, typeMap, field.Type) if err != nil { return typeMap, err } } case *Interface: fieldMap := objectType.Fields() if objectType.err != nil { return typeMap, objectType.err } for _, field := range fieldMap { for _, arg := range field.Args { typeMap, err = typeMapReducer(schema, typeMap, arg.Type) if err != nil { return typeMap, err } } typeMap, err = typeMapReducer(schema, typeMap, field.Type) if err != nil { return typeMap, err } } case *InputObject: fieldMap := objectType.Fields() if objectType.err != nil { return typeMap, objectType.err } for _, field := range fieldMap { typeMap, err = typeMapReducer(schema, typeMap, field.Type) if err != nil { return typeMap, err } } } return typeMap, nil } func assertObjectImplementsInterface(schema *Schema, object *Object, iface *Interface) error { objectFieldMap := object.Fields() ifaceFieldMap := iface.Fields() // Assert each interface field is implemented. for fieldName := range ifaceFieldMap { objectField := objectFieldMap[fieldName] ifaceField := ifaceFieldMap[fieldName] // Assert interface field exists on object. err := invariantf( objectField != nil, `"%v" expects field "%v" but "%v" does not `+ `provide it.`, iface, fieldName, object) if err != nil { return err } // Assert interface field type is satisfied by object field type, by being // a valid subtype. (covariant) err = invariantf( isTypeSubTypeOf(schema, objectField.Type, ifaceField.Type), `%v.%v expects type "%v" but `+ `%v.%v provides type "%v".`, iface, fieldName, ifaceField.Type, object, fieldName, objectField.Type, ) if err != nil { return err } // Assert each interface field arg is implemented. for _, ifaceArg := range ifaceField.Args { argName := ifaceArg.PrivateName var objectArg *Argument for _, arg := range objectField.Args { if arg.PrivateName == argName { objectArg = arg break } } // Assert interface field arg exists on object field. err = invariantf( objectArg != nil, `%v.%v expects argument "%v" but `+ `%v.%v does not provide it.`, iface, fieldName, argName, object, fieldName, ) if err != nil { return err } // Assert interface field arg type matches object field arg type. // (invariant) err = invariantf( isEqualType(ifaceArg.Type, objectArg.Type), `%v.%v(%v:) expects type "%v" `+ `but %v.%v(%v:) provides `+ `type "%v".`, iface, fieldName, argName, ifaceArg.Type, object, fieldName, argName, objectArg.Type, ) if err != nil { return err } } // Assert additional arguments must not be required. for _, objectArg := range objectField.Args { argName := objectArg.PrivateName var ifaceArg *Argument for _, arg := range ifaceField.Args { if arg.PrivateName == argName { ifaceArg = arg break } } if ifaceArg == nil { _, ok := objectArg.Type.(*NonNull) err = invariantf( !ok, `%v.%v(%v:) is of required type `+ `"%v" but is not also provided by the interface %v.%v.`, object, fieldName, argName, objectArg.Type, iface, fieldName, ) if err != nil { return err } } } } return nil } func isEqualType(typeA Type, typeB Type) bool { // Equivalent type is a valid subtype if typeA == typeB { return true } // If either type is non-null, the other must also be non-null. if typeA, ok := typeA.(*NonNull); ok { if typeB, ok := typeB.(*NonNull); ok { return isEqualType(typeA.OfType, typeB.OfType) } } // If either type is a list, the other must also be a list. if typeA, ok := typeA.(*List); ok { if typeB, ok := typeB.(*List); ok { return isEqualType(typeA.OfType, typeB.OfType) } } // Otherwise the types are not equal. return false } // isTypeSubTypeOf Provided a type and a super type, return true if the first type is either // equal or a subset of the second super type (covariant). func isTypeSubTypeOf(schema *Schema, maybeSubType Type, superType Type) bool { // Equivalent type is a valid subtype if maybeSubType == superType { return true } // If superType is non-null, maybeSubType must also be nullable. if superType, ok := superType.(*NonNull); ok { if maybeSubType, ok := maybeSubType.(*NonNull); ok { return isTypeSubTypeOf(schema, maybeSubType.OfType, superType.OfType) } return false } if maybeSubType, ok := maybeSubType.(*NonNull); ok { // If superType is nullable, maybeSubType may be non-null. return isTypeSubTypeOf(schema, maybeSubType.OfType, superType) } // If superType type is a list, maybeSubType type must also be a list. if superType, ok := superType.(*List); ok { if maybeSubType, ok := maybeSubType.(*List); ok { return isTypeSubTypeOf(schema, maybeSubType.OfType, superType.OfType) } return false } else if _, ok := maybeSubType.(*List); ok { // If superType is not a list, maybeSubType must also be not a list. return false } // If superType type is an abstract type, maybeSubType type may be a currently // possible object type. if superType, ok := superType.(*Interface); ok { if maybeSubType, ok := maybeSubType.(*Object); ok && schema.IsPossibleType(superType, maybeSubType) { return true } } if superType, ok := superType.(*Union); ok { if maybeSubType, ok := maybeSubType.(*Object); ok && schema.IsPossibleType(superType, maybeSubType) { return true } } // Otherwise, the child type is not a valid subtype of the parent type. return false } ================================================ FILE: subscription.go ================================================ package graphql import ( "context" "fmt" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/parser" "github.com/graphql-go/graphql/language/source" ) // SubscribeParams parameters for subscribing type SubscribeParams struct { Schema Schema RequestString string RootValue interface{} // ContextValue context.Context VariableValues map[string]interface{} OperationName string FieldResolver FieldResolveFn FieldSubscriber FieldResolveFn } // Subscribe performs a subscribe operation on the given query and schema // To finish a subscription you can simply close the channel from inside the `Subscribe` function // currently does not support extensions hooks func Subscribe(p Params) chan *Result { source := source.NewSource(&source.Source{ Body: []byte(p.RequestString), Name: "GraphQL request", }) // TODO run extensions hooks // parse the source AST, err := parser.Parse(parser.ParseParams{Source: source}) if err != nil { // merge the errors from extensions and the original error from parser return sendOneResultAndClose(&Result{ Errors: gqlerrors.FormatErrors(err), }) } // validate document validationResult := ValidateDocument(&p.Schema, AST, nil) if !validationResult.IsValid { // run validation finish functions for extensions return sendOneResultAndClose(&Result{ Errors: validationResult.Errors, }) } return ExecuteSubscription(ExecuteParams{ Schema: p.Schema, Root: p.RootObject, AST: AST, OperationName: p.OperationName, Args: p.VariableValues, Context: p.Context, }) } func sendOneResultAndClose(res *Result) chan *Result { resultChannel := make(chan *Result, 1) resultChannel <- res close(resultChannel) return resultChannel } // ExecuteSubscription is similar to graphql.Execute but returns a channel instead of a Result // currently does not support extensions func ExecuteSubscription(p ExecuteParams) chan *Result { if p.Context == nil { p.Context = context.Background() } var mapSourceToResponse = func(payload interface{}) *Result { return Execute(ExecuteParams{ Schema: p.Schema, Root: payload, AST: p.AST, OperationName: p.OperationName, Args: p.Args, Context: p.Context, }) } var resultChannel = make(chan *Result) go func() { defer close(resultChannel) defer func() { if err := recover(); err != nil { e, ok := err.(error) if !ok { return } resultChannel <- &Result{ Errors: gqlerrors.FormatErrors(e), } } return }() exeContext, err := buildExecutionContext(buildExecutionCtxParams{ Schema: p.Schema, Root: p.Root, AST: p.AST, OperationName: p.OperationName, Args: p.Args, Context: p.Context, }) if err != nil { resultChannel <- &Result{ Errors: gqlerrors.FormatErrors(err), } return } operationType, err := getOperationRootType(p.Schema, exeContext.Operation) if err != nil { resultChannel <- &Result{ Errors: gqlerrors.FormatErrors(err), } return } fields := collectFields(collectFieldsParams{ ExeContext: exeContext, RuntimeType: operationType, SelectionSet: exeContext.Operation.GetSelectionSet(), }) responseNames := []string{} for name := range fields { responseNames = append(responseNames, name) } responseName := responseNames[0] fieldNodes := fields[responseName] fieldNode := fieldNodes[0] fieldName := fieldNode.Name.Value fieldDef := getFieldDef(p.Schema, operationType, fieldName) if fieldDef == nil { resultChannel <- &Result{ Errors: gqlerrors.FormatErrors(fmt.Errorf("the subscription field %q is not defined", fieldName)), } return } resolveFn := fieldDef.Subscribe if resolveFn == nil { resultChannel <- &Result{ Errors: gqlerrors.FormatErrors(fmt.Errorf("the subscription function %q is not defined", fieldName)), } return } fieldPath := &ResponsePath{ Key: responseName, } args := getArgumentValues(fieldDef.Args, fieldNode.Arguments, exeContext.VariableValues) info := ResolveInfo{ FieldName: fieldName, FieldASTs: fieldNodes, Path: fieldPath, ReturnType: fieldDef.Type, ParentType: operationType, Schema: p.Schema, Fragments: exeContext.Fragments, RootValue: exeContext.Root, Operation: exeContext.Operation, VariableValues: exeContext.VariableValues, } fieldResult, err := resolveFn(ResolveParams{ Source: p.Root, Args: args, Info: info, Context: p.Context, }) if err != nil { resultChannel <- &Result{ Errors: gqlerrors.FormatErrors(err), } return } if fieldResult == nil { resultChannel <- &Result{ Errors: gqlerrors.FormatErrors(fmt.Errorf("no field result")), } return } switch fieldResult.(type) { case chan interface{}: sub := fieldResult.(chan interface{}) for { select { case <-p.Context.Done(): return case res, more := <-sub: if !more { return } resultChannel <- mapSourceToResponse(res) } } default: resultChannel <- mapSourceToResponse(fieldResult) return } }() // return a result channel return resultChannel } ================================================ FILE: subscription_test.go ================================================ package graphql_test import ( "errors" "fmt" "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/testutil" ) func TestSchemaSubscribe(t *testing.T) { testutil.RunSubscribes(t, []*testutil.TestSubscription{ { Name: "subscribe without resolver", Schema: makeSubscriptionSchema(t, graphql.ObjectConfig{ Name: "Subscription", Fields: graphql.Fields{ "sub_without_resolver": &graphql.Field{ Type: graphql.String, Subscribe: makeSubscribeToMapFunction([]map[string]interface{}{ { "sub_without_resolver": "a", }, { "sub_without_resolver": "b", }, { "sub_without_resolver": "c", }, }), }, }, }), Query: ` subscription { sub_without_resolver } `, ExpectedResults: []testutil.TestResponse{ {Data: `{ "sub_without_resolver": "a" }`}, {Data: `{ "sub_without_resolver": "b" }`}, {Data: `{ "sub_without_resolver": "c" }`}, }, }, { Name: "subscribe with resolver", Schema: makeSubscriptionSchema(t, graphql.ObjectConfig{ Name: "Subscription", Fields: graphql.Fields{ "sub_with_resolver": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return p.Source, nil }, Subscribe: makeSubscribeToStringFunction([]string{"a", "b", "c"}), }, }, }), Query: ` subscription { sub_with_resolver } `, ExpectedResults: []testutil.TestResponse{ {Data: `{ "sub_with_resolver": "a" }`}, {Data: `{ "sub_with_resolver": "b" }`}, {Data: `{ "sub_with_resolver": "c" }`}, }, }, { Name: "receive query validation error", Schema: makeSubscriptionSchema(t, graphql.ObjectConfig{ Name: "Subscription", Fields: graphql.Fields{ "sub_without_resolver": &graphql.Field{ Type: graphql.String, Subscribe: makeSubscribeToStringFunction([]string{"a", "b", "c"}), }, }, }), Query: ` subscription { sub_without_resolver xxx } `, ExpectedResults: []testutil.TestResponse{ {Errors: []string{"Cannot query field \"xxx\" on type \"Subscription\"."}}, }, }, { Name: "panic inside subscribe is recovered", Schema: makeSubscriptionSchema(t, graphql.ObjectConfig{ Name: "Subscription", Fields: graphql.Fields{ "should_error": &graphql.Field{ Type: graphql.String, Subscribe: func(p graphql.ResolveParams) (interface{}, error) { panic(errors.New("got a panic error")) }, }, }, }), Query: ` subscription { should_error } `, ExpectedResults: []testutil.TestResponse{ {Errors: []string{"got a panic error"}}, }, }, { Name: "subscribe with resolver changes output", Schema: makeSubscriptionSchema(t, graphql.ObjectConfig{ Name: "Subscription", Fields: graphql.Fields{ "sub_with_resolver": &graphql.Field{ Type: graphql.String, Subscribe: makeSubscribeToStringFunction([]string{"a", "b", "c", "d"}), Resolve: func(p graphql.ResolveParams) (interface{}, error) { return fmt.Sprintf("result=%v", p.Source), nil }, }, }, }), Query: ` subscription { sub_with_resolver } `, ExpectedResults: []testutil.TestResponse{ {Data: `{ "sub_with_resolver": "result=a" }`}, {Data: `{ "sub_with_resolver": "result=b" }`}, {Data: `{ "sub_with_resolver": "result=c" }`}, {Data: `{ "sub_with_resolver": "result=d" }`}, }, }, { Name: "subscribe to a nested object", Schema: makeSubscriptionSchema(t, graphql.ObjectConfig{ Name: "Subscription", Fields: graphql.Fields{ "sub_with_object": &graphql.Field{ Type: graphql.NewObject(graphql.ObjectConfig{ Name: "Obj", Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.String, }, }, }), Resolve: func(p graphql.ResolveParams) (interface{}, error) { return p.Source, nil }, Subscribe: makeSubscribeToMapFunction([]map[string]interface{}{ { "field": "hello", }, { "field": "bye", }, { "field": nil, }, }), }, }, }), Query: ` subscription { sub_with_object { field } } `, ExpectedResults: []testutil.TestResponse{ {Data: `{ "sub_with_object": { "field": "hello" } }`}, {Data: `{ "sub_with_object": { "field": "bye" } }`}, {Data: `{ "sub_with_object": { "field": null } }`}, }, }, { Name: "subscription_resolver_can_error", Schema: makeSubscriptionSchema(t, graphql.ObjectConfig{ Name: "Subscription", Fields: graphql.Fields{ "should_error": &graphql.Field{ Type: graphql.String, Subscribe: func(p graphql.ResolveParams) (interface{}, error) { return nil, errors.New("got a subscribe error") }, }, }, }), Query: ` subscription { should_error } `, ExpectedResults: []testutil.TestResponse{ { Errors: []string{"got a subscribe error"}, }, }, }, { Name: "schema_without_subscribe_errors", Schema: makeSubscriptionSchema(t, graphql.ObjectConfig{ Name: "Subscription", Fields: graphql.Fields{ "should_error": &graphql.Field{ Type: graphql.String, }, }, }), Query: ` subscription { should_error } `, ExpectedResults: []testutil.TestResponse{ { Errors: []string{"the subscription function \"should_error\" is not defined"}, }, }, }, }) } func makeSubscribeToStringFunction(elements []string) func(p graphql.ResolveParams) (interface{}, error) { return func(p graphql.ResolveParams) (interface{}, error) { c := make(chan interface{}) go func() { for _, r := range elements { select { case <-p.Context.Done(): close(c) return case c <- r: } } close(c) }() return c, nil } } func makeSubscribeToMapFunction(elements []map[string]interface{}) func(p graphql.ResolveParams) (interface{}, error) { return func(p graphql.ResolveParams) (interface{}, error) { c := make(chan interface{}) go func() { for _, r := range elements { select { case <-p.Context.Done(): close(c) return case c <- r: } } close(c) }() return c, nil } } func makeSubscriptionSchema(t *testing.T, c graphql.ObjectConfig) graphql.Schema { schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: dummyQuery, Subscription: graphql.NewObject(c), }) if err != nil { t.Errorf("failed to create schema: %v", err) } return schema } var dummyQuery = graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "hello": &graphql.Field{Type: graphql.String}, }, }) ================================================ FILE: suggested_list_internal_test.go ================================================ package graphql import ( "reflect" "testing" ) func TestSuggestionList_ReturnsResultsWhenInputIsEmpty(t *testing.T) { expected := []string{"a"} result := suggestionList("", []string{"a"}) if !reflect.DeepEqual(expected, result) { t.Fatalf("Expected %v, got: %v", expected, result) } } func TestSuggestionList_ReturnsEmptyArrayWhenThereAreNoOptions(t *testing.T) { expected := []string{} result := suggestionList("input", []string{}) if !reflect.DeepEqual(expected, result) { t.Fatalf("Expected %v, got: %v", expected, result) } } func TestSuggestionList_ReturnsOptionsSortedBasedOnSimilarity(t *testing.T) { expected := []string{"abc", "ab"} result := suggestionList("abc", []string{"a", "ab", "abc"}) if !reflect.DeepEqual(expected, result) { t.Fatalf("Expected %v, got: %v", expected, result) } } ================================================ FILE: testutil/introspection_query.go ================================================ package testutil var IntrospectionQuery = ` query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description locations args { ...InputValue } # deprecated, but included for coverage till removed onOperation onFragment onField } } } 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 } } } } } } } } ` ================================================ FILE: testutil/rules_test_harness.go ================================================ package testutil import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/location" "github.com/graphql-go/graphql/language/parser" "github.com/graphql-go/graphql/language/source" ) var TestSchema *graphql.Schema func init() { var beingInterface = graphql.NewInterface(graphql.InterfaceConfig{ Name: "Being", Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "surname": &graphql.ArgumentConfig{ Type: graphql.Boolean, }, }, }, }, }) var petInterface = graphql.NewInterface(graphql.InterfaceConfig{ Name: "Pet", Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "surname": &graphql.ArgumentConfig{ Type: graphql.Boolean, }, }, }, }, }) var canineInterface = graphql.NewInterface(graphql.InterfaceConfig{ Name: "Canine", Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "surname": &graphql.ArgumentConfig{ Type: graphql.Boolean, }, }, }, }, }) var dogCommandEnum = graphql.NewEnum(graphql.EnumConfig{ Name: "DogCommand", Values: graphql.EnumValueConfigMap{ "SIT": &graphql.EnumValueConfig{ Value: 0, }, "HEEL": &graphql.EnumValueConfig{ Value: 1, }, "DOWN": &graphql.EnumValueConfig{ Value: 2, }, }, }) var dogType = graphql.NewObject(graphql.ObjectConfig{ Name: "Dog", IsTypeOf: func(p graphql.IsTypeOfParams) bool { return true }, Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "surname": &graphql.ArgumentConfig{ Type: graphql.Boolean, }, }, }, "nickname": &graphql.Field{ Type: graphql.String, }, "barkVolume": &graphql.Field{ Type: graphql.Int, }, "barks": &graphql.Field{ Type: graphql.Boolean, }, "doesKnowCommand": &graphql.Field{ Type: graphql.Boolean, Args: graphql.FieldConfigArgument{ "dogCommand": &graphql.ArgumentConfig{ Type: dogCommandEnum, }, "nextDogCommand": &graphql.ArgumentConfig{ Type: dogCommandEnum, }, }, }, "isHousetrained": &graphql.Field{ Type: graphql.Boolean, Args: graphql.FieldConfigArgument{ "atOtherHomes": &graphql.ArgumentConfig{ Type: graphql.Boolean, DefaultValue: true, }, }, }, "isAtLocation": &graphql.Field{ Type: graphql.Boolean, Args: graphql.FieldConfigArgument{ "x": &graphql.ArgumentConfig{ Type: graphql.Int, }, "y": &graphql.ArgumentConfig{ Type: graphql.Int, }, }, }, }, Interfaces: []*graphql.Interface{ beingInterface, petInterface, canineInterface, }, }) var furColorEnum = graphql.NewEnum(graphql.EnumConfig{ Name: "FurColor", Values: graphql.EnumValueConfigMap{ "BROWN": &graphql.EnumValueConfig{ Value: 0, }, "BLACK": &graphql.EnumValueConfig{ Value: 1, }, "TAN": &graphql.EnumValueConfig{ Value: 2, }, "SPOTTED": &graphql.EnumValueConfig{ Value: 3, }, }, }) var catType = graphql.NewObject(graphql.ObjectConfig{ Name: "Cat", IsTypeOf: func(p graphql.IsTypeOfParams) bool { return true }, Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "surname": &graphql.ArgumentConfig{ Type: graphql.Boolean, }, }, }, "nickname": &graphql.Field{ Type: graphql.String, }, "meowVolume": &graphql.Field{ Type: graphql.Int, }, "meows": &graphql.Field{ Type: graphql.Boolean, }, "furColor": &graphql.Field{ Type: furColorEnum, }, }, Interfaces: []*graphql.Interface{ beingInterface, petInterface, }, }) var catOrDogUnion = graphql.NewUnion(graphql.UnionConfig{ Name: "CatOrDog", Types: []*graphql.Object{ dogType, catType, }, }) var intelligentInterface = graphql.NewInterface(graphql.InterfaceConfig{ Name: "Intelligent", Fields: graphql.Fields{ "iq": &graphql.Field{ Type: graphql.Int, }, }, }) var humanType = graphql.NewObject(graphql.ObjectConfig{ Name: "Human", IsTypeOf: func(p graphql.IsTypeOfParams) bool { return true }, Interfaces: []*graphql.Interface{ beingInterface, intelligentInterface, }, Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "surname": &graphql.ArgumentConfig{ Type: graphql.Boolean, }, }, }, "pets": &graphql.Field{ Type: graphql.NewList(petInterface), }, "iq": &graphql.Field{ Type: graphql.Int, }, }, }) humanType.AddFieldConfig("relatives", &graphql.Field{ Type: graphql.NewList(humanType), }) var alienType = graphql.NewObject(graphql.ObjectConfig{ Name: "Alien", IsTypeOf: func(p graphql.IsTypeOfParams) bool { return true }, Interfaces: []*graphql.Interface{ beingInterface, intelligentInterface, }, Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "surname": &graphql.ArgumentConfig{ Type: graphql.Boolean, }, }, }, "iq": &graphql.Field{ Type: graphql.Int, }, "numEyes": &graphql.Field{ Type: graphql.Int, }, }, }) var dogOrHumanUnion = graphql.NewUnion(graphql.UnionConfig{ Name: "DogOrHuman", Types: []*graphql.Object{ dogType, humanType, }, ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { // not used for validation return nil }, }) var humanOrAlienUnion = graphql.NewUnion(graphql.UnionConfig{ Name: "HumanOrAlien", Types: []*graphql.Object{ alienType, humanType, }, ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { // not used for validation return nil }, }) var complexInputObject = graphql.NewInputObject(graphql.InputObjectConfig{ Name: "ComplexInput", Fields: graphql.InputObjectConfigFieldMap{ "requiredField": &graphql.InputObjectFieldConfig{ Type: graphql.NewNonNull(graphql.Boolean), }, "intField": &graphql.InputObjectFieldConfig{ Type: graphql.Int, }, "stringField": &graphql.InputObjectFieldConfig{ Type: graphql.String, }, "booleanField": &graphql.InputObjectFieldConfig{ Type: graphql.Boolean, }, "stringListField": &graphql.InputObjectFieldConfig{ Type: graphql.NewList(graphql.String), }, }, }) var complicatedArgs = graphql.NewObject(graphql.ObjectConfig{ Name: "ComplicatedArgs", // TODO List // TODO Coercion // TODO NotNulls Fields: graphql.Fields{ "intArgField": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "intArg": &graphql.ArgumentConfig{ Type: graphql.Int, }, }, }, "nonNullIntArgField": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "nonNullIntArg": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.Int), }, }, }, "stringArgField": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "stringArg": &graphql.ArgumentConfig{ Type: graphql.String, }, }, }, "booleanArgField": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "booleanArg": &graphql.ArgumentConfig{ Type: graphql.Boolean, }, }, }, "enumArgField": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "enumArg": &graphql.ArgumentConfig{ Type: furColorEnum, }, }, }, "floatArgField": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "floatArg": &graphql.ArgumentConfig{ Type: graphql.Float, }, }, }, "idArgField": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "idArg": &graphql.ArgumentConfig{ Type: graphql.ID, }, }, }, "stringListArgField": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "stringListArg": &graphql.ArgumentConfig{ Type: graphql.NewList(graphql.String), }, }, }, "complexArgField": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "complexArg": &graphql.ArgumentConfig{ Type: complexInputObject, }, }, }, "multipleReqs": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "req1": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.Int), }, "req2": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.Int), }, }, }, "multipleOpts": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "opt1": &graphql.ArgumentConfig{ Type: graphql.Int, DefaultValue: 0, }, "opt2": &graphql.ArgumentConfig{ Type: graphql.Int, DefaultValue: 0, }, }, }, "multipleOptAndReq": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "req1": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.Int), }, "req2": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.Int), }, "opt1": &graphql.ArgumentConfig{ Type: graphql.Int, DefaultValue: 0, }, "opt2": &graphql.ArgumentConfig{ Type: graphql.Int, DefaultValue: 0, }, }, }, }, }) queryRoot := graphql.NewObject(graphql.ObjectConfig{ Name: "QueryRoot", Fields: graphql.Fields{ "human": &graphql.Field{ Args: graphql.FieldConfigArgument{ "id": &graphql.ArgumentConfig{ Type: graphql.ID, }, }, Type: humanType, }, "alien": &graphql.Field{ Type: alienType, }, "dog": &graphql.Field{ Type: dogType, }, "cat": &graphql.Field{ Type: catType, }, "pet": &graphql.Field{ Type: petInterface, }, "catOrDog": &graphql.Field{ Type: catOrDogUnion, }, "dogOrHuman": &graphql.Field{ Type: dogOrHumanUnion, }, "humanOrAlien": &graphql.Field{ Type: humanOrAlienUnion, }, "complicatedArgs": &graphql.Field{ Type: complicatedArgs, }, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: queryRoot, Directives: []*graphql.Directive{ graphql.IncludeDirective, graphql.SkipDirective, graphql.NewDirective(graphql.DirectiveConfig{ Name: "onQuery", Locations: []string{graphql.DirectiveLocationQuery}, }), graphql.NewDirective(graphql.DirectiveConfig{ Name: "onMutation", Locations: []string{graphql.DirectiveLocationMutation}, }), graphql.NewDirective(graphql.DirectiveConfig{ Name: "onSubscription", Locations: []string{graphql.DirectiveLocationSubscription}, }), graphql.NewDirective(graphql.DirectiveConfig{ Name: "onField", Locations: []string{graphql.DirectiveLocationField}, }), graphql.NewDirective(graphql.DirectiveConfig{ Name: "onFragmentDefinition", Locations: []string{graphql.DirectiveLocationFragmentDefinition}, }), graphql.NewDirective(graphql.DirectiveConfig{ Name: "onFragmentSpread", Locations: []string{graphql.DirectiveLocationFragmentSpread}, }), graphql.NewDirective(graphql.DirectiveConfig{ Name: "onInlineFragment", Locations: []string{graphql.DirectiveLocationInlineFragment}, }), graphql.NewDirective(graphql.DirectiveConfig{ Name: "onSchema", Locations: []string{graphql.DirectiveLocationSchema}, }), graphql.NewDirective(graphql.DirectiveConfig{ Name: "onScalar", Locations: []string{graphql.DirectiveLocationScalar}, }), graphql.NewDirective(graphql.DirectiveConfig{ Name: "onObject", Locations: []string{graphql.DirectiveLocationObject}, }), graphql.NewDirective(graphql.DirectiveConfig{ Name: "onFieldDefinition", Locations: []string{graphql.DirectiveLocationFieldDefinition}, }), graphql.NewDirective(graphql.DirectiveConfig{ Name: "onArgumentDefinition", Locations: []string{graphql.DirectiveLocationArgumentDefinition}, }), graphql.NewDirective(graphql.DirectiveConfig{ Name: "onInterface", Locations: []string{graphql.DirectiveLocationInterface}, }), graphql.NewDirective(graphql.DirectiveConfig{ Name: "onUnion", Locations: []string{graphql.DirectiveLocationUnion}, }), graphql.NewDirective(graphql.DirectiveConfig{ Name: "onEnum", Locations: []string{graphql.DirectiveLocationEnum}, }), graphql.NewDirective(graphql.DirectiveConfig{ Name: "onEnumValue", Locations: []string{graphql.DirectiveLocationEnumValue}, }), graphql.NewDirective(graphql.DirectiveConfig{ Name: "onInputObject", Locations: []string{graphql.DirectiveLocationInputObject}, }), graphql.NewDirective(graphql.DirectiveConfig{ Name: "onInputFieldDefinition", Locations: []string{graphql.DirectiveLocationInputFieldDefinition}, }), }, Types: []graphql.Type{ catType, dogType, humanType, alienType, }, }) if err != nil { panic(err) } TestSchema = &schema } func expectValidRule(t *testing.T, schema *graphql.Schema, rules []graphql.ValidationRuleFn, queryString string) { source := source.NewSource(&source.Source{ Body: []byte(queryString), }) AST, err := parser.Parse(parser.ParseParams{Source: source}) if err != nil { t.Fatal(err) } result := graphql.ValidateDocument(schema, AST, rules) if len(result.Errors) > 0 { t.Fatalf("Should validate, got %v", result.Errors) } if result.IsValid != true { t.Fatalf("IsValid should be true, got %v", result.IsValid) } } func expectInvalidRule(t *testing.T, schema *graphql.Schema, rules []graphql.ValidationRuleFn, queryString string, expectedErrors []gqlerrors.FormattedError) { source := source.NewSource(&source.Source{ Body: []byte(queryString), }) AST, err := parser.Parse(parser.ParseParams{Source: source}) if err != nil { t.Fatal(err) } result := graphql.ValidateDocument(schema, AST, rules) if len(result.Errors) != len(expectedErrors) { t.Fatalf("Should have %v errors, got %v", len(expectedErrors), len(result.Errors)) } if result.IsValid != false { t.Fatalf("IsValid should be false, got %v", result.IsValid) } for _, expectedErr := range expectedErrors { found := false for _, err := range result.Errors { if EqualFormattedError(expectedErr, err) { found = true break } } if found == false { t.Fatalf("Unexpected result, Diff: %v", Diff(expectedErrors, result.Errors)) } } } func ExpectPassesRule(t *testing.T, rule graphql.ValidationRuleFn, queryString string) { expectValidRule(t, TestSchema, []graphql.ValidationRuleFn{rule}, queryString) } func ExpectFailsRule(t *testing.T, rule graphql.ValidationRuleFn, queryString string, expectedErrors []gqlerrors.FormattedError) { expectInvalidRule(t, TestSchema, []graphql.ValidationRuleFn{rule}, queryString, expectedErrors) } func ExpectFailsRuleWithSchema(t *testing.T, schema *graphql.Schema, rule graphql.ValidationRuleFn, queryString string, expectedErrors []gqlerrors.FormattedError) { expectInvalidRule(t, schema, []graphql.ValidationRuleFn{rule}, queryString, expectedErrors) } func ExpectPassesRuleWithSchema(t *testing.T, schema *graphql.Schema, rule graphql.ValidationRuleFn, queryString string) { expectValidRule(t, schema, []graphql.ValidationRuleFn{rule}, queryString) } func RuleError(message string, locs ...int) gqlerrors.FormattedError { locations := []location.SourceLocation{} for i := 0; i < len(locs); i += 2 { line := locs[i] col := 0 if i+1 < len(locs) { col = locs[i+1] } locations = append(locations, location.SourceLocation{ Line: line, Column: col, }) } return gqlerrors.FormattedError{ Message: message, Locations: locations, } } ================================================ FILE: testutil/subscription.go ================================================ package testutil import ( "bytes" "context" "encoding/json" "errors" "strconv" "testing" "github.com/graphql-go/graphql" ) // TestResponse models the expected response type TestResponse struct { Data string Errors []string } // TestSubscription is a GraphQL test case to be used with RunSubscribe. type TestSubscription struct { Name string Schema graphql.Schema Query string OperationName string Variables map[string]interface{} ExpectedResults []TestResponse } // RunSubscribes runs the given GraphQL subscription test cases as subtests. func RunSubscribes(t *testing.T, tests []*TestSubscription) { for i, test := range tests { if test.Name == "" { test.Name = strconv.Itoa(i + 1) } t.Run(test.Name, func(t *testing.T) { RunSubscribe(t, test) }) } } // RunSubscribe runs a single GraphQL subscription test case. func RunSubscribe(t *testing.T, test *TestSubscription) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() c := graphql.Subscribe(graphql.Params{ Context: ctx, OperationName: test.OperationName, RequestString: test.Query, VariableValues: test.Variables, Schema: test.Schema, }) // if err != nil { // if err.Error() != test.ExpectedErr.Error() { // t.Fatalf("unexpected error: got %+v, want %+v", err, test.ExpectedErr) // } // return // } var results []*graphql.Result for res := range c { t.Log(pretty(res)) results = append(results, res) } for i, expected := range test.ExpectedResults { if len(results)-1 < i { t.Error(errors.New("not enough results, expected results are more than actual results")) return } res := results[i] var errs []string for _, err := range res.Errors { errs = append(errs, err.Message) } checkErrorStrings(t, expected.Errors, errs) if expected.Data == "" { continue } got, err := json.MarshalIndent(res.Data, "", " ") if err != nil { t.Fatalf("got: invalid JSON: %s; raw: %s", err, got) } if err != nil { t.Fatal(err) } want, err := formatJSON(expected.Data) if err != nil { t.Fatalf("got: invalid JSON: %s; raw: %s", err, res.Data) } if !bytes.Equal(got, want) { t.Logf("got: %s", got) t.Logf("want: %s", want) t.Fail() } } } func checkErrorStrings(t *testing.T, expected, actual []string) { expectedCount, actualCount := len(expected), len(actual) if expectedCount != actualCount { t.Fatalf("unexpected number of errors: want `%d`, got `%d`", expectedCount, actualCount) } if expectedCount > 0 { for i, want := range expected { got := actual[i] if got != want { t.Fatalf("unexpected error: got `%+v`, want `%+v`", got, want) } } // Return because we're done checking. return } for _, err := range actual { t.Errorf("unexpected error: '%s'", err) } } func formatJSON(data string) ([]byte, error) { var v interface{} if err := json.Unmarshal([]byte(data), &v); err != nil { return nil, err } formatted, err := json.MarshalIndent(v, "", " ") if err != nil { return nil, err } return formatted, nil } func pretty(x interface{}) string { got, err := json.MarshalIndent(x, "", " ") if err != nil { panic(err) } return string(got) } ================================================ FILE: testutil/testutil.go ================================================ package testutil import ( "encoding/json" "fmt" "reflect" "strconv" "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/parser" ) var ( Luke StarWarsChar Vader StarWarsChar Han StarWarsChar Leia StarWarsChar Tarkin StarWarsChar Threepio StarWarsChar Artoo StarWarsChar HumanData map[int]StarWarsChar DroidData map[int]StarWarsChar StarWarsSchema graphql.Schema humanType *graphql.Object droidType *graphql.Object ) type StarWarsChar struct { ID string Name string Friends []StarWarsChar AppearsIn []int HomePlanet string PrimaryFunction string } func init() { Luke = StarWarsChar{ ID: "1000", Name: "Luke Skywalker", AppearsIn: []int{4, 5, 6}, HomePlanet: "Tatooine", } Vader = StarWarsChar{ ID: "1001", Name: "Darth Vader", AppearsIn: []int{4, 5, 6}, HomePlanet: "Tatooine", } Han = StarWarsChar{ ID: "1002", Name: "Han Solo", AppearsIn: []int{4, 5, 6}, } Leia = StarWarsChar{ ID: "1003", Name: "Leia Organa", AppearsIn: []int{4, 5, 6}, HomePlanet: "Alderaa", } Tarkin = StarWarsChar{ ID: "1004", Name: "Wilhuff Tarkin", AppearsIn: []int{4}, } Threepio = StarWarsChar{ ID: "2000", Name: "C-3PO", AppearsIn: []int{4, 5, 6}, PrimaryFunction: "Protocol", } Artoo = StarWarsChar{ ID: "2001", Name: "R2-D2", AppearsIn: []int{4, 5, 6}, PrimaryFunction: "Astromech", } Luke.Friends = append(Luke.Friends, []StarWarsChar{Han, Leia, Threepio, Artoo}...) Vader.Friends = append(Vader.Friends, []StarWarsChar{Tarkin}...) Han.Friends = append(Han.Friends, []StarWarsChar{Luke, Leia, Artoo}...) Leia.Friends = append(Leia.Friends, []StarWarsChar{Luke, Han, Threepio, Artoo}...) Tarkin.Friends = append(Tarkin.Friends, []StarWarsChar{Vader}...) Threepio.Friends = append(Threepio.Friends, []StarWarsChar{Luke, Han, Leia, Artoo}...) Artoo.Friends = append(Artoo.Friends, []StarWarsChar{Luke, Han, Leia}...) HumanData = map[int]StarWarsChar{ 1000: Luke, 1001: Vader, 1002: Han, 1003: Leia, 1004: Tarkin, } DroidData = map[int]StarWarsChar{ 2000: Threepio, 2001: Artoo, } episodeEnum := graphql.NewEnum(graphql.EnumConfig{ Name: "Episode", Description: "One of the films in the Star Wars Trilogy", Values: graphql.EnumValueConfigMap{ "NEWHOPE": &graphql.EnumValueConfig{ Value: 4, Description: "Released in 1977.", }, "EMPIRE": &graphql.EnumValueConfig{ Value: 5, Description: "Released in 1980.", }, "JEDI": &graphql.EnumValueConfig{ Value: 6, Description: "Released in 1983.", }, }, }) characterInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "Character", Description: "A character in the Star Wars Trilogy", Fields: graphql.Fields{ "id": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), Description: "The id of the character.", }, "name": &graphql.Field{ Type: graphql.String, Description: "The name of the character.", }, "appearsIn": &graphql.Field{ Type: graphql.NewList(episodeEnum), Description: "Which movies they appear in.", }, }, ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { if character, ok := p.Value.(StarWarsChar); ok { id, _ := strconv.Atoi(character.ID) human := GetHuman(id) if human.ID != "" { return humanType } } return droidType }, }) characterInterface.AddFieldConfig("friends", &graphql.Field{ Type: graphql.NewList(characterInterface), Description: "The friends of the character, or an empty list if they have none.", }) humanType = graphql.NewObject(graphql.ObjectConfig{ Name: "Human", Description: "A humanoid creature in the Star Wars universe.", Fields: graphql.Fields{ "id": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), Description: "The id of the human.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if human, ok := p.Source.(StarWarsChar); ok { return human.ID, nil } return nil, nil }, }, "name": &graphql.Field{ Type: graphql.String, Description: "The name of the human.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if human, ok := p.Source.(StarWarsChar); ok { return human.Name, nil } return nil, nil }, }, "friends": &graphql.Field{ Type: graphql.NewList(characterInterface), Description: "The friends of the human, or an empty list if they have none.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if human, ok := p.Source.(StarWarsChar); ok { return human.Friends, nil } return []interface{}{}, nil }, }, "appearsIn": &graphql.Field{ Type: graphql.NewList(episodeEnum), Description: "Which movies they appear in.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if human, ok := p.Source.(StarWarsChar); ok { return human.AppearsIn, nil } return nil, nil }, }, "homePlanet": &graphql.Field{ Type: graphql.String, Description: "The home planet of the human, or null if unknown.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if human, ok := p.Source.(StarWarsChar); ok { return human.HomePlanet, nil } return nil, nil }, }, }, Interfaces: []*graphql.Interface{ characterInterface, }, }) droidType = graphql.NewObject(graphql.ObjectConfig{ Name: "Droid", Description: "A mechanical creature in the Star Wars universe.", Fields: graphql.Fields{ "id": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), Description: "The id of the droid.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if droid, ok := p.Source.(StarWarsChar); ok { return droid.ID, nil } return nil, nil }, }, "name": &graphql.Field{ Type: graphql.String, Description: "The name of the droid.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if droid, ok := p.Source.(StarWarsChar); ok { return droid.Name, nil } return nil, nil }, }, "friends": &graphql.Field{ Type: graphql.NewList(characterInterface), Description: "The friends of the droid, or an empty list if they have none.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if droid, ok := p.Source.(StarWarsChar); ok { friends := []map[string]interface{}{} for _, friend := range droid.Friends { friends = append(friends, map[string]interface{}{ "name": friend.Name, "id": friend.ID, }) } return droid.Friends, nil } return []interface{}{}, nil }, }, "appearsIn": &graphql.Field{ Type: graphql.NewList(episodeEnum), Description: "Which movies they appear in.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if droid, ok := p.Source.(StarWarsChar); ok { return droid.AppearsIn, nil } return nil, nil }, }, "primaryFunction": &graphql.Field{ Type: graphql.String, Description: "The primary function of the droid.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if droid, ok := p.Source.(StarWarsChar); ok { return droid.PrimaryFunction, nil } return nil, nil }, }, }, Interfaces: []*graphql.Interface{ characterInterface, }, }) queryType := graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "hero": &graphql.Field{ Type: characterInterface, Args: graphql.FieldConfigArgument{ "episode": &graphql.ArgumentConfig{ Description: "If omitted, returns the hero of the whole saga. If " + "provided, returns the hero of that particular episode.", Type: episodeEnum, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return GetHero(p.Args["episode"]), nil }, }, "human": &graphql.Field{ Type: humanType, Args: graphql.FieldConfigArgument{ "id": &graphql.ArgumentConfig{ Description: "id of the human", Type: graphql.NewNonNull(graphql.String), }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { id, err := strconv.Atoi(p.Args["id"].(string)) if err != nil { return nil, err } return GetHuman(id), nil }, }, "droid": &graphql.Field{ Type: droidType, Args: graphql.FieldConfigArgument{ "id": &graphql.ArgumentConfig{ Description: "id of the droid", Type: graphql.NewNonNull(graphql.String), }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return GetDroid(p.Args["id"].(int)), nil }, }, }, }) StarWarsSchema, _ = graphql.NewSchema(graphql.SchemaConfig{ Query: queryType, }) } func GetHuman(id int) StarWarsChar { if human, ok := HumanData[id]; ok { return human } return StarWarsChar{} } func GetDroid(id int) StarWarsChar { if droid, ok := DroidData[id]; ok { return droid } return StarWarsChar{} } func GetHero(episode interface{}) interface{} { if episode == 5 { return Luke } return Artoo } // Test helper functions func TestParse(t *testing.T, query string) *ast.Document { astDoc, err := parser.Parse(parser.ParseParams{ Source: query, Options: parser.ParseOptions{ // include source, for error reporting NoSource: false, }, }) if err != nil { t.Fatalf("Parse failed: %v", err) } return astDoc } func TestExecute(t *testing.T, ep graphql.ExecuteParams) *graphql.Result { return graphql.Execute(ep) } func Diff(want, got interface{}) []string { return []string{fmt.Sprintf("\ngot: %v", got), fmt.Sprintf("\nwant: %v\n", want)} } func ASTToJSON(t *testing.T, a ast.Node) interface{} { b, err := json.Marshal(a) if err != nil { t.Fatalf("Failed to marshal Node %v", err) } var f interface{} err = json.Unmarshal(b, &f) if err != nil { t.Fatalf("Failed to unmarshal Node %v", err) } return f } func ContainSubsetSlice(super []interface{}, sub []interface{}) bool { if len(sub) == 0 { return true } subLoop: for _, subVal := range sub { found := false innerLoop: for _, superVal := range super { if subVal, ok := subVal.(map[string]interface{}); ok { if superVal, ok := superVal.(map[string]interface{}); ok { if ContainSubset(superVal, subVal) { found = true break innerLoop } else { continue } } else { return false } } if subVal, ok := subVal.([]interface{}); ok { if superVal, ok := superVal.([]interface{}); ok { if ContainSubsetSlice(superVal, subVal) { found = true break innerLoop } else { continue } } else { return false } } if reflect.DeepEqual(superVal, subVal) { found = true break innerLoop } } if !found { return false } continue subLoop } return true } func ContainSubset(super map[string]interface{}, sub map[string]interface{}) bool { if len(sub) == 0 { return true } for subKey, subVal := range sub { if superVal, ok := super[subKey]; ok { switch superVal := superVal.(type) { case []interface{}: if subVal, ok := subVal.([]interface{}); ok { if !ContainSubsetSlice(superVal, subVal) { return false } } else { return false } case map[string]interface{}: if subVal, ok := subVal.(map[string]interface{}); ok { if !ContainSubset(superVal, subVal) { return false } } else { return false } default: if !reflect.DeepEqual(superVal, subVal) { return false } } } else { return false } } return true } func EqualErrorMessage(expected, result *graphql.Result, i int) bool { return expected.Errors[i].Message == result.Errors[i].Message } func EqualFormattedError(exp, act gqlerrors.FormattedError) bool { if exp.Message != act.Message { return false } if !reflect.DeepEqual(exp.Locations, act.Locations) { return false } if !reflect.DeepEqual(exp.Path, act.Path) { return false } if !reflect.DeepEqual(exp.Extensions, act.Extensions) { return false } return true } func EqualFormattedErrors(expected, actual []gqlerrors.FormattedError) bool { if len(expected) != len(actual) { return false } for i := range expected { if !EqualFormattedError(expected[i], actual[i]) { return false } } return true } func EqualResults(expected, result *graphql.Result) bool { if !reflect.DeepEqual(expected.Data, result.Data) { return false } return EqualFormattedErrors(expected.Errors, result.Errors) } ================================================ FILE: testutil/testutil_test.go ================================================ package testutil_test import ( "testing" "github.com/graphql-go/graphql/testutil" ) func TestSubsetSlice_Simple(t *testing.T) { super := []interface{}{ "1", "2", "3", } sub := []interface{}{ "3", } if !testutil.ContainSubsetSlice(super, sub) { t.Fatalf("expected slice to be subset of super, got false") } } func TestSubsetSlice_Simple_Fail(t *testing.T) { super := []interface{}{ "1", "2", "3", } sub := []interface{}{ "4", } if testutil.ContainSubsetSlice(super, sub) { t.Fatalf("expected slice to not be subset of super, got true") } } func TestSubsetSlice_NestedSlice(t *testing.T) { super := []interface{}{ []interface{}{ "1", "2", "3", }, []interface{}{ "4", "5", "6", }, []interface{}{ "7", "8", "9", }, } sub := []interface{}{ []interface{}{ "2", }, []interface{}{ "9", }, []interface{}{ "5", }, } if !testutil.ContainSubsetSlice(super, sub) { t.Fatalf("expected slice to be subset of super, got false") } } func TestSubsetSlice_NestedSlice_DifferentLength(t *testing.T) { super := []interface{}{ []interface{}{ "1", "2", "3", }, []interface{}{ "4", "5", "6", }, []interface{}{ "7", "8", "9", }, } sub := []interface{}{ []interface{}{ "3", }, []interface{}{ "6", }, } if !testutil.ContainSubsetSlice(super, sub) { t.Fatalf("expected slice to be subset of super, got false") } } func TestSubsetSlice_NestedSlice_Fail(t *testing.T) { super := []interface{}{ []interface{}{ "1", "2", "3", }, []interface{}{ "4", "5", "6", }, []interface{}{ "7", "8", "9", }, } sub := []interface{}{ []interface{}{ "3", }, []interface{}{ "3", }, []interface{}{ "9", }, } if !testutil.ContainSubsetSlice(super, sub) { t.Fatalf("expected slice to be subset of super, got false") } } func TestSubset_Simple(t *testing.T) { super := map[string]interface{}{ "a": "1", "b": "2", "c": "3", } sub := map[string]interface{}{ "c": "3", } if !testutil.ContainSubset(super, sub) { t.Fatalf("expected map to be subset of super, got false") } } func TestSubset_Simple_Fail(t *testing.T) { super := map[string]interface{}{ "a": "1", "b": "2", "c": "3", } sub := map[string]interface{}{ "d": "3", } if testutil.ContainSubset(super, sub) { t.Fatalf("expected map to not be subset of super, got true") } } func TestSubset_NestedMap(t *testing.T) { super := map[string]interface{}{ "a": "1", "b": "2", "c": "3", "d": map[string]interface{}{ "aa": "11", "bb": "22", "cc": "33", }, } sub := map[string]interface{}{ "c": "3", "d": map[string]interface{}{ "cc": "33", }, } if !testutil.ContainSubset(super, sub) { t.Fatalf("expected map to be subset of super, got false") } } func TestSubset_NestedMap_Fail(t *testing.T) { super := map[string]interface{}{ "a": "1", "b": "2", "c": "3", "d": map[string]interface{}{ "aa": "11", "bb": "22", "cc": "33", }, } sub := map[string]interface{}{ "c": "3", "d": map[string]interface{}{ "dd": "44", }, } if testutil.ContainSubset(super, sub) { t.Fatalf("expected map to not be subset of super, got true") } } func TestSubset_NestedSlice(t *testing.T) { super := map[string]interface{}{ "a": "1", "b": "2", "c": "3", "d": []interface{}{ "11", "22", }, } sub := map[string]interface{}{ "c": "3", "d": []interface{}{ "11", }, } if !testutil.ContainSubset(super, sub) { t.Fatalf("expected map to be subset of super, got false") } } func TestSubset_ComplexMixed(t *testing.T) { super := map[string]interface{}{ "a": "1", "b": "2", "c": "3", "d": map[string]interface{}{ "aa": "11", "bb": "22", "cc": []interface{}{ "ttt", "rrr", "sss", }, }, "e": []interface{}{ "111", "222", "333", }, "f": []interface{}{ []interface{}{ "9999", "8888", "7777", }, []interface{}{ "6666", "5555", "4444", }, }, } sub := map[string]interface{}{ "c": "3", "d": map[string]interface{}{ "bb": "22", "cc": []interface{}{ "sss", }, }, "e": []interface{}{ "111", }, "f": []interface{}{ []interface{}{ "8888", "9999", }, []interface{}{ "4444", }, }, } if !testutil.ContainSubset(super, sub) { t.Fatalf("expected map to be subset of super, got false") } } func TestSubset_ComplexMixed_Fail(t *testing.T) { super := map[string]interface{}{ "a": "1", "b": "2", "c": "3", "d": map[string]interface{}{ "aa": "11", "bb": "22", "cc": []interface{}{ "ttt", "rrr", "sss", }, }, "e": []interface{}{ "111", "222", "333", }, "f": []interface{}{ []interface{}{ "9999", "8888", "7777", }, []interface{}{ "6666", "5555", "4444", }, }, } sub := map[string]interface{}{ "c": "3", "d": map[string]interface{}{ "bb": "22", "cc": []interface{}{ "doesnotexist", }, }, "e": []interface{}{ "111", }, "f": []interface{}{ []interface{}{ "4444", }, }, } if testutil.ContainSubset(super, sub) { t.Fatalf("expected map to not be subset of super, got true") } } ================================================ FILE: type_comparators_internal_test.go ================================================ package graphql import ( "testing" ) func TestIsEqualType_SameReferenceAreEqual(t *testing.T) { if !isEqualType(String, String) { t.Fatalf("Expected same reference to be equal") } } func TestIsEqualType_IntAndFloatAreNotEqual(t *testing.T) { if isEqualType(Int, Float) { t.Fatalf("Expected GraphQLInt and GraphQLFloat to not equal") } } func TestIsEqualType_ListsOfSameTypeAreEqual(t *testing.T) { if !isEqualType(NewList(Int), NewList(Int)) { t.Fatalf("Expected lists of same type are equal") } } func TestIsEqualType_ListsAreNotEqualToItem(t *testing.T) { if isEqualType(NewList(Int), Int) { t.Fatalf("Expected lists are not equal to item") } } func TestIsEqualType_NonNullOfSameTypeAreEqual(t *testing.T) { if !isEqualType(NewNonNull(Int), NewNonNull(Int)) { t.Fatalf("Expected non-null of same type are equal") } } func TestIsEqualType_NonNullIsNotEqualToNullable(t *testing.T) { if isEqualType(NewNonNull(Int), Int) { t.Fatalf("Expected non-null is not equal to nullable") } } func testSchemaForIsTypeSubTypeOfTest(t *testing.T, fields Fields) *Schema { schema, err := NewSchema(SchemaConfig{ Query: NewObject(ObjectConfig{ Name: "Query", Fields: fields, }), }) if err != nil { t.Fatalf("Invalid schema: %v", err) } return &schema } func TestIsTypeSubTypeOf_SameReferenceIsSubtype(t *testing.T) { schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ "field": &Field{Type: String}, }) if !isTypeSubTypeOf(schema, String, String) { t.Fatalf("Expected same reference is subtype") } } func TestIsTypeSubTypeOf_IntIsNotSubtypeOfFloat(t *testing.T) { schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ "field": &Field{Type: String}, }) if isTypeSubTypeOf(schema, Int, Float) { t.Fatalf("Expected int is not subtype of float") } } func TestIsTypeSubTypeOf_NonNullIsSubtypeOfNullable(t *testing.T) { schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ "field": &Field{Type: String}, }) if !isTypeSubTypeOf(schema, NewNonNull(Int), Int) { t.Fatalf("Expected non-null is subtype of nullable") } } func TestIsTypeSubTypeOf_NullableIsNotSubtypeOfNonNull(t *testing.T) { schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ "field": &Field{Type: String}, }) if isTypeSubTypeOf(schema, Int, NewNonNull(Int)) { t.Fatalf("Expected nullable is not subtype of non-null") } } func TestIsTypeSubTypeOf_ItemIsNotSubTypeOfList(t *testing.T) { schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ "field": &Field{Type: String}, }) if isTypeSubTypeOf(schema, Int, NewList(Int)) { t.Fatalf("Expected item is not subtype of list") } } func TestIsTypeSubTypeOf_ListIsNotSubtypeOfItem(t *testing.T) { schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ "field": &Field{Type: String}, }) if isTypeSubTypeOf(schema, NewList(Int), Int) { t.Fatalf("Expected list is not subtype of item") } } func TestIsTypeSubTypeOf_MemberIsSubtypeOfUnion(t *testing.T) { memberType := NewObject(ObjectConfig{ Name: "Object", IsTypeOf: func(p IsTypeOfParams) bool { return true }, Fields: Fields{ "field": &Field{Type: String}, }, }) unionType := NewUnion(UnionConfig{ Name: "Union", Types: []*Object{memberType}, }) schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ "field": &Field{Type: unionType}, }) if !isTypeSubTypeOf(schema, memberType, unionType) { t.Fatalf("Expected member is subtype of union") } } func TestIsTypeSubTypeOf_ImplementationIsSubtypeOfInterface(t *testing.T) { ifaceType := NewInterface(InterfaceConfig{ Name: "Interface", Fields: Fields{ "field": &Field{Type: String}, }, }) implType := NewObject(ObjectConfig{ Name: "Object", IsTypeOf: func(p IsTypeOfParams) bool { return true }, Interfaces: []*Interface{ifaceType}, Fields: Fields{ "field": &Field{Type: String}, }, }) schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ "field": &Field{Type: implType}, }) if !isTypeSubTypeOf(schema, implType, ifaceType) { t.Fatalf("Expected implementation is subtype of interface") } } ================================================ FILE: type_info.go ================================================ package graphql import ( "github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/kinds" ) // TODO: can move TypeInfo to a utils package if there ever is one /** * TypeInfo is a utility class which, given a GraphQL schema, can keep track * of the current field and type definitions at any point in a GraphQL document * AST during a recursive descent by calling `enter(node)` and `leave(node)`. */ type fieldDefFn func(schema *Schema, parentType Type, fieldAST *ast.Field) *FieldDefinition type TypeInfo struct { schema *Schema typeStack []Output parentTypeStack []Composite inputTypeStack []Input fieldDefStack []*FieldDefinition directive *Directive argument *Argument getFieldDef fieldDefFn } type TypeInfoConfig struct { Schema *Schema // NOTE: this experimental optional second parameter is only needed in order // to support non-spec-compliant codebases. You should never need to use it. // It may disappear in the future. FieldDefFn fieldDefFn } func NewTypeInfo(opts *TypeInfoConfig) *TypeInfo { getFieldDef := opts.FieldDefFn if getFieldDef == nil { getFieldDef = DefaultTypeInfoFieldDef } return &TypeInfo{ schema: opts.Schema, getFieldDef: getFieldDef, } } func (ti *TypeInfo) Type() Output { if len(ti.typeStack) > 0 { return ti.typeStack[len(ti.typeStack)-1] } return nil } func (ti *TypeInfo) ParentType() Composite { if len(ti.parentTypeStack) > 0 { return ti.parentTypeStack[len(ti.parentTypeStack)-1] } return nil } func (ti *TypeInfo) InputType() Input { if len(ti.inputTypeStack) > 0 { return ti.inputTypeStack[len(ti.inputTypeStack)-1] } return nil } func (ti *TypeInfo) FieldDef() *FieldDefinition { if len(ti.fieldDefStack) > 0 { return ti.fieldDefStack[len(ti.fieldDefStack)-1] } return nil } func (ti *TypeInfo) Directive() *Directive { return ti.directive } func (ti *TypeInfo) Argument() *Argument { return ti.argument } func (ti *TypeInfo) Enter(node ast.Node) { schema := ti.schema var ttype Type switch node := node.(type) { case *ast.SelectionSet: namedType := GetNamed(ti.Type()) var compositeType Composite if IsCompositeType(namedType) { compositeType, _ = namedType.(Composite) } ti.parentTypeStack = append(ti.parentTypeStack, compositeType) case *ast.Field: parentType := ti.ParentType() var fieldDef *FieldDefinition if parentType != nil { fieldDef = ti.getFieldDef(schema, parentType.(Type), node) } ti.fieldDefStack = append(ti.fieldDefStack, fieldDef) if fieldDef != nil { ti.typeStack = append(ti.typeStack, fieldDef.Type) } else { ti.typeStack = append(ti.typeStack, nil) } case *ast.Directive: nameVal := "" if node.Name != nil { nameVal = node.Name.Value } ti.directive = schema.Directive(nameVal) case *ast.OperationDefinition: if node.Operation == ast.OperationTypeQuery { ttype = schema.QueryType() } else if node.Operation == ast.OperationTypeMutation { ttype = schema.MutationType() } else if node.Operation == ast.OperationTypeSubscription { ttype = schema.SubscriptionType() } ti.typeStack = append(ti.typeStack, ttype) case *ast.InlineFragment: typeConditionAST := node.TypeCondition if typeConditionAST != nil { ttype, _ = typeFromAST(*schema, node.TypeCondition) ti.typeStack = append(ti.typeStack, ttype) } else { ti.typeStack = append(ti.typeStack, ti.Type()) } case *ast.FragmentDefinition: typeConditionAST := node.TypeCondition if typeConditionAST != nil { ttype, _ = typeFromAST(*schema, typeConditionAST) ti.typeStack = append(ti.typeStack, ttype) } else { ti.typeStack = append(ti.typeStack, ti.Type()) } case *ast.VariableDefinition: ttype, _ = typeFromAST(*schema, node.Type) ti.inputTypeStack = append(ti.inputTypeStack, ttype) case *ast.Argument: nameVal := "" if node.Name != nil { nameVal = node.Name.Value } var argType Input var argDef *Argument directive := ti.Directive() fieldDef := ti.FieldDef() if directive != nil { for _, arg := range directive.Args { if arg.Name() == nameVal { argDef = arg } } } else if fieldDef != nil { for _, arg := range fieldDef.Args { if arg.Name() == nameVal { argDef = arg } } } if argDef != nil { argType = argDef.Type } ti.argument = argDef ti.inputTypeStack = append(ti.inputTypeStack, argType) case *ast.ListValue: listType := GetNullable(ti.InputType()) if list, ok := listType.(*List); ok { ti.inputTypeStack = append(ti.inputTypeStack, list.OfType) } else { ti.inputTypeStack = append(ti.inputTypeStack, nil) } case *ast.ObjectField: var fieldType Input objectType := GetNamed(ti.InputType()) if objectType, ok := objectType.(*InputObject); ok { nameVal := "" if node.Name != nil { nameVal = node.Name.Value } if inputField, ok := objectType.Fields()[nameVal]; ok { fieldType = inputField.Type } } ti.inputTypeStack = append(ti.inputTypeStack, fieldType) } } func (ti *TypeInfo) Leave(node ast.Node) { kind := node.GetKind() switch kind { case kinds.SelectionSet: // pop ti.parentTypeStack if len(ti.parentTypeStack) > 0 { _, ti.parentTypeStack = ti.parentTypeStack[len(ti.parentTypeStack)-1], ti.parentTypeStack[:len(ti.parentTypeStack)-1] } case kinds.Field: // pop ti.fieldDefStack if len(ti.fieldDefStack) > 0 { _, ti.fieldDefStack = ti.fieldDefStack[len(ti.fieldDefStack)-1], ti.fieldDefStack[:len(ti.fieldDefStack)-1] } // pop ti.typeStack if len(ti.typeStack) > 0 { _, ti.typeStack = ti.typeStack[len(ti.typeStack)-1], ti.typeStack[:len(ti.typeStack)-1] } case kinds.Directive: ti.directive = nil case kinds.OperationDefinition, kinds.InlineFragment, kinds.FragmentDefinition: // pop ti.typeStack if len(ti.typeStack) > 0 { _, ti.typeStack = ti.typeStack[len(ti.typeStack)-1], ti.typeStack[:len(ti.typeStack)-1] } case kinds.VariableDefinition: // pop ti.inputTypeStack if len(ti.inputTypeStack) > 0 { _, ti.inputTypeStack = ti.inputTypeStack[len(ti.inputTypeStack)-1], ti.inputTypeStack[:len(ti.inputTypeStack)-1] } case kinds.Argument: ti.argument = nil // pop ti.inputTypeStack if len(ti.inputTypeStack) > 0 { _, ti.inputTypeStack = ti.inputTypeStack[len(ti.inputTypeStack)-1], ti.inputTypeStack[:len(ti.inputTypeStack)-1] } case kinds.ListValue, kinds.ObjectField: // pop ti.inputTypeStack if len(ti.inputTypeStack) > 0 { _, ti.inputTypeStack = ti.inputTypeStack[len(ti.inputTypeStack)-1], ti.inputTypeStack[:len(ti.inputTypeStack)-1] } } } // DefaultTypeInfoFieldDef Not exactly the same as the executor's definition of FieldDef, in this // statically evaluated environment we do not always have an Object type, // and need to handle Interface and Union types. func DefaultTypeInfoFieldDef(schema *Schema, parentType Type, fieldAST *ast.Field) *FieldDefinition { name := "" if fieldAST.Name != nil { name = fieldAST.Name.Value } if name == SchemaMetaFieldDef.Name && schema.QueryType() == parentType { return SchemaMetaFieldDef } if name == TypeMetaFieldDef.Name && schema.QueryType() == parentType { return TypeMetaFieldDef } if name == TypeNameMetaFieldDef.Name && parentType != nil { if t, ok := parentType.(*Object); ok && t != nil { return TypeNameMetaFieldDef } if t, ok := parentType.(*Interface); ok && t != nil { return TypeNameMetaFieldDef } if t, ok := parentType.(*Union); ok && t != nil { return TypeNameMetaFieldDef } } if parentType, ok := parentType.(*Object); ok && parentType != nil { field, _ := parentType.Fields()[name] return field } if parentType, ok := parentType.(*Interface); ok && parentType != nil { field, _ := parentType.Fields()[name] return field } return nil } ================================================ FILE: types.go ================================================ package graphql import ( "github.com/graphql-go/graphql/gqlerrors" ) // type Schema interface{} // Result has the response, errors and extensions from the resolved schema type Result struct { Data interface{} `json:"data"` Errors []gqlerrors.FormattedError `json:"errors,omitempty"` Extensions map[string]interface{} `json:"extensions,omitempty"` } // HasErrors just a simple function to help you decide if the result has errors or not func (r *Result) HasErrors() bool { return len(r.Errors) > 0 } ================================================ FILE: union_interface_test.go ================================================ package graphql_test import ( "context" "reflect" "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/testutil" ) type testNamedType interface { } type testPet interface { } type testDog2 struct { Name string `json:"name"` Barks bool `json:"barks"` } type testCat2 struct { Name string `json:"name"` Meows bool `json:"meows"` } type testPerson struct { Name string `json:"name"` Pets []testPet `json:"pets"` Friends []testNamedType `json:"friends"` } var namedType = graphql.NewInterface(graphql.InterfaceConfig{ Name: "Named", Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, }, }, }) var dogType = graphql.NewObject(graphql.ObjectConfig{ Name: "Dog", Interfaces: []*graphql.Interface{ namedType, }, Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, }, "barks": &graphql.Field{ Type: graphql.Boolean, }, }, IsTypeOf: func(p graphql.IsTypeOfParams) bool { _, ok := p.Value.(*testDog2) return ok }, }) var catType = graphql.NewObject(graphql.ObjectConfig{ Name: "Cat", Interfaces: []*graphql.Interface{ namedType, }, Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, }, "meows": &graphql.Field{ Type: graphql.Boolean, }, }, IsTypeOf: func(p graphql.IsTypeOfParams) bool { _, ok := p.Value.(*testCat2) return ok }, }) var petType = graphql.NewUnion(graphql.UnionConfig{ Name: "Pet", Types: []*graphql.Object{ dogType, catType, }, ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { if _, ok := p.Value.(*testCat2); ok { return catType } if _, ok := p.Value.(*testDog2); ok { return dogType } return nil }, }) var personType = graphql.NewObject(graphql.ObjectConfig{ Name: "Person", Interfaces: []*graphql.Interface{ namedType, }, Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, }, "pets": &graphql.Field{ Type: graphql.NewList(petType), }, "friends": &graphql.Field{ Type: graphql.NewList(namedType), }, }, IsTypeOf: func(p graphql.IsTypeOfParams) bool { _, ok := p.Value.(*testPerson) return ok }, }) var unionInterfaceTestSchema, _ = graphql.NewSchema(graphql.SchemaConfig{ Query: personType, Types: []graphql.Type{petType}, }) var garfield = &testCat2{"Garfield", false} var odie = &testDog2{"Odie", true} var liz = &testPerson{ Name: "Liz", } var john = &testPerson{ Name: "John", Pets: []testPet{ garfield, odie, }, Friends: []testNamedType{ liz, odie, }, } func TestUnionIntersectionTypes_CanIntrospectOnUnionAndIntersectionTypes(t *testing.T) { doc := ` { Named: __type(name: "Named") { kind name fields { name } interfaces { name } possibleTypes { name } enumValues { name } inputFields { name } } Pet: __type(name: "Pet") { kind name fields { name } interfaces { name } possibleTypes { name } enumValues { name } inputFields { name } } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "Named": map[string]interface{}{ "kind": "INTERFACE", "name": "Named", "fields": []interface{}{ map[string]interface{}{ "name": "name", }, }, "interfaces": nil, "possibleTypes": []interface{}{ map[string]interface{}{ "name": "Dog", }, map[string]interface{}{ "name": "Cat", }, map[string]interface{}{ "name": "Person", }, }, "enumValues": nil, "inputFields": nil, }, "Pet": map[string]interface{}{ "kind": "UNION", "name": "Pet", "fields": nil, "interfaces": nil, "possibleTypes": []interface{}{ map[string]interface{}{ "name": "Dog", }, map[string]interface{}{ "name": "Cat", }, }, "enumValues": nil, "inputFields": nil, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: unionInterfaceTestSchema, AST: ast, } result := testutil.TestExecute(t, ep) if len(result.Errors) != len(expected.Errors) { t.Fatalf("Unexpected errors, Diff: %v", testutil.Diff(expected.Errors, result.Errors)) } if !testutil.ContainSubset(expected.Data.(map[string]interface{}), result.Data.(map[string]interface{})) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected.Data, result.Data)) } } func TestUnionIntersectionTypes_ExecutesUsingUnionTypes(t *testing.T) { // NOTE: This is an *invalid* query, but it should be an *executable* query. doc := ` { __typename name pets { __typename name barks meows } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "__typename": "Person", "name": "John", "pets": []interface{}{ map[string]interface{}{ "__typename": "Cat", "name": "Garfield", "meows": false, }, map[string]interface{}{ "__typename": "Dog", "name": "Odie", "barks": true, }, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: unionInterfaceTestSchema, AST: ast, Root: john, } result := testutil.TestExecute(t, ep) if len(result.Errors) != len(expected.Errors) { t.Fatalf("Unexpected errors, Diff: %v", testutil.Diff(expected.Errors, result.Errors)) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestUnionIntersectionTypes_ExecutesUnionTypesWithInlineFragments(t *testing.T) { // This is the valid version of the query in the above test. doc := ` { __typename name pets { __typename ... on Dog { name barks } ... on Cat { name meows } } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "__typename": "Person", "name": "John", "pets": []interface{}{ map[string]interface{}{ "__typename": "Cat", "name": "Garfield", "meows": false, }, map[string]interface{}{ "__typename": "Dog", "name": "Odie", "barks": true, }, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: unionInterfaceTestSchema, AST: ast, Root: john, } result := testutil.TestExecute(t, ep) if len(result.Errors) != len(expected.Errors) { t.Fatalf("Unexpected errors, Diff: %v", testutil.Diff(expected.Errors, result.Errors)) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestUnionIntersectionTypes_ExecutesUsingInterfaceTypes(t *testing.T) { // NOTE: This is an *invalid* query, but it should be an *executable* query. doc := ` { __typename name friends { __typename name barks meows } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "__typename": "Person", "name": "John", "friends": []interface{}{ map[string]interface{}{ "__typename": "Person", "name": "Liz", }, map[string]interface{}{ "__typename": "Dog", "name": "Odie", "barks": true, }, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: unionInterfaceTestSchema, AST: ast, Root: john, } result := testutil.TestExecute(t, ep) if len(result.Errors) != len(expected.Errors) { t.Fatalf("Unexpected errors, Diff: %v", testutil.Diff(expected.Errors, result.Errors)) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestUnionIntersectionTypes_ExecutesInterfaceTypesWithInlineFragments(t *testing.T) { // This is the valid version of the query in the above test. doc := ` { __typename name friends { __typename name ... on Dog { barks } ... on Cat { meows } } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "__typename": "Person", "name": "John", "friends": []interface{}{ map[string]interface{}{ "__typename": "Person", "name": "Liz", }, map[string]interface{}{ "__typename": "Dog", "name": "Odie", "barks": true, }, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: unionInterfaceTestSchema, AST: ast, Root: john, } result := testutil.TestExecute(t, ep) if len(result.Errors) != len(expected.Errors) { t.Fatalf("Unexpected errors, Diff: %v", testutil.Diff(expected.Errors, result.Errors)) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestUnionIntersectionTypes_AllowsFragmentConditionsToBeAbstractTypes(t *testing.T) { doc := ` { __typename name pets { ...PetFields } friends { ...FriendFields } } fragment PetFields on Pet { __typename ... on Dog { name barks } ... on Cat { name meows } } fragment FriendFields on Named { __typename name ... on Dog { barks } ... on Cat { meows } } ` expected := &graphql.Result{ Data: map[string]interface{}{ "__typename": "Person", "name": "John", "friends": []interface{}{ map[string]interface{}{ "__typename": "Person", "name": "Liz", }, map[string]interface{}{ "__typename": "Dog", "name": "Odie", "barks": true, }, }, "pets": []interface{}{ map[string]interface{}{ "__typename": "Cat", "name": "Garfield", "meows": false, }, map[string]interface{}{ "__typename": "Dog", "name": "Odie", "barks": true, }, }, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: unionInterfaceTestSchema, AST: ast, Root: john, } result := testutil.TestExecute(t, ep) if len(result.Errors) != len(expected.Errors) { t.Fatalf("Unexpected errors, Diff: %v", testutil.Diff(expected.Errors, result.Errors)) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestUnionIntersectionTypes_GetsExecutionInfoInResolver(t *testing.T) { var encounteredContextValue string var encounteredSchema graphql.Schema var encounteredRootValue string var personType2 *graphql.Object namedType2 := graphql.NewInterface(graphql.InterfaceConfig{ Name: "Named", Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, }, }, ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { encounteredSchema = p.Info.Schema encounteredContextValue, _ = p.Context.Value("authToken").(string) encounteredRootValue = p.Info.RootValue.(*testPerson).Name return personType2 }, }) personType2 = graphql.NewObject(graphql.ObjectConfig{ Name: "Person", Interfaces: []*graphql.Interface{ namedType2, }, Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, }, "friends": &graphql.Field{ Type: graphql.NewList(namedType2), }, }, }) schema2, _ := graphql.NewSchema(graphql.SchemaConfig{ Query: personType2, }) john2 := &testPerson{ Name: "John", Friends: []testNamedType{ liz, }, } doc := `{ name, friends { name } }` expected := &graphql.Result{ Data: map[string]interface{}{ "name": "John", "friends": []interface{}{ map[string]interface{}{ "name": "Liz", }, }, }, } // parse query ast := testutil.TestParse(t, doc) // create context ctx := context.Background() ctx = context.WithValue(ctx, "authToken", "contextStringValue123") // execute ep := graphql.ExecuteParams{ Schema: schema2, AST: ast, Root: john2, Context: ctx, } result := testutil.TestExecute(t, ep) if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } if !reflect.DeepEqual("contextStringValue123", encounteredContextValue) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff("contextStringValue123", encounteredContextValue)) } if !reflect.DeepEqual("John", encounteredRootValue) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff("John", encounteredRootValue)) } if !reflect.DeepEqual(schema2, encounteredSchema) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(schema2, encounteredSchema)) } } func TestUnionIntersectionTypes_ValueMayBeNilPointer(t *testing.T) { var unionInterfaceTestSchema, _ = graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "query": &graphql.Field{ Type: graphql.NewObject(graphql.ObjectConfig{ Name: "query", Fields: graphql.Fields{ "pet": &graphql.Field{ Type: petType, }, "named": &graphql.Field{ Type: namedType, }, }, }), Resolve: func(_ graphql.ResolveParams) (interface{}, error) { return struct { Pet *testCat2 `graphql:"pet"` Named *testCat2 `graphql:"named"` }{nil, nil}, nil }, }, }, }), }) query := `{ query { pet { __typename } named { __typename name } } }` expected := &graphql.Result{ Data: map[string]interface{}{ "query": map[string]interface{}{ "pet": nil, "named": nil, }}, } result := g(t, graphql.Params{ Schema: unionInterfaceTestSchema, RequestString: query, }) if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } ================================================ FILE: util.go ================================================ package graphql import ( "encoding" "fmt" "reflect" "strings" ) const TAG = "json" // can't take recursive slice type // e.g // type Person struct{ // Friends []Person // } // it will throw panic stack-overflow func BindFields(obj interface{}) Fields { t := reflect.TypeOf(obj) v := reflect.ValueOf(obj) fields := make(map[string]*Field) if t.Kind() == reflect.Ptr { t = t.Elem() v = v.Elem() } for i := 0; i < t.NumField(); i++ { field := t.Field(i) tag := extractTag(field.Tag) if tag == "-" { continue } fieldType := field.Type if fieldType.Kind() == reflect.Ptr { fieldType = fieldType.Elem() } var graphType Output if fieldType.Kind() == reflect.Struct { itf := v.Field(i).Interface() if _, ok := itf.(encoding.TextMarshaler); ok { fieldType = reflect.TypeOf("") goto nonStruct } structFields := BindFields(itf) if tag == "" { fields = appendFields(fields, structFields) continue } else { graphType = NewObject(ObjectConfig{ Name: tag, Fields: structFields, }) } } nonStruct: if tag == "" { continue } if graphType == nil { graphType = getGraphType(fieldType) } fields[tag] = &Field{ Type: graphType, Resolve: func(p ResolveParams) (interface{}, error) { return extractValue(tag, p.Source), nil }, } } return fields } func getGraphType(tipe reflect.Type) Output { kind := tipe.Kind() switch kind { case reflect.String: return String case reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64: return Int case reflect.Float32, reflect.Float64: return Float case reflect.Bool: return Boolean case reflect.Slice: return getGraphList(tipe) } return String } func getGraphList(tipe reflect.Type) *List { if tipe.Kind() == reflect.Slice { switch tipe.Elem().Kind() { case reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64: return NewList(Int) case reflect.Bool: return NewList(Boolean) case reflect.Float32, reflect.Float64: return NewList(Float) case reflect.String: return NewList(String) } } // finally bind object t := reflect.New(tipe.Elem()) name := strings.Replace(fmt.Sprint(tipe.Elem()), ".", "_", -1) obj := NewObject(ObjectConfig{ Name: name, Fields: BindFields(t.Elem().Interface()), }) return NewList(obj) } func appendFields(dest, origin Fields) Fields { for key, value := range origin { dest[key] = value } return dest } func extractValue(originTag string, obj interface{}) interface{} { val := reflect.Indirect(reflect.ValueOf(obj)) for j := 0; j < val.NumField(); j++ { field := val.Type().Field(j) found := originTag == extractTag(field.Tag) if field.Type.Kind() == reflect.Struct { itf := val.Field(j).Interface() if str, ok := itf.(encoding.TextMarshaler); ok && found { byt, _ := str.MarshalText() return string(byt) } res := extractValue(originTag, itf) if res != nil { return res } } if found { return reflect.Indirect(val.Field(j)).Interface() } } return nil } func extractTag(tag reflect.StructTag) string { t := tag.Get(TAG) if t != "" { t = strings.Split(t, ",")[0] } return t } // lazy way of binding args func BindArg(obj interface{}, tags ...string) FieldConfigArgument { v := reflect.Indirect(reflect.ValueOf(obj)) var config = make(FieldConfigArgument) for i := 0; i < v.NumField(); i++ { field := v.Type().Field(i) mytag := extractTag(field.Tag) if inArray(tags, mytag) { config[mytag] = &ArgumentConfig{ Type: getGraphType(field.Type), } } } return config } func inArray(slice interface{}, item interface{}) bool { s := reflect.ValueOf(slice) if s.Kind() != reflect.Slice { panic("inArray() given a non-slice type") } for i := 0; i < s.Len(); i++ { if reflect.DeepEqual(item, s.Index(i).Interface()) { return true } } return false } ================================================ FILE: util_test.go ================================================ package graphql_test import ( "encoding/json" "log" "reflect" "testing" "time" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/testutil" ) type Person struct { Human Name string `json:"name"` Home Address `json:"home"` Hobbies []string `json:"hobbies"` Friends []Friend `json:"friends"` } type Human struct { Alive bool `json:"alive,omitempty"` Age int `json:"age"` Weight float64 `json:"weight"` DoB time.Time `json:"dob"` } type Friend struct { Name string `json:"name"` Address string `json:"address"` } type Address struct { Street string `json:"street"` City string `json:"city"` Test string `json:",omitempty"` } var personSource = Person{ Human: Human{ Age: 24, Weight: 70.1, Alive: true, DoB: time.Date(2019, 01, 01, 01, 01, 01, 0, time.UTC), }, Name: "John Doe", Home: Address{ Street: "Jl. G1", City: "Jakarta", }, Friends: friendSource, Hobbies: []string{"eat", "sleep", "code"}, } var friendSource = []Friend{ {Name: "Arief", Address: "palembang"}, {Name: "Al", Address: "semarang"}, } func TestBindFields(t *testing.T) { // create person type based on Person struct personType := graphql.NewObject(graphql.ObjectConfig{ Name: "Person", // pass empty Person struct to bind all of it's fields Fields: graphql.BindFields(Person{}), }) fields := graphql.Fields{ "person": &graphql.Field{ Type: personType, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return personSource, nil }, }, } rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: fields} schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)} schema, err := graphql.NewSchema(schemaConfig) if err != nil { log.Fatalf("failed to create new schema, error: %v", err) } // Query query := ` { person{ name, dob, home{street,city}, friends{name,address}, age, weight, alive, hobbies } } ` params := graphql.Params{Schema: schema, RequestString: query} r := graphql.Do(params) if len(r.Errors) > 0 { log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors) } rJSON, _ := json.Marshal(r) data := struct { Data struct { Person Person `json:"person"` } `json:"data"` }{} err = json.Unmarshal(rJSON, &data) if err != nil { log.Fatalf("failed to unmarshal. error: %v", err) } newPerson := data.Data.Person if !reflect.DeepEqual(newPerson, personSource) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(personSource, newPerson)) } } func TestBindArg(t *testing.T) { var friendObj = graphql.NewObject(graphql.ObjectConfig{ Name: "friend", Fields: graphql.BindFields(Friend{}), }) fields := graphql.Fields{ "friend": &graphql.Field{ Type: friendObj, //it can be added more than one since it's a slice Args: graphql.BindArg(Friend{}, "name"), Resolve: func(p graphql.ResolveParams) (interface{}, error) { if name, ok := p.Args["name"].(string); ok { for _, friend := range friendSource { if friend.Name == name { return friend, nil } } } return nil, nil }, }, } rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: fields} schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)} schema, err := graphql.NewSchema(schemaConfig) if err != nil { log.Fatalf("failed to create new schema, error: %v", err) } // Query query := ` { friend(name:"Arief"){ address } } ` params := graphql.Params{Schema: schema, RequestString: query} r := graphql.Do(params) if len(r.Errors) > 0 { log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors) } rJSON, _ := json.Marshal(r) data := struct { Data struct { Friend Friend `json:"friend"` } `json:"data"` }{} err = json.Unmarshal(rJSON, &data) if err != nil { log.Fatalf("failed to unmarshal. error: %v", err) } expectedAddress := "palembang" newFriend := data.Data.Friend if newFriend.Address != expectedAddress { t.Fatalf("Unexpected result, expected address to be %s but got %s", expectedAddress, newFriend.Address) } } ================================================ FILE: validation_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/language/ast" ) var someScalarType = graphql.NewScalar(graphql.ScalarConfig{ Name: "SomeScalar", Serialize: func(value interface{}) interface{} { return nil }, ParseValue: func(value interface{}) interface{} { return nil }, ParseLiteral: func(valueAST ast.Value) interface{} { return nil }, }) var someObjectType = graphql.NewObject(graphql.ObjectConfig{ Name: "SomeObject", Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, }, }, }) var objectWithIsTypeOf = graphql.NewObject(graphql.ObjectConfig{ Name: "ObjectWithIsTypeOf", IsTypeOf: func(p graphql.IsTypeOfParams) bool { return true }, Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, }, }, }) var someUnionType = graphql.NewUnion(graphql.UnionConfig{ Name: "SomeUnion", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Types: []*graphql.Object{ someObjectType, }, }) var someInterfaceType = graphql.NewInterface(graphql.InterfaceConfig{ Name: "SomeInterface", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, }, }, }) var someEnumType = graphql.NewEnum(graphql.EnumConfig{ Name: "SomeEnum", Values: graphql.EnumValueConfigMap{ "ONLY": &graphql.EnumValueConfig{}, }, }) var someInputObject = graphql.NewInputObject(graphql.InputObjectConfig{ Name: "SomeInputObject", Fields: graphql.InputObjectConfigFieldMap{ "f": &graphql.InputObjectFieldConfig{ Type: graphql.String, DefaultValue: "Hello", }, }, }) func withModifiers(ttypes []graphql.Type) []graphql.Type { res := ttypes for _, ttype := range ttypes { res = append(res, graphql.NewList(ttype)) } for _, ttype := range ttypes { res = append(res, graphql.NewNonNull(ttype)) } for _, ttype := range ttypes { res = append(res, graphql.NewNonNull(graphql.NewList(ttype))) } return res } var outputTypes = withModifiers([]graphql.Type{ graphql.String, someScalarType, someEnumType, someObjectType, someUnionType, someInterfaceType, }) var inputTypes = withModifiers([]graphql.Type{ graphql.String, someScalarType, someEnumType, someInputObject, }) func schemaWithFieldType(ttype graphql.Output) (graphql.Schema, error) { return graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "f": &graphql.Field{ Type: ttype, }, }, }), Types: []graphql.Type{ttype}, }) } func schemaWithInputObject(ttype graphql.Input) (graphql.Schema, error) { return graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "args": &graphql.ArgumentConfig{ Type: ttype, }, }, }, }, }), }) } func schemaWithObjectFieldOfType(fieldType graphql.Input) (graphql.Schema, error) { badObjectType := graphql.NewObject(graphql.ObjectConfig{ Name: "BadObject", Fields: graphql.Fields{ "badField": &graphql.Field{ Type: fieldType, }, }, }) return graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "f": &graphql.Field{ Type: badObjectType, }, }, }), }) } func schemaWithObjectImplementingType(implementedType *graphql.Interface) (graphql.Schema, error) { badObjectType := graphql.NewObject(graphql.ObjectConfig{ Name: "BadObject", Interfaces: []*graphql.Interface{implementedType}, Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, }, }, }) return graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "f": &graphql.Field{ Type: badObjectType, }, }, }), Types: []graphql.Type{badObjectType}, }) } func schemaWithUnionOfType(ttype *graphql.Object) (graphql.Schema, error) { badObjectType := graphql.NewUnion(graphql.UnionConfig{ Name: "BadUnion", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Types: []*graphql.Object{ttype}, }) return graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "f": &graphql.Field{ Type: badObjectType, }, }, }), }) } func schemaWithInterfaceFieldOfType(ttype graphql.Type) (graphql.Schema, error) { badInterfaceType := graphql.NewInterface(graphql.InterfaceConfig{ Name: "BadInterface", Fields: graphql.Fields{ "badField": &graphql.Field{ Type: ttype, }, }, }) return graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "f": &graphql.Field{ Type: badInterfaceType, }, }, }), }) } func schemaWithArgOfType(ttype graphql.Type) (graphql.Schema, error) { badObject := graphql.NewObject(graphql.ObjectConfig{ Name: "BadObject", Fields: graphql.Fields{ "badField": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "badArg": &graphql.ArgumentConfig{ Type: ttype, }, }, }, }, }) return graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "f": &graphql.Field{ Type: badObject, }, }, }), }) } func schemaWithInputFieldOfType(ttype graphql.Type) (graphql.Schema, error) { badInputObject := graphql.NewInputObject(graphql.InputObjectConfig{ Name: "BadInputObject", Fields: graphql.InputObjectConfigFieldMap{ "badField": &graphql.InputObjectFieldConfig{ Type: ttype, }, }, }) return graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "badArg": &graphql.ArgumentConfig{ Type: badInputObject, }, }, }, }, }), }) } func TestTypeSystem_SchemaMustHaveObjectRootTypes_AcceptsASchemaWhoseQueryTypeIsAnObjectType(t *testing.T) { _, err := graphql.NewSchema(graphql.SchemaConfig{ Query: someObjectType, }) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestTypeSystem_SchemaMustHaveObjectRootTypes_AcceptsASchemaWhoseQueryAndMutationTypesAreObjectType(t *testing.T) { mutationObject := graphql.NewObject(graphql.ObjectConfig{ Name: "Mutation", Fields: graphql.Fields{ "edit": &graphql.Field{ Type: graphql.String, }, }, }) _, err := graphql.NewSchema(graphql.SchemaConfig{ Query: someObjectType, Mutation: mutationObject, }) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestTypeSystem_SchemaMustHaveObjectRootTypes_AcceptsASchemaWhoseQueryAndSubscriptionTypesAreObjectType(t *testing.T) { subscriptionType := graphql.NewObject(graphql.ObjectConfig{ Name: "Subscription", Fields: graphql.Fields{ "subscribe": &graphql.Field{ Type: graphql.String, }, }, }) _, err := graphql.NewSchema(graphql.SchemaConfig{ Query: someObjectType, Mutation: subscriptionType, }) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestTypeSystem_SchemaMustHaveObjectRootTypes_RejectsASchemaWithoutAQueryType(t *testing.T) { _, err := graphql.NewSchema(graphql.SchemaConfig{}) expectedError := "Schema query must be Object Type but got: nil." if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_SchemaMustContainUniquelyNamedTypes_RejectsASchemaWhichRedefinesABuiltInType(t *testing.T) { fakeString := graphql.NewScalar(graphql.ScalarConfig{ Name: "String", Serialize: func(value interface{}) interface{} { return nil }, }) queryType := graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "normal": &graphql.Field{ Type: graphql.String, }, "fake": &graphql.Field{ Type: fakeString, }, }, }) _, err := graphql.NewSchema(graphql.SchemaConfig{ Query: queryType, }) expectedError := `Schema must contain unique named types but contains multiple types named "String".` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_SchemaMustContainUniquelyNamedTypes_RejectsASchemaWhichDefinesAnObjectTypeTwice(t *testing.T) { a := graphql.NewObject(graphql.ObjectConfig{ Name: "SameName", Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, }, }, }) b := graphql.NewObject(graphql.ObjectConfig{ Name: "SameName", Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, }, }, }) queryType := graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "a": &graphql.Field{ Type: a, }, "b": &graphql.Field{ Type: b, }, }, }) _, err := graphql.NewSchema(graphql.SchemaConfig{ Query: queryType, }) expectedError := `Schema must contain unique named types but contains multiple types named "SameName".` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_SchemaMustContainUniquelyNamedTypes_RejectsASchemaWhichHaveSameNamedObjectsImplementingAnInterface(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, }, }, }) FirstBadObject := graphql.NewObject(graphql.ObjectConfig{ Name: "BadObject", Interfaces: []*graphql.Interface{ anotherInterface, }, Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, }, }, }) SecondBadObject := graphql.NewObject(graphql.ObjectConfig{ Name: "BadObject", Interfaces: []*graphql.Interface{ anotherInterface, }, Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, }, }, }) queryType := graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "iface": &graphql.Field{ Type: anotherInterface, }, }, }) _, err := graphql.NewSchema(graphql.SchemaConfig{ Query: queryType, Types: []graphql.Type{FirstBadObject, SecondBadObject}, }) expectedError := `Schema must contain unique named types but contains multiple types named "BadObject".` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_ObjectsMustHaveFields_AcceptsAnObjectTypeWithFieldsObject(t *testing.T) { _, err := schemaWithFieldType(graphql.NewObject(graphql.ObjectConfig{ Name: "SomeObject", Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, }, }, })) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestTypeSystem_ObjectsMustHaveFields_RejectsAnObjectTypeWithMissingFields(t *testing.T) { badObject := graphql.NewObject(graphql.ObjectConfig{ Name: "SomeObject", }) _, err := schemaWithFieldType(badObject) expectedError := `SomeObject fields must be an object with field names as keys or a function which return such an object.` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_ObjectsMustHaveFields_RejectsAnObjectTypeWithIncorrectlyNamedFields(t *testing.T) { badObject := graphql.NewObject(graphql.ObjectConfig{ Name: "SomeObject", Fields: graphql.Fields{ "bad-name-with-dashes": &graphql.Field{ Type: graphql.String, }, }, }) _, err := schemaWithFieldType(badObject) expectedError := `Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "bad-name-with-dashes" does not.` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_ObjectsMustHaveFields_RejectsAnObjectTypeWithEmptyFields(t *testing.T) { badObject := graphql.NewObject(graphql.ObjectConfig{ Name: "SomeObject", Fields: graphql.Fields{}, }) _, err := schemaWithFieldType(badObject) expectedError := `SomeObject fields must be an object with field names as keys or a function which return such an object.` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_FieldsArgsMustBeProperlyNamed_AcceptsFieldArgsWithValidNames(t *testing.T) { _, err := schemaWithFieldType(graphql.NewObject(graphql.ObjectConfig{ Name: "SomeObject", Fields: graphql.Fields{ "goodField": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "goodArgs": &graphql.ArgumentConfig{ Type: graphql.String, }, }, }, }, })) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestTypeSystem_FieldsArgsMustBeProperlyNamed_RejectsFieldArgWithInvalidNames(t *testing.T) { _, err := schemaWithFieldType(graphql.NewObject(graphql.ObjectConfig{ Name: "SomeObject", Fields: graphql.Fields{ "badField": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "bad-name-with-dashes": &graphql.ArgumentConfig{ Type: graphql.String, }, }, }, }, })) expectedError := `Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "bad-name-with-dashes" does not.` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_FieldsArgsMustBeObjects_AcceptsAnObjectTypeWithFieldArgs(t *testing.T) { _, err := schemaWithFieldType(graphql.NewObject(graphql.ObjectConfig{ Name: "SomeObject", Fields: graphql.Fields{ "goodField": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "goodArgs": &graphql.ArgumentConfig{ Type: graphql.String, }, }, }, }, })) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestTypeSystem_ObjectInterfacesMustBeArray_AcceptsAnObjectTypeWithArrayInterfaces(t *testing.T) { anotherInterfaceType := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, }, }, }) _, err := schemaWithFieldType(graphql.NewObject(graphql.ObjectConfig{ Name: "SomeObject", Interfaces: (graphql.InterfacesThunk)(func() []*graphql.Interface { return []*graphql.Interface{anotherInterfaceType} }), Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, }, }, })) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestTypeSystem_ObjectInterfacesMustBeArray_AcceptsAnObjectTypeWithInterfacesAsFunctionReturningAnArray(t *testing.T) { anotherInterfaceType := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, }, }, }) _, err := schemaWithFieldType(graphql.NewObject(graphql.ObjectConfig{ Name: "SomeObject", Interfaces: []*graphql.Interface{anotherInterfaceType}, Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, }, }, })) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestTypeSystem_UnionTypesMustBeArray_AcceptsAUnionTypeWithArrayTypes(t *testing.T) { _, err := schemaWithFieldType(graphql.NewUnion(graphql.UnionConfig{ Name: "SomeUnion", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Types: []*graphql.Object{ someObjectType, }, })) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestTypeSystem_UnionTypesMustBeArray_RejectsAUnionTypeWithoutTypes(t *testing.T) { _, err := schemaWithFieldType(graphql.NewUnion(graphql.UnionConfig{ Name: "SomeUnion", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, })) expectedError := "Must provide Array of types for Union SomeUnion." if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_UnionTypesMustBeArray_RejectsAUnionTypeWithEmptyTypes(t *testing.T) { _, err := schemaWithFieldType(graphql.NewUnion(graphql.UnionConfig{ Name: "SomeUnion", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Types: []*graphql.Object{}, })) expectedError := "Must provide Array of types for Union SomeUnion." if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_InputObjectsMustHaveFields_AcceptsAnInputObjectTypeWithFields(t *testing.T) { _, err := schemaWithInputObject(graphql.NewInputObject(graphql.InputObjectConfig{ Name: "SomeInputObject", Fields: graphql.InputObjectConfigFieldMap{ "f": &graphql.InputObjectFieldConfig{ Type: graphql.String, }, }, })) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestTypeSystem_InputObjectsMustHaveFields_AcceptsAnInputObjectTypeWithAFieldFunction(t *testing.T) { _, err := schemaWithInputObject(graphql.NewInputObject(graphql.InputObjectConfig{ Name: "SomeInputObject", Fields: (graphql.InputObjectConfigFieldMapThunk)(func() graphql.InputObjectConfigFieldMap { return graphql.InputObjectConfigFieldMap{ "f": &graphql.InputObjectFieldConfig{ Type: graphql.String, }, } }), })) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestTypeSystem_InputObjectsMustHaveFields_RejectsAnInputObjectTypeWithMissingFields(t *testing.T) { _, err := schemaWithInputObject(graphql.NewInputObject(graphql.InputObjectConfig{ Name: "SomeInputObject", })) expectedError := "SomeInputObject fields must be an object with field names as keys or a function which return such an object." if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_InputObjectsMustHaveFields_RejectsAnInputObjectTypeWithEmptyFields(t *testing.T) { _, err := schemaWithInputObject(graphql.NewInputObject(graphql.InputObjectConfig{ Name: "SomeInputObject", Fields: graphql.InputObjectConfigFieldMap{}, })) expectedError := "SomeInputObject fields must be an object with field names as keys or a function which return such an object." if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_ObjectTypesMustBeAssertable_AcceptsAnObjectTypeWithAnIsTypeOfFunction(t *testing.T) { _, err := schemaWithFieldType(graphql.NewObject(graphql.ObjectConfig{ Name: "AnotherObject", IsTypeOf: func(p graphql.IsTypeOfParams) bool { return true }, Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, }, }, })) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestTypeSystem_InterfaceTypesMustBeResolvable_AcceptsAnInterfaceTypeDefiningResolveType(t *testing.T) { anotherInterfaceType := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, }, }, }) _, err := schemaWithFieldType(graphql.NewObject(graphql.ObjectConfig{ Name: "SomeObject", Interfaces: []*graphql.Interface{anotherInterfaceType}, Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, }, }, })) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestTypeSystem_InterfaceTypesMustBeResolvable_AcceptsAnInterfaceWithImplementingTypeDefiningIsTypeOf(t *testing.T) { anotherInterfaceType := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, }, }, }) _, err := schemaWithFieldType(graphql.NewObject(graphql.ObjectConfig{ Name: "SomeObject", Interfaces: []*graphql.Interface{anotherInterfaceType}, IsTypeOf: func(p graphql.IsTypeOfParams) bool { return true }, Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, }, }, })) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestTypeSystem_InterfaceTypesMustBeResolvable_AcceptsAnInterfaceTypeDefiningResolveTypeWithImplementingTypeDefiningIsTypeOf(t *testing.T) { anotherInterfaceType := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, }, }, }) _, err := schemaWithFieldType(graphql.NewObject(graphql.ObjectConfig{ Name: "SomeObject", Interfaces: []*graphql.Interface{anotherInterfaceType}, IsTypeOf: func(p graphql.IsTypeOfParams) bool { return true }, Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, }, }, })) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestTypeSystem_UnionTypesMustBeResolvable_AcceptsAUnionTypeDefiningResolveType(t *testing.T) { _, err := schemaWithFieldType(graphql.NewUnion(graphql.UnionConfig{ Name: "SomeUnion", Types: []*graphql.Object{someObjectType}, ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, })) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestTypeSystem_UnionTypesMustBeResolvable_AcceptsAUnionOfObjectTypesDefiningIsTypeOf(t *testing.T) { _, err := schemaWithFieldType(graphql.NewUnion(graphql.UnionConfig{ Name: "SomeUnion", Types: []*graphql.Object{objectWithIsTypeOf}, })) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestTypeSystem_UnionTypesMustBeResolvable_AcceptsAUnionTypeDefiningResolveTypeOfObjectTypesDefiningIsTypeOf(t *testing.T) { _, err := schemaWithFieldType(graphql.NewUnion(graphql.UnionConfig{ Name: "SomeUnion", Types: []*graphql.Object{objectWithIsTypeOf}, ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, })) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestTypeSystem_UnionTypesMustBeResolvable_RejectsAUnionTypeNotDefiningResolveTypeOfObjectTypesNotDefiningIsTypeOf(t *testing.T) { _, err := schemaWithFieldType(graphql.NewUnion(graphql.UnionConfig{ Name: "SomeUnion", Types: []*graphql.Object{someObjectType}, })) expectedError := `Union Type SomeUnion does not provide a "resolveType" function and ` + `possible Type SomeObject does not provide a "isTypeOf" function. ` + `There is no way to resolve this possible type during execution.` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_ScalarTypesMustBeSerializable_AcceptsAScalarTypeDefiningSerialize(t *testing.T) { _, err := schemaWithFieldType(graphql.NewScalar(graphql.ScalarConfig{ Name: "SomeScalar", Serialize: func(value interface{}) interface{} { return nil }, })) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestTypeSystem_ScalarTypesMustBeSerializable_RejectsAScalarTypeNotDefiningSerialize(t *testing.T) { _, err := schemaWithFieldType(graphql.NewScalar(graphql.ScalarConfig{ Name: "SomeScalar", })) expectedError := `SomeScalar must provide "serialize" function. If this custom Scalar ` + `is also used as an input type, ensure "parseValue" and "parseLiteral" ` + `functions are also provided.` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_ScalarTypesMustBeSerializable_AcceptsAScalarTypeDefiningParseValueAndParseLiteral(t *testing.T) { _, err := schemaWithFieldType(graphql.NewScalar(graphql.ScalarConfig{ Name: "SomeScalar", Serialize: func(value interface{}) interface{} { return nil }, ParseValue: func(value interface{}) interface{} { return nil }, ParseLiteral: func(valueAST ast.Value) interface{} { return nil }, })) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestTypeSystem_ScalarTypesMustBeSerializable_RejectsAScalarTypeDefiningParseValueButNotParseLiteral(t *testing.T) { _, err := schemaWithFieldType(graphql.NewScalar(graphql.ScalarConfig{ Name: "SomeScalar", Serialize: func(value interface{}) interface{} { return nil }, ParseValue: func(value interface{}) interface{} { return nil }, })) expectedError := `SomeScalar must provide both "parseValue" and "parseLiteral" functions.` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_ScalarTypesMustBeSerializable_RejectsAScalarTypeDefiningParseLiteralButNotParseValue(t *testing.T) { _, err := schemaWithFieldType(graphql.NewScalar(graphql.ScalarConfig{ Name: "SomeScalar", Serialize: func(value interface{}) interface{} { return nil }, ParseLiteral: func(valueAST ast.Value) interface{} { return nil }, })) expectedError := `SomeScalar must provide both "parseValue" and "parseLiteral" functions.` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_EnumTypesMustBeWellDefined_AcceptsAWellDefinedEnumTypeWithEmptyValueDefinition(t *testing.T) { _, err := schemaWithFieldType(graphql.NewEnum(graphql.EnumConfig{ Name: "SomeEnum", Values: graphql.EnumValueConfigMap{ "FOO": &graphql.EnumValueConfig{}, "BAR": &graphql.EnumValueConfig{}, }, })) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestTypeSystem_EnumTypesMustBeWellDefined_AcceptsAWellDefinedEnumTypeWithInternalValueDefinition(t *testing.T) { _, err := schemaWithFieldType(graphql.NewEnum(graphql.EnumConfig{ Name: "SomeEnum", Values: graphql.EnumValueConfigMap{ "FOO": &graphql.EnumValueConfig{ Value: 10, }, "BAR": &graphql.EnumValueConfig{ Value: 20, }, }, })) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestTypeSystem_EnumTypesMustBeWellDefined_RejectsAnEnumTypeWithoutValues(t *testing.T) { _, err := schemaWithFieldType(graphql.NewEnum(graphql.EnumConfig{ Name: "SomeEnum", })) expectedError := `SomeEnum values must be an object with value names as keys.` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_EnumTypesMustBeWellDefined_RejectsAnEnumTypeWithEmptyValues(t *testing.T) { _, err := schemaWithFieldType(graphql.NewEnum(graphql.EnumConfig{ Name: "SomeEnum", Values: graphql.EnumValueConfigMap{}, })) expectedError := `SomeEnum values must be an object with value names as keys.` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_ObjectFieldsMustHaveOutputTypes_AcceptAnOutputTypeAsAnObjectFieldType(t *testing.T) { for _, ttype := range outputTypes { _, err := schemaWithObjectFieldOfType(ttype) if err != nil { t.Fatalf(`unexpected error: %v for type "%v"`, err, ttype) } } } func TestTypeSystem_ObjectFieldsMustHaveOutputTypes_RejectsAnEmptyObjectFieldType(t *testing.T) { _, err := schemaWithObjectFieldOfType(nil) expectedError := `BadObject.badField field type must be Output Type but got: .` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_ObjectsCanOnlyImplementInterfaces_AcceptsAnObjectImplementingAnInterface(t *testing.T) { anotherInterfaceType := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ "f": &graphql.Field{ Type: graphql.String, }, }, }) _, err := schemaWithObjectImplementingType(anotherInterfaceType) if err != nil { t.Fatalf(`unexpected error: %v"`, err) } } func TestTypeSystem_ObjectsCanOnlyImplementInterfaces_RejectsAnObjectImplementingANonInterfaceType(t *testing.T) { _, err := schemaWithObjectImplementingType(nil) expectedError := `BadObject may only implement Interface types, it cannot implement: .` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_UnionsMustRepresentObjectTypes_AcceptsAUnionOfAnObjectType(t *testing.T) { _, err := schemaWithUnionOfType(someObjectType) if err != nil { t.Fatalf(`unexpected error: %v"`, err) } } func TestTypeSystem_UnionsMustRepresentObjectTypes_RejectsAUnionOfNonObjectTypes(t *testing.T) { _, err := schemaWithUnionOfType(nil) expectedError := `BadUnion may only contain Object types, it cannot contain: .` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_InterfaceFieldsMustHaveOutputTypes_AcceptsAnOutputTypeAsAnInterfaceFieldType(t *testing.T) { for _, ttype := range outputTypes { _, err := schemaWithInterfaceFieldOfType(ttype) if err != nil { t.Fatalf(`unexpected error: %v for type "%v"`, err, ttype) } } } func TestTypeSystem_InterfaceFieldsMustHaveOutputTypes_RejectsAnEmptyInterfaceFieldType(t *testing.T) { _, err := schemaWithInterfaceFieldOfType(nil) expectedError := `BadInterface.badField field type must be Output Type but got: .` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_FieldArgumentsMustHaveInputTypes_AcceptsAnInputTypeAsFieldArgType(t *testing.T) { for _, ttype := range inputTypes { _, err := schemaWithArgOfType(ttype) if err != nil { t.Fatalf(`unexpected error: %v for type "%v"`, err, ttype) } } } func TestTypeSystem_FieldArgumentsMustHaveInputTypes_RejectsAnEmptyFieldArgType(t *testing.T) { _, err := schemaWithArgOfType(nil) expectedError := `BadObject.badField(badArg:) argument type must be Input Type but got: .` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_InputObjectFieldsMustHaveInputTypes_AcceptsAnInputTypeAsInputFieldType(t *testing.T) { for _, ttype := range inputTypes { _, err := schemaWithInputFieldOfType(ttype) if err != nil { t.Fatalf(`unexpected error: %v for type "%v"`, err, ttype) } } } func TestTypeSystem_InputObjectFieldsMustHaveInputTypes_RejectsAnEmptyInputFieldType(t *testing.T) { _, err := schemaWithInputFieldOfType(nil) expectedError := `BadInputObject.badField field type must be Input Type but got: .` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_ListMustAcceptGraphQLTypes_AcceptsAnTypeAsItemTypeOfList(t *testing.T) { testTypes := withModifiers([]graphql.Type{ graphql.String, someScalarType, someEnumType, someObjectType, someUnionType, someInterfaceType, }) for _, ttype := range testTypes { result := graphql.NewList(ttype) if result.Error() != nil { t.Fatalf(`unexpected error: %v for type "%v"`, result.Error(), ttype) } } } func TestTypeSystem_ListMustAcceptGraphQLTypes_RejectsANilTypeAsItemTypeOfList(t *testing.T) { result := graphql.NewList(nil) expectedError := `Can only create List of a Type but got: .` if result.Error() == nil || result.Error().Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, result.Error()) } } func TestTypeSystem_NonNullMustAcceptGraphQLTypes_AcceptsAnTypeAsNullableTypeOfNonNull(t *testing.T) { nullableTypes := []graphql.Type{ graphql.String, someScalarType, someObjectType, someUnionType, someInterfaceType, someEnumType, someInputObject, graphql.NewList(graphql.String), graphql.NewList(graphql.NewNonNull(graphql.String)), } for _, ttype := range nullableTypes { result := graphql.NewNonNull(ttype) if result.Error() != nil { t.Fatalf(`unexpected error: %v for type "%v"`, result.Error(), ttype) } } } func TestTypeSystem_NonNullMustAcceptGraphQLTypes_RejectsNilAsNonNullableType(t *testing.T) { result := graphql.NewNonNull(nil) expectedError := `Can only create NonNull of a Nullable Type but got: .` if result.Error() == nil || result.Error().Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, result.Error()) } } func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_AcceptsAnObjectWhichImplementsAnInterface(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: graphql.String, }, }, }, }, }) anotherObject := graphql.NewObject(graphql.ObjectConfig{ Name: "AnotherObject", Interfaces: []*graphql.Interface{anotherInterface}, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: graphql.String, }, }, }, }, }) _, err := schemaWithObjectFieldOfType(anotherObject) if err != nil { t.Fatalf(`unexpected error: %v for type "%v"`, err, anotherObject) } } func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_AcceptsAnObjectWhichImplementsAnInterfaceAlongWithMoreFields(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: graphql.String, }, }, }, }, }) anotherObject := graphql.NewObject(graphql.ObjectConfig{ Name: "AnotherObject", Interfaces: []*graphql.Interface{anotherInterface}, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: graphql.String, }, }, }, "anotherfield": &graphql.Field{ Type: graphql.String, }, }, }) _, err := schemaWithObjectFieldOfType(anotherObject) if err != nil { t.Fatalf(`unexpected error: %v for type "%v"`, err, anotherObject) } } func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_AcceptsAnObjectWhichImpementsAnInterfaceFieldAlongWithAdditionalOptionalArguments(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: graphql.String, }, }, }, }, }) anotherObject := graphql.NewObject(graphql.ObjectConfig{ Name: "AnotherObject", Interfaces: []*graphql.Interface{anotherInterface}, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: graphql.String, }, "anotherInput": &graphql.ArgumentConfig{ Type: graphql.String, }, }, }, }, }) _, err := schemaWithObjectFieldOfType(anotherObject) if err != nil { t.Fatalf(`unexpected error: %v for type "%v"`, err, anotherObject) } } func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectWhichImplementsAnInterfaceFieldAlongWithAdditionalRequiredArguments(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: graphql.String, }, }, }, }, }) anotherObject := graphql.NewObject(graphql.ObjectConfig{ Name: "AnotherObject", Interfaces: []*graphql.Interface{anotherInterface}, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: graphql.String, }, "anotherInput": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, }, }, }, }) _, err := schemaWithObjectFieldOfType(anotherObject) expectedError := `AnotherObject.field(anotherInput:) is of required type "String!" but is not also provided by the interface AnotherInterface.field.` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectMissingAnInterfaceField(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: graphql.String, }, }, }, }, }) anotherObject := graphql.NewObject(graphql.ObjectConfig{ Name: "AnotherObject", Interfaces: []*graphql.Interface{anotherInterface}, Fields: graphql.Fields{ "anotherfield": &graphql.Field{ Type: graphql.String, }, }, }) _, err := schemaWithObjectFieldOfType(anotherObject) expectedError := `"AnotherInterface" expects field "field" but "AnotherObject" does not provide it.` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectWithAnIncorrectlyTypedInterfaceField(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.String, }, }, }) anotherObject := graphql.NewObject(graphql.ObjectConfig{ Name: "AnotherObject", Interfaces: []*graphql.Interface{anotherInterface}, Fields: graphql.Fields{ "field": &graphql.Field{ Type: someScalarType, }, }, }) _, err := schemaWithObjectFieldOfType(anotherObject) expectedError := `AnotherInterface.field expects type "String" but AnotherObject.field provides type "SomeScalar".` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectWithADifferentlyTypeInterfaceField(t *testing.T) { typeA := graphql.NewObject(graphql.ObjectConfig{ Name: "A", Fields: graphql.Fields{ "foo": &graphql.Field{ Type: graphql.String, }, }, }) typeB := graphql.NewObject(graphql.ObjectConfig{ Name: "B", Fields: graphql.Fields{ "foo": &graphql.Field{ Type: graphql.String, }, }, }) anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ "field": &graphql.Field{ Type: typeA, }, }, }) anotherObject := graphql.NewObject(graphql.ObjectConfig{ Name: "AnotherObject", Interfaces: []*graphql.Interface{anotherInterface}, Fields: graphql.Fields{ "field": &graphql.Field{ Type: typeB, }, }, }) _, err := schemaWithObjectFieldOfType(anotherObject) expectedError := `AnotherInterface.field expects type "A" but AnotherObject.field provides type "B".` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_AcceptsAnObjectWithASubtypedInterfaceField_Interface(t *testing.T) { var anotherInterface *graphql.Interface anotherInterface = graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: (graphql.FieldsThunk)(func() graphql.Fields { return graphql.Fields{ "field": &graphql.Field{ Type: anotherInterface, }, } }), }) var anotherObject *graphql.Object anotherObject = graphql.NewObject(graphql.ObjectConfig{ Name: "AnotherObject", Interfaces: []*graphql.Interface{anotherInterface}, Fields: (graphql.FieldsThunk)(func() graphql.Fields { return graphql.Fields{ "field": &graphql.Field{ Type: anotherObject, }, } }), }) _, err := schemaWithFieldType(anotherObject) if err != nil { t.Fatalf(`unexpected error: %v for type "%v"`, err, anotherObject) } } func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_AcceptsAnObjectWithASubtypedInterfaceField_Union(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ "field": &graphql.Field{ Type: someUnionType, }, }, }) anotherObject := graphql.NewObject(graphql.ObjectConfig{ Name: "AnotherObject", Interfaces: []*graphql.Interface{anotherInterface}, Fields: graphql.Fields{ "field": &graphql.Field{ Type: someObjectType, }, }, }) _, err := schemaWithFieldType(anotherObject) if err != nil { t.Fatalf(`unexpected error: %v for type "%v"`, err, anotherObject) } } func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectMissingAnInterfaceArgument(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: graphql.String, }, }, }, }, }) anotherObject := graphql.NewObject(graphql.ObjectConfig{ Name: "AnotherObject", Interfaces: []*graphql.Interface{anotherInterface}, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.String, }, }, }) _, err := schemaWithObjectFieldOfType(anotherObject) expectedError := `AnotherInterface.field expects argument "input" but AnotherObject.field does not provide it.` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectWithAnIncorrectlyTypedInterfaceArgument(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: graphql.String, }, }, }, }, }) anotherObject := graphql.NewObject(graphql.ObjectConfig{ Name: "AnotherObject", Interfaces: []*graphql.Interface{anotherInterface}, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: someScalarType, }, }, }, }, }) _, err := schemaWithObjectFieldOfType(anotherObject) expectedError := `AnotherInterface.field(input:) expects type "String" but AnotherObject.field(input:) provides type "SomeScalar".` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_AcceptsAnObjectWithAnEquivalentlyModifiedInterfaceField(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.NewNonNull(graphql.NewList(graphql.String)), }, }, }) anotherObject := graphql.NewObject(graphql.ObjectConfig{ Name: "AnotherObject", Interfaces: []*graphql.Interface{anotherInterface}, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.NewNonNull(graphql.NewList(graphql.String)), }, }, }) _, err := schemaWithObjectFieldOfType(anotherObject) if err != nil { t.Fatalf(`unexpected error: %v for type "%v"`, err, anotherObject) } } func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectWithANonListInterfaceFieldListType(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.NewList(graphql.String), }, }, }) anotherObject := graphql.NewObject(graphql.ObjectConfig{ Name: "AnotherObject", Interfaces: []*graphql.Interface{anotherInterface}, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.String, }, }, }) _, err := schemaWithFieldType(anotherObject) expectedError := `AnotherInterface.field expects type "[String]" but AnotherObject.field provides type "String".` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectWithAListInterfaceFieldNonListType(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.String, }, }, }) anotherObject := graphql.NewObject(graphql.ObjectConfig{ Name: "AnotherObject", Interfaces: []*graphql.Interface{anotherInterface}, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.NewList(graphql.String), }, }, }) _, err := schemaWithFieldType(anotherObject) expectedError := `AnotherInterface.field expects type "String" but AnotherObject.field provides type "[String]".` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_AcceptsAnObjectWithSubsetNonNullInterfaceFieldType(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.String, }, }, }) anotherObject := graphql.NewObject(graphql.ObjectConfig{ Name: "AnotherObject", Interfaces: []*graphql.Interface{anotherInterface}, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), }, }, }) _, err := schemaWithFieldType(anotherObject) if err != nil { t.Fatalf(`unexpected error: %v for type "%v"`, err, anotherObject) } } func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectWithASupersetNullableInterfaceFieldType(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), }, }, }) anotherObject := graphql.NewObject(graphql.ObjectConfig{ Name: "AnotherObject", Interfaces: []*graphql.Interface{anotherInterface}, Fields: graphql.Fields{ "field": &graphql.Field{ Type: graphql.String, }, }, }) _, err := schemaWithFieldType(anotherObject) expectedError := `AnotherInterface.field expects type "String!" but AnotherObject.field provides type "String".` if err == nil || err.Error() != expectedError { t.Fatalf("Expected error: %v, got %v", expectedError, err) } } ================================================ FILE: validator.go ================================================ package graphql import ( "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/kinds" "github.com/graphql-go/graphql/language/visitor" ) type ValidationResult struct { IsValid bool Errors []gqlerrors.FormattedError } /** * Implements the "Validation" section of the spec. * * Validation runs synchronously, returning an array of encountered errors, or * an empty array if no errors were encountered and the document is valid. * * A list of specific validation rules may be provided. If not provided, the * default list of rules defined by the GraphQL specification will be used. * * Each validation rules is a function which returns a visitor * (see the language/visitor API). Visitor methods are expected to return * GraphQLErrors, or Arrays of GraphQLErrors when invalid. */ func ValidateDocument(schema *Schema, astDoc *ast.Document, rules []ValidationRuleFn) (vr ValidationResult) { if len(rules) == 0 { rules = SpecifiedRules } if schema == nil { vr.Errors = append(vr.Errors, gqlerrors.NewFormattedError("Must provide schema")) return vr } if astDoc == nil { vr.Errors = append(vr.Errors, gqlerrors.NewFormattedError("Must provide document")) return vr } typeInfo := NewTypeInfo(&TypeInfoConfig{ Schema: schema, }) vr.Errors = VisitUsingRules(schema, typeInfo, astDoc, rules) if len(vr.Errors) == 0 { vr.IsValid = true } return vr } // VisitUsingRules This uses a specialized visitor which runs multiple visitors in parallel, // while maintaining the visitor skip and break API. // // @internal // Had to expose it to unit test experimental customizable validation feature, // but not meant for public consumption func VisitUsingRules(schema *Schema, typeInfo *TypeInfo, astDoc *ast.Document, rules []ValidationRuleFn) []gqlerrors.FormattedError { context := NewValidationContext(schema, astDoc, typeInfo) visitors := []*visitor.VisitorOptions{} for _, rule := range rules { instance := rule(context) visitors = append(visitors, instance.VisitorOpts) } // Visit the whole document with each instance of all provided rules. visitor.Visit(astDoc, visitor.VisitWithTypeInfo(typeInfo, visitor.VisitInParallel(visitors...)), nil) return context.Errors() } type HasSelectionSet interface { GetKind() string GetLoc() *ast.Location GetSelectionSet() *ast.SelectionSet } var _ HasSelectionSet = (*ast.OperationDefinition)(nil) var _ HasSelectionSet = (*ast.FragmentDefinition)(nil) type VariableUsage struct { Node *ast.Variable Type Input } type ValidationContext struct { schema *Schema astDoc *ast.Document typeInfo *TypeInfo errors []gqlerrors.FormattedError fragments map[string]*ast.FragmentDefinition variableUsages map[HasSelectionSet][]*VariableUsage recursiveVariableUsages map[*ast.OperationDefinition][]*VariableUsage recursivelyReferencedFragments map[*ast.OperationDefinition][]*ast.FragmentDefinition fragmentSpreads map[*ast.SelectionSet][]*ast.FragmentSpread } func NewValidationContext(schema *Schema, astDoc *ast.Document, typeInfo *TypeInfo) *ValidationContext { return &ValidationContext{ schema: schema, astDoc: astDoc, typeInfo: typeInfo, fragments: map[string]*ast.FragmentDefinition{}, variableUsages: map[HasSelectionSet][]*VariableUsage{}, recursiveVariableUsages: map[*ast.OperationDefinition][]*VariableUsage{}, recursivelyReferencedFragments: map[*ast.OperationDefinition][]*ast.FragmentDefinition{}, fragmentSpreads: map[*ast.SelectionSet][]*ast.FragmentSpread{}, } } func (ctx *ValidationContext) ReportError(err error) { formattedErr := gqlerrors.FormatError(err) ctx.errors = append(ctx.errors, formattedErr) } func (ctx *ValidationContext) Errors() []gqlerrors.FormattedError { return ctx.errors } func (ctx *ValidationContext) Schema() *Schema { return ctx.schema } func (ctx *ValidationContext) Document() *ast.Document { return ctx.astDoc } func (ctx *ValidationContext) Fragment(name string) *ast.FragmentDefinition { if len(ctx.fragments) == 0 { if ctx.Document() == nil { return nil } defs := ctx.Document().Definitions fragments := map[string]*ast.FragmentDefinition{} for _, def := range defs { if def, ok := def.(*ast.FragmentDefinition); ok { defName := "" if def.Name != nil { defName = def.Name.Value } fragments[defName] = def } } ctx.fragments = fragments } f, _ := ctx.fragments[name] return f } func (ctx *ValidationContext) FragmentSpreads(node *ast.SelectionSet) []*ast.FragmentSpread { if spreads, ok := ctx.fragmentSpreads[node]; ok && spreads != nil { return spreads } spreads := []*ast.FragmentSpread{} setsToVisit := []*ast.SelectionSet{node} for { if len(setsToVisit) == 0 { break } var set *ast.SelectionSet // pop set, setsToVisit = setsToVisit[len(setsToVisit)-1], setsToVisit[:len(setsToVisit)-1] if set.Selections != nil { for _, selection := range set.Selections { switch selection := selection.(type) { case *ast.FragmentSpread: spreads = append(spreads, selection) case *ast.Field: if selection.SelectionSet != nil { setsToVisit = append(setsToVisit, selection.SelectionSet) } case *ast.InlineFragment: if selection.SelectionSet != nil { setsToVisit = append(setsToVisit, selection.SelectionSet) } } } } ctx.fragmentSpreads[node] = spreads } return spreads } func (ctx *ValidationContext) RecursivelyReferencedFragments(operation *ast.OperationDefinition) []*ast.FragmentDefinition { if fragments, ok := ctx.recursivelyReferencedFragments[operation]; ok && fragments != nil { return fragments } fragments := []*ast.FragmentDefinition{} collectedNames := map[string]bool{} nodesToVisit := []*ast.SelectionSet{operation.SelectionSet} for { if len(nodesToVisit) == 0 { break } var node *ast.SelectionSet node, nodesToVisit = nodesToVisit[len(nodesToVisit)-1], nodesToVisit[:len(nodesToVisit)-1] spreads := ctx.FragmentSpreads(node) for _, spread := range spreads { fragName := "" if spread.Name != nil { fragName = spread.Name.Value } if res, ok := collectedNames[fragName]; !ok || !res { collectedNames[fragName] = true fragment := ctx.Fragment(fragName) if fragment != nil { fragments = append(fragments, fragment) nodesToVisit = append(nodesToVisit, fragment.SelectionSet) } } } } ctx.recursivelyReferencedFragments[operation] = fragments return fragments } func (ctx *ValidationContext) VariableUsages(node HasSelectionSet) []*VariableUsage { if usages, ok := ctx.variableUsages[node]; ok && usages != nil { return usages } usages := []*VariableUsage{} typeInfo := NewTypeInfo(&TypeInfoConfig{ Schema: ctx.schema, }) visitor.Visit(node, visitor.VisitWithTypeInfo(typeInfo, &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ kinds.VariableDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { return visitor.ActionSkip, nil }, }, kinds.Variable: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.Variable); ok && node != nil { usages = append(usages, &VariableUsage{ Node: node, Type: typeInfo.InputType(), }) } return visitor.ActionNoChange, nil }, }, }, }), nil) ctx.variableUsages[node] = usages return usages } func (ctx *ValidationContext) RecursiveVariableUsages(operation *ast.OperationDefinition) []*VariableUsage { if usages, ok := ctx.recursiveVariableUsages[operation]; ok && usages != nil { return usages } usages := ctx.VariableUsages(operation) fragments := ctx.RecursivelyReferencedFragments(operation) for _, fragment := range fragments { fragmentUsages := ctx.VariableUsages(fragment) usages = append(usages, fragmentUsages...) } ctx.recursiveVariableUsages[operation] = usages return usages } func (ctx *ValidationContext) Type() Output { return ctx.typeInfo.Type() } func (ctx *ValidationContext) ParentType() Composite { return ctx.typeInfo.ParentType() } func (ctx *ValidationContext) InputType() Input { return ctx.typeInfo.InputType() } func (ctx *ValidationContext) FieldDef() *FieldDefinition { return ctx.typeInfo.FieldDef() } func (ctx *ValidationContext) Directive() *Directive { return ctx.typeInfo.Directive() } func (ctx *ValidationContext) Argument() *Argument { return ctx.typeInfo.Argument() } ================================================ FILE: validator_test.go ================================================ package graphql_test import ( "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/location" "github.com/graphql-go/graphql/language/parser" "github.com/graphql-go/graphql/language/source" "github.com/graphql-go/graphql/testutil" ) func expectValid(t *testing.T, schema *graphql.Schema, queryString string) { source := source.NewSource(&source.Source{ Body: []byte(queryString), Name: "GraphQL request", }) AST, err := parser.Parse(parser.ParseParams{Source: source}) if err != nil { t.Fatalf("Unexpected error: %v", err) } validationResult := graphql.ValidateDocument(schema, AST, nil) if !validationResult.IsValid || len(validationResult.Errors) > 0 { t.Fatalf("Unexpected error: %v", validationResult.Errors) } } func TestValidator_SupportsFullValidation_ValidatesQueries(t *testing.T) { expectValid(t, testutil.TestSchema, ` query { catOrDog { ... on Cat { furColor } ... on Dog { isHousetrained } } } `) } // NOTE: experimental func TestValidator_SupportsFullValidation_ValidatesUsingACustomTypeInfo(t *testing.T) { // This TypeInfo will never return a valid field. typeInfo := graphql.NewTypeInfo(&graphql.TypeInfoConfig{ Schema: testutil.TestSchema, FieldDefFn: func(schema *graphql.Schema, parentType graphql.Type, fieldAST *ast.Field) *graphql.FieldDefinition { return nil }, }) ast := testutil.TestParse(t, ` query { catOrDog { ... on Cat { furColor } ... on Dog { isHousetrained } } } `) errors := graphql.VisitUsingRules(testutil.TestSchema, typeInfo, ast, graphql.SpecifiedRules) expectedErrors := []gqlerrors.FormattedError{ { Message: `Cannot query field "catOrDog" on type "QueryRoot". Did you mean "catOrDog"?`, Locations: []location.SourceLocation{ {Line: 3, Column: 9}, }, }, { Message: `Cannot query field "furColor" on type "Cat". Did you mean "furColor"?`, Locations: []location.SourceLocation{ {Line: 5, Column: 13}, }, }, { Message: `Cannot query field "isHousetrained" on type "Dog". Did you mean "isHousetrained"?`, Locations: []location.SourceLocation{ {Line: 8, Column: 13}, }, }, } if !testutil.EqualFormattedErrors(expectedErrors, errors) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedErrors, errors)) } } ================================================ FILE: values.go ================================================ package graphql import ( "encoding/json" "fmt" "math" "reflect" "sort" "strings" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/kinds" "github.com/graphql-go/graphql/language/printer" ) // Prepares an object map of variableValues of the correct type based on the // provided variable definitions and arbitrary input. If the input cannot be // parsed to match the variable definitions, a GraphQLError will be returned. func getVariableValues( schema Schema, definitionASTs []*ast.VariableDefinition, inputs map[string]interface{}) (map[string]interface{}, error) { values := map[string]interface{}{} for _, defAST := range definitionASTs { if defAST == nil || defAST.Variable == nil || defAST.Variable.Name == nil { continue } varName := defAST.Variable.Name.Value if varValue, err := getVariableValue(schema, defAST, inputs[varName]); err != nil { return values, err } else { values[varName] = varValue } } return values, nil } // Prepares an object map of argument values given a list of argument // definitions and list of argument AST nodes. func getArgumentValues( argDefs []*Argument, argASTs []*ast.Argument, variableValues map[string]interface{}) map[string]interface{} { argASTMap := map[string]*ast.Argument{} for _, argAST := range argASTs { if argAST.Name != nil { argASTMap[argAST.Name.Value] = argAST } } results := map[string]interface{}{} for _, argDef := range argDefs { var ( tmp interface{} value ast.Value ) if tmpValue, ok := argASTMap[argDef.PrivateName]; ok { value = tmpValue.Value } if tmp = valueFromAST(value, argDef.Type, variableValues); isNullish(tmp) { tmp = argDef.DefaultValue } if !isNullish(tmp) { results[argDef.PrivateName] = tmp } } return results } // Given a variable definition, and any value of input, return a value which // adheres to the variable definition, or throw an error. func getVariableValue(schema Schema, definitionAST *ast.VariableDefinition, input interface{}) (interface{}, error) { ttype, err := typeFromAST(schema, definitionAST.Type) if err != nil { return nil, err } variable := definitionAST.Variable if ttype == nil || !IsInputType(ttype) { return "", gqlerrors.NewError( fmt.Sprintf(`Variable "$%v" expected value of type `+ `"%v" which cannot be used as an input type.`, variable.Name.Value, printer.Print(definitionAST.Type)), []ast.Node{definitionAST}, "", nil, []int{}, nil, ) } isValid, messages := isValidInputValue(input, ttype) if isValid { if isNullish(input) { if definitionAST.DefaultValue != nil { return valueFromAST(definitionAST.DefaultValue, ttype, nil), nil } } return coerceValue(ttype, input), nil } if isNullish(input) { return "", gqlerrors.NewError( fmt.Sprintf(`Variable "$%v" of required type `+ `"%v" was not provided.`, variable.Name.Value, printer.Print(definitionAST.Type)), []ast.Node{definitionAST}, "", nil, []int{}, nil, ) } // convert input interface into string for error message bts, _ := json.Marshal(input) var ( inputStr = string(bts) msg string ) if len(messages) > 0 { msg = "\n" + strings.Join(messages, "\n") } return "", gqlerrors.NewError( fmt.Sprintf(`Variable "$%v" got invalid value `+ `%v.%v`, variable.Name.Value, inputStr, msg), []ast.Node{definitionAST}, "", nil, []int{}, nil, ) } // Given a type and any value, return a runtime value coerced to match the type. func coerceValue(ttype Input, value interface{}) interface{} { if isNullish(value) { return nil } switch ttype := ttype.(type) { case *NonNull: return coerceValue(ttype.OfType, value) case *List: var values = []interface{}{} valType := reflect.ValueOf(value) if valType.Kind() == reflect.Slice { for i := 0; i < valType.Len(); i++ { val := valType.Index(i).Interface() values = append(values, coerceValue(ttype.OfType, val)) } return values } return append(values, coerceValue(ttype.OfType, value)) case *InputObject: var obj = map[string]interface{}{} valueMap, _ := value.(map[string]interface{}) if valueMap == nil { valueMap = map[string]interface{}{} } for name, field := range ttype.Fields() { fieldValue := coerceValue(field.Type, valueMap[name]) if isNullish(fieldValue) { fieldValue = field.DefaultValue } if !isNullish(fieldValue) { obj[name] = fieldValue } } return obj case *Scalar: if parsed := ttype.ParseValue(value); !isNullish(parsed) { return parsed } case *Enum: if parsed := ttype.ParseValue(value); !isNullish(parsed) { return parsed } } return nil } // graphql-js/src/utilities.js` // TODO: figure out where to organize utils // TODO: change to *Schema func typeFromAST(schema Schema, inputTypeAST ast.Type) (Type, error) { switch inputTypeAST := inputTypeAST.(type) { case *ast.List: innerType, err := typeFromAST(schema, inputTypeAST.Type) if err != nil { return nil, err } return NewList(innerType), nil case *ast.NonNull: innerType, err := typeFromAST(schema, inputTypeAST.Type) if err != nil { return nil, err } return NewNonNull(innerType), nil case *ast.Named: nameValue := "" if inputTypeAST.Name != nil { nameValue = inputTypeAST.Name.Value } ttype := schema.Type(nameValue) return ttype, nil default: return nil, invariant(inputTypeAST.GetKind() == kinds.Named, "Must be a named type.") } } // isValidInputValue alias isValidJSValue // Given a value and a GraphQL type, determine if the value will be // accepted for that type. This is primarily useful for validating the // runtime values of query variables. func isValidInputValue(value interface{}, ttype Input) (bool, []string) { if isNullish(value) { if ttype, ok := ttype.(*NonNull); ok { if ttype.OfType.Name() != "" { return false, []string{fmt.Sprintf(`Expected "%v!", found null.`, ttype.OfType.Name())} } return false, []string{"Expected non-null value, found null."} } return true, nil } switch ttype := ttype.(type) { case *NonNull: return isValidInputValue(value, ttype.OfType) case *List: valType := reflect.ValueOf(value) if valType.Kind() == reflect.Ptr { valType = valType.Elem() } if valType.Kind() == reflect.Slice { messagesReduce := []string{} for i := 0; i < valType.Len(); i++ { val := valType.Index(i).Interface() _, messages := isValidInputValue(val, ttype.OfType) for idx, message := range messages { messagesReduce = append(messagesReduce, fmt.Sprintf(`In element #%v: %v`, idx+1, message)) } } return (len(messagesReduce) == 0), messagesReduce } return isValidInputValue(value, ttype.OfType) case *InputObject: messagesReduce := []string{} valueMap, ok := value.(map[string]interface{}) if !ok { return false, []string{fmt.Sprintf(`Expected "%v", found not an object.`, ttype.Name())} } fields := ttype.Fields() // to ensure stable order of field evaluation fieldNames := []string{} valueMapFieldNames := []string{} for fieldName := range fields { fieldNames = append(fieldNames, fieldName) } sort.Strings(fieldNames) for fieldName := range valueMap { valueMapFieldNames = append(valueMapFieldNames, fieldName) } sort.Strings(valueMapFieldNames) // Ensure every provided field is defined. for _, fieldName := range valueMapFieldNames { if _, ok := fields[fieldName]; !ok { messagesReduce = append(messagesReduce, fmt.Sprintf(`In field "%v": Unknown field.`, fieldName)) } } // Ensure every defined field is valid. for _, fieldName := range fieldNames { _, messages := isValidInputValue(valueMap[fieldName], fields[fieldName].Type) if messages != nil { for _, message := range messages { messagesReduce = append(messagesReduce, fmt.Sprintf(`In field "%v": %v`, fieldName, message)) } } } return (len(messagesReduce) == 0), messagesReduce case *Scalar: if parsedVal := ttype.ParseValue(value); isNullish(parsedVal) { return false, []string{fmt.Sprintf(`Expected type "%v", found "%v".`, ttype.Name(), value)} } case *Enum: if parsedVal := ttype.ParseValue(value); isNullish(parsedVal) { return false, []string{fmt.Sprintf(`Expected type "%v", found "%v".`, ttype.Name(), value)} } } return true, nil } // Returns true if a value is null, undefined, or NaN. func isNullish(src interface{}) bool { if src == nil { return true } value := reflect.ValueOf(src) if value.Kind() == reflect.Ptr { if value.IsNil() { return true } value = value.Elem() } switch value.Kind() { case reflect.String: // if src is ptr type and len(string)=0, it returns false if !value.IsValid() { return true } case reflect.Int: return math.IsNaN(float64(value.Int())) case reflect.Float32, reflect.Float64: return math.IsNaN(float64(value.Float())) } return false } // Returns true if src is a slice or an array func isIterable(src interface{}) bool { if src == nil { return false } t := reflect.TypeOf(src) if t.Kind() == reflect.Ptr { t = t.Elem() } return t.Kind() == reflect.Slice || t.Kind() == reflect.Array } /** * Produces a value given a GraphQL Value AST. * * A GraphQL type must be provided, which will be used to interpret different * GraphQL Value literals. * * | GraphQL Value | JSON Value | * | -------------------- | ------------- | * | Input Object | Object | * | List | Array | * | Boolean | Boolean | * | String / Enum Value | String | * | Int / Float | Number | * */ func valueFromAST(valueAST ast.Value, ttype Input, variables map[string]interface{}) interface{} { if valueAST == nil { return nil } // precedence: value > type if valueAST, ok := valueAST.(*ast.Variable); ok { if valueAST.Name == nil || variables == nil { return nil } // Note: we're not doing any checking that this variable is correct. We're // assuming that this query has been validated and the variable usage here // is of the correct type. return variables[valueAST.Name.Value] } switch ttype := ttype.(type) { case *NonNull: return valueFromAST(valueAST, ttype.OfType, variables) case *List: values := []interface{}{} if valueAST, ok := valueAST.(*ast.ListValue); ok { for _, itemAST := range valueAST.Values { values = append(values, valueFromAST(itemAST, ttype.OfType, variables)) } return values } return append(values, valueFromAST(valueAST, ttype.OfType, variables)) case *InputObject: var ( ok bool ov *ast.ObjectValue of *ast.ObjectField ) if ov, ok = valueAST.(*ast.ObjectValue); !ok { return nil } fieldASTs := map[string]*ast.ObjectField{} for _, of = range ov.Fields { if of == nil || of.Name == nil { continue } fieldASTs[of.Name.Value] = of } obj := map[string]interface{}{} for name, field := range ttype.Fields() { var value interface{} if of, ok = fieldASTs[name]; ok { value = valueFromAST(of.Value, field.Type, variables) } else { value = field.DefaultValue } if !isNullish(value) { obj[name] = value } } return obj case *Scalar: return ttype.ParseLiteral(valueAST) case *Enum: return ttype.ParseLiteral(valueAST) } return nil } func invariant(condition bool, message string) error { if !condition { return gqlerrors.NewFormattedError(message) } return nil } func invariantf(condition bool, format string, a ...interface{}) error { if !condition { return gqlerrors.NewFormattedError(fmt.Sprintf(format, a...)) } return nil } ================================================ FILE: values_test.go ================================================ package graphql import "testing" func TestIsIterable(t *testing.T) { if !isIterable([]int{}) { t.Fatal("expected isIterable to return true for a slice, got false") } if !isIterable([]int{}) { t.Fatal("expected isIterable to return true for an array, got false") } if isIterable(1) { t.Fatal("expected isIterable to return false for an int, got true") } if isIterable(nil) { t.Fatal("expected isIterable to return false for nil, got true") } } ================================================ FILE: variables_test.go ================================================ package graphql_test import ( "encoding/json" "reflect" "testing" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/gqlerrors" "github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/location" "github.com/graphql-go/graphql/testutil" ) var testComplexScalar *graphql.Scalar = graphql.NewScalar(graphql.ScalarConfig{ Name: "ComplexScalar", Serialize: func(value interface{}) interface{} { if value == "DeserializedValue" { return "SerializedValue" } return nil }, ParseValue: func(value interface{}) interface{} { if value == "SerializedValue" { return "DeserializedValue" } return nil }, ParseLiteral: func(valueAST ast.Value) interface{} { astValue := valueAST.GetValue() if astValue, ok := astValue.(string); ok && astValue == "SerializedValue" { return "DeserializedValue" } return nil }, }) var testInputObject *graphql.InputObject = graphql.NewInputObject(graphql.InputObjectConfig{ Name: "TestInputObject", Fields: graphql.InputObjectConfigFieldMap{ "a": &graphql.InputObjectFieldConfig{ Type: graphql.String, }, "b": &graphql.InputObjectFieldConfig{ Type: graphql.NewList(graphql.String), }, "c": &graphql.InputObjectFieldConfig{ Type: graphql.NewNonNull(graphql.String), }, "d": &graphql.InputObjectFieldConfig{ Type: testComplexScalar, }, }, }) var testNestedInputObject *graphql.InputObject = graphql.NewInputObject(graphql.InputObjectConfig{ Name: "TestNestedInputObject", Fields: graphql.InputObjectConfigFieldMap{ "na": &graphql.InputObjectFieldConfig{ Type: graphql.NewNonNull(testInputObject), }, "nb": &graphql.InputObjectFieldConfig{ Type: graphql.NewNonNull(graphql.String), }, }, }) func inputResolved(p graphql.ResolveParams) (interface{}, error) { input, ok := p.Args["input"] if !ok { return nil, nil } b, err := json.Marshal(input) if err != nil { return nil, nil } return string(b), nil } var testType *graphql.Object = graphql.NewObject(graphql.ObjectConfig{ Name: "TestType", Fields: graphql.Fields{ "fieldWithObjectInput": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: testInputObject, }, }, Resolve: inputResolved, }, "fieldWithNullableStringInput": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: graphql.String, }, }, Resolve: inputResolved, }, "fieldWithNonNullableStringInput": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, }, Resolve: inputResolved, }, "fieldWithDefaultArgumentValue": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: graphql.String, DefaultValue: "Hello World", }, }, Resolve: inputResolved, }, "fieldWithNestedInputObject": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: testNestedInputObject, DefaultValue: "Hello World", }, }, Resolve: inputResolved, }, "list": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: graphql.NewList(graphql.String), }, }, Resolve: inputResolved, }, "nnList": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.NewList(graphql.String)), }, }, Resolve: inputResolved, }, "listNN": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: graphql.NewList(graphql.NewNonNull(graphql.String)), }, }, Resolve: inputResolved, }, "nnListNN": &graphql.Field{ Type: graphql.String, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.NewList(graphql.NewNonNull(graphql.String))), }, }, Resolve: inputResolved, }, }, }) var variablesTestSchema, _ = graphql.NewSchema(graphql.SchemaConfig{ Query: testType, }) func TestVariables_ObjectsAndNullability_UsingInlineStructs_ExecutesWithComplexInput(t *testing.T) { doc := ` { fieldWithObjectInput(input: {a: "foo", b: ["bar"], c: "baz"}) } ` expected := &graphql.Result{ Data: map[string]interface{}{ "fieldWithObjectInput": `{"a":"foo","b":["bar"],"c":"baz"}`, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ObjectsAndNullability_UsingInlineStructs_ProperlyParsesSingleValueToList(t *testing.T) { doc := ` { fieldWithObjectInput(input: {a: "foo", b: "bar", c: "baz"}) } ` expected := &graphql.Result{ Data: map[string]interface{}{ "fieldWithObjectInput": `{"a":"foo","b":["bar"],"c":"baz"}`, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ObjectsAndNullability_UsingInlineStructs_DoesNotUseIncorrectValue(t *testing.T) { doc := ` { fieldWithObjectInput(input: ["foo", "bar", "baz"]) } ` expected := &graphql.Result{ Data: map[string]interface{}{ "fieldWithObjectInput": nil, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ObjectsAndNullability_UsingInlineStructs_ProperlyRunsParseLiteralOnComplexScalarTypes(t *testing.T) { doc := ` { fieldWithObjectInput(input: {a: "foo", d: "SerializedValue"}) } ` expected := &graphql.Result{ Data: map[string]interface{}{ "fieldWithObjectInput": `{"a":"foo","d":"DeserializedValue"}`, }, } // parse query ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func testVariables_ObjectsAndNullability_UsingVariables_GetAST(t *testing.T) *ast.Document { doc := ` query q($input: TestInputObject) { fieldWithObjectInput(input: $input) } ` return testutil.TestParse(t, doc) } func TestVariables_ObjectsAndNullability_UsingVariables_ExecutesWithComplexInput(t *testing.T) { params := map[string]interface{}{ "input": map[string]interface{}{ "a": "foo", "b": []interface{}{"bar"}, "c": "baz", }, } expected := &graphql.Result{ Data: map[string]interface{}{ "fieldWithObjectInput": `{"a":"foo","b":["bar"],"c":"baz"}`, }, } ast := testVariables_ObjectsAndNullability_UsingVariables_GetAST(t) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ObjectsAndNullability_UsingVariables_UsesDefaultValueWhenNotProvided(t *testing.T) { doc := ` query q($input: TestInputObject = {a: "foo", b: ["bar"], c: "baz"}) { fieldWithObjectInput(input: $input) } ` expected := &graphql.Result{ Data: map[string]interface{}{ "fieldWithObjectInput": `{"a":"foo","b":["bar"],"c":"baz"}`, }, } withDefaultsAST := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: withDefaultsAST, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ObjectsAndNullability_UsingVariables_ProperlyParsesSingleValueToList(t *testing.T) { params := map[string]interface{}{ "input": map[string]interface{}{ "a": "foo", "b": "bar", "c": "baz", }, } expected := &graphql.Result{ Data: map[string]interface{}{ "fieldWithObjectInput": `{"a":"foo","b":["bar"],"c":"baz"}`, }, } ast := testVariables_ObjectsAndNullability_UsingVariables_GetAST(t) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ObjectsAndNullability_UsingVariables_ExecutesWithComplexScalarInput(t *testing.T) { params := map[string]interface{}{ "input": map[string]interface{}{ "c": "foo", "d": "SerializedValue", }, } expected := &graphql.Result{ Data: map[string]interface{}{ "fieldWithObjectInput": `{"c":"foo","d":"DeserializedValue"}`, }, } ast := testVariables_ObjectsAndNullability_UsingVariables_GetAST(t) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ObjectsAndNullability_UsingVariables_ErrorsOnNullForNestedNonNull(t *testing.T) { params := map[string]interface{}{ "input": map[string]interface{}{ "a": "foo", "b": "bar", "c": nil, }, } expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Variable "$input" got invalid value {"a":"foo","b":"bar","c":null}.` + "\nIn field \"c\": Expected \"String!\", found null.", Locations: []location.SourceLocation{ { Line: 2, Column: 17, }, }, }, }, } ast := testVariables_ObjectsAndNullability_UsingVariables_GetAST(t) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ObjectsAndNullability_UsingVariables_ErrorsOnIncorrectType(t *testing.T) { params := map[string]interface{}{ "input": "foo bar", } expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: "Variable \"$input\" got invalid value \"foo bar\".\nExpected \"TestInputObject\", found not an object.", Locations: []location.SourceLocation{ { Line: 2, Column: 17, }, }, }, }, } ast := testVariables_ObjectsAndNullability_UsingVariables_GetAST(t) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ObjectsAndNullability_UsingVariables_ErrorsOnOmissionOfNestedNonNull(t *testing.T) { params := map[string]interface{}{ "input": map[string]interface{}{ "a": "foo", "b": "bar", }, } expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Variable "$input" got invalid value {"a":"foo","b":"bar"}.` + "\nIn field \"c\": Expected \"String!\", found null.", Locations: []location.SourceLocation{ { Line: 2, Column: 17, }, }, }, }, } ast := testVariables_ObjectsAndNullability_UsingVariables_GetAST(t) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ObjectsAndNullability_UsingVariables_ErrorsOnDeepNestedErrorsAndWithManyErrors(t *testing.T) { params := map[string]interface{}{ "input": map[string]interface{}{ "na": map[string]interface{}{ "a": "foo", }, }, } expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Variable "$input" got invalid value {"na":{"a":"foo"}}.` + "\nIn field \"na\": In field \"c\": Expected \"String!\", found null." + "\nIn field \"nb\": Expected \"String!\", found null.", Locations: []location.SourceLocation{ { Line: 2, Column: 19, }, }, }, }, } doc := ` query q($input: TestNestedInputObject) { fieldWithNestedObjectInput(input: $input) } ` nestedAST := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: nestedAST, Args: params, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ObjectsAndNullability_UsingVariables_ErrorsOnAdditionOfUnknownInputField(t *testing.T) { params := map[string]interface{}{ "input": map[string]interface{}{ "a": "foo", "b": "bar", "c": "baz", "extra": "dog", }, } expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Variable "$input" got invalid value {"a":"foo","b":"bar","c":"baz","extra":"dog"}.` + "\nIn field \"extra\": Unknown field.", Locations: []location.SourceLocation{ { Line: 2, Column: 17, }, }, }, }, } ast := testVariables_ObjectsAndNullability_UsingVariables_GetAST(t) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_NullableScalars_AllowsNullableInputsToBeOmitted(t *testing.T) { doc := ` { fieldWithNullableStringInput } ` expected := &graphql.Result{ Data: map[string]interface{}{ "fieldWithNullableStringInput": nil, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_NullableScalars_AllowsNullableInputsToBeOmittedInAVariable(t *testing.T) { doc := ` query SetsNullable($value: String) { fieldWithNullableStringInput(input: $value) } ` expected := &graphql.Result{ Data: map[string]interface{}{ "fieldWithNullableStringInput": nil, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_NullableScalars_AllowsNullableInputsToBeOmittedInAnUnlistedVariable(t *testing.T) { doc := ` query SetsNullable { fieldWithNullableStringInput(input: $value) } ` expected := &graphql.Result{ Data: map[string]interface{}{ "fieldWithNullableStringInput": nil, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_NullableScalars_AllowsNullableInputsToBeSetToNullInAVariable(t *testing.T) { doc := ` query SetsNullable($value: String) { fieldWithNullableStringInput(input: $value) } ` params := map[string]interface{}{ "value": nil, } expected := &graphql.Result{ Data: map[string]interface{}{ "fieldWithNullableStringInput": nil, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_NullableScalars_AllowsNullableInputsToBeSetToAValueInAVariable(t *testing.T) { doc := ` query SetsNullable($value: String) { fieldWithNullableStringInput(input: $value) } ` params := map[string]interface{}{ "value": "a", } expected := &graphql.Result{ Data: map[string]interface{}{ "fieldWithNullableStringInput": `"a"`, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_NullableScalars_AllowsNullableInputsToBeSetToAValueDirectly(t *testing.T) { doc := ` { fieldWithNullableStringInput(input: "a") } ` expected := &graphql.Result{ Data: map[string]interface{}{ "fieldWithNullableStringInput": `"a"`, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_NonNullableScalars_DoesNotAllowNonNullableInputsToBeOmittedInAVariable(t *testing.T) { doc := ` query SetsNonNullable($value: String!) { fieldWithNonNullableStringInput(input: $value) } ` expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Variable "$value" of required type "String!" was not provided.`, Locations: []location.SourceLocation{ { Line: 2, Column: 31, }, }, }, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_NonNullableScalars_DoesNotAllowNonNullableInputsToBeSetToNullInAVariable(t *testing.T) { doc := ` query SetsNonNullable($value: String!) { fieldWithNonNullableStringInput(input: $value) } ` params := map[string]interface{}{ "value": nil, } expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Variable "$value" of required type "String!" was not provided.`, Locations: []location.SourceLocation{ { Line: 2, Column: 31, }, }, }, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_NonNullableScalars_AllowsNonNullableInputsToBeSetToAValueInAVariable(t *testing.T) { doc := ` query SetsNonNullable($value: String!) { fieldWithNonNullableStringInput(input: $value) } ` params := map[string]interface{}{ "value": "a", } expected := &graphql.Result{ Data: map[string]interface{}{ "fieldWithNonNullableStringInput": `"a"`, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_NonNullableScalars_AllowsNonNullableInputsToBeSetToAValueDirectly(t *testing.T) { doc := ` { fieldWithNonNullableStringInput(input: "a") } ` params := map[string]interface{}{ "value": "a", } expected := &graphql.Result{ Data: map[string]interface{}{ "fieldWithNonNullableStringInput": `"a"`, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_NonNullableScalars_PassesAlongNullForNonNullableInputsIfExplicitlySetInTheQuery(t *testing.T) { doc := ` { fieldWithNonNullableStringInput } ` params := map[string]interface{}{ "value": "a", } expected := &graphql.Result{ Data: map[string]interface{}{ "fieldWithNonNullableStringInput": nil, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ListsAndNullability_AllowsListsToBeNull(t *testing.T) { doc := ` query q($input: [String]) { list(input: $input) } ` params := map[string]interface{}{ "input": nil, } expected := &graphql.Result{ Data: map[string]interface{}{ "list": nil, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ListsAndNullability_AllowsListsToContainValues(t *testing.T) { doc := ` query q($input: [String]) { list(input: $input) } ` params := map[string]interface{}{ "input": []interface{}{"A"}, } expected := &graphql.Result{ Data: map[string]interface{}{ "list": `["A"]`, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ListsAndNullability_AllowsListsToContainNull(t *testing.T) { doc := ` query q($input: [String]) { list(input: $input) } ` params := map[string]interface{}{ "input": []interface{}{"A", nil, "B"}, } expected := &graphql.Result{ Data: map[string]interface{}{ "list": `["A",null,"B"]`, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ListsAndNullability_DoesNotAllowNonNullListsToBeNull(t *testing.T) { doc := ` query q($input: [String]!) { nnList(input: $input) } ` expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Variable "$input" of required type "[String]!" was not provided.`, Locations: []location.SourceLocation{ { Line: 2, Column: 17, }, }, }, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ListsAndNullability_AllowsNonNullListsToContainValues(t *testing.T) { doc := ` query q($input: [String]!) { nnList(input: $input) } ` params := map[string]interface{}{ "input": []interface{}{"A"}, } expected := &graphql.Result{ Data: map[string]interface{}{ "nnList": `["A"]`, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ListsAndNullability_AllowsNonNullListsToContainNull(t *testing.T) { doc := ` query q($input: [String]!) { nnList(input: $input) } ` params := map[string]interface{}{ "input": []interface{}{"A", nil, "B"}, } expected := &graphql.Result{ Data: map[string]interface{}{ "nnList": `["A",null,"B"]`, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ListsAndNullability_AllowsListsOfNonNullsToBeNull(t *testing.T) { doc := ` query q($input: [String!]) { listNN(input: $input) } ` params := map[string]interface{}{ "input": nil, } expected := &graphql.Result{ Data: map[string]interface{}{ "listNN": nil, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ListsAndNullability_AllowsListsOfNonNullsToContainValues(t *testing.T) { doc := ` query q($input: [String!]) { listNN(input: $input) } ` params := map[string]interface{}{ "input": []interface{}{"A"}, } expected := &graphql.Result{ Data: map[string]interface{}{ "listNN": `["A"]`, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if len(result.Errors) > 0 { t.Fatalf("wrong result, unexpected errors: %v", result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ListsAndNullability_DoesNotAllowListOfNonNullsToContainNull(t *testing.T) { doc := ` query q($input: [String!]) { listNN(input: $input) } ` params := map[string]interface{}{ "input": []interface{}{"A", nil, "B"}, } expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Variable "$input" got invalid value ` + `["A",null,"B"].` + "\nIn element #1: Expected \"String!\", found null.", Locations: []location.SourceLocation{ { Line: 2, Column: 17, }, }, }, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ListsAndNullability_DoesNotAllowNonNullListOfNonNullsToBeNull(t *testing.T) { doc := ` query q($input: [String!]!) { nnListNN(input: $input) } ` params := map[string]interface{}{ "input": nil, } expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Variable "$input" of required type "[String!]!" was not provided.`, Locations: []location.SourceLocation{ { Line: 2, Column: 17, }, }, }, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ListsAndNullability_AllowsNonNullListsOfNonNulsToContainValues(t *testing.T) { doc := ` query q($input: [String!]!) { nnListNN(input: $input) } ` params := map[string]interface{}{ "input": []interface{}{"A"}, } expected := &graphql.Result{ Data: map[string]interface{}{ "nnListNN": `["A"]`, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if len(result.Errors) != len(expected.Errors) { t.Fatalf("Unexpected errors, Diff: %v", testutil.Diff(expected.Errors, result.Errors)) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ListsAndNullability_DoesNotAllowNonNullListOfNonNullsToContainNull(t *testing.T) { doc := ` query q($input: [String!]!) { nnListNN(input: $input) } ` params := map[string]interface{}{ "input": []interface{}{"A", nil, "B"}, } expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Variable "$input" got invalid value ` + `["A",null,"B"].` + "\nIn element #1: Expected \"String!\", found null.", Locations: []location.SourceLocation{ { Line: 2, Column: 17, }, }, }, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ListsAndNullability_DoesNotAllowInvalidTypesToBeUsedAsValues(t *testing.T) { doc := ` query q($input: TestType!) { fieldWithObjectInput(input: $input) } ` params := map[string]interface{}{ "input": map[string]interface{}{ "list": []interface{}{"A", "B"}, }, } expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Variable "$input" expected value of type "TestType!" which cannot be used as an input type.`, Locations: []location.SourceLocation{ { Line: 2, Column: 17, }, }, }, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if len(result.Errors) != len(expected.Errors) { t.Fatalf("Unexpected errors, Diff: %v", testutil.Diff(expected.Errors, result.Errors)) } if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_ListsAndNullability_DoesNotAllowUnknownTypesToBeUsedAsValues(t *testing.T) { doc := ` query q($input: UnknownType!) { fieldWithObjectInput(input: $input) } ` params := map[string]interface{}{ "input": "whoknows", } expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Variable "$input" expected value of type "UnknownType!" which cannot be used as an input type.`, Locations: []location.SourceLocation{ { Line: 2, Column: 17, }, }, }, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, Args: params, } result := testutil.TestExecute(t, ep) if !testutil.EqualResults(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_UsesArgumentDefaultValues_WhenNoArgumentProvided(t *testing.T) { doc := ` { fieldWithDefaultArgumentValue } ` expected := &graphql.Result{ Data: map[string]interface{}{ "fieldWithDefaultArgumentValue": `"Hello World"`, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, } result := testutil.TestExecute(t, ep) if len(result.Errors) != len(expected.Errors) { t.Fatalf("Unexpected errors, Diff: %v", testutil.Diff(expected.Errors, result.Errors)) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_UsesArgumentDefaultValues_WhenNullableVariableProvided(t *testing.T) { doc := ` query optionalVariable($optional: String) { fieldWithDefaultArgumentValue(input: $optional) } ` expected := &graphql.Result{ Data: map[string]interface{}{ "fieldWithDefaultArgumentValue": `"Hello World"`, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, } result := testutil.TestExecute(t, ep) if len(result.Errors) != len(expected.Errors) { t.Fatalf("Unexpected errors, Diff: %v", testutil.Diff(expected.Errors, result.Errors)) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } func TestVariables_UsesArgumentDefaultValues_WhenArgumentProvidedCannotBeParsed(t *testing.T) { doc := ` { fieldWithDefaultArgumentValue(input: WRONG_TYPE) } ` expected := &graphql.Result{ Data: map[string]interface{}{ "fieldWithDefaultArgumentValue": `"Hello World"`, }, } ast := testutil.TestParse(t, doc) // execute ep := graphql.ExecuteParams{ Schema: variablesTestSchema, AST: ast, } result := testutil.TestExecute(t, ep) if len(result.Errors) != len(expected.Errors) { t.Fatalf("Unexpected errors, Diff: %v", testutil.Diff(expected.Errors, result.Errors)) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } }