├── .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-post │ └── main.go ├── http │ ├── data.json │ └── 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 /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | test_with_go_modules: &test_with_go_modules 2 | steps: 3 | - checkout 4 | - run: go test ./... 5 | - run: go vet ./... 6 | 7 | test_without_go_modules: &test_without_go_modules 8 | working_directory: /go/src/github.com/graphql-go/graphql 9 | steps: 10 | - checkout 11 | - run: go get -v -t -d ./... 12 | - run: go test ./... 13 | - run: go vet ./... 14 | 15 | defaults: &defaults 16 | <<: *test_with_go_modules 17 | 18 | version: 2 19 | jobs: 20 | golang:1.8.7: 21 | <<: *test_without_go_modules 22 | docker: 23 | - image: circleci/golang:1.8.7 24 | golang:1.9.7: 25 | <<: *test_without_go_modules 26 | docker: 27 | - image: circleci/golang:1.9.7 28 | golang:1.11: 29 | <<: *defaults 30 | docker: 31 | - image: circleci/golang:1.11 32 | golang:latest: 33 | <<: *defaults 34 | docker: 35 | - image: circleci/golang:latest 36 | coveralls: 37 | docker: 38 | - image: circleci/golang:latest 39 | steps: 40 | - checkout 41 | - run: go get github.com/mattn/goveralls 42 | - run: go test -v -cover -race -coverprofile=coverage.out 43 | - run: /go/bin/goveralls -coverprofile=coverage.out -service=circle-ci -repotoken $COVERALLS_TOKEN 44 | 45 | workflows: 46 | version: 2 47 | build: 48 | jobs: 49 | - golang:1.8.7 50 | - golang:1.9.7 51 | - golang:1.11 52 | - golang:latest 53 | - coveralls 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to graphql 2 | 3 | This document is based on the [Node.js contribution guidelines](https://github.com/nodejs/node/blob/master/CONTRIBUTING.md) 4 | 5 | ## Chat room 6 | 7 | [![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) 8 | 9 | Feel free to participate in the chat room for informal discussions and queries. 10 | 11 | Just drop by and say hi! 12 | 13 | ## Issue Contributions 14 | 15 | When opening new issues or commenting on existing issues on this repository 16 | please make sure discussions are related to concrete technical issues with the 17 | `graphql` implementation. 18 | 19 | ## Code Contributions 20 | 21 | The `graphql` project welcomes new contributors. 22 | 23 | This document will guide you through the contribution process. 24 | 25 | What do you want to contribute? 26 | 27 | - I want to otherwise correct or improve the docs or examples 28 | - I want to report a bug 29 | - I want to add some feature or functionality to an existing hardware platform 30 | - I want to add support for a new hardware platform 31 | 32 | Descriptions for each of these will eventually be provided below. 33 | 34 | ## General Guidelines 35 | * Reading up on [CodeReviewComments](https://github.com/golang/go/wiki/CodeReviewComments) would be a great start. 36 | * Submit a Github Pull Request to the appropriate branch and ideally discuss the changes with us in the [chat room](#chat-room). 37 | * We will look at the patch, test it out, and give you feedback. 38 | * 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. 39 | * Take care to maintain the existing coding style. 40 | * Always `golint` and `go fmt` your code. 41 | * Add unit tests for any new or changed functionality, especially for public APIs. 42 | * Run `go test` before submitting a PR. 43 | * For git help see [progit](http://git-scm.com/book) which is an awesome (and free) book on git 44 | 45 | 46 | ## Creating Pull Requests 47 | Because `graphql` makes use of self-referencing import paths, you will want 48 | to implement the local copy of your fork as a remote on your copy of the 49 | original `graphql` repo. Katrina Owen has [an excellent post on this workflow](https://splice.com/blog/contributing-open-source-git-repositories-go/). 50 | 51 | The basics are as follows: 52 | 53 | 1. Fork the project via the GitHub UI 54 | 55 | 2. `go get` the upstream repo and set it up as the `upstream` remote and your own repo as the `origin` remote: 56 | 57 | ```bash 58 | $ go get github.com/graphql-go/graphql 59 | $ cd $GOPATH/src/github.com/graphql-go/graphql 60 | $ git remote rename origin upstream 61 | $ git remote add origin git@github.com/YOUR_GITHUB_NAME/graphql 62 | ``` 63 | All import paths should now work fine assuming that you've got the 64 | proper branch checked out. 65 | 66 | 67 | ## Landing Pull Requests 68 | (This is for committers only. If you are unsure whether you are a committer, you are not.) 69 | 70 | 1. Set the contributor's fork as an upstream on your checkout 71 | 72 | ```git remote add contrib1 https://github.com/contrib1/graphql``` 73 | 74 | 2. Fetch the contributor's repo 75 | 76 | ```git fetch contrib1``` 77 | 78 | 3. Checkout a copy of the PR branch 79 | 80 | ```git checkout pr-1234 --track contrib1/branch-for-pr-1234``` 81 | 82 | 4. Review the PR as normal 83 | 84 | 5. Land when you're ready via the GitHub UI 85 | 86 | ## Developer's Certificate of Origin 1.0 87 | 88 | By making a contribution to this project, I certify that: 89 | 90 | * (a) The contribution was created in whole or in part by me and I 91 | have the right to submit it under the open source license indicated 92 | in the file; or 93 | * (b) The contribution is based upon previous work that, to the best 94 | of my knowledge, is covered under an appropriate open source license 95 | and I have the right under that license to submit that work with 96 | modifications, whether created in whole or in part by me, under the 97 | same open source license (unless I am permitted to submit under a 98 | different license), as indicated in the file; or 99 | * (c) The contribution was provided directly to me by some other 100 | person who certified (a), (b) or (c) and I have not modified it. 101 | 102 | 103 | ## Code of Conduct 104 | 105 | This Code of Conduct is adapted from [Rust's wonderful 106 | CoC](http://www.rust-lang.org/conduct.html). 107 | 108 | * We are committed to providing a friendly, safe and welcoming 109 | environment for all, regardless of gender, sexual orientation, 110 | disability, ethnicity, religion, or similar personal characteristic. 111 | * Please avoid using overtly sexual nicknames or other nicknames that 112 | might detract from a friendly, safe and welcoming environment for 113 | all. 114 | * Please be kind and courteous. There's no need to be mean or rude. 115 | * Respect that people have differences of opinion and that every 116 | design or implementation choice carries a trade-off and numerous 117 | costs. There is seldom a right answer. 118 | * Please keep unstructured critique to a minimum. If you have solid 119 | ideas you want to experiment with, make a fork and see how it works. 120 | * We will exclude you from interaction if you insult, demean or harass 121 | anyone. That is not welcome behaviour. We interpret the term 122 | "harassment" as including the definition in the [Citizen Code of 123 | Conduct](http://citizencodeofconduct.org/); if you have any lack of 124 | clarity about what might be included in that concept, please read 125 | their definition. In particular, we don't tolerate behavior that 126 | excludes people in socially marginalized groups. 127 | * Private harassment is also unacceptable. No matter who you are, if 128 | you feel you have been or are being harassed or made uncomfortable 129 | by a community member, please contact one of the channel ops or any 130 | of the TC members immediately with a capture (log, photo, email) of 131 | the harassment if possible. Whether you're a regular contributor or 132 | a newcomer, we care about making this community a safe place for you 133 | and we've got your back. 134 | * Likewise any spamming, trolling, flaming, baiting or other 135 | attention-stealing behaviour is not welcome. 136 | * Avoid the use of personal pronouns in code comments or 137 | documentation. There is no need to address persons when explaining 138 | code (e.g. "When the developer") 139 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Chris Ramón 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 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) 2 | 3 | An implementation of GraphQL in Go. Follows the official reference implementation [`graphql-js`](https://github.com/graphql/graphql-js). 4 | 5 | Supports: queries, mutations & subscriptions. 6 | 7 | ### Documentation 8 | 9 | godoc: https://pkg.go.dev/github.com/graphql-go/graphql 10 | 11 | ### Contribute Back 12 | 13 | Friendly reminder links are available in case you would like to contribute back into our commitment with Go and open-source. 14 | 15 | | Author | PayPal Link | 16 | |:-------------:|:-------------:| 17 | | [Hafiz Ismail](https://github.com/sogko) | Not available yet. | 18 | | [Chris Ramón](https://github.com/chris-ramon) | https://www.paypal.com/donate/?hosted_button_id=WHUQQYEMTRQBJ | 19 | 20 | ### Getting Started 21 | 22 | To install the library, run: 23 | ```bash 24 | go get github.com/graphql-go/graphql 25 | ``` 26 | 27 | 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. 28 | 29 | ```go 30 | package main 31 | 32 | import ( 33 | "encoding/json" 34 | "fmt" 35 | "log" 36 | 37 | "github.com/graphql-go/graphql" 38 | ) 39 | 40 | func main() { 41 | // Schema 42 | fields := graphql.Fields{ 43 | "hello": &graphql.Field{ 44 | Type: graphql.String, 45 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 46 | return "world", nil 47 | }, 48 | }, 49 | } 50 | rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: fields} 51 | schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)} 52 | schema, err := graphql.NewSchema(schemaConfig) 53 | if err != nil { 54 | log.Fatalf("failed to create new schema, error: %v", err) 55 | } 56 | 57 | // Query 58 | query := ` 59 | { 60 | hello 61 | } 62 | ` 63 | params := graphql.Params{Schema: schema, RequestString: query} 64 | r := graphql.Do(params) 65 | if len(r.Errors) > 0 { 66 | log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors) 67 | } 68 | rJSON, _ := json.Marshal(r) 69 | fmt.Printf("%s \n", rJSON) // {"data":{"hello":"world"}} 70 | } 71 | ``` 72 | 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). 73 | 74 | ### Third Party Libraries 75 | | Name | Author | Description | 76 | |:-------------:|:-------------:|:------------:| 77 | | [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. | 78 | | [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. | 79 | | [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. | 80 | | [dataloader](https://github.com/nicksrandall/dataloader) | [Nick Randall](https://github.com/nicksrandall) | [DataLoader](https://github.com/facebook/dataloader) implementation in Go. | 81 | 82 | ### Blog Posts 83 | - [Golang + GraphQL + Relay](https://wehavefaces.net/learn-golang-graphql-relay-1-e59ea174a902) 84 | 85 | -------------------------------------------------------------------------------- /benchutil/list_schema.go: -------------------------------------------------------------------------------- 1 | package benchutil 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/graphql-go/graphql" 7 | ) 8 | 9 | type color struct { 10 | Hex string 11 | R int 12 | G int 13 | B int 14 | } 15 | 16 | func ListSchemaWithXItems(x int) graphql.Schema { 17 | 18 | list := generateXListItems(x) 19 | 20 | color := graphql.NewObject(graphql.ObjectConfig{ 21 | Name: "Color", 22 | Description: "A color", 23 | Fields: graphql.Fields{ 24 | "hex": &graphql.Field{ 25 | Type: graphql.NewNonNull(graphql.String), 26 | Description: "Hex color code.", 27 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 28 | if c, ok := p.Source.(color); ok { 29 | return c.Hex, nil 30 | } 31 | return nil, nil 32 | }, 33 | }, 34 | "r": &graphql.Field{ 35 | Type: graphql.NewNonNull(graphql.Int), 36 | Description: "Red value.", 37 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 38 | if c, ok := p.Source.(color); ok { 39 | return c.R, nil 40 | } 41 | return nil, nil 42 | }, 43 | }, 44 | "g": &graphql.Field{ 45 | Type: graphql.NewNonNull(graphql.Int), 46 | Description: "Green value.", 47 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 48 | if c, ok := p.Source.(color); ok { 49 | return c.G, nil 50 | } 51 | return nil, nil 52 | }, 53 | }, 54 | "b": &graphql.Field{ 55 | Type: graphql.NewNonNull(graphql.Int), 56 | Description: "Blue value.", 57 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 58 | if c, ok := p.Source.(color); ok { 59 | return c.B, nil 60 | } 61 | return nil, nil 62 | }, 63 | }, 64 | }, 65 | }) 66 | 67 | queryType := graphql.NewObject(graphql.ObjectConfig{ 68 | Name: "Query", 69 | Fields: graphql.Fields{ 70 | "colors": { 71 | Type: graphql.NewList(color), 72 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 73 | return list, nil 74 | }, 75 | }, 76 | }, 77 | }) 78 | 79 | colorSchema, _ := graphql.NewSchema(graphql.SchemaConfig{ 80 | Query: queryType, 81 | }) 82 | 83 | return colorSchema 84 | } 85 | 86 | var colors []color 87 | 88 | func init() { 89 | colors = make([]color, 0, 256*16*16) 90 | 91 | for r := 0; r < 256; r++ { 92 | for g := 0; g < 16; g++ { 93 | for b := 0; b < 16; b++ { 94 | colors = append(colors, color{ 95 | Hex: fmt.Sprintf("#%x%x%x", r, g, b), 96 | R: r, 97 | G: g, 98 | B: b, 99 | }) 100 | } 101 | } 102 | } 103 | } 104 | 105 | func generateXListItems(x int) []color { 106 | if x > len(colors) { 107 | x = len(colors) 108 | } 109 | return colors[0:x] 110 | } 111 | -------------------------------------------------------------------------------- /benchutil/wide_schema.go: -------------------------------------------------------------------------------- 1 | package benchutil 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/graphql-go/graphql" 7 | ) 8 | 9 | func WideSchemaWithXFieldsAndYItems(x int, y int) graphql.Schema { 10 | wide := graphql.NewObject(graphql.ObjectConfig{ 11 | Name: "Wide", 12 | Description: "An object", 13 | Fields: generateXWideFields(x), 14 | }) 15 | 16 | queryType := graphql.NewObject(graphql.ObjectConfig{ 17 | Name: "Query", 18 | Fields: graphql.Fields{ 19 | "wide": { 20 | Type: graphql.NewList(wide), 21 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 22 | out := make([]struct{}, 0, y) 23 | for i := 0; i < y; i++ { 24 | out = append(out, struct{}{}) 25 | } 26 | return out, nil 27 | }, 28 | }, 29 | }, 30 | }) 31 | 32 | wideSchema, _ := graphql.NewSchema(graphql.SchemaConfig{ 33 | Query: queryType, 34 | }) 35 | 36 | return wideSchema 37 | } 38 | 39 | func generateXWideFields(x int) graphql.Fields { 40 | fields := graphql.Fields{} 41 | for i := 0; i < x; i++ { 42 | fields[generateFieldNameFromX(i)] = generateWideFieldFromX(i) 43 | } 44 | return fields 45 | } 46 | 47 | func generateWideFieldFromX(x int) *graphql.Field { 48 | return &graphql.Field{ 49 | Type: generateWideTypeFromX(x), 50 | Resolve: generateWideResolveFromX(x), 51 | } 52 | } 53 | 54 | func generateWideTypeFromX(x int) graphql.Type { 55 | switch x % 8 { 56 | case 0: 57 | return graphql.String 58 | case 1: 59 | return graphql.NewNonNull(graphql.String) 60 | case 2: 61 | return graphql.Int 62 | case 3: 63 | return graphql.NewNonNull(graphql.Int) 64 | case 4: 65 | return graphql.Float 66 | case 5: 67 | return graphql.NewNonNull(graphql.Float) 68 | case 6: 69 | return graphql.Boolean 70 | case 7: 71 | return graphql.NewNonNull(graphql.Boolean) 72 | } 73 | 74 | return nil 75 | } 76 | 77 | func generateFieldNameFromX(x int) string { 78 | var out string 79 | alphabet := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "z"} 80 | v := x 81 | for { 82 | r := v % 10 83 | out = alphabet[r] + out 84 | v /= 10 85 | if v == 0 { 86 | break 87 | } 88 | } 89 | return out 90 | } 91 | 92 | func generateWideResolveFromX(x int) func(p graphql.ResolveParams) (interface{}, error) { 93 | switch x % 8 { 94 | case 0: 95 | return func(p graphql.ResolveParams) (interface{}, error) { 96 | return fmt.Sprint(x), nil 97 | } 98 | case 1: 99 | return func(p graphql.ResolveParams) (interface{}, error) { 100 | return fmt.Sprint(x), nil 101 | } 102 | case 2: 103 | return func(p graphql.ResolveParams) (interface{}, error) { 104 | return x, nil 105 | } 106 | case 3: 107 | return func(p graphql.ResolveParams) (interface{}, error) { 108 | return x, nil 109 | } 110 | case 4: 111 | return func(p graphql.ResolveParams) (interface{}, error) { 112 | return float64(x), nil 113 | } 114 | case 5: 115 | return func(p graphql.ResolveParams) (interface{}, error) { 116 | return float64(x), nil 117 | } 118 | case 6: 119 | return func(p graphql.ResolveParams) (interface{}, error) { 120 | if x%2 == 0 { 121 | return false, nil 122 | } 123 | return true, nil 124 | } 125 | case 7: 126 | return func(p graphql.ResolveParams) (interface{}, error) { 127 | if x%2 == 0 { 128 | return false, nil 129 | } 130 | return true, nil 131 | } 132 | } 133 | 134 | return nil 135 | } 136 | 137 | func WideSchemaQuery(x int) string { 138 | var fields string 139 | for i := 0; i < x; i++ { 140 | fields = fields + generateFieldNameFromX(i) + " " 141 | } 142 | 143 | return fmt.Sprintf("query { wide { %s} }", fields) 144 | } 145 | -------------------------------------------------------------------------------- /directives.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | const ( 4 | // Operations 5 | DirectiveLocationQuery = "QUERY" 6 | DirectiveLocationMutation = "MUTATION" 7 | DirectiveLocationSubscription = "SUBSCRIPTION" 8 | DirectiveLocationField = "FIELD" 9 | DirectiveLocationFragmentDefinition = "FRAGMENT_DEFINITION" 10 | DirectiveLocationFragmentSpread = "FRAGMENT_SPREAD" 11 | DirectiveLocationInlineFragment = "INLINE_FRAGMENT" 12 | 13 | // Schema Definitions 14 | DirectiveLocationSchema = "SCHEMA" 15 | DirectiveLocationScalar = "SCALAR" 16 | DirectiveLocationObject = "OBJECT" 17 | DirectiveLocationFieldDefinition = "FIELD_DEFINITION" 18 | DirectiveLocationArgumentDefinition = "ARGUMENT_DEFINITION" 19 | DirectiveLocationInterface = "INTERFACE" 20 | DirectiveLocationUnion = "UNION" 21 | DirectiveLocationEnum = "ENUM" 22 | DirectiveLocationEnumValue = "ENUM_VALUE" 23 | DirectiveLocationInputObject = "INPUT_OBJECT" 24 | DirectiveLocationInputFieldDefinition = "INPUT_FIELD_DEFINITION" 25 | ) 26 | 27 | // DefaultDeprecationReason Constant string used for default reason for a deprecation. 28 | const DefaultDeprecationReason = "No longer supported" 29 | 30 | // SpecifiedRules The full list of specified directives. 31 | var SpecifiedDirectives = []*Directive{ 32 | IncludeDirective, 33 | SkipDirective, 34 | DeprecatedDirective, 35 | } 36 | 37 | // Directive structs are used by the GraphQL runtime as a way of modifying execution 38 | // behavior. Type system creators will usually not create these directly. 39 | type Directive struct { 40 | Name string `json:"name"` 41 | Description string `json:"description"` 42 | Locations []string `json:"locations"` 43 | Args []*Argument `json:"args"` 44 | 45 | err error 46 | } 47 | 48 | // DirectiveConfig options for creating a new GraphQLDirective 49 | type DirectiveConfig struct { 50 | Name string `json:"name"` 51 | Description string `json:"description"` 52 | Locations []string `json:"locations"` 53 | Args FieldConfigArgument `json:"args"` 54 | } 55 | 56 | func NewDirective(config DirectiveConfig) *Directive { 57 | dir := &Directive{} 58 | 59 | // Ensure directive is named 60 | if dir.err = invariant(config.Name != "", "Directive must be named."); dir.err != nil { 61 | return dir 62 | } 63 | 64 | // Ensure directive name is valid 65 | if dir.err = assertValidName(config.Name); dir.err != nil { 66 | return dir 67 | } 68 | 69 | // Ensure locations are provided for directive 70 | if dir.err = invariant(len(config.Locations) > 0, "Must provide locations for directive."); dir.err != nil { 71 | return dir 72 | } 73 | 74 | args := []*Argument{} 75 | 76 | for argName, argConfig := range config.Args { 77 | if dir.err = assertValidName(argName); dir.err != nil { 78 | return dir 79 | } 80 | args = append(args, &Argument{ 81 | PrivateName: argName, 82 | PrivateDescription: argConfig.Description, 83 | Type: argConfig.Type, 84 | DefaultValue: argConfig.DefaultValue, 85 | }) 86 | } 87 | 88 | dir.Name = config.Name 89 | dir.Description = config.Description 90 | dir.Locations = config.Locations 91 | dir.Args = args 92 | return dir 93 | } 94 | 95 | // IncludeDirective is used to conditionally include fields or fragments. 96 | var IncludeDirective = NewDirective(DirectiveConfig{ 97 | Name: "include", 98 | Description: "Directs the executor to include this field or fragment only when " + 99 | "the `if` argument is true.", 100 | Locations: []string{ 101 | DirectiveLocationField, 102 | DirectiveLocationFragmentSpread, 103 | DirectiveLocationInlineFragment, 104 | }, 105 | Args: FieldConfigArgument{ 106 | "if": &ArgumentConfig{ 107 | Type: NewNonNull(Boolean), 108 | Description: "Included when true.", 109 | }, 110 | }, 111 | }) 112 | 113 | // SkipDirective Used to conditionally skip (exclude) fields or fragments. 114 | var SkipDirective = NewDirective(DirectiveConfig{ 115 | Name: "skip", 116 | Description: "Directs the executor to skip this field or fragment when the `if` " + 117 | "argument is true.", 118 | Args: FieldConfigArgument{ 119 | "if": &ArgumentConfig{ 120 | Type: NewNonNull(Boolean), 121 | Description: "Skipped when true.", 122 | }, 123 | }, 124 | Locations: []string{ 125 | DirectiveLocationField, 126 | DirectiveLocationFragmentSpread, 127 | DirectiveLocationInlineFragment, 128 | }, 129 | }) 130 | 131 | // DeprecatedDirective Used to declare element of a GraphQL schema as deprecated. 132 | var DeprecatedDirective = NewDirective(DirectiveConfig{ 133 | Name: "deprecated", 134 | Description: "Marks an element of a GraphQL schema as no longer supported.", 135 | Args: FieldConfigArgument{ 136 | "reason": &ArgumentConfig{ 137 | Type: String, 138 | Description: "Explains why this element was deprecated, usually also including a " + 139 | "suggestion for how to access supported similar data. Formatted" + 140 | "in [Markdown](https://daringfireball.net/projects/markdown/).", 141 | DefaultValue: DefaultDeprecationReason, 142 | }, 143 | }, 144 | Locations: []string{ 145 | DirectiveLocationFieldDefinition, 146 | DirectiveLocationEnumValue, 147 | }, 148 | }) 149 | -------------------------------------------------------------------------------- /examples/concurrent-resolvers/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/graphql-go/graphql" 9 | ) 10 | 11 | type Foo struct { 12 | Name string 13 | } 14 | 15 | var FieldFooType = graphql.NewObject(graphql.ObjectConfig{ 16 | Name: "Foo", 17 | Fields: graphql.Fields{ 18 | "name": &graphql.Field{Type: graphql.String}, 19 | }, 20 | }) 21 | 22 | type Bar struct { 23 | Name string 24 | } 25 | 26 | var FieldBarType = graphql.NewObject(graphql.ObjectConfig{ 27 | Name: "Bar", 28 | Fields: graphql.Fields{ 29 | "name": &graphql.Field{Type: graphql.String}, 30 | }, 31 | }) 32 | 33 | // QueryType fields: `concurrentFieldFoo` and `concurrentFieldBar` are resolved 34 | // concurrently because they belong to the same field-level and their `Resolve` 35 | // function returns a function (thunk). 36 | var QueryType = graphql.NewObject(graphql.ObjectConfig{ 37 | Name: "Query", 38 | Fields: graphql.Fields{ 39 | "concurrentFieldFoo": &graphql.Field{ 40 | Type: FieldFooType, 41 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 42 | var foo = Foo{Name: "Foo's name"} 43 | return func() (interface{}, error) { 44 | return &foo, nil 45 | }, nil 46 | }, 47 | }, 48 | "concurrentFieldBar": &graphql.Field{ 49 | Type: FieldBarType, 50 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 51 | type result struct { 52 | data interface{} 53 | err error 54 | } 55 | ch := make(chan *result, 1) 56 | go func() { 57 | defer close(ch) 58 | bar := &Bar{Name: "Bar's name"} 59 | ch <- &result{data: bar, err: nil} 60 | }() 61 | return func() (interface{}, error) { 62 | r := <-ch 63 | return r.data, r.err 64 | }, nil 65 | }, 66 | }, 67 | }, 68 | }) 69 | 70 | func main() { 71 | schema, err := graphql.NewSchema(graphql.SchemaConfig{ 72 | Query: QueryType, 73 | }) 74 | if err != nil { 75 | log.Fatal(err) 76 | } 77 | query := ` 78 | query { 79 | concurrentFieldFoo { 80 | name 81 | } 82 | concurrentFieldBar { 83 | name 84 | } 85 | } 86 | ` 87 | result := graphql.Do(graphql.Params{ 88 | RequestString: query, 89 | Schema: schema, 90 | }) 91 | b, err := json.Marshal(result) 92 | if err != nil { 93 | log.Fatal(err) 94 | } 95 | fmt.Printf("%s", b) 96 | /* 97 | { 98 | "data": { 99 | "concurrentFieldBar": { 100 | "name": "Bar's name" 101 | }, 102 | "concurrentFieldFoo": { 103 | "name": "Foo's name" 104 | } 105 | } 106 | } 107 | */ 108 | } 109 | -------------------------------------------------------------------------------- /examples/context/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | 10 | "github.com/graphql-go/graphql" 11 | ) 12 | 13 | var Schema graphql.Schema 14 | 15 | var userType = graphql.NewObject( 16 | graphql.ObjectConfig{ 17 | Name: "User", 18 | Fields: graphql.Fields{ 19 | "id": &graphql.Field{ 20 | Type: graphql.String, 21 | }, 22 | "name": &graphql.Field{ 23 | Type: graphql.String, 24 | }, 25 | }, 26 | }, 27 | ) 28 | 29 | var queryType = graphql.NewObject( 30 | graphql.ObjectConfig{ 31 | Name: "Query", 32 | Fields: graphql.Fields{ 33 | "me": &graphql.Field{ 34 | Type: userType, 35 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 36 | return p.Context.Value("currentUser"), nil 37 | }, 38 | }, 39 | }, 40 | }) 41 | 42 | func graphqlHandler(w http.ResponseWriter, r *http.Request) { 43 | user := struct { 44 | ID int `json:"id"` 45 | Name string `json:"name"` 46 | }{1, "cool user"} 47 | result := graphql.Do(graphql.Params{ 48 | Schema: Schema, 49 | RequestString: r.URL.Query().Get("query"), 50 | Context: context.WithValue(context.Background(), "currentUser", user), 51 | }) 52 | if len(result.Errors) > 0 { 53 | log.Printf("wrong result, unexpected errors: %v", result.Errors) 54 | return 55 | } 56 | json.NewEncoder(w).Encode(result) 57 | } 58 | 59 | func main() { 60 | http.HandleFunc("/graphql", graphqlHandler) 61 | fmt.Println("Now server is running on port 8080") 62 | fmt.Println("Test with Get : curl -g 'http://localhost:8080/graphql?query={me{id,name}}'") 63 | http.ListenAndServe(":8080", nil) 64 | } 65 | 66 | func init() { 67 | s, err := graphql.NewSchema(graphql.SchemaConfig{ 68 | Query: queryType, 69 | }) 70 | if err != nil { 71 | log.Fatalf("failed to create schema, error: %v", err) 72 | } 73 | Schema = s 74 | } 75 | -------------------------------------------------------------------------------- /examples/crud/Readme.md: -------------------------------------------------------------------------------- 1 | # Go GraphQL CRUD example 2 | 3 | Implement create, read, update and delete on Go. 4 | 5 | To run the program: 6 | 7 | 1. go to the directory: `cd examples/crud` 8 | 2. Run the example: `go run main.go` 9 | 10 | ## Create 11 | 12 | `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}}` 13 | 14 | ## Read 15 | 16 | * Get single product by id: `http://localhost:8080/product?query={product(id:1){name,info,price}}` 17 | * Get product list: `http://localhost:8080/product?query={list{id,name,info,price}}` 18 | 19 | ## Update 20 | 21 | `http://localhost:8080/product?query=mutation+_{update(id:1,price:3.95){id,name,info,price}}` 22 | 23 | ## Delete 24 | 25 | `http://localhost:8080/product?query=mutation+_{delete(id:1){id,name,info,price}}` 26 | -------------------------------------------------------------------------------- /examples/custom-scalar-type/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/graphql-go/graphql" 9 | "github.com/graphql-go/graphql/language/ast" 10 | ) 11 | 12 | type CustomID struct { 13 | value string 14 | } 15 | 16 | func (id *CustomID) String() string { 17 | return id.value 18 | } 19 | 20 | func NewCustomID(v string) *CustomID { 21 | return &CustomID{value: v} 22 | } 23 | 24 | var CustomScalarType = graphql.NewScalar(graphql.ScalarConfig{ 25 | Name: "CustomScalarType", 26 | Description: "The `CustomScalarType` scalar type represents an ID Object.", 27 | // Serialize serializes `CustomID` to string. 28 | Serialize: func(value interface{}) interface{} { 29 | switch value := value.(type) { 30 | case CustomID: 31 | return value.String() 32 | case *CustomID: 33 | v := *value 34 | return v.String() 35 | default: 36 | return nil 37 | } 38 | }, 39 | // ParseValue parses GraphQL variables from `string` to `CustomID`. 40 | ParseValue: func(value interface{}) interface{} { 41 | switch value := value.(type) { 42 | case string: 43 | return NewCustomID(value) 44 | case *string: 45 | return NewCustomID(*value) 46 | default: 47 | return nil 48 | } 49 | }, 50 | // ParseLiteral parses GraphQL AST value to `CustomID`. 51 | ParseLiteral: func(valueAST ast.Value) interface{} { 52 | switch valueAST := valueAST.(type) { 53 | case *ast.StringValue: 54 | return NewCustomID(valueAST.Value) 55 | default: 56 | return nil 57 | } 58 | }, 59 | }) 60 | 61 | type Customer struct { 62 | ID *CustomID `json:"id"` 63 | } 64 | 65 | var CustomerType = graphql.NewObject(graphql.ObjectConfig{ 66 | Name: "Customer", 67 | Fields: graphql.Fields{ 68 | "id": &graphql.Field{ 69 | Type: CustomScalarType, 70 | }, 71 | }, 72 | }) 73 | 74 | func main() { 75 | schema, err := graphql.NewSchema(graphql.SchemaConfig{ 76 | Query: graphql.NewObject(graphql.ObjectConfig{ 77 | Name: "Query", 78 | Fields: graphql.Fields{ 79 | "customers": &graphql.Field{ 80 | Type: graphql.NewList(CustomerType), 81 | Args: graphql.FieldConfigArgument{ 82 | "id": &graphql.ArgumentConfig{ 83 | Type: CustomScalarType, 84 | }, 85 | }, 86 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 87 | // id := p.Args["id"] 88 | // log.Printf("id from arguments: %+v", id) 89 | customers := []Customer{ 90 | Customer{ID: NewCustomID("fb278f2a4a13f")}, 91 | } 92 | return customers, nil 93 | }, 94 | }, 95 | }, 96 | }), 97 | }) 98 | if err != nil { 99 | log.Fatal(err) 100 | } 101 | query := ` 102 | query { 103 | customers { 104 | id 105 | } 106 | } 107 | ` 108 | /* 109 | queryWithVariable := ` 110 | query($id: CustomScalarType) { 111 | customers(id: $id) { 112 | id 113 | } 114 | } 115 | ` 116 | */ 117 | /* 118 | queryWithArgument := ` 119 | query { 120 | customers(id: "5b42ba57289") { 121 | id 122 | } 123 | } 124 | ` 125 | */ 126 | result := graphql.Do(graphql.Params{ 127 | Schema: schema, 128 | RequestString: query, 129 | VariableValues: map[string]interface{}{ 130 | "id": "5b42ba57289", 131 | }, 132 | }) 133 | if len(result.Errors) > 0 { 134 | log.Fatal(result) 135 | } 136 | b, err := json.Marshal(result) 137 | if err != nil { 138 | log.Fatal(err) 139 | } 140 | fmt.Println(string(b)) 141 | } 142 | -------------------------------------------------------------------------------- /examples/hello-world/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/graphql-go/graphql" 9 | ) 10 | 11 | func main() { 12 | // Schema 13 | fields := graphql.Fields{ 14 | "hello": &graphql.Field{ 15 | Type: graphql.String, 16 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 17 | return "world", nil 18 | }, 19 | }, 20 | } 21 | rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: fields} 22 | schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)} 23 | schema, err := graphql.NewSchema(schemaConfig) 24 | if err != nil { 25 | log.Fatalf("failed to create new schema, error: %v", err) 26 | } 27 | 28 | // Query 29 | query := ` 30 | { 31 | hello 32 | } 33 | ` 34 | params := graphql.Params{Schema: schema, RequestString: query} 35 | r := graphql.Do(params) 36 | if len(r.Errors) > 0 { 37 | log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors) 38 | } 39 | rJSON, _ := json.Marshal(r) 40 | fmt.Printf("%s \n", rJSON) // {“data”:{“hello”:”world”}} 41 | } 42 | -------------------------------------------------------------------------------- /examples/http-post/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/graphql-go/graphql" 9 | "github.com/graphql-go/graphql/examples/todo/schema" 10 | ) 11 | 12 | type postData struct { 13 | Query string `json:"query"` 14 | Operation string `json:"operationName"` 15 | Variables map[string]interface{} `json:"variables"` 16 | } 17 | 18 | func main() { 19 | http.HandleFunc("/graphql", func(w http.ResponseWriter, req *http.Request) { 20 | var p postData 21 | if err := json.NewDecoder(req.Body).Decode(&p); err != nil { 22 | w.WriteHeader(400) 23 | return 24 | } 25 | result := graphql.Do(graphql.Params{ 26 | Context: req.Context(), 27 | Schema: schema.TodoSchema, 28 | RequestString: p.Query, 29 | VariableValues: p.Variables, 30 | OperationName: p.Operation, 31 | }) 32 | if err := json.NewEncoder(w).Encode(result); err != nil { 33 | fmt.Printf("could not write result to response: %s", err) 34 | } 35 | }) 36 | 37 | fmt.Println("Now server is running on port 8080") 38 | 39 | fmt.Println("") 40 | 41 | fmt.Println(`Get single todo: 42 | curl \ 43 | -X POST \ 44 | -H "Content-Type: application/json" \ 45 | --data '{ "query": "{ todo(id:\"b\") { id text done } }" }' \ 46 | http://localhost:8080/graphql`) 47 | 48 | fmt.Println("") 49 | 50 | fmt.Println(`Create new todo: 51 | curl \ 52 | -X POST \ 53 | -H "Content-Type: application/json" \ 54 | --data '{ "query": "mutation { createTodo(text:\"My New todo\") { id text done } }" }' \ 55 | http://localhost:8080/graphql`) 56 | 57 | fmt.Println("") 58 | 59 | fmt.Println(`Update todo: 60 | curl \ 61 | -X POST \ 62 | -H "Content-Type: application/json" \ 63 | --data '{ "query": "mutation { updateTodo(id:\"a\", done: true) { id text done } }" }' \ 64 | http://localhost:8080/graphql`) 65 | 66 | fmt.Println("") 67 | 68 | fmt.Println(`Load todo list: 69 | curl \ 70 | -X POST \ 71 | -H "Content-Type: application/json" \ 72 | --data '{ "query": "{ todoList { id text done } }" }' \ 73 | http://localhost:8080/graphql`) 74 | 75 | http.ListenAndServe(":8080", nil) 76 | } 77 | -------------------------------------------------------------------------------- /examples/http/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "1": { 3 | "id": "1", 4 | "name": "Dan" 5 | }, 6 | "2": { 7 | "id": "2", 8 | "name": "Lee" 9 | }, 10 | "3": { 11 | "id": "3", 12 | "name": "Nick" 13 | } 14 | } -------------------------------------------------------------------------------- /examples/http/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | 9 | "github.com/graphql-go/graphql" 10 | ) 11 | 12 | type user struct { 13 | ID string `json:"id"` 14 | Name string `json:"name"` 15 | } 16 | 17 | var data map[string]user 18 | 19 | /* 20 | Create User object type with fields "id" and "name" by using GraphQLObjectTypeConfig: 21 | - Name: name of object type 22 | - Fields: a map of fields by using GraphQLFields 23 | Setup type of field use GraphQLFieldConfig 24 | */ 25 | var userType = graphql.NewObject( 26 | graphql.ObjectConfig{ 27 | Name: "User", 28 | Fields: graphql.Fields{ 29 | "id": &graphql.Field{ 30 | Type: graphql.String, 31 | }, 32 | "name": &graphql.Field{ 33 | Type: graphql.String, 34 | }, 35 | }, 36 | }, 37 | ) 38 | 39 | /* 40 | Create Query object type with fields "user" has type [userType] by using GraphQLObjectTypeConfig: 41 | - Name: name of object type 42 | - Fields: a map of fields by using GraphQLFields 43 | Setup type of field use GraphQLFieldConfig to define: 44 | - Type: type of field 45 | - Args: arguments to query with current field 46 | - Resolve: function to query data using params from [Args] and return value with current type 47 | */ 48 | var queryType = graphql.NewObject( 49 | graphql.ObjectConfig{ 50 | Name: "Query", 51 | Fields: graphql.Fields{ 52 | "user": &graphql.Field{ 53 | Type: userType, 54 | Args: graphql.FieldConfigArgument{ 55 | "id": &graphql.ArgumentConfig{ 56 | Type: graphql.String, 57 | }, 58 | }, 59 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 60 | idQuery, isOK := p.Args["id"].(string) 61 | if isOK { 62 | return data[idQuery], nil 63 | } 64 | return nil, nil 65 | }, 66 | }, 67 | }, 68 | }) 69 | 70 | var schema, _ = graphql.NewSchema( 71 | graphql.SchemaConfig{ 72 | Query: queryType, 73 | }, 74 | ) 75 | 76 | func executeQuery(query string, schema graphql.Schema) *graphql.Result { 77 | result := graphql.Do(graphql.Params{ 78 | Schema: schema, 79 | RequestString: query, 80 | }) 81 | if len(result.Errors) > 0 { 82 | fmt.Printf("wrong result, unexpected errors: %v", result.Errors) 83 | } 84 | return result 85 | } 86 | 87 | func main() { 88 | _ = importJSONDataFromFile("data.json", &data) 89 | 90 | http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) { 91 | result := executeQuery(r.URL.Query().Get("query"), schema) 92 | json.NewEncoder(w).Encode(result) 93 | }) 94 | 95 | fmt.Println("Now server is running on port 8080") 96 | fmt.Println("Test with Get : curl -g 'http://localhost:8080/graphql?query={user(id:\"1\"){name}}'") 97 | http.ListenAndServe(":8080", nil) 98 | } 99 | 100 | //Helper function to import json from file to map 101 | func importJSONDataFromFile(fileName string, result interface{}) (isOK bool) { 102 | isOK = true 103 | content, err := ioutil.ReadFile(fileName) 104 | if err != nil { 105 | fmt.Print("Error:", err) 106 | isOK = false 107 | } 108 | err = json.Unmarshal(content, result) 109 | if err != nil { 110 | isOK = false 111 | fmt.Print("Error:", err) 112 | } 113 | return 114 | } 115 | -------------------------------------------------------------------------------- /examples/httpdynamic/README.md: -------------------------------------------------------------------------------- 1 | Basically, if we have `data.json` like this: 2 | 3 | [ 4 | { "id": "1", "name": "Dan" }, 5 | { "id": "2", "name": "Lee" }, 6 | { "id": "3", "name": "Nick" } 7 | ] 8 | 9 | ... and `go run main.go`, we can query records: 10 | 11 | $ curl -g 'http://localhost:8080/graphql?query={user(name:"Dan"){id}}' 12 | {"data":{"user":{"id":"1"}}} 13 | 14 | ... now let's give Dan a surname: 15 | 16 | [ 17 | { "id": "1", "name": "Dan", "surname": "Jones" }, 18 | { "id": "2", "name": "Lee" }, 19 | { "id": "3", "name": "Nick" } 20 | ] 21 | 22 | ... and kick the server: 23 | 24 | kill -SIGUSR1 52114 25 | 26 | And ask for Dan's surname: 27 | 28 | $ curl -g 'http://localhost:8080/graphql?query={user(name:"Dan"){id,surname}}' 29 | {"data":{"user":{"id":"1","surname":"Jones"}}} 30 | 31 | ... or ask Jones's name and ID: 32 | 33 | $ curl -g 'http://localhost:8080/graphql?query={user(surname:"Jones"){id,name}}' 34 | {"data":{"user":{"id":"1","name":"Dan"}}} 35 | 36 | If you look at `main.go`, the file is not field-aware. That is, all it knows is 37 | how to work with `[]map[string]string` type. 38 | 39 | With this, we are not that far from exposing dynamic fields and filters which 40 | fully depend on what we have stored, all without changing our tooling. 41 | -------------------------------------------------------------------------------- /examples/httpdynamic/data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "1", 4 | "name": "Dan", 5 | "surname": "Jones" 6 | }, 7 | { 8 | "id": "2", 9 | "name": "Lee" 10 | }, 11 | { 12 | "id": "3", 13 | "name": "Nick" 14 | } 15 | ] 16 | -------------------------------------------------------------------------------- /examples/httpdynamic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "os" 9 | "os/signal" 10 | "strconv" 11 | "syscall" 12 | 13 | "github.com/graphql-go/graphql" 14 | ) 15 | 16 | /*****************************************************************************/ 17 | /* Shared data variables to allow dynamic reloads 18 | /*****************************************************************************/ 19 | 20 | var schema graphql.Schema 21 | 22 | const jsonDataFile = "data.json" 23 | 24 | func handleSIGUSR1(c chan os.Signal) { 25 | for { 26 | <-c 27 | fmt.Printf("Caught SIGUSR1. Reloading %s\n", jsonDataFile) 28 | err := importJSONDataFromFile(jsonDataFile) 29 | if err != nil { 30 | fmt.Printf("Error: %s\n", err.Error()) 31 | return 32 | } 33 | } 34 | } 35 | 36 | func filterUser(data []map[string]interface{}, args map[string]interface{}) map[string]interface{} { 37 | for _, user := range data { 38 | for k, v := range args { 39 | if user[k] != v { 40 | goto nextuser 41 | } 42 | return user 43 | } 44 | 45 | nextuser: 46 | } 47 | return nil 48 | } 49 | 50 | func executeQuery(query string, schema graphql.Schema) *graphql.Result { 51 | result := graphql.Do(graphql.Params{ 52 | Schema: schema, 53 | RequestString: query, 54 | }) 55 | if len(result.Errors) > 0 { 56 | fmt.Printf("wrong result, unexpected errors: %v\n", result.Errors) 57 | } 58 | return result 59 | } 60 | 61 | func importJSONDataFromFile(fileName string) error { 62 | content, err := ioutil.ReadFile(fileName) 63 | if err != nil { 64 | return err 65 | } 66 | 67 | var data []map[string]interface{} 68 | 69 | err = json.Unmarshal(content, &data) 70 | if err != nil { 71 | return err 72 | } 73 | 74 | fields := make(graphql.Fields) 75 | args := make(graphql.FieldConfigArgument) 76 | for _, item := range data { 77 | for k := range item { 78 | fields[k] = &graphql.Field{ 79 | Type: graphql.String, 80 | } 81 | args[k] = &graphql.ArgumentConfig{ 82 | Type: graphql.String, 83 | } 84 | } 85 | } 86 | 87 | var userType = graphql.NewObject( 88 | graphql.ObjectConfig{ 89 | Name: "User", 90 | Fields: fields, 91 | }, 92 | ) 93 | 94 | var queryType = graphql.NewObject( 95 | graphql.ObjectConfig{ 96 | Name: "Query", 97 | Fields: graphql.Fields{ 98 | "user": &graphql.Field{ 99 | Type: userType, 100 | Args: args, 101 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 102 | return filterUser(data, p.Args), nil 103 | }, 104 | }, 105 | }, 106 | }) 107 | 108 | schema, _ = graphql.NewSchema( 109 | graphql.SchemaConfig{ 110 | Query: queryType, 111 | }, 112 | ) 113 | 114 | return nil 115 | } 116 | 117 | func main() { 118 | // Catch SIGUSR1 and reload the data file 119 | c := make(chan os.Signal, 1) 120 | signal.Notify(c, syscall.SIGUSR1) 121 | go handleSIGUSR1(c) 122 | 123 | err := importJSONDataFromFile(jsonDataFile) 124 | if err != nil { 125 | fmt.Printf("Error: %s\n", err.Error()) 126 | return 127 | } 128 | 129 | http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) { 130 | result := executeQuery(r.URL.Query().Get("query"), schema) 131 | json.NewEncoder(w).Encode(result) 132 | }) 133 | 134 | fmt.Println("Now server is running on port 8080") 135 | fmt.Println("Test with Get : curl -g 'http://localhost:8080/graphql?query={user(name:\"Dan\"){id,surname}}'") 136 | fmt.Printf("Reload json file : kill -SIGUSR1 %s\n", strconv.Itoa(os.Getpid())) 137 | http.ListenAndServe(":8080", nil) 138 | } 139 | -------------------------------------------------------------------------------- /examples/modify-context/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | 9 | "github.com/graphql-go/graphql" 10 | ) 11 | 12 | type User struct { 13 | ID int `json:"id"` 14 | } 15 | 16 | var UserType = graphql.NewObject(graphql.ObjectConfig{ 17 | Name: "User", 18 | Fields: graphql.Fields{ 19 | "id": &graphql.Field{ 20 | Type: graphql.Int, 21 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 22 | rootValue := p.Info.RootValue.(map[string]interface{}) 23 | if rootValue["data-from-parent"] == "ok" && 24 | rootValue["data-before-execution"] == "ok" { 25 | user := p.Source.(User) 26 | return user.ID, nil 27 | } 28 | return nil, nil 29 | }, 30 | }, 31 | }, 32 | }) 33 | 34 | func main() { 35 | schema, err := graphql.NewSchema(graphql.SchemaConfig{ 36 | Query: graphql.NewObject(graphql.ObjectConfig{ 37 | Name: "Query", 38 | Fields: graphql.Fields{ 39 | "users": &graphql.Field{ 40 | Type: graphql.NewList(UserType), 41 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 42 | rootValue := p.Info.RootValue.(map[string]interface{}) 43 | rootValue["data-from-parent"] = "ok" 44 | result := []User{ 45 | User{ID: 1}, 46 | } 47 | return result, nil 48 | 49 | }, 50 | }, 51 | }, 52 | }), 53 | }) 54 | if err != nil { 55 | log.Fatal(err) 56 | } 57 | ctx := context.WithValue(context.Background(), "currentUser", User{ID: 100}) 58 | // Instead of trying to modify context within a resolve function, use: 59 | // `graphql.Params.RootObject` is a mutable optional variable and available on 60 | // each resolve function via: `graphql.ResolveParams.Info.RootValue`. 61 | rootObject := map[string]interface{}{ 62 | "data-before-execution": "ok", 63 | } 64 | result := graphql.Do(graphql.Params{ 65 | Context: ctx, 66 | RequestString: "{ users { id } }", 67 | RootObject: rootObject, 68 | Schema: schema, 69 | }) 70 | b, err := json.Marshal(result) 71 | if err != nil { 72 | log.Fatal(err) 73 | } 74 | fmt.Printf("%s\n", string(b)) // {"data":{"users":[{"id":1}]}} 75 | } 76 | -------------------------------------------------------------------------------- /examples/sql-nullstring/README.md: -------------------------------------------------------------------------------- 1 | # Go GraphQL SQL null string example 2 | 3 | database/sql Nullstring implementation, with JSON marshalling interfaces. 4 | 5 | To run the program, go to the directory 6 | `cd examples/sql-nullstring` 7 | 8 | Run the example 9 | `go run main.go` 10 | 11 | ## sql.NullString 12 | 13 | On occasion you will encounter sql fields that are nullable, as in 14 | 15 | ```sql 16 | CREATE TABLE persons ( 17 | id INT PRIMARY KEY, 18 | name TEXT NOT NULL, 19 | favorite_dog TEXT -- this field can have a NULL value 20 | ) 21 | ``` 22 | 23 | For the struct 24 | 25 | ```golang 26 | import "database/sql" 27 | 28 | type Person struct { 29 | ID int `json:"id" sql:"id"` 30 | Name string `json:"name" sql:"name"` 31 | FavoriteDog sql.NullString `json:"favorite_dog" sql:"favorite_dog"` 32 | } 33 | ``` 34 | 35 | But `graphql` would render said field as an object `{{ false}}` or `{{Bulldog true}}`, depending on their validity. 36 | 37 | With this implementation, `graphql` would render the null items as an empty string (`""`), but would be saved in the database as `NULL`, appropriately. 38 | 39 | The pattern can be extended to include other `database/sql` null types. 40 | -------------------------------------------------------------------------------- /examples/star-wars/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/graphql-go/graphql" 9 | "github.com/graphql-go/graphql/testutil" 10 | ) 11 | 12 | func main() { 13 | http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) { 14 | query := r.URL.Query().Get("query") 15 | result := graphql.Do(graphql.Params{ 16 | Schema: testutil.StarWarsSchema, 17 | RequestString: query, 18 | }) 19 | json.NewEncoder(w).Encode(result) 20 | }) 21 | fmt.Println("Now server is running on port 8080") 22 | fmt.Println("Test with Get : curl -g 'http://localhost:8080/graphql?query={hero{name}}'") 23 | http.ListenAndServe(":8080", nil) 24 | } 25 | -------------------------------------------------------------------------------- /examples/todo/README.md: -------------------------------------------------------------------------------- 1 | # Go GraphQL ToDo example 2 | 3 | An example that consists of basic core GraphQL queries and mutations. 4 | 5 | To run the example navigate to the example directory by using your shell of choice. 6 | 7 | ``` 8 | cd examples/todo 9 | ``` 10 | 11 | Run the example, it will spawn a GraphQL HTTP endpoint 12 | 13 | ``` 14 | go run main.go 15 | ``` 16 | 17 | Execute queries via shell. 18 | 19 | ``` 20 | // To get single ToDo item by ID 21 | curl -g 'http://localhost:8080/graphql?query={todo(id:"b"){id,text,done}}' 22 | 23 | // To create a ToDo item 24 | curl -g 'http://localhost:8080/graphql?query=mutation+_{createTodo(text:"My+new+todo"){id,text,done}}' 25 | 26 | // To get a list of ToDo items 27 | curl -g 'http://localhost:8080/graphql?query={todoList{id,text,done}}' 28 | 29 | // To update a ToDo 30 | curl -g 'http://localhost:8080/graphql?query=mutation+_{updateTodo(id:"b",text:"My+new+todo+updated",done:true){id,text,done}}' 31 | ``` 32 | 33 | ## Web App 34 | 35 | Access the web app at `http://localhost:8080/`. It is work in progress and currently is simply loading todos by using jQuery ajax call. 36 | -------------------------------------------------------------------------------- /examples/todo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "math/rand" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/graphql-go/graphql" 11 | "github.com/graphql-go/graphql/examples/todo/schema" 12 | ) 13 | 14 | func init() { 15 | todo1 := schema.Todo{ID: "a", Text: "A todo not to forget", Done: false} 16 | todo2 := schema.Todo{ID: "b", Text: "This is the most important", Done: false} 17 | todo3 := schema.Todo{ID: "c", Text: "Please do this or else", Done: false} 18 | schema.TodoList = append(schema.TodoList, todo1, todo2, todo3) 19 | 20 | rand.Seed(time.Now().UnixNano()) 21 | } 22 | 23 | func executeQuery(query string, schema graphql.Schema) *graphql.Result { 24 | result := graphql.Do(graphql.Params{ 25 | Schema: schema, 26 | RequestString: query, 27 | }) 28 | if len(result.Errors) > 0 { 29 | fmt.Printf("wrong result, unexpected errors: %v", result.Errors) 30 | } 31 | return result 32 | } 33 | 34 | func main() { 35 | http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) { 36 | result := executeQuery(r.URL.Query().Get("query"), schema.TodoSchema) 37 | json.NewEncoder(w).Encode(result) 38 | }) 39 | // Serve static files 40 | fs := http.FileServer(http.Dir("static")) 41 | http.Handle("/", fs) 42 | // Display some basic instructions 43 | fmt.Println("Now server is running on port 8080") 44 | fmt.Println("Get single todo: curl -g 'http://localhost:8080/graphql?query={todo(id:\"b\"){id,text,done}}'") 45 | fmt.Println("Create new todo: curl -g 'http://localhost:8080/graphql?query=mutation+_{createTodo(text:\"My+new+todo\"){id,text,done}}'") 46 | fmt.Println("Update todo: curl -g 'http://localhost:8080/graphql?query=mutation+_{updateTodo(id:\"a\",done:true){id,text,done}}'") 47 | fmt.Println("Load todo list: curl -g 'http://localhost:8080/graphql?query={todoList{id,text,done}}'") 48 | fmt.Println("Access the web app via browser at 'http://localhost:8080'") 49 | 50 | http.ListenAndServe(":8080", nil) 51 | } 52 | -------------------------------------------------------------------------------- /examples/todo/schema/schema.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "math/rand" 5 | 6 | "github.com/graphql-go/graphql" 7 | ) 8 | 9 | var TodoList []Todo 10 | 11 | type Todo struct { 12 | ID string `json:"id"` 13 | Text string `json:"text"` 14 | Done bool `json:"done"` 15 | } 16 | 17 | var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 18 | 19 | func RandStringRunes(n int) string { 20 | b := make([]rune, n) 21 | for i := range b { 22 | b[i] = letterRunes[rand.Intn(len(letterRunes))] 23 | } 24 | return string(b) 25 | } 26 | 27 | // define custom GraphQL ObjectType `todoType` for our Golang struct `Todo` 28 | // Note that 29 | // - the fields in our todoType maps with the json tags for the fields in our struct 30 | // - the field type matches the field type in our struct 31 | var todoType = graphql.NewObject(graphql.ObjectConfig{ 32 | Name: "Todo", 33 | Fields: graphql.Fields{ 34 | "id": &graphql.Field{ 35 | Type: graphql.String, 36 | }, 37 | "text": &graphql.Field{ 38 | Type: graphql.String, 39 | }, 40 | "done": &graphql.Field{ 41 | Type: graphql.Boolean, 42 | }, 43 | }, 44 | }) 45 | 46 | // root mutation 47 | var rootMutation = graphql.NewObject(graphql.ObjectConfig{ 48 | Name: "RootMutation", 49 | Fields: graphql.Fields{ 50 | /* 51 | curl -g 'http://localhost:8080/graphql?query=mutation+_{createTodo(text:"My+new+todo"){id,text,done}}' 52 | */ 53 | "createTodo": &graphql.Field{ 54 | Type: todoType, // the return type for this field 55 | Description: "Create new todo", 56 | Args: graphql.FieldConfigArgument{ 57 | "text": &graphql.ArgumentConfig{ 58 | Type: graphql.NewNonNull(graphql.String), 59 | }, 60 | }, 61 | Resolve: func(params graphql.ResolveParams) (interface{}, error) { 62 | 63 | // marshall and cast the argument value 64 | text, _ := params.Args["text"].(string) 65 | 66 | // figure out new id 67 | newID := RandStringRunes(8) 68 | 69 | // perform mutation operation here 70 | // for e.g. create a Todo and save to DB. 71 | newTodo := Todo{ 72 | ID: newID, 73 | Text: text, 74 | Done: false, 75 | } 76 | 77 | TodoList = append(TodoList, newTodo) 78 | 79 | // return the new Todo object that we supposedly save to DB 80 | // Note here that 81 | // - we are returning a `Todo` struct instance here 82 | // - we previously specified the return Type to be `todoType` 83 | // - `Todo` struct maps to `todoType`, as defined in `todoType` ObjectConfig` 84 | return newTodo, nil 85 | }, 86 | }, 87 | /* 88 | curl -g 'http://localhost:8080/graphql?query=mutation+_{updateTodo(id:"a",done:true){id,text,done}}' 89 | */ 90 | "updateTodo": &graphql.Field{ 91 | Type: todoType, // the return type for this field 92 | Description: "Update existing todo, mark it done or not done", 93 | Args: graphql.FieldConfigArgument{ 94 | "done": &graphql.ArgumentConfig{ 95 | Type: graphql.Boolean, 96 | }, 97 | "id": &graphql.ArgumentConfig{ 98 | Type: graphql.NewNonNull(graphql.String), 99 | }, 100 | }, 101 | Resolve: func(params graphql.ResolveParams) (interface{}, error) { 102 | // marshall and cast the argument value 103 | done, _ := params.Args["done"].(bool) 104 | id, _ := params.Args["id"].(string) 105 | affectedTodo := Todo{} 106 | 107 | // Search list for todo with id and change the done variable 108 | for i := 0; i < len(TodoList); i++ { 109 | if TodoList[i].ID == id { 110 | TodoList[i].Done = done 111 | // Assign updated todo so we can return it 112 | affectedTodo = TodoList[i] 113 | break 114 | } 115 | } 116 | // Return affected todo 117 | return affectedTodo, nil 118 | }, 119 | }, 120 | }, 121 | }) 122 | 123 | // root query 124 | // we just define a trivial example here, since root query is required. 125 | // Test with curl 126 | // curl -g 'http://localhost:8080/graphql?query={lastTodo{id,text,done}}' 127 | var rootQuery = graphql.NewObject(graphql.ObjectConfig{ 128 | Name: "RootQuery", 129 | Fields: graphql.Fields{ 130 | 131 | /* 132 | curl -g 'http://localhost:8080/graphql?query={todo(id:"b"){id,text,done}}' 133 | */ 134 | "todo": &graphql.Field{ 135 | Type: todoType, 136 | Description: "Get single todo", 137 | Args: graphql.FieldConfigArgument{ 138 | "id": &graphql.ArgumentConfig{ 139 | Type: graphql.String, 140 | }, 141 | }, 142 | Resolve: func(params graphql.ResolveParams) (interface{}, error) { 143 | 144 | idQuery, isOK := params.Args["id"].(string) 145 | if isOK { 146 | // Search for el with id 147 | for _, todo := range TodoList { 148 | if todo.ID == idQuery { 149 | return todo, nil 150 | } 151 | } 152 | } 153 | 154 | return Todo{}, nil 155 | }, 156 | }, 157 | 158 | "lastTodo": &graphql.Field{ 159 | Type: todoType, 160 | Description: "Last todo added", 161 | Resolve: func(params graphql.ResolveParams) (interface{}, error) { 162 | return TodoList[len(TodoList)-1], nil 163 | }, 164 | }, 165 | 166 | /* 167 | curl -g 'http://localhost:8080/graphql?query={todoList{id,text,done}}' 168 | */ 169 | "todoList": &graphql.Field{ 170 | Type: graphql.NewList(todoType), 171 | Description: "List of todos", 172 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 173 | return TodoList, nil 174 | }, 175 | }, 176 | }, 177 | }) 178 | 179 | // define schema, with our rootQuery and rootMutation 180 | var TodoSchema, _ = graphql.NewSchema(graphql.SchemaConfig{ 181 | Query: rootQuery, 182 | Mutation: rootMutation, 183 | }) 184 | -------------------------------------------------------------------------------- /examples/todo/static/assets/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #cccccc; 3 | color: #333333; 4 | font-family: sans-serif; 5 | } 6 | 7 | .todo-done { 8 | opacity: 0.5; 9 | } -------------------------------------------------------------------------------- /examples/todo/static/assets/js/app.js: -------------------------------------------------------------------------------- 1 | var updateTodo = function(id, isDone){ 2 | $.ajax({ 3 | url: '/graphql?query=mutation+_{updateTodo(id:"' + id + '",done:' + isDone + '){id,text,done}}' 4 | }).done(function(data) { 5 | console.log(data); 6 | var dataParsed = JSON.parse(data); 7 | var updatedTodo = dataParsed.data.updateTodo; 8 | if (updatedTodo.done) { 9 | $('#' + updatedTodo.id).parent().parent().parent().addClass('todo-done'); 10 | } else { 11 | $('#' + updatedTodo.id).parent().parent().parent().removeClass('todo-done'); 12 | } 13 | }); 14 | }; 15 | 16 | var handleTodoList = function(object) { 17 | var todos = object; 18 | 19 | if (!todos.length) { 20 | $('.todo-list-container').append('

There are no tasks for you today

'); 21 | return 22 | } else { 23 | $('.todo-list-container p').remove(); 24 | } 25 | 26 | $.each(todos, function(i, v) { 27 | var todoTemplate = $('#todoItemTemplate').html(); 28 | var todo = todoTemplate.replace('{{todo-id}}', v.id); 29 | todo = todo.replace('{{todo-text}}', v.text); 30 | todo = todo.replace('{{todo-checked}}', (v.done ? ' checked="checked"' : '')); 31 | todo = todo.replace('{{todo-done}}', (v.done ? ' todo-done' : '')); 32 | 33 | $('.todo-list-container').append(todo); 34 | $('#' + v.id).click(function(){ 35 | var id = $(this).prop('id'); 36 | var isDone = $(this).prop('checked'); 37 | updateTodo(id, isDone); 38 | }); 39 | }); 40 | }; 41 | 42 | var loadTodos = function() { 43 | $.ajax({ 44 | url: "/graphql?query={todoList{id,text,done}}" 45 | }).done(function(data) { 46 | console.log(data); 47 | var dataParsed = JSON.parse(data); 48 | handleTodoList(dataParsed.data.todoList); 49 | }); 50 | }; 51 | 52 | var addTodo = function(todoText) { 53 | if (!todoText || todoText === "") { 54 | alert('Please specify a task'); 55 | return; 56 | } 57 | 58 | $.ajax({ 59 | url: '/graphql?query=mutation+_{createTodo(text:"' + todoText + '"){id,text,done}}' 60 | }).done(function(data) { 61 | console.log(data); 62 | var dataParsed = JSON.parse(data); 63 | var todoList = [dataParsed.data.createTodo]; 64 | handleTodoList(todoList); 65 | }); 66 | }; 67 | 68 | $(document).ready(function() { 69 | $('.todo-add-form').submit(function(e){ 70 | e.preventDefault(); 71 | addTodo($('.todo-add-form #task').val()); 72 | $('.todo-add-form #task').val(''); 73 | }); 74 | 75 | loadTodos(); 76 | }); 77 | -------------------------------------------------------------------------------- /examples/todo/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ToDo 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |

ToDo

16 | 17 |
18 |

Tasks

19 |
20 | 21 |
22 | 23 |
24 |
25 | 26 |
27 | 28 |

Add task

29 | 30 |
31 |
32 | 38 |
39 | 40 |
41 | 42 |
43 | 44 |
45 | 46 |
47 | 48 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/graphql-go/graphql 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gqlerrors/error.go: -------------------------------------------------------------------------------- 1 | package gqlerrors 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/graphql-go/graphql/language/ast" 8 | "github.com/graphql-go/graphql/language/location" 9 | "github.com/graphql-go/graphql/language/source" 10 | ) 11 | 12 | type Error struct { 13 | Message string 14 | Stack string 15 | Nodes []ast.Node 16 | Source *source.Source 17 | Positions []int 18 | Locations []location.SourceLocation 19 | OriginalError error 20 | Path []interface{} 21 | } 22 | 23 | // implements Golang's built-in `error` interface 24 | func (g Error) Error() string { 25 | return fmt.Sprintf("%v", g.Message) 26 | } 27 | 28 | func NewError(message string, nodes []ast.Node, stack string, source *source.Source, positions []int, origError error) *Error { 29 | return newError(message, nodes, stack, source, positions, nil, origError) 30 | } 31 | 32 | func NewErrorWithPath(message string, nodes []ast.Node, stack string, source *source.Source, positions []int, path []interface{}, origError error) *Error { 33 | return newError(message, nodes, stack, source, positions, path, origError) 34 | } 35 | 36 | func newError(message string, nodes []ast.Node, stack string, source *source.Source, positions []int, path []interface{}, origError error) *Error { 37 | if stack == "" && message != "" { 38 | stack = message 39 | } 40 | if source == nil { 41 | for _, node := range nodes { 42 | // get source from first node 43 | if node == nil || reflect.ValueOf(node).IsNil() { 44 | continue 45 | } 46 | if node.GetLoc() != nil { 47 | source = node.GetLoc().Source 48 | } 49 | break 50 | } 51 | } 52 | if len(positions) == 0 && len(nodes) > 0 { 53 | for _, node := range nodes { 54 | if node == nil || reflect.ValueOf(node).IsNil() { 55 | continue 56 | } 57 | if node.GetLoc() == nil { 58 | continue 59 | } 60 | positions = append(positions, node.GetLoc().Start) 61 | } 62 | } 63 | locations := []location.SourceLocation{} 64 | for _, pos := range positions { 65 | loc := location.GetLocation(source, pos) 66 | locations = append(locations, loc) 67 | } 68 | return &Error{ 69 | Message: message, 70 | Stack: stack, 71 | Nodes: nodes, 72 | Source: source, 73 | Positions: positions, 74 | Locations: locations, 75 | OriginalError: origError, 76 | Path: path, 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /gqlerrors/formatted.go: -------------------------------------------------------------------------------- 1 | package gqlerrors 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/graphql-go/graphql/language/location" 7 | ) 8 | 9 | type ExtendedError interface { 10 | error 11 | Extensions() map[string]interface{} 12 | } 13 | 14 | type FormattedError struct { 15 | Message string `json:"message"` 16 | Locations []location.SourceLocation `json:"locations"` 17 | Path []interface{} `json:"path,omitempty"` 18 | Extensions map[string]interface{} `json:"extensions,omitempty"` 19 | originalError error 20 | } 21 | 22 | func (g FormattedError) OriginalError() error { 23 | return g.originalError 24 | } 25 | 26 | func (g FormattedError) Error() string { 27 | return g.Message 28 | } 29 | 30 | func NewFormattedError(message string) FormattedError { 31 | err := errors.New(message) 32 | return FormatError(err) 33 | } 34 | 35 | func FormatError(err error) FormattedError { 36 | switch err := err.(type) { 37 | case FormattedError: 38 | return err 39 | case *Error: 40 | ret := FormattedError{ 41 | Message: err.Error(), 42 | Locations: err.Locations, 43 | Path: err.Path, 44 | originalError: err, 45 | } 46 | if err := err.OriginalError; err != nil { 47 | if extended, ok := err.(ExtendedError); ok { 48 | ret.Extensions = extended.Extensions() 49 | } 50 | } 51 | return ret 52 | case Error: 53 | return FormatError(&err) 54 | default: 55 | return FormattedError{ 56 | Message: err.Error(), 57 | Locations: []location.SourceLocation{}, 58 | originalError: err, 59 | } 60 | } 61 | } 62 | 63 | func FormatErrors(errs ...error) []FormattedError { 64 | formattedErrors := []FormattedError{} 65 | for _, err := range errs { 66 | formattedErrors = append(formattedErrors, FormatError(err)) 67 | } 68 | return formattedErrors 69 | } 70 | -------------------------------------------------------------------------------- /gqlerrors/located.go: -------------------------------------------------------------------------------- 1 | package gqlerrors 2 | 3 | import ( 4 | "errors" 5 | "github.com/graphql-go/graphql/language/ast" 6 | ) 7 | 8 | // NewLocatedError creates a graphql.Error with location info 9 | // @deprecated 0.4.18 10 | // Already exists in `graphql.NewLocatedError()` 11 | func NewLocatedError(err interface{}, nodes []ast.Node) *Error { 12 | var origError error 13 | message := "An unknown error occurred." 14 | if err, ok := err.(error); ok { 15 | message = err.Error() 16 | origError = err 17 | } 18 | if err, ok := err.(string); ok { 19 | message = err 20 | origError = errors.New(err) 21 | } 22 | stack := message 23 | return NewError( 24 | message, 25 | nodes, 26 | stack, 27 | nil, 28 | []int{}, 29 | origError, 30 | ) 31 | } 32 | 33 | func FieldASTsToNodeASTs(fieldASTs []*ast.Field) []ast.Node { 34 | nodes := []ast.Node{} 35 | for _, fieldAST := range fieldASTs { 36 | nodes = append(nodes, fieldAST) 37 | } 38 | return nodes 39 | } 40 | -------------------------------------------------------------------------------- /gqlerrors/sortutil.go: -------------------------------------------------------------------------------- 1 | package gqlerrors 2 | 3 | import "bytes" 4 | 5 | type FormattedErrors []FormattedError 6 | 7 | func (errs FormattedErrors) Len() int { 8 | return len(errs) 9 | } 10 | 11 | func (errs FormattedErrors) Swap(i, j int) { 12 | errs[i], errs[j] = errs[j], errs[i] 13 | } 14 | 15 | func (errs FormattedErrors) Less(i, j int) bool { 16 | mCompare := bytes.Compare([]byte(errs[i].Message), []byte(errs[j].Message)) 17 | lesserLine := errs[i].Locations[0].Line < errs[j].Locations[0].Line 18 | eqLine := errs[i].Locations[0].Line == errs[j].Locations[0].Line 19 | lesserColumn := errs[i].Locations[0].Column < errs[j].Locations[0].Column 20 | if mCompare < 0 { 21 | return true 22 | } 23 | if mCompare == 0 && lesserLine { 24 | return true 25 | } 26 | if mCompare == 0 && eqLine && lesserColumn { 27 | return true 28 | } 29 | return false 30 | } 31 | -------------------------------------------------------------------------------- /gqlerrors/syntax.go: -------------------------------------------------------------------------------- 1 | package gqlerrors 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strings" 7 | 8 | "github.com/graphql-go/graphql/language/ast" 9 | "github.com/graphql-go/graphql/language/location" 10 | "github.com/graphql-go/graphql/language/source" 11 | ) 12 | 13 | func NewSyntaxError(s *source.Source, position int, description string) *Error { 14 | l := location.GetLocation(s, position) 15 | return NewError( 16 | fmt.Sprintf("Syntax Error %s (%d:%d) %s\n\n%s", s.Name, l.Line, l.Column, description, highlightSourceAtLocation(s, l)), 17 | []ast.Node{}, 18 | "", 19 | s, 20 | []int{position}, 21 | nil, 22 | ) 23 | } 24 | 25 | // printCharCode here is slightly different from lexer.printCharCode() 26 | func printCharCode(code rune) string { 27 | // print as ASCII for printable range 28 | if code >= 0x0020 { 29 | return fmt.Sprintf(`%c`, code) 30 | } 31 | // Otherwise print the escaped form. e.g. `"\\u0007"` 32 | return fmt.Sprintf(`\u%04X`, code) 33 | } 34 | func printLine(str string) string { 35 | strSlice := []string{} 36 | for _, runeValue := range str { 37 | strSlice = append(strSlice, printCharCode(runeValue)) 38 | } 39 | return fmt.Sprintf(`%s`, strings.Join(strSlice, "")) 40 | } 41 | func highlightSourceAtLocation(s *source.Source, l location.SourceLocation) string { 42 | line := l.Line 43 | prevLineNum := fmt.Sprintf("%d", (line - 1)) 44 | lineNum := fmt.Sprintf("%d", line) 45 | nextLineNum := fmt.Sprintf("%d", (line + 1)) 46 | padLen := len(nextLineNum) 47 | lines := regexp.MustCompile("\r\n|[\n\r]").Split(string(s.Body), -1) 48 | var highlight string 49 | if line >= 2 { 50 | highlight += fmt.Sprintf("%s: %s\n", lpad(padLen, prevLineNum), printLine(lines[line-2])) 51 | } 52 | highlight += fmt.Sprintf("%s: %s\n", lpad(padLen, lineNum), printLine(lines[line-1])) 53 | for i := 1; i < (2 + padLen + l.Column); i++ { 54 | highlight += " " 55 | } 56 | highlight += "^\n" 57 | if line < len(lines) { 58 | highlight += fmt.Sprintf("%s: %s\n", lpad(padLen, nextLineNum), printLine(lines[line])) 59 | } 60 | return highlight 61 | } 62 | 63 | func lpad(l int, s string) string { 64 | var r string 65 | for i := 1; i < (l - len(s) + 1); i++ { 66 | r += " " 67 | } 68 | return r + s 69 | } 70 | -------------------------------------------------------------------------------- /graphql.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/graphql-go/graphql/gqlerrors" 7 | "github.com/graphql-go/graphql/language/parser" 8 | "github.com/graphql-go/graphql/language/source" 9 | ) 10 | 11 | type Params struct { 12 | // The GraphQL type system to use when validating and executing a query. 13 | Schema Schema 14 | 15 | // A GraphQL language formatted string representing the requested operation. 16 | RequestString string 17 | 18 | // The value provided as the first argument to resolver functions on the top 19 | // level type (e.g. the query object type). 20 | RootObject map[string]interface{} 21 | 22 | // A mapping of variable name to runtime value to use for all variables 23 | // defined in the requestString. 24 | VariableValues map[string]interface{} 25 | 26 | // The name of the operation to use if requestString contains multiple 27 | // possible operations. Can be omitted if requestString contains only 28 | // one operation. 29 | OperationName string 30 | 31 | // Context may be provided to pass application-specific per-request 32 | // information to resolve functions. 33 | Context context.Context 34 | } 35 | 36 | func Do(p Params) *Result { 37 | source := source.NewSource(&source.Source{ 38 | Body: []byte(p.RequestString), 39 | Name: "GraphQL request", 40 | }) 41 | 42 | // run init on the extensions 43 | extErrs := handleExtensionsInits(&p) 44 | if len(extErrs) != 0 { 45 | return &Result{ 46 | Errors: extErrs, 47 | } 48 | } 49 | 50 | extErrs, parseFinishFn := handleExtensionsParseDidStart(&p) 51 | if len(extErrs) != 0 { 52 | return &Result{ 53 | Errors: extErrs, 54 | } 55 | } 56 | 57 | // parse the source 58 | AST, err := parser.Parse(parser.ParseParams{Source: source}) 59 | if err != nil { 60 | // run parseFinishFuncs for extensions 61 | extErrs = parseFinishFn(err) 62 | 63 | // merge the errors from extensions and the original error from parser 64 | extErrs = append(extErrs, gqlerrors.FormatErrors(err)...) 65 | return &Result{ 66 | Errors: extErrs, 67 | } 68 | } 69 | 70 | // run parseFinish functions for extensions 71 | extErrs = parseFinishFn(err) 72 | if len(extErrs) != 0 { 73 | return &Result{ 74 | Errors: extErrs, 75 | } 76 | } 77 | 78 | // notify extensions about the start of the validation 79 | extErrs, validationFinishFn := handleExtensionsValidationDidStart(&p) 80 | if len(extErrs) != 0 { 81 | return &Result{ 82 | Errors: extErrs, 83 | } 84 | } 85 | 86 | // validate document 87 | validationResult := ValidateDocument(&p.Schema, AST, nil) 88 | 89 | if !validationResult.IsValid { 90 | // run validation finish functions for extensions 91 | extErrs = validationFinishFn(validationResult.Errors) 92 | 93 | // merge the errors from extensions and the original error from parser 94 | extErrs = append(extErrs, validationResult.Errors...) 95 | return &Result{ 96 | Errors: extErrs, 97 | } 98 | } 99 | 100 | // run the validationFinishFuncs for extensions 101 | extErrs = validationFinishFn(validationResult.Errors) 102 | if len(extErrs) != 0 { 103 | return &Result{ 104 | Errors: extErrs, 105 | } 106 | } 107 | 108 | return Execute(ExecuteParams{ 109 | Schema: p.Schema, 110 | Root: p.RootObject, 111 | AST: AST, 112 | OperationName: p.OperationName, 113 | Args: p.VariableValues, 114 | Context: p.Context, 115 | }) 116 | } 117 | -------------------------------------------------------------------------------- /graphql_bench_test.go: -------------------------------------------------------------------------------- 1 | package graphql_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/graphql-go/graphql" 7 | "github.com/graphql-go/graphql/benchutil" 8 | ) 9 | 10 | type B struct { 11 | Query string 12 | Schema graphql.Schema 13 | } 14 | 15 | func benchGraphql(bench B, p graphql.Params, t testing.TB) { 16 | result := graphql.Do(p) 17 | if len(result.Errors) > 0 { 18 | t.Fatalf("wrong result, unexpected errors: %v", result.Errors) 19 | } 20 | } 21 | 22 | // Benchmark a reasonably large list of small items. 23 | func BenchmarkListQuery_1(b *testing.B) { 24 | nItemsListQueryBenchmark(1)(b) 25 | } 26 | 27 | func BenchmarkListQuery_100(b *testing.B) { 28 | nItemsListQueryBenchmark(100)(b) 29 | } 30 | 31 | func BenchmarkListQuery_1K(b *testing.B) { 32 | nItemsListQueryBenchmark(1000)(b) 33 | } 34 | 35 | func BenchmarkListQuery_10K(b *testing.B) { 36 | nItemsListQueryBenchmark(10 * 1000)(b) 37 | } 38 | 39 | func BenchmarkListQuery_100K(b *testing.B) { 40 | nItemsListQueryBenchmark(100 * 1000)(b) 41 | } 42 | 43 | func nItemsListQueryBenchmark(x int) func(b *testing.B) { 44 | return func(b *testing.B) { 45 | schema := benchutil.ListSchemaWithXItems(x) 46 | 47 | bench := B{ 48 | Query: ` 49 | query { 50 | colors { 51 | hex 52 | r 53 | g 54 | b 55 | } 56 | } 57 | `, 58 | Schema: schema, 59 | } 60 | 61 | for i := 0; i < b.N; i++ { 62 | 63 | params := graphql.Params{ 64 | Schema: schema, 65 | RequestString: bench.Query, 66 | } 67 | benchGraphql(bench, params, b) 68 | } 69 | } 70 | } 71 | 72 | func BenchmarkWideQuery_1_1(b *testing.B) { 73 | nFieldsyItemsQueryBenchmark(1, 1)(b) 74 | } 75 | 76 | func BenchmarkWideQuery_10_1(b *testing.B) { 77 | nFieldsyItemsQueryBenchmark(10, 1)(b) 78 | } 79 | 80 | func BenchmarkWideQuery_100_1(b *testing.B) { 81 | nFieldsyItemsQueryBenchmark(100, 1)(b) 82 | } 83 | 84 | func BenchmarkWideQuery_1K_1(b *testing.B) { 85 | nFieldsyItemsQueryBenchmark(1000, 1)(b) 86 | } 87 | 88 | func BenchmarkWideQuery_1_10(b *testing.B) { 89 | nFieldsyItemsQueryBenchmark(1, 10)(b) 90 | } 91 | 92 | func BenchmarkWideQuery_10_10(b *testing.B) { 93 | nFieldsyItemsQueryBenchmark(10, 10)(b) 94 | } 95 | 96 | func BenchmarkWideQuery_100_10(b *testing.B) { 97 | nFieldsyItemsQueryBenchmark(100, 10)(b) 98 | } 99 | 100 | func BenchmarkWideQuery_1K_10(b *testing.B) { 101 | nFieldsyItemsQueryBenchmark(1000, 10)(b) 102 | } 103 | 104 | func nFieldsyItemsQueryBenchmark(x int, y int) func(b *testing.B) { 105 | return func(b *testing.B) { 106 | schema := benchutil.WideSchemaWithXFieldsAndYItems(x, y) 107 | query := benchutil.WideSchemaQuery(x) 108 | 109 | bench := B{ 110 | Query: query, 111 | Schema: schema, 112 | } 113 | 114 | b.ResetTimer() 115 | 116 | for i := 0; i < b.N; i++ { 117 | params := graphql.Params{ 118 | Schema: schema, 119 | RequestString: bench.Query, 120 | } 121 | benchGraphql(bench, params, b) 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /kitchen-sink.graphql: -------------------------------------------------------------------------------- 1 | # Filename: kitchen-sink.graphql 2 | 3 | query namedQuery($foo: ComplexFooType, $bar: Bar = DefaultBarValue) { 4 | customUser: user(id: [987, 654]) { 5 | id, 6 | ... on User @defer { 7 | field2 { 8 | id , 9 | alias: field1(first:10, after:$foo,) @include(if: $foo) { 10 | id, 11 | ...frag 12 | } 13 | } 14 | } 15 | ... @skip(unless: $foo) { 16 | id 17 | } 18 | ... { 19 | id 20 | } 21 | } 22 | } 23 | 24 | mutation favPost { 25 | fav(post: 123) @defer { 26 | post { 27 | id 28 | } 29 | } 30 | } 31 | 32 | subscription PostFavSubscription($input: StoryLikeSubscribeInput) { 33 | postFavSubscribe(input: $input) { 34 | post { 35 | favers { 36 | count 37 | } 38 | favSentence { 39 | text 40 | } 41 | } 42 | } 43 | } 44 | 45 | fragment frag on Follower { 46 | foo(size: $size, bar: $b, obj: {key: "value"}) 47 | } 48 | 49 | { 50 | unnamed(truthyVal: true, falseyVal: false), 51 | query 52 | } 53 | -------------------------------------------------------------------------------- /language/ast/arguments.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "github.com/graphql-go/graphql/language/kinds" 5 | ) 6 | 7 | // Argument implements Node 8 | type Argument struct { 9 | Kind string 10 | Loc *Location 11 | Name *Name 12 | Value Value 13 | } 14 | 15 | func NewArgument(arg *Argument) *Argument { 16 | if arg == nil { 17 | arg = &Argument{} 18 | } 19 | arg.Kind = kinds.Argument 20 | return arg 21 | } 22 | 23 | func (arg *Argument) GetKind() string { 24 | return arg.Kind 25 | } 26 | 27 | func (arg *Argument) GetLoc() *Location { 28 | return arg.Loc 29 | } 30 | -------------------------------------------------------------------------------- /language/ast/definitions.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "github.com/graphql-go/graphql/language/kinds" 5 | ) 6 | 7 | type Definition interface { 8 | GetOperation() string 9 | GetVariableDefinitions() []*VariableDefinition 10 | GetSelectionSet() *SelectionSet 11 | GetKind() string 12 | GetLoc() *Location 13 | } 14 | 15 | // Ensure that all definition types implements Definition interface 16 | var _ Definition = (*OperationDefinition)(nil) 17 | var _ Definition = (*FragmentDefinition)(nil) 18 | var _ Definition = (TypeSystemDefinition)(nil) // experimental non-spec addition. 19 | 20 | // Note: subscription is an experimental non-spec addition. 21 | const ( 22 | OperationTypeQuery = "query" 23 | OperationTypeMutation = "mutation" 24 | OperationTypeSubscription = "subscription" 25 | ) 26 | 27 | // OperationDefinition implements Node, Definition 28 | type OperationDefinition struct { 29 | Kind string 30 | Loc *Location 31 | Operation string 32 | Name *Name 33 | VariableDefinitions []*VariableDefinition 34 | Directives []*Directive 35 | SelectionSet *SelectionSet 36 | } 37 | 38 | func NewOperationDefinition(op *OperationDefinition) *OperationDefinition { 39 | if op == nil { 40 | op = &OperationDefinition{} 41 | } 42 | op.Kind = kinds.OperationDefinition 43 | return op 44 | } 45 | 46 | func (op *OperationDefinition) GetKind() string { 47 | return op.Kind 48 | } 49 | 50 | func (op *OperationDefinition) GetLoc() *Location { 51 | return op.Loc 52 | } 53 | 54 | func (op *OperationDefinition) GetOperation() string { 55 | return op.Operation 56 | } 57 | 58 | func (op *OperationDefinition) GetName() *Name { 59 | return op.Name 60 | } 61 | 62 | func (op *OperationDefinition) GetVariableDefinitions() []*VariableDefinition { 63 | return op.VariableDefinitions 64 | } 65 | 66 | func (op *OperationDefinition) GetDirectives() []*Directive { 67 | return op.Directives 68 | } 69 | 70 | func (op *OperationDefinition) GetSelectionSet() *SelectionSet { 71 | return op.SelectionSet 72 | } 73 | 74 | // FragmentDefinition implements Node, Definition 75 | type FragmentDefinition struct { 76 | Kind string 77 | Loc *Location 78 | Operation string 79 | Name *Name 80 | VariableDefinitions []*VariableDefinition 81 | TypeCondition *Named 82 | Directives []*Directive 83 | SelectionSet *SelectionSet 84 | } 85 | 86 | func NewFragmentDefinition(fd *FragmentDefinition) *FragmentDefinition { 87 | if fd == nil { 88 | fd = &FragmentDefinition{} 89 | } 90 | return &FragmentDefinition{ 91 | Kind: kinds.FragmentDefinition, 92 | Loc: fd.Loc, 93 | Operation: fd.Operation, 94 | Name: fd.Name, 95 | VariableDefinitions: fd.VariableDefinitions, 96 | TypeCondition: fd.TypeCondition, 97 | Directives: fd.Directives, 98 | SelectionSet: fd.SelectionSet, 99 | } 100 | } 101 | 102 | func (fd *FragmentDefinition) GetKind() string { 103 | return fd.Kind 104 | } 105 | 106 | func (fd *FragmentDefinition) GetLoc() *Location { 107 | return fd.Loc 108 | } 109 | 110 | func (fd *FragmentDefinition) GetOperation() string { 111 | return fd.Operation 112 | } 113 | 114 | func (fd *FragmentDefinition) GetName() *Name { 115 | return fd.Name 116 | } 117 | 118 | func (fd *FragmentDefinition) GetVariableDefinitions() []*VariableDefinition { 119 | return fd.VariableDefinitions 120 | } 121 | 122 | func (fd *FragmentDefinition) GetSelectionSet() *SelectionSet { 123 | return fd.SelectionSet 124 | } 125 | 126 | // VariableDefinition implements Node 127 | type VariableDefinition struct { 128 | Kind string 129 | Loc *Location 130 | Variable *Variable 131 | Type Type 132 | DefaultValue Value 133 | } 134 | 135 | func NewVariableDefinition(vd *VariableDefinition) *VariableDefinition { 136 | if vd == nil { 137 | vd = &VariableDefinition{} 138 | } 139 | vd.Kind = kinds.VariableDefinition 140 | return vd 141 | } 142 | 143 | func (vd *VariableDefinition) GetKind() string { 144 | return vd.Kind 145 | } 146 | 147 | func (vd *VariableDefinition) GetLoc() *Location { 148 | return vd.Loc 149 | } 150 | 151 | // TypeExtensionDefinition implements Node, Definition 152 | type TypeExtensionDefinition struct { 153 | Kind string 154 | Loc *Location 155 | Definition *ObjectDefinition 156 | } 157 | 158 | func NewTypeExtensionDefinition(def *TypeExtensionDefinition) *TypeExtensionDefinition { 159 | if def == nil { 160 | def = &TypeExtensionDefinition{} 161 | } 162 | return &TypeExtensionDefinition{ 163 | Kind: kinds.TypeExtensionDefinition, 164 | Loc: def.Loc, 165 | Definition: def.Definition, 166 | } 167 | } 168 | 169 | func (def *TypeExtensionDefinition) GetKind() string { 170 | return def.Kind 171 | } 172 | 173 | func (def *TypeExtensionDefinition) GetLoc() *Location { 174 | return def.Loc 175 | } 176 | 177 | func (def *TypeExtensionDefinition) GetVariableDefinitions() []*VariableDefinition { 178 | return []*VariableDefinition{} 179 | } 180 | 181 | func (def *TypeExtensionDefinition) GetSelectionSet() *SelectionSet { 182 | return &SelectionSet{} 183 | } 184 | 185 | func (def *TypeExtensionDefinition) GetOperation() string { 186 | return "" 187 | } 188 | 189 | // DirectiveDefinition implements Node, Definition 190 | type DirectiveDefinition struct { 191 | Kind string 192 | Loc *Location 193 | Name *Name 194 | Description *StringValue 195 | Arguments []*InputValueDefinition 196 | Locations []*Name 197 | } 198 | 199 | func NewDirectiveDefinition(def *DirectiveDefinition) *DirectiveDefinition { 200 | if def == nil { 201 | def = &DirectiveDefinition{} 202 | } 203 | return &DirectiveDefinition{ 204 | Kind: kinds.DirectiveDefinition, 205 | Loc: def.Loc, 206 | Name: def.Name, 207 | Description: def.Description, 208 | Arguments: def.Arguments, 209 | Locations: def.Locations, 210 | } 211 | } 212 | 213 | func (def *DirectiveDefinition) GetKind() string { 214 | return def.Kind 215 | } 216 | 217 | func (def *DirectiveDefinition) GetLoc() *Location { 218 | return def.Loc 219 | } 220 | 221 | func (def *DirectiveDefinition) GetVariableDefinitions() []*VariableDefinition { 222 | return []*VariableDefinition{} 223 | } 224 | 225 | func (def *DirectiveDefinition) GetSelectionSet() *SelectionSet { 226 | return &SelectionSet{} 227 | } 228 | 229 | func (def *DirectiveDefinition) GetOperation() string { 230 | return "" 231 | } 232 | 233 | func (def *DirectiveDefinition) GetDescription() *StringValue { 234 | return def.Description 235 | } 236 | -------------------------------------------------------------------------------- /language/ast/directives.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "github.com/graphql-go/graphql/language/kinds" 5 | ) 6 | 7 | // Directive implements Node 8 | type Directive struct { 9 | Kind string 10 | Loc *Location 11 | Name *Name 12 | Arguments []*Argument 13 | } 14 | 15 | func NewDirective(dir *Directive) *Directive { 16 | if dir == nil { 17 | dir = &Directive{} 18 | } 19 | return &Directive{ 20 | Kind: kinds.Directive, 21 | Loc: dir.Loc, 22 | Name: dir.Name, 23 | Arguments: dir.Arguments, 24 | } 25 | } 26 | 27 | func (dir *Directive) GetKind() string { 28 | return dir.Kind 29 | } 30 | 31 | func (dir *Directive) GetLoc() *Location { 32 | return dir.Loc 33 | } 34 | -------------------------------------------------------------------------------- /language/ast/document.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "github.com/graphql-go/graphql/language/kinds" 5 | ) 6 | 7 | // Document implements Node 8 | type Document struct { 9 | Kind string 10 | Loc *Location 11 | Definitions []Node 12 | } 13 | 14 | func NewDocument(d *Document) *Document { 15 | if d == nil { 16 | d = &Document{} 17 | } 18 | return &Document{ 19 | Kind: kinds.Document, 20 | Loc: d.Loc, 21 | Definitions: d.Definitions, 22 | } 23 | } 24 | 25 | func (node *Document) GetKind() string { 26 | return node.Kind 27 | } 28 | 29 | func (node *Document) GetLoc() *Location { 30 | return node.Loc 31 | } 32 | -------------------------------------------------------------------------------- /language/ast/location.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "github.com/graphql-go/graphql/language/source" 5 | ) 6 | 7 | type Location struct { 8 | Start int 9 | End int 10 | Source *source.Source 11 | } 12 | 13 | func NewLocation(loc *Location) *Location { 14 | if loc == nil { 15 | loc = &Location{} 16 | } 17 | return &Location{ 18 | Start: loc.Start, 19 | End: loc.End, 20 | Source: loc.Source, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /language/ast/name.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "github.com/graphql-go/graphql/language/kinds" 5 | ) 6 | 7 | // Name implements Node 8 | type Name struct { 9 | Kind string 10 | Loc *Location 11 | Value string 12 | } 13 | 14 | func NewName(node *Name) *Name { 15 | if node == nil { 16 | node = &Name{} 17 | } 18 | node.Kind = kinds.Name 19 | return node 20 | } 21 | 22 | func (node *Name) GetKind() string { 23 | return node.Kind 24 | } 25 | 26 | func (node *Name) GetLoc() *Location { 27 | return node.Loc 28 | } 29 | -------------------------------------------------------------------------------- /language/ast/node.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | type Node interface { 4 | GetKind() string 5 | GetLoc() *Location 6 | } 7 | 8 | // The list of all possible AST node graphql. 9 | // Ensure that all node types implements Node interface 10 | var _ Node = (*Name)(nil) 11 | var _ Node = (*Document)(nil) 12 | var _ Node = (*OperationDefinition)(nil) 13 | var _ Node = (*VariableDefinition)(nil) 14 | var _ Node = (*Variable)(nil) 15 | var _ Node = (*SelectionSet)(nil) 16 | var _ Node = (*Field)(nil) 17 | var _ Node = (*Argument)(nil) 18 | var _ Node = (*FragmentSpread)(nil) 19 | var _ Node = (*InlineFragment)(nil) 20 | var _ Node = (*FragmentDefinition)(nil) 21 | var _ Node = (*IntValue)(nil) 22 | var _ Node = (*FloatValue)(nil) 23 | var _ Node = (*StringValue)(nil) 24 | var _ Node = (*BooleanValue)(nil) 25 | var _ Node = (*EnumValue)(nil) 26 | var _ Node = (*ListValue)(nil) 27 | var _ Node = (*ObjectValue)(nil) 28 | var _ Node = (*ObjectField)(nil) 29 | var _ Node = (*Directive)(nil) 30 | var _ Node = (*Named)(nil) 31 | var _ Node = (*List)(nil) 32 | var _ Node = (*NonNull)(nil) 33 | var _ Node = (*SchemaDefinition)(nil) 34 | var _ Node = (*OperationTypeDefinition)(nil) 35 | var _ Node = (*ScalarDefinition)(nil) 36 | var _ Node = (*ObjectDefinition)(nil) 37 | var _ Node = (*FieldDefinition)(nil) 38 | var _ Node = (*InputValueDefinition)(nil) 39 | var _ Node = (*InterfaceDefinition)(nil) 40 | var _ Node = (*UnionDefinition)(nil) 41 | var _ Node = (*EnumDefinition)(nil) 42 | var _ Node = (*EnumValueDefinition)(nil) 43 | var _ Node = (*InputObjectDefinition)(nil) 44 | var _ Node = (*TypeExtensionDefinition)(nil) 45 | var _ Node = (*DirectiveDefinition)(nil) 46 | -------------------------------------------------------------------------------- /language/ast/selections.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "github.com/graphql-go/graphql/language/kinds" 5 | ) 6 | 7 | type Selection interface { 8 | GetSelectionSet() *SelectionSet 9 | } 10 | 11 | // Ensure that all definition types implements Selection interface 12 | var _ Selection = (*Field)(nil) 13 | var _ Selection = (*FragmentSpread)(nil) 14 | var _ Selection = (*InlineFragment)(nil) 15 | 16 | // Field implements Node, Selection 17 | type Field struct { 18 | Kind string 19 | Loc *Location 20 | Alias *Name 21 | Name *Name 22 | Arguments []*Argument 23 | Directives []*Directive 24 | SelectionSet *SelectionSet 25 | } 26 | 27 | func NewField(f *Field) *Field { 28 | if f == nil { 29 | f = &Field{} 30 | } 31 | f.Kind = kinds.Field 32 | return f 33 | } 34 | 35 | func (f *Field) GetKind() string { 36 | return f.Kind 37 | } 38 | 39 | func (f *Field) GetLoc() *Location { 40 | return f.Loc 41 | } 42 | 43 | func (f *Field) GetSelectionSet() *SelectionSet { 44 | return f.SelectionSet 45 | } 46 | 47 | // FragmentSpread implements Node, Selection 48 | type FragmentSpread struct { 49 | Kind string 50 | Loc *Location 51 | Name *Name 52 | Directives []*Directive 53 | } 54 | 55 | func NewFragmentSpread(fs *FragmentSpread) *FragmentSpread { 56 | if fs == nil { 57 | fs = &FragmentSpread{} 58 | } 59 | return &FragmentSpread{ 60 | Kind: kinds.FragmentSpread, 61 | Loc: fs.Loc, 62 | Name: fs.Name, 63 | Directives: fs.Directives, 64 | } 65 | } 66 | 67 | func (fs *FragmentSpread) GetKind() string { 68 | return fs.Kind 69 | } 70 | 71 | func (fs *FragmentSpread) GetLoc() *Location { 72 | return fs.Loc 73 | } 74 | 75 | func (fs *FragmentSpread) GetSelectionSet() *SelectionSet { 76 | return nil 77 | } 78 | 79 | // InlineFragment implements Node, Selection 80 | type InlineFragment struct { 81 | Kind string 82 | Loc *Location 83 | TypeCondition *Named 84 | Directives []*Directive 85 | SelectionSet *SelectionSet 86 | } 87 | 88 | func NewInlineFragment(f *InlineFragment) *InlineFragment { 89 | if f == nil { 90 | f = &InlineFragment{} 91 | } 92 | return &InlineFragment{ 93 | Kind: kinds.InlineFragment, 94 | Loc: f.Loc, 95 | TypeCondition: f.TypeCondition, 96 | Directives: f.Directives, 97 | SelectionSet: f.SelectionSet, 98 | } 99 | } 100 | 101 | func (f *InlineFragment) GetKind() string { 102 | return f.Kind 103 | } 104 | 105 | func (f *InlineFragment) GetLoc() *Location { 106 | return f.Loc 107 | } 108 | 109 | func (f *InlineFragment) GetSelectionSet() *SelectionSet { 110 | return f.SelectionSet 111 | } 112 | 113 | // SelectionSet implements Node 114 | type SelectionSet struct { 115 | Kind string 116 | Loc *Location 117 | Selections []Selection 118 | } 119 | 120 | func NewSelectionSet(ss *SelectionSet) *SelectionSet { 121 | if ss == nil { 122 | ss = &SelectionSet{} 123 | } 124 | ss.Kind = kinds.SelectionSet 125 | return ss 126 | } 127 | 128 | func (ss *SelectionSet) GetKind() string { 129 | return ss.Kind 130 | } 131 | 132 | func (ss *SelectionSet) GetLoc() *Location { 133 | return ss.Loc 134 | } 135 | -------------------------------------------------------------------------------- /language/ast/types.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "github.com/graphql-go/graphql/language/kinds" 5 | ) 6 | 7 | type Type interface { 8 | GetKind() string 9 | GetLoc() *Location 10 | String() string 11 | } 12 | 13 | // Ensure that all value types implements Value interface 14 | var _ Type = (*Named)(nil) 15 | var _ Type = (*List)(nil) 16 | var _ Type = (*NonNull)(nil) 17 | 18 | // Named implements Node, Type 19 | type Named struct { 20 | Kind string 21 | Loc *Location 22 | Name *Name 23 | } 24 | 25 | func NewNamed(t *Named) *Named { 26 | if t == nil { 27 | t = &Named{} 28 | } 29 | t.Kind = kinds.Named 30 | return t 31 | } 32 | 33 | func (t *Named) GetKind() string { 34 | return t.Kind 35 | } 36 | 37 | func (t *Named) GetLoc() *Location { 38 | return t.Loc 39 | } 40 | 41 | func (t *Named) String() string { 42 | return t.GetKind() 43 | } 44 | 45 | // List implements Node, Type 46 | type List struct { 47 | Kind string 48 | Loc *Location 49 | Type Type 50 | } 51 | 52 | func NewList(t *List) *List { 53 | if t == nil { 54 | t = &List{} 55 | } 56 | return &List{ 57 | Kind: kinds.List, 58 | Loc: t.Loc, 59 | Type: t.Type, 60 | } 61 | } 62 | 63 | func (t *List) GetKind() string { 64 | return t.Kind 65 | } 66 | 67 | func (t *List) GetLoc() *Location { 68 | return t.Loc 69 | } 70 | 71 | func (t *List) String() string { 72 | return t.GetKind() 73 | } 74 | 75 | // NonNull implements Node, Type 76 | type NonNull struct { 77 | Kind string 78 | Loc *Location 79 | Type Type 80 | } 81 | 82 | func NewNonNull(t *NonNull) *NonNull { 83 | if t == nil { 84 | t = &NonNull{} 85 | } 86 | return &NonNull{ 87 | Kind: kinds.NonNull, 88 | Loc: t.Loc, 89 | Type: t.Type, 90 | } 91 | } 92 | 93 | func (t *NonNull) GetKind() string { 94 | return t.Kind 95 | } 96 | 97 | func (t *NonNull) GetLoc() *Location { 98 | return t.Loc 99 | } 100 | 101 | func (t *NonNull) String() string { 102 | return t.GetKind() 103 | } 104 | -------------------------------------------------------------------------------- /language/ast/values.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "github.com/graphql-go/graphql/language/kinds" 5 | ) 6 | 7 | type Value interface { 8 | GetValue() interface{} 9 | GetKind() string 10 | GetLoc() *Location 11 | } 12 | 13 | // Ensure that all value types implements Value interface 14 | var _ Value = (*Variable)(nil) 15 | var _ Value = (*IntValue)(nil) 16 | var _ Value = (*FloatValue)(nil) 17 | var _ Value = (*StringValue)(nil) 18 | var _ Value = (*BooleanValue)(nil) 19 | var _ Value = (*EnumValue)(nil) 20 | var _ Value = (*ListValue)(nil) 21 | var _ Value = (*ObjectValue)(nil) 22 | 23 | // Variable implements Node, Value 24 | type Variable struct { 25 | Kind string 26 | Loc *Location 27 | Name *Name 28 | } 29 | 30 | func NewVariable(v *Variable) *Variable { 31 | if v == nil { 32 | v = &Variable{} 33 | } 34 | v.Kind = kinds.Variable 35 | return v 36 | } 37 | 38 | func (v *Variable) GetKind() string { 39 | return v.Kind 40 | } 41 | 42 | func (v *Variable) GetLoc() *Location { 43 | return v.Loc 44 | } 45 | 46 | // GetValue alias to Variable.GetName() 47 | func (v *Variable) GetValue() interface{} { 48 | return v.GetName() 49 | } 50 | 51 | func (v *Variable) GetName() interface{} { 52 | return v.Name 53 | } 54 | 55 | // IntValue implements Node, Value 56 | type IntValue struct { 57 | Kind string 58 | Loc *Location 59 | Value string 60 | } 61 | 62 | func NewIntValue(v *IntValue) *IntValue { 63 | if v == nil { 64 | v = &IntValue{} 65 | } 66 | return &IntValue{ 67 | Kind: kinds.IntValue, 68 | Loc: v.Loc, 69 | Value: v.Value, 70 | } 71 | } 72 | 73 | func (v *IntValue) GetKind() string { 74 | return v.Kind 75 | } 76 | 77 | func (v *IntValue) GetLoc() *Location { 78 | return v.Loc 79 | } 80 | 81 | func (v *IntValue) GetValue() interface{} { 82 | return v.Value 83 | } 84 | 85 | // FloatValue implements Node, Value 86 | type FloatValue struct { 87 | Kind string 88 | Loc *Location 89 | Value string 90 | } 91 | 92 | func NewFloatValue(v *FloatValue) *FloatValue { 93 | if v == nil { 94 | v = &FloatValue{} 95 | } 96 | return &FloatValue{ 97 | Kind: kinds.FloatValue, 98 | Loc: v.Loc, 99 | Value: v.Value, 100 | } 101 | } 102 | 103 | func (v *FloatValue) GetKind() string { 104 | return v.Kind 105 | } 106 | 107 | func (v *FloatValue) GetLoc() *Location { 108 | return v.Loc 109 | } 110 | 111 | func (v *FloatValue) GetValue() interface{} { 112 | return v.Value 113 | } 114 | 115 | // StringValue implements Node, Value 116 | type StringValue struct { 117 | Kind string 118 | Loc *Location 119 | Value string 120 | } 121 | 122 | func NewStringValue(v *StringValue) *StringValue { 123 | if v == nil { 124 | v = &StringValue{} 125 | } 126 | return &StringValue{ 127 | Kind: kinds.StringValue, 128 | Loc: v.Loc, 129 | Value: v.Value, 130 | } 131 | } 132 | 133 | func (v *StringValue) GetKind() string { 134 | return v.Kind 135 | } 136 | 137 | func (v *StringValue) GetLoc() *Location { 138 | return v.Loc 139 | } 140 | 141 | func (v *StringValue) GetValue() interface{} { 142 | return v.Value 143 | } 144 | 145 | // BooleanValue implements Node, Value 146 | type BooleanValue struct { 147 | Kind string 148 | Loc *Location 149 | Value bool 150 | } 151 | 152 | func NewBooleanValue(v *BooleanValue) *BooleanValue { 153 | if v == nil { 154 | v = &BooleanValue{} 155 | } 156 | return &BooleanValue{ 157 | Kind: kinds.BooleanValue, 158 | Loc: v.Loc, 159 | Value: v.Value, 160 | } 161 | } 162 | 163 | func (v *BooleanValue) GetKind() string { 164 | return v.Kind 165 | } 166 | 167 | func (v *BooleanValue) GetLoc() *Location { 168 | return v.Loc 169 | } 170 | 171 | func (v *BooleanValue) GetValue() interface{} { 172 | return v.Value 173 | } 174 | 175 | // EnumValue implements Node, Value 176 | type EnumValue struct { 177 | Kind string 178 | Loc *Location 179 | Value string 180 | } 181 | 182 | func NewEnumValue(v *EnumValue) *EnumValue { 183 | if v == nil { 184 | v = &EnumValue{} 185 | } 186 | return &EnumValue{ 187 | Kind: kinds.EnumValue, 188 | Loc: v.Loc, 189 | Value: v.Value, 190 | } 191 | } 192 | 193 | func (v *EnumValue) GetKind() string { 194 | return v.Kind 195 | } 196 | 197 | func (v *EnumValue) GetLoc() *Location { 198 | return v.Loc 199 | } 200 | 201 | func (v *EnumValue) GetValue() interface{} { 202 | return v.Value 203 | } 204 | 205 | // ListValue implements Node, Value 206 | type ListValue struct { 207 | Kind string 208 | Loc *Location 209 | Values []Value 210 | } 211 | 212 | func NewListValue(v *ListValue) *ListValue { 213 | if v == nil { 214 | v = &ListValue{} 215 | } 216 | return &ListValue{ 217 | Kind: kinds.ListValue, 218 | Loc: v.Loc, 219 | Values: v.Values, 220 | } 221 | } 222 | 223 | func (v *ListValue) GetKind() string { 224 | return v.Kind 225 | } 226 | 227 | func (v *ListValue) GetLoc() *Location { 228 | return v.Loc 229 | } 230 | 231 | // GetValue alias to ListValue.GetValues() 232 | func (v *ListValue) GetValue() interface{} { 233 | return v.GetValues() 234 | } 235 | 236 | func (v *ListValue) GetValues() interface{} { 237 | // TODO: verify ObjectValue.GetValue() 238 | return v.Values 239 | } 240 | 241 | // ObjectValue implements Node, Value 242 | type ObjectValue struct { 243 | Kind string 244 | Loc *Location 245 | Fields []*ObjectField 246 | } 247 | 248 | func NewObjectValue(v *ObjectValue) *ObjectValue { 249 | if v == nil { 250 | v = &ObjectValue{} 251 | } 252 | return &ObjectValue{ 253 | Kind: kinds.ObjectValue, 254 | Loc: v.Loc, 255 | Fields: v.Fields, 256 | } 257 | } 258 | 259 | func (v *ObjectValue) GetKind() string { 260 | return v.Kind 261 | } 262 | 263 | func (v *ObjectValue) GetLoc() *Location { 264 | return v.Loc 265 | } 266 | 267 | func (v *ObjectValue) GetValue() interface{} { 268 | // TODO: verify ObjectValue.GetValue() 269 | return v.Fields 270 | } 271 | 272 | // ObjectField implements Node, Value 273 | type ObjectField struct { 274 | Kind string 275 | Name *Name 276 | Loc *Location 277 | Value Value 278 | } 279 | 280 | func NewObjectField(f *ObjectField) *ObjectField { 281 | if f == nil { 282 | f = &ObjectField{} 283 | } 284 | return &ObjectField{ 285 | Kind: kinds.ObjectField, 286 | Loc: f.Loc, 287 | Name: f.Name, 288 | Value: f.Value, 289 | } 290 | } 291 | 292 | func (f *ObjectField) GetKind() string { 293 | return f.Kind 294 | } 295 | 296 | func (f *ObjectField) GetLoc() *Location { 297 | return f.Loc 298 | } 299 | 300 | func (f *ObjectField) GetValue() interface{} { 301 | return f.Value 302 | } 303 | -------------------------------------------------------------------------------- /language/kinds/kinds.go: -------------------------------------------------------------------------------- 1 | package kinds 2 | 3 | const ( 4 | // Name 5 | Name = "Name" 6 | 7 | // Document 8 | Document = "Document" 9 | OperationDefinition = "OperationDefinition" 10 | VariableDefinition = "VariableDefinition" 11 | Variable = "Variable" 12 | SelectionSet = "SelectionSet" 13 | Field = "Field" 14 | Argument = "Argument" 15 | 16 | // Fragments 17 | FragmentSpread = "FragmentSpread" 18 | InlineFragment = "InlineFragment" 19 | FragmentDefinition = "FragmentDefinition" 20 | 21 | // Values 22 | IntValue = "IntValue" 23 | FloatValue = "FloatValue" 24 | StringValue = "StringValue" 25 | BooleanValue = "BooleanValue" 26 | EnumValue = "EnumValue" 27 | ListValue = "ListValue" 28 | ObjectValue = "ObjectValue" 29 | ObjectField = "ObjectField" 30 | 31 | // Directives 32 | Directive = "Directive" 33 | 34 | // Types 35 | Named = "Named" // previously NamedType 36 | List = "List" // previously ListType 37 | NonNull = "NonNull" // previously NonNull 38 | 39 | // Type System Definitions 40 | SchemaDefinition = "SchemaDefinition" 41 | OperationTypeDefinition = "OperationTypeDefinition" 42 | 43 | // Types Definitions 44 | ScalarDefinition = "ScalarDefinition" // previously ScalarTypeDefinition 45 | ObjectDefinition = "ObjectDefinition" // previously ObjectTypeDefinition 46 | FieldDefinition = "FieldDefinition" 47 | InputValueDefinition = "InputValueDefinition" 48 | InterfaceDefinition = "InterfaceDefinition" // previously InterfaceTypeDefinition 49 | UnionDefinition = "UnionDefinition" // previously UnionTypeDefinition 50 | EnumDefinition = "EnumDefinition" // previously EnumTypeDefinition 51 | EnumValueDefinition = "EnumValueDefinition" 52 | InputObjectDefinition = "InputObjectDefinition" // previously InputObjectTypeDefinition 53 | 54 | // Types Extensions 55 | TypeExtensionDefinition = "TypeExtensionDefinition" 56 | 57 | // Directive Definitions 58 | DirectiveDefinition = "DirectiveDefinition" 59 | ) 60 | -------------------------------------------------------------------------------- /language/location/location.go: -------------------------------------------------------------------------------- 1 | package location 2 | 3 | import ( 4 | "regexp" 5 | 6 | "github.com/graphql-go/graphql/language/source" 7 | ) 8 | 9 | type SourceLocation struct { 10 | Line int `json:"line"` 11 | Column int `json:"column"` 12 | } 13 | 14 | func GetLocation(s *source.Source, position int) SourceLocation { 15 | body := []byte{} 16 | if s != nil { 17 | body = s.Body 18 | } 19 | line := 1 20 | column := position + 1 21 | lineRegexp := regexp.MustCompile("\r\n|[\n\r]") 22 | matches := lineRegexp.FindAllIndex(body, -1) 23 | for _, match := range matches { 24 | matchIndex := match[0] 25 | if matchIndex < position { 26 | line++ 27 | l := len(s.Body[match[0]:match[1]]) 28 | column = position + 1 - (matchIndex + l) 29 | continue 30 | } else { 31 | break 32 | } 33 | } 34 | return SourceLocation{Line: line, Column: column} 35 | } 36 | -------------------------------------------------------------------------------- /language/printer/printer_test.go: -------------------------------------------------------------------------------- 1 | package printer_test 2 | 3 | import ( 4 | "io/ioutil" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/graphql-go/graphql/language/ast" 9 | "github.com/graphql-go/graphql/language/parser" 10 | "github.com/graphql-go/graphql/language/printer" 11 | "github.com/graphql-go/graphql/testutil" 12 | ) 13 | 14 | func parse(t *testing.T, query string) *ast.Document { 15 | astDoc, err := parser.Parse(parser.ParseParams{ 16 | Source: query, 17 | Options: parser.ParseOptions{ 18 | NoLocation: true, 19 | }, 20 | }) 21 | if err != nil { 22 | t.Fatalf("Parse failed: %v", err) 23 | } 24 | return astDoc 25 | } 26 | 27 | func TestPrinter_DoesNotAlterAST(t *testing.T) { 28 | b, err := ioutil.ReadFile("../../kitchen-sink.graphql") 29 | if err != nil { 30 | t.Fatalf("unable to load kitchen-sink.graphql") 31 | } 32 | 33 | query := string(b) 34 | astDoc := parse(t, query) 35 | 36 | astDocBefore := testutil.ASTToJSON(t, astDoc) 37 | 38 | _ = printer.Print(astDoc) 39 | 40 | astDocAfter := testutil.ASTToJSON(t, astDoc) 41 | 42 | _ = testutil.ASTToJSON(t, astDoc) 43 | 44 | if !reflect.DeepEqual(astDocAfter, astDocBefore) { 45 | t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(astDocBefore, astDocAfter)) 46 | } 47 | } 48 | 49 | func TestPrinter_PrintsMinimalAST(t *testing.T) { 50 | astDoc := ast.NewField(&ast.Field{ 51 | Name: ast.NewName(&ast.Name{ 52 | Value: "foo", 53 | }), 54 | }) 55 | results := printer.Print(astDoc) 56 | expected := "foo" 57 | if !reflect.DeepEqual(results, expected) { 58 | t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, results)) 59 | } 60 | } 61 | 62 | // TestPrinter_ProducesHelpfulErrorMessages 63 | // Skipped, can't figure out how to pass in an invalid astDoc, which is already strongly-typed 64 | 65 | func TestPrinter_CorrectlyPrintsNonQueryOperationsWithoutName(t *testing.T) { 66 | 67 | // Test #1 68 | queryAstShorthanded := `query { id, name }` 69 | expected := `{ 70 | id 71 | name 72 | } 73 | ` 74 | astDoc := parse(t, queryAstShorthanded) 75 | results := printer.Print(astDoc) 76 | 77 | if !reflect.DeepEqual(expected, results) { 78 | t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, results)) 79 | } 80 | 81 | // Test #2 82 | mutationAst := `mutation { id, name }` 83 | expected = `mutation { 84 | id 85 | name 86 | } 87 | ` 88 | astDoc = parse(t, mutationAst) 89 | results = printer.Print(astDoc) 90 | 91 | if !reflect.DeepEqual(expected, results) { 92 | t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, results)) 93 | } 94 | 95 | // Test #3 96 | queryAstWithArtifacts := `query ($foo: TestType) @testDirective { id, name }` 97 | expected = `query ($foo: TestType) @testDirective { 98 | id 99 | name 100 | } 101 | ` 102 | astDoc = parse(t, queryAstWithArtifacts) 103 | results = printer.Print(astDoc) 104 | 105 | if !reflect.DeepEqual(expected, results) { 106 | t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, results)) 107 | } 108 | 109 | // Test #4 110 | mutationAstWithArtifacts := `mutation ($foo: TestType) @testDirective { id, name }` 111 | expected = `mutation ($foo: TestType) @testDirective { 112 | id 113 | name 114 | } 115 | ` 116 | astDoc = parse(t, mutationAstWithArtifacts) 117 | results = printer.Print(astDoc) 118 | 119 | if !reflect.DeepEqual(expected, results) { 120 | t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, results)) 121 | } 122 | } 123 | 124 | func TestPrinter_PrintsKitchenSink(t *testing.T) { 125 | b, err := ioutil.ReadFile("../../kitchen-sink.graphql") 126 | if err != nil { 127 | t.Fatalf("unable to load kitchen-sink.graphql") 128 | } 129 | 130 | query := string(b) 131 | astDoc := parse(t, query) 132 | expected := `query namedQuery($foo: ComplexFooType, $bar: Bar = DefaultBarValue) { 133 | customUser: user(id: [987, 654]) { 134 | id 135 | ... on User @defer { 136 | field2 { 137 | id 138 | alias: field1(first: 10, after: $foo) @include(if: $foo) { 139 | id 140 | ...frag 141 | } 142 | } 143 | } 144 | ... @skip(unless: $foo) { 145 | id 146 | } 147 | ... { 148 | id 149 | } 150 | } 151 | } 152 | 153 | mutation favPost { 154 | fav(post: 123) @defer { 155 | post { 156 | id 157 | } 158 | } 159 | } 160 | 161 | subscription PostFavSubscription($input: StoryLikeSubscribeInput) { 162 | postFavSubscribe(input: $input) { 163 | post { 164 | favers { 165 | count 166 | } 167 | favSentence { 168 | text 169 | } 170 | } 171 | } 172 | } 173 | 174 | fragment frag on Follower { 175 | foo(size: $size, bar: $b, obj: {key: "value"}) 176 | } 177 | 178 | { 179 | unnamed(truthyVal: true, falseyVal: false) 180 | query 181 | } 182 | ` 183 | results := printer.Print(astDoc) 184 | 185 | if !reflect.DeepEqual(expected, results) { 186 | t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, results)) 187 | } 188 | } 189 | 190 | func TestPrinter_CorrectlyPrintsStringArgumentsWithProperQuoting(t *testing.T) { 191 | queryAst := `query { foo(jsonStr: "{\"foo\": \"bar\"}") }` 192 | expected := `{ 193 | foo(jsonStr: "{\"foo\": \"bar\"}") 194 | } 195 | ` 196 | astDoc := parse(t, queryAst) 197 | results := printer.Print(astDoc) 198 | 199 | if !reflect.DeepEqual(expected, results) { 200 | t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, results)) 201 | } 202 | } 203 | 204 | func BenchmarkPrint(b *testing.B) { 205 | q, err := ioutil.ReadFile("../../kitchen-sink.graphql") 206 | if err != nil { 207 | b.Fatalf("unable to load kitchen-sink.graphql") 208 | } 209 | 210 | query := string(q) 211 | 212 | astDoc, err := parser.Parse(parser.ParseParams{ 213 | Source: query, 214 | Options: parser.ParseOptions{ 215 | NoLocation: true, 216 | }, 217 | }) 218 | if err != nil { 219 | b.Fatalf("Parse failed: %v", err) 220 | } 221 | 222 | b.ResetTimer() 223 | for i := 0; i < b.N; i++ { 224 | _ = printer.Print(astDoc) 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /language/source/source.go: -------------------------------------------------------------------------------- 1 | package source 2 | 3 | const ( 4 | name = "GraphQL" 5 | ) 6 | 7 | type Source struct { 8 | Body []byte 9 | Name string 10 | } 11 | 12 | func NewSource(s *Source) *Source { 13 | if s == nil { 14 | s = &Source{Name: name} 15 | } 16 | if s.Name == "" { 17 | s.Name = name 18 | } 19 | return s 20 | } 21 | -------------------------------------------------------------------------------- /language/typeInfo/type_info.go: -------------------------------------------------------------------------------- 1 | package typeInfo 2 | 3 | import ( 4 | "github.com/graphql-go/graphql/language/ast" 5 | ) 6 | 7 | // TypeInfoI defines the interface for TypeInfo Implementation 8 | type TypeInfoI interface { 9 | Enter(node ast.Node) 10 | Leave(node ast.Node) 11 | } 12 | -------------------------------------------------------------------------------- /located.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/graphql-go/graphql/gqlerrors" 7 | "github.com/graphql-go/graphql/language/ast" 8 | ) 9 | 10 | func NewLocatedError(err interface{}, nodes []ast.Node) *gqlerrors.Error { 11 | return newLocatedError(err, nodes, nil) 12 | } 13 | 14 | func NewLocatedErrorWithPath(err interface{}, nodes []ast.Node, path []interface{}) *gqlerrors.Error { 15 | return newLocatedError(err, nodes, path) 16 | } 17 | 18 | func newLocatedError(err interface{}, nodes []ast.Node, path []interface{}) *gqlerrors.Error { 19 | if err, ok := err.(*gqlerrors.Error); ok { 20 | return err 21 | } 22 | 23 | var origError error 24 | message := "An unknown error occurred." 25 | if err, ok := err.(error); ok { 26 | message = err.Error() 27 | origError = err 28 | } 29 | if err, ok := err.(string); ok { 30 | message = err 31 | origError = errors.New(err) 32 | } 33 | stack := message 34 | return gqlerrors.NewErrorWithPath( 35 | message, 36 | nodes, 37 | stack, 38 | nil, 39 | []int{}, 40 | path, 41 | origError, 42 | ) 43 | } 44 | 45 | func FieldASTsToNodeASTs(fieldASTs []*ast.Field) []ast.Node { 46 | nodes := []ast.Node{} 47 | for _, fieldAST := range fieldASTs { 48 | nodes = append(nodes, fieldAST) 49 | } 50 | return nodes 51 | } 52 | -------------------------------------------------------------------------------- /quoted_or_list_internal_test.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestQuotedOrList_DoesNoAcceptAnEmptyList(t *testing.T) { 9 | expected := "" 10 | result := quotedOrList([]string{}) 11 | if !reflect.DeepEqual(expected, result) { 12 | t.Fatalf("Expected %v, got: %v", expected, result) 13 | } 14 | } 15 | func TestQuotedOrList_ReturnsSingleQuotedItem(t *testing.T) { 16 | expected := `"A"` 17 | result := quotedOrList([]string{"A"}) 18 | if !reflect.DeepEqual(expected, result) { 19 | t.Fatalf("Expected %v, got: %v", expected, result) 20 | } 21 | } 22 | func TestQuotedOrList_ReturnsTwoItems(t *testing.T) { 23 | expected := `"A" or "B"` 24 | result := quotedOrList([]string{"A", "B"}) 25 | if !reflect.DeepEqual(expected, result) { 26 | t.Fatalf("Expected %v, got: %v", expected, result) 27 | } 28 | } 29 | func TestQuotedOrList_ReturnsCommaSeparatedManyItemList(t *testing.T) { 30 | expected := `"A", "B", "C", "D", or "E"` 31 | result := quotedOrList([]string{"A", "B", "C", "D", "E", "F"}) 32 | if !reflect.DeepEqual(expected, result) { 33 | t.Fatalf("Expected %v, got: %v", expected, result) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /race_test.go: -------------------------------------------------------------------------------- 1 | package graphql_test 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | "testing" 9 | ) 10 | 11 | func TestRace(t *testing.T) { 12 | tempdir, err := ioutil.TempDir("", "race") 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | defer os.RemoveAll(tempdir) 17 | 18 | filename := filepath.Join(tempdir, "example.go") 19 | err = ioutil.WriteFile(filename, []byte(` 20 | package main 21 | 22 | import ( 23 | "runtime" 24 | "sync" 25 | 26 | "github.com/graphql-go/graphql" 27 | ) 28 | 29 | func main() { 30 | var wg sync.WaitGroup 31 | wg.Add(2) 32 | for i := 0; i < 2; i++ { 33 | go func() { 34 | defer wg.Done() 35 | schema, _ := graphql.NewSchema(graphql.SchemaConfig{ 36 | Query: graphql.NewObject(graphql.ObjectConfig{ 37 | Name: "RootQuery", 38 | Fields: graphql.Fields{ 39 | "hello": &graphql.Field{ 40 | Type: graphql.String, 41 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 42 | return "world", nil 43 | }, 44 | }, 45 | }, 46 | }), 47 | }) 48 | runtime.KeepAlive(schema) 49 | }() 50 | } 51 | 52 | wg.Wait() 53 | } 54 | `), 0755) 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | 59 | result, err := exec.Command("go", "run", "-race", filename).CombinedOutput() 60 | if err != nil || len(result) != 0 { 61 | t.Log(string(result)) 62 | t.Fatal(err) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /rules_default_values_of_correct_type_test.go: -------------------------------------------------------------------------------- 1 | package graphql_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/graphql-go/graphql" 7 | "github.com/graphql-go/graphql/gqlerrors" 8 | "github.com/graphql-go/graphql/testutil" 9 | ) 10 | 11 | func TestValidate_VariableDefaultValuesOfCorrectType_VariablesWithNoDefaultValues(t *testing.T) { 12 | testutil.ExpectPassesRule(t, graphql.DefaultValuesOfCorrectTypeRule, ` 13 | query NullableValues($a: Int, $b: String, $c: ComplexInput) { 14 | dog { name } 15 | } 16 | `) 17 | } 18 | func TestValidate_VariableDefaultValuesOfCorrectType_RequiredVariablesWithoutDefaultValues(t *testing.T) { 19 | testutil.ExpectPassesRule(t, graphql.DefaultValuesOfCorrectTypeRule, ` 20 | query RequiredValues($a: Int!, $b: String!) { 21 | dog { name } 22 | } 23 | `) 24 | } 25 | func TestValidate_VariableDefaultValuesOfCorrectType_VariablesWithValidDefaultValues(t *testing.T) { 26 | testutil.ExpectPassesRule(t, graphql.DefaultValuesOfCorrectTypeRule, ` 27 | query WithDefaultValues( 28 | $a: Int = 1, 29 | $b: String = "ok", 30 | $c: ComplexInput = { requiredField: true, intField: 3 } 31 | ) { 32 | dog { name } 33 | } 34 | `) 35 | } 36 | func TestValidate_VariableDefaultValuesOfCorrectType_NoRequiredVariablesWithDefaultValues(t *testing.T) { 37 | testutil.ExpectFailsRule(t, graphql.DefaultValuesOfCorrectTypeRule, ` 38 | query UnreachableDefaultValues($a: Int! = 3, $b: String! = "default") { 39 | dog { name } 40 | } 41 | `, 42 | []gqlerrors.FormattedError{ 43 | testutil.RuleError( 44 | `Variable "$a" of type "Int!" is required and will not `+ 45 | `use the default value. Perhaps you meant to use type "Int".`, 46 | 2, 49, 47 | ), 48 | testutil.RuleError( 49 | `Variable "$b" of type "String!" is required and will not `+ 50 | `use the default value. Perhaps you meant to use type "String".`, 51 | 2, 66, 52 | ), 53 | }) 54 | } 55 | func TestValidate_VariableDefaultValuesOfCorrectType_VariablesWithInvalidDefaultValues(t *testing.T) { 56 | testutil.ExpectFailsRule(t, graphql.DefaultValuesOfCorrectTypeRule, ` 57 | query InvalidDefaultValues( 58 | $a: Int = "one", 59 | $b: String = 4, 60 | $c: ComplexInput = "notverycomplex" 61 | ) { 62 | dog { name } 63 | } 64 | `, 65 | []gqlerrors.FormattedError{ 66 | testutil.RuleError(`Variable "$a" has invalid default value: "one".`+ 67 | "\nExpected type \"Int\", found \"one\".", 68 | 3, 19), 69 | testutil.RuleError(`Variable "$b" has invalid default value: 4.`+ 70 | "\nExpected type \"String\", found 4.", 71 | 4, 22), 72 | testutil.RuleError( 73 | `Variable "$c" has invalid default value: "notverycomplex".`+ 74 | "\nExpected \"ComplexInput\", found not an object.", 75 | 5, 28), 76 | }) 77 | } 78 | func TestValidate_VariableDefaultValuesOfCorrectType_ComplexVariablesMissingRequiredField(t *testing.T) { 79 | testutil.ExpectFailsRule(t, graphql.DefaultValuesOfCorrectTypeRule, ` 80 | query MissingRequiredField($a: ComplexInput = {intField: 3}) { 81 | dog { name } 82 | } 83 | `, 84 | []gqlerrors.FormattedError{ 85 | testutil.RuleError( 86 | `Variable "$a" has invalid default value: {intField: 3}.`+ 87 | "\nIn field \"requiredField\": Expected \"Boolean!\", found null.", 88 | 2, 53), 89 | }) 90 | } 91 | func TestValidate_VariableDefaultValuesOfCorrectType_ListVariablesWithInvalidItem(t *testing.T) { 92 | testutil.ExpectFailsRule(t, graphql.DefaultValuesOfCorrectTypeRule, ` 93 | query InvalidItem($a: [String] = ["one", 2]) { 94 | dog { name } 95 | } 96 | `, 97 | []gqlerrors.FormattedError{ 98 | testutil.RuleError( 99 | `Variable "$a" has invalid default value: ["one", 2].`+ 100 | "\nIn element #1: Expected type \"String\", found 2.", 101 | 2, 40), 102 | }) 103 | } 104 | 105 | func TestValidate_VariableDefaultValuesOfCorrectType_InvalidNonNull(t *testing.T) { 106 | testutil.ExpectPassesRule(t, graphql.DefaultValuesOfCorrectTypeRule, `query($g:e!){a}`) 107 | } 108 | -------------------------------------------------------------------------------- /rules_fragments_on_composite_types_test.go: -------------------------------------------------------------------------------- 1 | package graphql_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/graphql-go/graphql" 7 | "github.com/graphql-go/graphql/gqlerrors" 8 | "github.com/graphql-go/graphql/testutil" 9 | ) 10 | 11 | func TestValidate_FragmentsOnCompositeTypes_ObjectIsValidFragmentType(t *testing.T) { 12 | testutil.ExpectPassesRule(t, graphql.FragmentsOnCompositeTypesRule, ` 13 | fragment validFragment on Dog { 14 | barks 15 | } 16 | `) 17 | } 18 | func TestValidate_FragmentsOnCompositeTypes_InterfaceIsValidFragmentType(t *testing.T) { 19 | testutil.ExpectPassesRule(t, graphql.FragmentsOnCompositeTypesRule, ` 20 | fragment validFragment on Pet { 21 | name 22 | } 23 | `) 24 | } 25 | func TestValidate_FragmentsOnCompositeTypes_ObjectIsValidInlineFragmentType(t *testing.T) { 26 | testutil.ExpectPassesRule(t, graphql.FragmentsOnCompositeTypesRule, ` 27 | fragment validFragment on Pet { 28 | ... on Dog { 29 | barks 30 | } 31 | } 32 | `) 33 | } 34 | func TestValidate_FragmentsOnCompositeTypes_InlineFragmentWithoutTypeIsValid(t *testing.T) { 35 | testutil.ExpectPassesRule(t, graphql.FragmentsOnCompositeTypesRule, ` 36 | fragment validFragment on Pet { 37 | ... { 38 | name 39 | } 40 | } 41 | `) 42 | } 43 | func TestValidate_FragmentsOnCompositeTypes_UnionIsValidFragmentType(t *testing.T) { 44 | testutil.ExpectPassesRule(t, graphql.FragmentsOnCompositeTypesRule, ` 45 | fragment validFragment on CatOrDog { 46 | __typename 47 | } 48 | `) 49 | } 50 | func TestValidate_FragmentsOnCompositeTypes_ScalarIsInvalidFragmentType(t *testing.T) { 51 | testutil.ExpectFailsRule(t, graphql.FragmentsOnCompositeTypesRule, ` 52 | fragment scalarFragment on Boolean { 53 | bad 54 | } 55 | `, []gqlerrors.FormattedError{ 56 | testutil.RuleError(`Fragment "scalarFragment" cannot condition on non composite type "Boolean".`, 2, 34), 57 | }) 58 | } 59 | func TestValidate_FragmentsOnCompositeTypes_EnumIsInvalidFragmentType(t *testing.T) { 60 | testutil.ExpectFailsRule(t, graphql.FragmentsOnCompositeTypesRule, ` 61 | fragment scalarFragment on FurColor { 62 | bad 63 | } 64 | `, []gqlerrors.FormattedError{ 65 | testutil.RuleError(`Fragment "scalarFragment" cannot condition on non composite type "FurColor".`, 2, 34), 66 | }) 67 | } 68 | func TestValidate_FragmentsOnCompositeTypes_InputObjectIsInvalidFragmentType(t *testing.T) { 69 | testutil.ExpectFailsRule(t, graphql.FragmentsOnCompositeTypesRule, ` 70 | fragment inputFragment on ComplexInput { 71 | stringField 72 | } 73 | `, []gqlerrors.FormattedError{ 74 | testutil.RuleError(`Fragment "inputFragment" cannot condition on non composite type "ComplexInput".`, 2, 33), 75 | }) 76 | } 77 | func TestValidate_FragmentsOnCompositeTypes_ScalarIsInvalidInlineFragmentType(t *testing.T) { 78 | testutil.ExpectFailsRule(t, graphql.FragmentsOnCompositeTypesRule, ` 79 | fragment invalidFragment on Pet { 80 | ... on String { 81 | barks 82 | } 83 | } 84 | `, []gqlerrors.FormattedError{ 85 | testutil.RuleError(`Fragment cannot condition on non composite type "String".`, 3, 16), 86 | }) 87 | } 88 | -------------------------------------------------------------------------------- /rules_known_argument_names_test.go: -------------------------------------------------------------------------------- 1 | package graphql_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/graphql-go/graphql" 7 | "github.com/graphql-go/graphql/gqlerrors" 8 | "github.com/graphql-go/graphql/testutil" 9 | ) 10 | 11 | func TestValidate_KnownArgumentNames_SingleArgIsKnown(t *testing.T) { 12 | testutil.ExpectPassesRule(t, graphql.KnownArgumentNamesRule, ` 13 | fragment argOnRequiredArg on Dog { 14 | doesKnowCommand(dogCommand: SIT) 15 | } 16 | `) 17 | } 18 | func TestValidate_KnownArgumentNames_MultipleArgsAreKnown(t *testing.T) { 19 | testutil.ExpectPassesRule(t, graphql.KnownArgumentNamesRule, ` 20 | fragment multipleArgs on ComplicatedArgs { 21 | multipleReqs(req1: 1, req2: 2) 22 | } 23 | `) 24 | } 25 | func TestValidate_KnownArgumentNames_IgnoresArgsOfUnknownFields(t *testing.T) { 26 | testutil.ExpectPassesRule(t, graphql.KnownArgumentNamesRule, ` 27 | fragment argOnUnknownField on Dog { 28 | unknownField(unknownArg: SIT) 29 | } 30 | `) 31 | } 32 | func TestValidate_KnownArgumentNames_MultipleArgsInReverseOrderAreKnown(t *testing.T) { 33 | testutil.ExpectPassesRule(t, graphql.KnownArgumentNamesRule, ` 34 | fragment multipleArgsReverseOrder on ComplicatedArgs { 35 | multipleReqs(req2: 2, req1: 1) 36 | } 37 | `) 38 | } 39 | func TestValidate_KnownArgumentNames_NoArgsOnOptionalArg(t *testing.T) { 40 | testutil.ExpectPassesRule(t, graphql.KnownArgumentNamesRule, ` 41 | fragment noArgOnOptionalArg on Dog { 42 | isHousetrained 43 | } 44 | `) 45 | } 46 | func TestValidate_KnownArgumentNames_ArgsAreKnownDeeply(t *testing.T) { 47 | testutil.ExpectPassesRule(t, graphql.KnownArgumentNamesRule, ` 48 | { 49 | dog { 50 | doesKnowCommand(dogCommand: SIT) 51 | } 52 | human { 53 | pet { 54 | ... on Dog { 55 | doesKnowCommand(dogCommand: SIT) 56 | } 57 | } 58 | } 59 | } 60 | `) 61 | } 62 | func TestValidate_KnownArgumentNames_DirectiveArgsAreKnown(t *testing.T) { 63 | testutil.ExpectPassesRule(t, graphql.KnownArgumentNamesRule, ` 64 | { 65 | dog @skip(if: true) 66 | } 67 | `) 68 | } 69 | func TestValidate_KnownArgumentNames_UndirectiveArgsAreInvalid(t *testing.T) { 70 | testutil.ExpectFailsRule(t, graphql.KnownArgumentNamesRule, ` 71 | { 72 | dog @skip(unless: true) 73 | } 74 | `, []gqlerrors.FormattedError{ 75 | testutil.RuleError(`Unknown argument "unless" on directive "@skip".`, 3, 19), 76 | }) 77 | } 78 | func TestValidate_KnownArgumentNames_UndirectiveArgsAreInvalidWithSuggestion(t *testing.T) { 79 | testutil.ExpectFailsRule(t, graphql.KnownArgumentNamesRule, ` 80 | { 81 | dog @skip(of: true) 82 | } 83 | `, []gqlerrors.FormattedError{ 84 | testutil.RuleError(`Unknown argument "of" on directive "@skip". `+ 85 | `Did you mean "if"?`, 3, 19), 86 | }) 87 | } 88 | func TestValidate_KnownArgumentNames_InvalidArgName(t *testing.T) { 89 | testutil.ExpectFailsRule(t, graphql.KnownArgumentNamesRule, ` 90 | fragment invalidArgName on Dog { 91 | doesKnowCommand(unknown: true) 92 | } 93 | `, []gqlerrors.FormattedError{ 94 | testutil.RuleError(`Unknown argument "unknown" on field "doesKnowCommand" of type "Dog".`, 3, 25), 95 | }) 96 | } 97 | func TestValidate_KnownArgumentNames_UnknownArgsAmongstKnownArgs(t *testing.T) { 98 | testutil.ExpectFailsRule(t, graphql.KnownArgumentNamesRule, ` 99 | fragment oneGoodArgOneInvalidArg on Dog { 100 | doesKnowCommand(whoknows: 1, dogCommand: SIT, unknown: true) 101 | } 102 | `, []gqlerrors.FormattedError{ 103 | testutil.RuleError(`Unknown argument "whoknows" on field "doesKnowCommand" of type "Dog".`, 3, 25), 104 | testutil.RuleError(`Unknown argument "unknown" on field "doesKnowCommand" of type "Dog".`, 3, 55), 105 | }) 106 | } 107 | func TestValidate_KnownArgumentNames_UnknownArgsAmongstKnownArgsWithSuggestions(t *testing.T) { 108 | testutil.ExpectFailsRule(t, graphql.KnownArgumentNamesRule, ` 109 | fragment oneGoodArgOneInvalidArg on Dog { 110 | doesKnowCommand(ddogCommand: SIT,) 111 | } 112 | `, []gqlerrors.FormattedError{ 113 | testutil.RuleError(`Unknown argument "ddogCommand" on field "doesKnowCommand" of type "Dog". `+ 114 | `Did you mean "dogCommand" or "nextDogCommand"?`, 3, 25), 115 | }) 116 | } 117 | func TestValidate_KnownArgumentNames_UnknownArgsDeeply(t *testing.T) { 118 | testutil.ExpectFailsRule(t, graphql.KnownArgumentNamesRule, ` 119 | { 120 | dog { 121 | doesKnowCommand(unknown: true) 122 | } 123 | human { 124 | pet { 125 | ... on Dog { 126 | doesKnowCommand(unknown: true) 127 | } 128 | } 129 | } 130 | } 131 | `, []gqlerrors.FormattedError{ 132 | testutil.RuleError(`Unknown argument "unknown" on field "doesKnowCommand" of type "Dog".`, 4, 27), 133 | testutil.RuleError(`Unknown argument "unknown" on field "doesKnowCommand" of type "Dog".`, 9, 31), 134 | }) 135 | } 136 | -------------------------------------------------------------------------------- /rules_known_directives_rule_test.go: -------------------------------------------------------------------------------- 1 | package graphql_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/graphql-go/graphql" 7 | "github.com/graphql-go/graphql/gqlerrors" 8 | "github.com/graphql-go/graphql/testutil" 9 | ) 10 | 11 | func TestValidate_KnownDirectives_WithNoDirectives(t *testing.T) { 12 | testutil.ExpectPassesRule(t, graphql.KnownDirectivesRule, ` 13 | query Foo { 14 | name 15 | ...Frag 16 | } 17 | 18 | fragment Frag on Dog { 19 | name 20 | } 21 | `) 22 | } 23 | func TestValidate_KnownDirectives_WithKnownDirective(t *testing.T) { 24 | testutil.ExpectPassesRule(t, graphql.KnownDirectivesRule, ` 25 | { 26 | dog @include(if: true) { 27 | name 28 | } 29 | human @skip(if: false) { 30 | name 31 | } 32 | } 33 | `) 34 | } 35 | func TestValidate_KnownDirectives_WithUnknownDirective(t *testing.T) { 36 | testutil.ExpectFailsRule(t, graphql.KnownDirectivesRule, ` 37 | { 38 | dog @unknown(directive: "value") { 39 | name 40 | } 41 | } 42 | `, []gqlerrors.FormattedError{ 43 | testutil.RuleError(`Unknown directive "unknown".`, 3, 13), 44 | }) 45 | } 46 | func TestValidate_KnownDirectives_WithManyUnknownDirectives(t *testing.T) { 47 | testutil.ExpectFailsRule(t, graphql.KnownDirectivesRule, ` 48 | { 49 | dog @unknown(directive: "value") { 50 | name 51 | } 52 | human @unknown(directive: "value") { 53 | name 54 | pets @unknown(directive: "value") { 55 | name 56 | } 57 | } 58 | } 59 | `, []gqlerrors.FormattedError{ 60 | testutil.RuleError(`Unknown directive "unknown".`, 3, 13), 61 | testutil.RuleError(`Unknown directive "unknown".`, 6, 15), 62 | testutil.RuleError(`Unknown directive "unknown".`, 8, 16), 63 | }) 64 | } 65 | func TestValidate_KnownDirectives_WithWellPlacedDirectives(t *testing.T) { 66 | testutil.ExpectPassesRule(t, graphql.KnownDirectivesRule, ` 67 | query Foo @onQuery { 68 | name @include(if: true) 69 | ...Frag @include(if: true) 70 | skippedField @skip(if: true) 71 | ...SkippedFrag @skip(if: true) 72 | } 73 | 74 | mutation Bar @onMutation { 75 | someField 76 | } 77 | `) 78 | } 79 | func TestValidate_KnownDirectives_WithMisplacedDirectives(t *testing.T) { 80 | testutil.ExpectFailsRule(t, graphql.KnownDirectivesRule, ` 81 | query Foo @include(if: true) { 82 | name @onQuery 83 | ...Frag @onQuery 84 | } 85 | 86 | mutation Bar @onQuery { 87 | someField 88 | } 89 | `, []gqlerrors.FormattedError{ 90 | testutil.RuleError(`Directive "include" may not be used on QUERY.`, 2, 17), 91 | testutil.RuleError(`Directive "onQuery" may not be used on FIELD.`, 3, 14), 92 | testutil.RuleError(`Directive "onQuery" may not be used on FRAGMENT_SPREAD.`, 4, 17), 93 | testutil.RuleError(`Directive "onQuery" may not be used on MUTATION.`, 7, 20), 94 | }) 95 | } 96 | 97 | func TestValidate_KnownDirectives_WithinSchemaLanguage_WithWellPlacedDirectives(t *testing.T) { 98 | testutil.ExpectPassesRule(t, graphql.KnownDirectivesRule, ` 99 | type MyObj implements MyInterface @onObject { 100 | myField(myArg: Int @onArgumentDefinition): String @onFieldDefinition 101 | } 102 | 103 | scalar MyScalar @onScalar 104 | 105 | interface MyInterface @onInterface { 106 | myField(myArg: Int @onArgumentDefinition): String @onFieldDefinition 107 | } 108 | 109 | union MyUnion @onUnion = MyObj | Other 110 | 111 | enum MyEnum @onEnum { 112 | MY_VALUE @onEnumValue 113 | } 114 | 115 | input MyInput @onInputObject { 116 | myField: Int @onInputFieldDefinition 117 | } 118 | 119 | schema @onSchema { 120 | query: MyQuery 121 | } 122 | `) 123 | } 124 | 125 | func TestValidate_KnownDirectives_WithinSchemaLanguage_WithMisplacedDirectives(t *testing.T) { 126 | testutil.ExpectFailsRule(t, graphql.KnownDirectivesRule, ` 127 | type MyObj implements MyInterface @onInterface { 128 | myField(myArg: Int @onInputFieldDefinition): String @onInputFieldDefinition 129 | } 130 | 131 | scalar MyScalar @onEnum 132 | 133 | interface MyInterface @onObject { 134 | myField(myArg: Int @onInputFieldDefinition): String @onInputFieldDefinition 135 | } 136 | 137 | union MyUnion @onEnumValue = MyObj | Other 138 | 139 | enum MyEnum @onScalar { 140 | MY_VALUE @onUnion 141 | } 142 | 143 | input MyInput @onEnum { 144 | myField: Int @onArgumentDefinition 145 | } 146 | 147 | schema @onObject { 148 | query: MyQuery 149 | } 150 | `, []gqlerrors.FormattedError{ 151 | testutil.RuleError(`Directive "onInterface" may not be used on OBJECT.`, 2, 43), 152 | testutil.RuleError(`Directive "onInputFieldDefinition" may not be used on ARGUMENT_DEFINITION.`, 3, 30), 153 | testutil.RuleError(`Directive "onInputFieldDefinition" may not be used on FIELD_DEFINITION.`, 3, 63), 154 | testutil.RuleError(`Directive "onEnum" may not be used on SCALAR.`, 6, 25), 155 | testutil.RuleError(`Directive "onObject" may not be used on INTERFACE.`, 8, 31), 156 | testutil.RuleError(`Directive "onInputFieldDefinition" may not be used on ARGUMENT_DEFINITION.`, 9, 30), 157 | testutil.RuleError(`Directive "onInputFieldDefinition" may not be used on FIELD_DEFINITION.`, 9, 63), 158 | testutil.RuleError(`Directive "onEnumValue" may not be used on UNION.`, 12, 23), 159 | testutil.RuleError(`Directive "onScalar" may not be used on ENUM.`, 14, 21), 160 | testutil.RuleError(`Directive "onUnion" may not be used on ENUM_VALUE.`, 15, 20), 161 | testutil.RuleError(`Directive "onEnum" may not be used on INPUT_OBJECT.`, 18, 23), 162 | testutil.RuleError(`Directive "onArgumentDefinition" may not be used on INPUT_FIELD_DEFINITION.`, 19, 24), 163 | testutil.RuleError(`Directive "onObject" may not be used on SCHEMA.`, 22, 16), 164 | }) 165 | } 166 | -------------------------------------------------------------------------------- /rules_known_fragment_names_test.go: -------------------------------------------------------------------------------- 1 | package graphql_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/graphql-go/graphql" 7 | "github.com/graphql-go/graphql/gqlerrors" 8 | "github.com/graphql-go/graphql/testutil" 9 | ) 10 | 11 | func TestValidate_KnownFragmentNames_KnownFragmentNamesAreValid(t *testing.T) { 12 | testutil.ExpectPassesRule(t, graphql.KnownFragmentNamesRule, ` 13 | { 14 | human(id: 4) { 15 | ...HumanFields1 16 | ... on Human { 17 | ...HumanFields2 18 | } 19 | ... { 20 | name 21 | } 22 | } 23 | } 24 | fragment HumanFields1 on Human { 25 | name 26 | ...HumanFields3 27 | } 28 | fragment HumanFields2 on Human { 29 | name 30 | } 31 | fragment HumanFields3 on Human { 32 | name 33 | } 34 | `) 35 | } 36 | func TestValidate_KnownFragmentNames_UnknownFragmentNamesAreInvalid(t *testing.T) { 37 | testutil.ExpectFailsRule(t, graphql.KnownFragmentNamesRule, ` 38 | { 39 | human(id: 4) { 40 | ...UnknownFragment1 41 | ... on Human { 42 | ...UnknownFragment2 43 | } 44 | } 45 | } 46 | fragment HumanFields on Human { 47 | name 48 | ...UnknownFragment3 49 | } 50 | `, []gqlerrors.FormattedError{ 51 | testutil.RuleError(`Unknown fragment "UnknownFragment1".`, 4, 14), 52 | testutil.RuleError(`Unknown fragment "UnknownFragment2".`, 6, 16), 53 | testutil.RuleError(`Unknown fragment "UnknownFragment3".`, 12, 12), 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /rules_known_type_names_test.go: -------------------------------------------------------------------------------- 1 | package graphql_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/graphql-go/graphql" 7 | "github.com/graphql-go/graphql/gqlerrors" 8 | "github.com/graphql-go/graphql/testutil" 9 | ) 10 | 11 | func TestValidate_KnownTypeNames_KnownTypeNamesAreValid(t *testing.T) { 12 | testutil.ExpectPassesRule(t, graphql.KnownTypeNamesRule, ` 13 | query Foo($var: String, $required: [String!]!) { 14 | user(id: 4) { 15 | pets { ... on Pet { name }, ...PetFields, ... { name } } 16 | } 17 | } 18 | fragment PetFields on Pet { 19 | name 20 | } 21 | `) 22 | } 23 | func TestValidate_KnownTypeNames_UnknownTypeNamesAreInValid(t *testing.T) { 24 | testutil.ExpectFailsRule(t, graphql.KnownTypeNamesRule, ` 25 | query Foo($var: JumbledUpLetters) { 26 | user(id: 4) { 27 | name 28 | pets { ... on Badger { name }, ...PetFields } 29 | } 30 | } 31 | fragment PetFields on Peettt { 32 | name 33 | } 34 | `, []gqlerrors.FormattedError{ 35 | testutil.RuleError(`Unknown type "JumbledUpLetters".`, 2, 23), 36 | testutil.RuleError(`Unknown type "Badger".`, 5, 25), 37 | testutil.RuleError(`Unknown type "Peettt". Did you mean "Pet"?`, 8, 29), 38 | }) 39 | } 40 | 41 | func TestValidate_KnownTypeNames_IgnoresTypeDefinitions(t *testing.T) { 42 | testutil.ExpectFailsRule(t, graphql.KnownTypeNamesRule, ` 43 | type NotInTheSchema { 44 | field: FooBar 45 | } 46 | interface FooBar { 47 | field: NotInTheSchema 48 | } 49 | union U = A | B 50 | input Blob { 51 | field: UnknownType 52 | } 53 | query Foo($var: NotInTheSchema) { 54 | user(id: $var) { 55 | id 56 | } 57 | } 58 | `, []gqlerrors.FormattedError{ 59 | testutil.RuleError(`Unknown type "NotInTheSchema".`, 12, 23), 60 | }) 61 | } 62 | -------------------------------------------------------------------------------- /rules_lone_anonymous_operation_rule_test.go: -------------------------------------------------------------------------------- 1 | package graphql_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/graphql-go/graphql" 7 | "github.com/graphql-go/graphql/gqlerrors" 8 | "github.com/graphql-go/graphql/testutil" 9 | ) 10 | 11 | func TestValidate_AnonymousOperationMustBeAlone_NoOperations(t *testing.T) { 12 | testutil.ExpectPassesRule(t, graphql.LoneAnonymousOperationRule, ` 13 | fragment fragA on Type { 14 | field 15 | } 16 | `) 17 | } 18 | func TestValidate_AnonymousOperationMustBeAlone_OneAnonOperation(t *testing.T) { 19 | testutil.ExpectPassesRule(t, graphql.LoneAnonymousOperationRule, ` 20 | { 21 | field 22 | } 23 | `) 24 | } 25 | func TestValidate_AnonymousOperationMustBeAlone_MultipleNamedOperations(t *testing.T) { 26 | testutil.ExpectPassesRule(t, graphql.LoneAnonymousOperationRule, ` 27 | query Foo { 28 | field 29 | } 30 | 31 | query Bar { 32 | field 33 | } 34 | `) 35 | } 36 | func TestValidate_AnonymousOperationMustBeAlone_AnonOperationWithFragment(t *testing.T) { 37 | testutil.ExpectPassesRule(t, graphql.LoneAnonymousOperationRule, ` 38 | { 39 | ...Foo 40 | } 41 | fragment Foo on Type { 42 | field 43 | } 44 | `) 45 | } 46 | func TestValidate_AnonymousOperationMustBeAlone_MultipleAnonOperations(t *testing.T) { 47 | testutil.ExpectFailsRule(t, graphql.LoneAnonymousOperationRule, ` 48 | { 49 | fieldA 50 | } 51 | { 52 | fieldB 53 | } 54 | `, []gqlerrors.FormattedError{ 55 | testutil.RuleError(`This anonymous operation must be the only defined operation.`, 2, 7), 56 | testutil.RuleError(`This anonymous operation must be the only defined operation.`, 5, 7), 57 | }) 58 | } 59 | func TestValidate_AnonymousOperationMustBeAlone_AnonOperationWithAMutation(t *testing.T) { 60 | testutil.ExpectFailsRule(t, graphql.LoneAnonymousOperationRule, ` 61 | { 62 | fieldA 63 | } 64 | mutation Foo { 65 | fieldB 66 | } 67 | `, []gqlerrors.FormattedError{ 68 | testutil.RuleError(`This anonymous operation must be the only defined operation.`, 2, 7), 69 | }) 70 | } 71 | 72 | func TestValidate_AnonymousOperationMustBeAlone_AnonOperationWithASubscription(t *testing.T) { 73 | testutil.ExpectFailsRule(t, graphql.LoneAnonymousOperationRule, ` 74 | { 75 | fieldA 76 | } 77 | mutation Foo { 78 | fieldB 79 | } 80 | `, []gqlerrors.FormattedError{ 81 | testutil.RuleError(`This anonymous operation must be the only defined operation.`, 2, 7), 82 | }) 83 | } 84 | -------------------------------------------------------------------------------- /rules_no_unused_fragments_test.go: -------------------------------------------------------------------------------- 1 | package graphql_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/graphql-go/graphql" 7 | "github.com/graphql-go/graphql/gqlerrors" 8 | "github.com/graphql-go/graphql/testutil" 9 | ) 10 | 11 | func TestValidate_NoUnusedFragments_AllFragmentNamesAreUsed(t *testing.T) { 12 | testutil.ExpectPassesRule(t, graphql.NoUnusedFragmentsRule, ` 13 | { 14 | human(id: 4) { 15 | ...HumanFields1 16 | ... on Human { 17 | ...HumanFields2 18 | } 19 | } 20 | } 21 | fragment HumanFields1 on Human { 22 | name 23 | ...HumanFields3 24 | } 25 | fragment HumanFields2 on Human { 26 | name 27 | } 28 | fragment HumanFields3 on Human { 29 | name 30 | } 31 | `) 32 | } 33 | func TestValidate_NoUnusedFragments_AllFragmentNamesAreUsedByMultipleOperations(t *testing.T) { 34 | testutil.ExpectPassesRule(t, graphql.NoUnusedFragmentsRule, ` 35 | query Foo { 36 | human(id: 4) { 37 | ...HumanFields1 38 | } 39 | } 40 | query Bar { 41 | human(id: 4) { 42 | ...HumanFields2 43 | } 44 | } 45 | fragment HumanFields1 on Human { 46 | name 47 | ...HumanFields3 48 | } 49 | fragment HumanFields2 on Human { 50 | name 51 | } 52 | fragment HumanFields3 on Human { 53 | name 54 | } 55 | `) 56 | } 57 | func TestValidate_NoUnusedFragments_ContainsUnknownFragments(t *testing.T) { 58 | testutil.ExpectFailsRule(t, graphql.NoUnusedFragmentsRule, ` 59 | query Foo { 60 | human(id: 4) { 61 | ...HumanFields1 62 | } 63 | } 64 | query Bar { 65 | human(id: 4) { 66 | ...HumanFields2 67 | } 68 | } 69 | fragment HumanFields1 on Human { 70 | name 71 | ...HumanFields3 72 | } 73 | fragment HumanFields2 on Human { 74 | name 75 | } 76 | fragment HumanFields3 on Human { 77 | name 78 | } 79 | fragment Unused1 on Human { 80 | name 81 | } 82 | fragment Unused2 on Human { 83 | name 84 | } 85 | `, []gqlerrors.FormattedError{ 86 | testutil.RuleError(`Fragment "Unused1" is never used.`, 22, 7), 87 | testutil.RuleError(`Fragment "Unused2" is never used.`, 25, 7), 88 | }) 89 | } 90 | func TestValidate_NoUnusedFragments_ContainsUnknownFragmentsWithRefCycle(t *testing.T) { 91 | testutil.ExpectFailsRule(t, graphql.NoUnusedFragmentsRule, ` 92 | query Foo { 93 | human(id: 4) { 94 | ...HumanFields1 95 | } 96 | } 97 | query Bar { 98 | human(id: 4) { 99 | ...HumanFields2 100 | } 101 | } 102 | fragment HumanFields1 on Human { 103 | name 104 | ...HumanFields3 105 | } 106 | fragment HumanFields2 on Human { 107 | name 108 | } 109 | fragment HumanFields3 on Human { 110 | name 111 | } 112 | fragment Unused1 on Human { 113 | name 114 | ...Unused2 115 | } 116 | fragment Unused2 on Human { 117 | name 118 | ...Unused1 119 | } 120 | `, []gqlerrors.FormattedError{ 121 | testutil.RuleError(`Fragment "Unused1" is never used.`, 22, 7), 122 | testutil.RuleError(`Fragment "Unused2" is never used.`, 26, 7), 123 | }) 124 | } 125 | func TestValidate_NoUnusedFragments_ContainsUnknownAndUndefFragments(t *testing.T) { 126 | testutil.ExpectFailsRule(t, graphql.NoUnusedFragmentsRule, ` 127 | query Foo { 128 | human(id: 4) { 129 | ...bar 130 | } 131 | } 132 | fragment foo on Human { 133 | name 134 | } 135 | `, []gqlerrors.FormattedError{ 136 | testutil.RuleError(`Fragment "foo" is never used.`, 7, 7), 137 | }) 138 | } 139 | -------------------------------------------------------------------------------- /rules_no_unused_variables_test.go: -------------------------------------------------------------------------------- 1 | package graphql_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/graphql-go/graphql" 7 | "github.com/graphql-go/graphql/gqlerrors" 8 | "github.com/graphql-go/graphql/testutil" 9 | ) 10 | 11 | func TestValidate_NoUnusedVariables_UsesAllVariables(t *testing.T) { 12 | testutil.ExpectPassesRule(t, graphql.NoUnusedVariablesRule, ` 13 | query ($a: String, $b: String, $c: String) { 14 | field(a: $a, b: $b, c: $c) 15 | } 16 | `) 17 | } 18 | func TestValidate_NoUnusedVariables_UsesAllVariablesDeeply(t *testing.T) { 19 | testutil.ExpectPassesRule(t, graphql.NoUnusedVariablesRule, ` 20 | query Foo($a: String, $b: String, $c: String) { 21 | field(a: $a) { 22 | field(b: $b) { 23 | field(c: $c) 24 | } 25 | } 26 | } 27 | `) 28 | } 29 | func TestValidate_NoUnusedVariables_UsesAllVariablesDeeplyInInlineFragments(t *testing.T) { 30 | testutil.ExpectPassesRule(t, graphql.NoUnusedVariablesRule, ` 31 | query Foo($a: String, $b: String, $c: String) { 32 | ... on Type { 33 | field(a: $a) { 34 | field(b: $b) { 35 | ... on Type { 36 | field(c: $c) 37 | } 38 | } 39 | } 40 | } 41 | } 42 | `) 43 | } 44 | func TestValidate_NoUnusedVariables_UsesAllVariablesInFragments(t *testing.T) { 45 | testutil.ExpectPassesRule(t, graphql.NoUnusedVariablesRule, ` 46 | query Foo($a: String, $b: String, $c: String) { 47 | ...FragA 48 | } 49 | fragment FragA on Type { 50 | field(a: $a) { 51 | ...FragB 52 | } 53 | } 54 | fragment FragB on Type { 55 | field(b: $b) { 56 | ...FragC 57 | } 58 | } 59 | fragment FragC on Type { 60 | field(c: $c) 61 | } 62 | `) 63 | } 64 | func TestValidate_NoUnusedVariables_VariableUsedByFragmentInMultipleOperations(t *testing.T) { 65 | testutil.ExpectPassesRule(t, graphql.NoUnusedVariablesRule, ` 66 | query Foo($a: String) { 67 | ...FragA 68 | } 69 | query Bar($b: String) { 70 | ...FragB 71 | } 72 | fragment FragA on Type { 73 | field(a: $a) 74 | } 75 | fragment FragB on Type { 76 | field(b: $b) 77 | } 78 | `) 79 | } 80 | func TestValidate_NoUnusedVariables_VariableUsedByRecursiveFragment(t *testing.T) { 81 | testutil.ExpectPassesRule(t, graphql.NoUnusedVariablesRule, ` 82 | query Foo($a: String) { 83 | ...FragA 84 | } 85 | fragment FragA on Type { 86 | field(a: $a) { 87 | ...FragA 88 | } 89 | } 90 | `) 91 | } 92 | func TestValidate_NoUnusedVariables_VariableNotUsed(t *testing.T) { 93 | testutil.ExpectFailsRule(t, graphql.NoUnusedVariablesRule, ` 94 | query ($a: String, $b: String, $c: String) { 95 | field(a: $a, b: $b) 96 | } 97 | `, []gqlerrors.FormattedError{ 98 | testutil.RuleError(`Variable "$c" is never used.`, 2, 38), 99 | }) 100 | } 101 | func TestValidate_NoUnusedVariables_MultipleVariablesNotUsed(t *testing.T) { 102 | testutil.ExpectFailsRule(t, graphql.NoUnusedVariablesRule, ` 103 | query Foo($a: String, $b: String, $c: String) { 104 | field(b: $b) 105 | } 106 | `, []gqlerrors.FormattedError{ 107 | testutil.RuleError(`Variable "$a" is never used in operation "Foo".`, 2, 17), 108 | testutil.RuleError(`Variable "$c" is never used in operation "Foo".`, 2, 41), 109 | }) 110 | } 111 | func TestValidate_NoUnusedVariables_VariableNotUsedInFragments(t *testing.T) { 112 | testutil.ExpectFailsRule(t, graphql.NoUnusedVariablesRule, ` 113 | query Foo($a: String, $b: String, $c: String) { 114 | ...FragA 115 | } 116 | fragment FragA on Type { 117 | field(a: $a) { 118 | ...FragB 119 | } 120 | } 121 | fragment FragB on Type { 122 | field(b: $b) { 123 | ...FragC 124 | } 125 | } 126 | fragment FragC on Type { 127 | field 128 | } 129 | `, []gqlerrors.FormattedError{ 130 | testutil.RuleError(`Variable "$c" is never used in operation "Foo".`, 2, 41), 131 | }) 132 | } 133 | func TestValidate_NoUnusedVariables_MultipleVariablesNotUsed2(t *testing.T) { 134 | testutil.ExpectFailsRule(t, graphql.NoUnusedVariablesRule, ` 135 | query Foo($a: String, $b: String, $c: String) { 136 | ...FragA 137 | } 138 | fragment FragA on Type { 139 | field { 140 | ...FragB 141 | } 142 | } 143 | fragment FragB on Type { 144 | field(b: $b) { 145 | ...FragC 146 | } 147 | } 148 | fragment FragC on Type { 149 | field 150 | } 151 | `, []gqlerrors.FormattedError{ 152 | testutil.RuleError(`Variable "$a" is never used in operation "Foo".`, 2, 17), 153 | testutil.RuleError(`Variable "$c" is never used in operation "Foo".`, 2, 41), 154 | }) 155 | } 156 | func TestValidate_NoUnusedVariables_VariableNotUsedByUnreferencedFragment(t *testing.T) { 157 | testutil.ExpectFailsRule(t, graphql.NoUnusedVariablesRule, ` 158 | query Foo($b: String) { 159 | ...FragA 160 | } 161 | fragment FragA on Type { 162 | field(a: $a) 163 | } 164 | fragment FragB on Type { 165 | field(b: $b) 166 | } 167 | `, []gqlerrors.FormattedError{ 168 | testutil.RuleError(`Variable "$b" is never used in operation "Foo".`, 2, 17), 169 | }) 170 | } 171 | func TestValidate_NoUnusedVariables_VariableNotUsedByFragmentUsedByOtherOperation(t *testing.T) { 172 | testutil.ExpectFailsRule(t, graphql.NoUnusedVariablesRule, ` 173 | query Foo($b: String) { 174 | ...FragA 175 | } 176 | query Bar($a: String) { 177 | ...FragB 178 | } 179 | fragment FragA on Type { 180 | field(a: $a) 181 | } 182 | fragment FragB on Type { 183 | field(b: $b) 184 | } 185 | `, []gqlerrors.FormattedError{ 186 | testutil.RuleError(`Variable "$b" is never used in operation "Foo".`, 2, 17), 187 | testutil.RuleError(`Variable "$a" is never used in operation "Bar".`, 5, 17), 188 | }) 189 | } 190 | -------------------------------------------------------------------------------- /rules_provided_non_null_arguments_test.go: -------------------------------------------------------------------------------- 1 | package graphql_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/graphql-go/graphql" 7 | "github.com/graphql-go/graphql/gqlerrors" 8 | "github.com/graphql-go/graphql/testutil" 9 | ) 10 | 11 | func TestValidate_ProvidedNonNullArguments_IgnoresUnknownArguments(t *testing.T) { 12 | testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` 13 | { 14 | dog { 15 | isHousetrained(unknownArgument: true) 16 | } 17 | } 18 | `) 19 | } 20 | 21 | func TestValidate_ProvidedNonNullArguments_ValidNonNullableValue_ArgOnOptionalArg(t *testing.T) { 22 | testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` 23 | { 24 | dog { 25 | isHousetrained(atOtherHomes: true) 26 | } 27 | } 28 | `) 29 | } 30 | func TestValidate_ProvidedNonNullArguments_ValidNonNullableValue_NoArgOnOptionalArg(t *testing.T) { 31 | testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` 32 | { 33 | dog { 34 | isHousetrained 35 | } 36 | } 37 | `) 38 | } 39 | func TestValidate_ProvidedNonNullArguments_ValidNonNullableValue_MultipleArgs(t *testing.T) { 40 | testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` 41 | { 42 | complicatedArgs { 43 | multipleReqs(req1: 1, req2: 2) 44 | } 45 | } 46 | `) 47 | } 48 | func TestValidate_ProvidedNonNullArguments_ValidNonNullableValue_MultipleArgsReverseOrder(t *testing.T) { 49 | testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` 50 | { 51 | complicatedArgs { 52 | multipleReqs(req2: 2, req1: 1) 53 | } 54 | } 55 | `) 56 | } 57 | func TestValidate_ProvidedNonNullArguments_ValidNonNullableValue_NoArgsOnMultipleOptional(t *testing.T) { 58 | testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` 59 | { 60 | complicatedArgs { 61 | multipleOpts 62 | } 63 | } 64 | `) 65 | } 66 | func TestValidate_ProvidedNonNullArguments_ValidNonNullableValue_OneArgOnMultipleOptional(t *testing.T) { 67 | testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` 68 | { 69 | complicatedArgs { 70 | multipleOpts(opt1: 1) 71 | } 72 | } 73 | `) 74 | } 75 | func TestValidate_ProvidedNonNullArguments_ValidNonNullableValue_SecondArgOnMultipleOptional(t *testing.T) { 76 | testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` 77 | { 78 | complicatedArgs { 79 | multipleOpts(opt2: 1) 80 | } 81 | } 82 | `) 83 | } 84 | func TestValidate_ProvidedNonNullArguments_ValidNonNullableValue_MultipleReqsOnMixedList(t *testing.T) { 85 | testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` 86 | { 87 | complicatedArgs { 88 | multipleOptAndReq(req1: 3, req2: 4) 89 | } 90 | } 91 | `) 92 | } 93 | func TestValidate_ProvidedNonNullArguments_ValidNonNullableValue_MultipleReqsAndOneOptOnMixedList(t *testing.T) { 94 | testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` 95 | { 96 | complicatedArgs { 97 | multipleOptAndReq(req1: 3, req2: 4, opt1: 5) 98 | } 99 | } 100 | `) 101 | } 102 | func TestValidate_ProvidedNonNullArguments_ValidNonNullableValue_AllReqsAndOptsOnMixedList(t *testing.T) { 103 | testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` 104 | { 105 | complicatedArgs { 106 | multipleOptAndReq(req1: 3, req2: 4, opt1: 5, opt2: 6) 107 | } 108 | } 109 | `) 110 | } 111 | 112 | func TestValidate_ProvidedNonNullArguments_InvalidNonNullableValue_MissingOneNonNullableArgument(t *testing.T) { 113 | testutil.ExpectFailsRule(t, graphql.ProvidedNonNullArgumentsRule, ` 114 | { 115 | complicatedArgs { 116 | multipleReqs(req2: 2) 117 | } 118 | } 119 | `, []gqlerrors.FormattedError{ 120 | testutil.RuleError(`Field "multipleReqs" argument "req1" of type "Int!" is required but not provided.`, 4, 13), 121 | }) 122 | } 123 | func TestValidate_ProvidedNonNullArguments_InvalidNonNullableValue_MissingMultipleNonNullableArguments(t *testing.T) { 124 | testutil.ExpectFailsRule(t, graphql.ProvidedNonNullArgumentsRule, ` 125 | { 126 | complicatedArgs { 127 | multipleReqs 128 | } 129 | } 130 | `, []gqlerrors.FormattedError{ 131 | testutil.RuleError(`Field "multipleReqs" argument "req1" of type "Int!" is required but not provided.`, 4, 13), 132 | testutil.RuleError(`Field "multipleReqs" argument "req2" of type "Int!" is required but not provided.`, 4, 13), 133 | }) 134 | } 135 | func TestValidate_ProvidedNonNullArguments_InvalidNonNullableValue_IncorrectValueAndMissingArgument(t *testing.T) { 136 | testutil.ExpectFailsRule(t, graphql.ProvidedNonNullArgumentsRule, ` 137 | { 138 | complicatedArgs { 139 | multipleReqs(req1: "one") 140 | } 141 | } 142 | `, []gqlerrors.FormattedError{ 143 | testutil.RuleError(`Field "multipleReqs" argument "req2" of type "Int!" is required but not provided.`, 4, 13), 144 | }) 145 | } 146 | 147 | func TestValidate_ProvidedNonNullArguments_DirectiveArguments_IgnoresUnknownDirectives(t *testing.T) { 148 | testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` 149 | { 150 | dog @unknown 151 | } 152 | `) 153 | } 154 | func TestValidate_ProvidedNonNullArguments_DirectiveArguments_WithDirectivesOfValidTypes(t *testing.T) { 155 | testutil.ExpectPassesRule(t, graphql.ProvidedNonNullArgumentsRule, ` 156 | { 157 | dog @include(if: true) { 158 | name 159 | } 160 | human @skip(if: false) { 161 | name 162 | } 163 | } 164 | `) 165 | } 166 | func TestValidate_ProvidedNonNullArguments_DirectiveArguments_WithDirectiveWithMissingTypes(t *testing.T) { 167 | testutil.ExpectFailsRule(t, graphql.ProvidedNonNullArgumentsRule, ` 168 | { 169 | dog @include { 170 | name @skip 171 | } 172 | } 173 | `, []gqlerrors.FormattedError{ 174 | testutil.RuleError(`Directive "@include" argument "if" of type "Boolean!" is required but not provided.`, 3, 15), 175 | testutil.RuleError(`Directive "@skip" argument "if" of type "Boolean!" is required but not provided.`, 4, 18), 176 | }) 177 | } 178 | -------------------------------------------------------------------------------- /rules_scalar_leafs_test.go: -------------------------------------------------------------------------------- 1 | package graphql_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/graphql-go/graphql" 7 | "github.com/graphql-go/graphql/gqlerrors" 8 | "github.com/graphql-go/graphql/testutil" 9 | ) 10 | 11 | func TestValidate_ScalarLeafs_ValidScalarSelection(t *testing.T) { 12 | testutil.ExpectPassesRule(t, graphql.ScalarLeafsRule, ` 13 | fragment scalarSelection on Dog { 14 | barks 15 | } 16 | `) 17 | } 18 | func TestValidate_ScalarLeafs_ObjectTypeMissingSelection(t *testing.T) { 19 | testutil.ExpectFailsRule(t, graphql.ScalarLeafsRule, ` 20 | query directQueryOnObjectWithoutSubFields { 21 | human 22 | } 23 | `, []gqlerrors.FormattedError{ 24 | testutil.RuleError(`Field "human" of type "Human" must have a sub selection.`, 3, 9), 25 | }) 26 | } 27 | func TestValidate_ScalarLeafs_InterfaceTypeMissingSelection(t *testing.T) { 28 | testutil.ExpectFailsRule(t, graphql.ScalarLeafsRule, ` 29 | { 30 | human { pets } 31 | } 32 | `, []gqlerrors.FormattedError{ 33 | testutil.RuleError(`Field "pets" of type "[Pet]" must have a sub selection.`, 3, 17), 34 | }) 35 | } 36 | func TestValidate_ScalarLeafs_ValidScalarSelectionWithArgs(t *testing.T) { 37 | testutil.ExpectPassesRule(t, graphql.ScalarLeafsRule, ` 38 | fragment scalarSelectionWithArgs on Dog { 39 | doesKnowCommand(dogCommand: SIT) 40 | } 41 | `) 42 | } 43 | 44 | func TestValidate_ScalarLeafs_ScalarSelectionNotAllowedOnBoolean(t *testing.T) { 45 | testutil.ExpectFailsRule(t, graphql.ScalarLeafsRule, ` 46 | fragment scalarSelectionsNotAllowedOnBoolean on Dog { 47 | barks { sinceWhen } 48 | } 49 | `, []gqlerrors.FormattedError{ 50 | testutil.RuleError(`Field "barks" of type "Boolean" must not have a sub selection.`, 3, 15), 51 | }) 52 | } 53 | func TestValidate_ScalarLeafs_ScalarSelectionNotAllowedOnEnum(t *testing.T) { 54 | testutil.ExpectFailsRule(t, graphql.ScalarLeafsRule, ` 55 | fragment scalarSelectionsNotAllowedOnEnum on Cat { 56 | furColor { inHexdec } 57 | } 58 | `, []gqlerrors.FormattedError{ 59 | testutil.RuleError(`Field "furColor" of type "FurColor" must not have a sub selection.`, 3, 18), 60 | }) 61 | } 62 | func TestValidate_ScalarLeafs_ScalarSelectionNotAllowedWithArgs(t *testing.T) { 63 | testutil.ExpectFailsRule(t, graphql.ScalarLeafsRule, ` 64 | fragment scalarSelectionsNotAllowedWithArgs on Dog { 65 | doesKnowCommand(dogCommand: SIT) { sinceWhen } 66 | } 67 | `, []gqlerrors.FormattedError{ 68 | testutil.RuleError(`Field "doesKnowCommand" of type "Boolean" must not have a sub selection.`, 3, 42), 69 | }) 70 | } 71 | func TestValidate_ScalarLeafs_ScalarSelectionNotAllowedWithDirectives(t *testing.T) { 72 | testutil.ExpectFailsRule(t, graphql.ScalarLeafsRule, ` 73 | fragment scalarSelectionsNotAllowedWithDirectives on Dog { 74 | name @include(if: true) { isAlsoHumanName } 75 | } 76 | `, []gqlerrors.FormattedError{ 77 | testutil.RuleError(`Field "name" of type "String" must not have a sub selection.`, 3, 33), 78 | }) 79 | } 80 | func TestValidate_ScalarLeafs_ScalarSelectionNotAllowedWithDirectivesAndArgs(t *testing.T) { 81 | testutil.ExpectFailsRule(t, graphql.ScalarLeafsRule, ` 82 | fragment scalarSelectionsNotAllowedWithDirectivesAndArgs on Dog { 83 | doesKnowCommand(dogCommand: SIT) @include(if: true) { sinceWhen } 84 | } 85 | `, []gqlerrors.FormattedError{ 86 | testutil.RuleError(`Field "doesKnowCommand" of type "Boolean" must not have a sub selection.`, 3, 61), 87 | }) 88 | } 89 | -------------------------------------------------------------------------------- /rules_unique_argument_names_test.go: -------------------------------------------------------------------------------- 1 | package graphql_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/graphql-go/graphql" 7 | "github.com/graphql-go/graphql/gqlerrors" 8 | "github.com/graphql-go/graphql/testutil" 9 | ) 10 | 11 | func TestValidate_UniqueArgumentNames_NoArgumentsOnField(t *testing.T) { 12 | testutil.ExpectPassesRule(t, graphql.UniqueArgumentNamesRule, ` 13 | { 14 | field 15 | } 16 | `) 17 | } 18 | func TestValidate_UniqueArgumentNames_NoArgumentsOnDirective(t *testing.T) { 19 | testutil.ExpectPassesRule(t, graphql.UniqueArgumentNamesRule, ` 20 | { 21 | field @directive 22 | } 23 | `) 24 | } 25 | func TestValidate_UniqueArgumentNames_ArgumentOnField(t *testing.T) { 26 | testutil.ExpectPassesRule(t, graphql.UniqueArgumentNamesRule, ` 27 | { 28 | field(arg: "value") 29 | } 30 | `) 31 | } 32 | func TestValidate_UniqueArgumentNames_ArgumentOnDirective(t *testing.T) { 33 | testutil.ExpectPassesRule(t, graphql.UniqueArgumentNamesRule, ` 34 | { 35 | field @directive(arg: "value") 36 | } 37 | `) 38 | } 39 | func TestValidate_UniqueArgumentNames_SameArgumentOnTwoFields(t *testing.T) { 40 | testutil.ExpectPassesRule(t, graphql.UniqueArgumentNamesRule, ` 41 | { 42 | one: field(arg: "value") 43 | two: field(arg: "value") 44 | } 45 | `) 46 | } 47 | func TestValidate_UniqueArgumentNames_SameArgumentOnFieldAndDirective(t *testing.T) { 48 | testutil.ExpectPassesRule(t, graphql.UniqueArgumentNamesRule, ` 49 | { 50 | field(arg: "value") @directive(arg: "value") 51 | } 52 | `) 53 | } 54 | func TestValidate_UniqueArgumentNames_SameArgumentOnTwoDirectives(t *testing.T) { 55 | testutil.ExpectPassesRule(t, graphql.UniqueArgumentNamesRule, ` 56 | { 57 | field @directive1(arg: "value") @directive2(arg: "value") 58 | } 59 | `) 60 | } 61 | func TestValidate_UniqueArgumentNames_MultipleFieldArguments(t *testing.T) { 62 | testutil.ExpectPassesRule(t, graphql.UniqueArgumentNamesRule, ` 63 | { 64 | field(arg1: "value", arg2: "value", arg3: "value") 65 | } 66 | `) 67 | } 68 | func TestValidate_UniqueArgumentNames_MultipleDirectiveArguments(t *testing.T) { 69 | testutil.ExpectPassesRule(t, graphql.UniqueArgumentNamesRule, ` 70 | { 71 | field @directive(arg1: "value", arg2: "value", arg3: "value") 72 | } 73 | `) 74 | } 75 | func TestValidate_UniqueArgumentNames_DuplicateFieldArguments(t *testing.T) { 76 | testutil.ExpectFailsRule(t, graphql.UniqueArgumentNamesRule, ` 77 | { 78 | field(arg1: "value", arg1: "value") 79 | } 80 | `, []gqlerrors.FormattedError{ 81 | testutil.RuleError(`There can be only one argument named "arg1".`, 3, 15, 3, 30), 82 | }) 83 | } 84 | func TestValidate_UniqueArgumentNames_ManyDuplicateFieldArguments(t *testing.T) { 85 | testutil.ExpectFailsRule(t, graphql.UniqueArgumentNamesRule, ` 86 | { 87 | field(arg1: "value", arg1: "value", arg1: "value") 88 | } 89 | `, []gqlerrors.FormattedError{ 90 | testutil.RuleError(`There can be only one argument named "arg1".`, 3, 15, 3, 30), 91 | testutil.RuleError(`There can be only one argument named "arg1".`, 3, 15, 3, 45), 92 | }) 93 | } 94 | func TestValidate_UniqueArgumentNames_DuplicateDirectiveArguments(t *testing.T) { 95 | testutil.ExpectFailsRule(t, graphql.UniqueArgumentNamesRule, ` 96 | { 97 | field @directive(arg1: "value", arg1: "value") 98 | } 99 | `, []gqlerrors.FormattedError{ 100 | testutil.RuleError(`There can be only one argument named "arg1".`, 3, 26, 3, 41), 101 | }) 102 | } 103 | func TestValidate_UniqueArgumentNames_ManyDuplicateDirectiveArguments(t *testing.T) { 104 | testutil.ExpectFailsRule(t, graphql.UniqueArgumentNamesRule, ` 105 | { 106 | field @directive(arg1: "value", arg1: "value", arg1: "value") 107 | } 108 | `, []gqlerrors.FormattedError{ 109 | testutil.RuleError(`There can be only one argument named "arg1".`, 3, 26, 3, 41), 110 | testutil.RuleError(`There can be only one argument named "arg1".`, 3, 26, 3, 56), 111 | }) 112 | } 113 | -------------------------------------------------------------------------------- /rules_unique_fragment_names_test.go: -------------------------------------------------------------------------------- 1 | package graphql_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/graphql-go/graphql" 7 | "github.com/graphql-go/graphql/gqlerrors" 8 | "github.com/graphql-go/graphql/testutil" 9 | ) 10 | 11 | func TestValidate_UniqueFragmentNames_NoFragments(t *testing.T) { 12 | testutil.ExpectPassesRule(t, graphql.UniqueFragmentNamesRule, ` 13 | { 14 | field 15 | } 16 | `) 17 | } 18 | func TestValidate_UniqueFragmentNames_OneFragment(t *testing.T) { 19 | testutil.ExpectPassesRule(t, graphql.UniqueFragmentNamesRule, ` 20 | { 21 | ...fragA 22 | } 23 | 24 | fragment fragA on Type { 25 | field 26 | } 27 | `) 28 | } 29 | func TestValidate_UniqueFragmentNames_ManyFragments(t *testing.T) { 30 | testutil.ExpectPassesRule(t, graphql.UniqueFragmentNamesRule, ` 31 | { 32 | ...fragA 33 | ...fragB 34 | ...fragC 35 | } 36 | fragment fragA on Type { 37 | fieldA 38 | } 39 | fragment fragB on Type { 40 | fieldB 41 | } 42 | fragment fragC on Type { 43 | fieldC 44 | } 45 | `) 46 | } 47 | func TestValidate_UniqueFragmentNames_InlineFragmentsAreAlwaysUnique(t *testing.T) { 48 | testutil.ExpectPassesRule(t, graphql.UniqueFragmentNamesRule, ` 49 | { 50 | ...on Type { 51 | fieldA 52 | } 53 | ...on Type { 54 | fieldB 55 | } 56 | } 57 | `) 58 | } 59 | func TestValidate_UniqueFragmentNames_FragmentAndOperationNamedTheSame(t *testing.T) { 60 | testutil.ExpectPassesRule(t, graphql.UniqueFragmentNamesRule, ` 61 | query Foo { 62 | ...Foo 63 | } 64 | fragment Foo on Type { 65 | field 66 | } 67 | `) 68 | } 69 | func TestValidate_UniqueFragmentNames_FragmentsNamedTheSame(t *testing.T) { 70 | testutil.ExpectFailsRule(t, graphql.UniqueFragmentNamesRule, ` 71 | { 72 | ...fragA 73 | } 74 | fragment fragA on Type { 75 | fieldA 76 | } 77 | fragment fragA on Type { 78 | fieldB 79 | } 80 | `, []gqlerrors.FormattedError{ 81 | testutil.RuleError(`There can only be one fragment named "fragA".`, 5, 16, 8, 16), 82 | }) 83 | } 84 | func TestValidate_UniqueFragmentNames_FragmentsNamedTheSameWithoutBeingReferenced(t *testing.T) { 85 | testutil.ExpectFailsRule(t, graphql.UniqueFragmentNamesRule, ` 86 | fragment fragA on Type { 87 | fieldA 88 | } 89 | fragment fragA on Type { 90 | fieldB 91 | } 92 | `, []gqlerrors.FormattedError{ 93 | testutil.RuleError(`There can only be one fragment named "fragA".`, 2, 16, 5, 16), 94 | }) 95 | } 96 | -------------------------------------------------------------------------------- /rules_unique_input_field_names_test.go: -------------------------------------------------------------------------------- 1 | package graphql_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/graphql-go/graphql" 7 | "github.com/graphql-go/graphql/gqlerrors" 8 | "github.com/graphql-go/graphql/testutil" 9 | ) 10 | 11 | func TestValidate_UniqueInputFieldNames_InputObjectWithFields(t *testing.T) { 12 | testutil.ExpectPassesRule(t, graphql.UniqueInputFieldNamesRule, ` 13 | { 14 | field(arg: { f: true }) 15 | } 16 | `) 17 | } 18 | func TestValidate_UniqueInputFieldNames_SameInputObjectWithinTwoArgs(t *testing.T) { 19 | testutil.ExpectPassesRule(t, graphql.UniqueInputFieldNamesRule, ` 20 | { 21 | field(arg1: { f: true }, arg2: { f: true }) 22 | } 23 | `) 24 | } 25 | func TestValidate_UniqueInputFieldNames_MultipleInputObjectFields(t *testing.T) { 26 | testutil.ExpectPassesRule(t, graphql.UniqueInputFieldNamesRule, ` 27 | { 28 | field(arg: { f1: "value", f2: "value", f3: "value" }) 29 | } 30 | `) 31 | } 32 | func TestValidate_UniqueInputFieldNames_AllowsForNestedInputObjectsWithSimilarFields(t *testing.T) { 33 | testutil.ExpectPassesRule(t, graphql.UniqueInputFieldNamesRule, ` 34 | { 35 | field(arg: { 36 | deep: { 37 | deep: { 38 | id: 1 39 | } 40 | id: 1 41 | } 42 | id: 1 43 | }) 44 | } 45 | `) 46 | } 47 | func TestValidate_UniqueInputFieldNames_DuplicateInputObjectFields(t *testing.T) { 48 | testutil.ExpectFailsRule(t, graphql.UniqueInputFieldNamesRule, ` 49 | { 50 | field(arg: { f1: "value", f1: "value" }) 51 | } 52 | `, []gqlerrors.FormattedError{ 53 | testutil.RuleError(`There can be only one input field named "f1".`, 3, 22, 3, 35), 54 | }) 55 | } 56 | func TestValidate_UniqueInputFieldNames_ManyDuplicateInputObjectFields(t *testing.T) { 57 | testutil.ExpectFailsRule(t, graphql.UniqueInputFieldNamesRule, ` 58 | { 59 | field(arg: { f1: "value", f1: "value", f1: "value" }) 60 | } 61 | `, []gqlerrors.FormattedError{ 62 | testutil.RuleError(`There can be only one input field named "f1".`, 3, 22, 3, 35), 63 | testutil.RuleError(`There can be only one input field named "f1".`, 3, 22, 3, 48), 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /rules_unique_operation_names_test.go: -------------------------------------------------------------------------------- 1 | package graphql_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/graphql-go/graphql" 7 | "github.com/graphql-go/graphql/gqlerrors" 8 | "github.com/graphql-go/graphql/testutil" 9 | ) 10 | 11 | func TestValidate_UniqueOperationNames_NoOperations(t *testing.T) { 12 | testutil.ExpectPassesRule(t, graphql.UniqueOperationNamesRule, ` 13 | fragment fragA on Type { 14 | field 15 | } 16 | `) 17 | } 18 | func TestValidate_UniqueOperationNames_OneAnonOperation(t *testing.T) { 19 | testutil.ExpectPassesRule(t, graphql.UniqueOperationNamesRule, ` 20 | { 21 | field 22 | } 23 | `) 24 | } 25 | func TestValidate_UniqueOperationNames_OneNamedOperation(t *testing.T) { 26 | testutil.ExpectPassesRule(t, graphql.UniqueOperationNamesRule, ` 27 | query Foo { 28 | field 29 | } 30 | `) 31 | } 32 | func TestValidate_UniqueOperationNames_MultipleOperations(t *testing.T) { 33 | testutil.ExpectPassesRule(t, graphql.UniqueOperationNamesRule, ` 34 | query Foo { 35 | field 36 | } 37 | 38 | query Bar { 39 | field 40 | } 41 | `) 42 | } 43 | func TestValidate_UniqueOperationNames_MultipleOperationsOfDifferentTypes(t *testing.T) { 44 | testutil.ExpectPassesRule(t, graphql.UniqueOperationNamesRule, ` 45 | query Foo { 46 | field 47 | } 48 | 49 | mutation Bar { 50 | field 51 | } 52 | 53 | subscription Baz { 54 | field 55 | } 56 | `) 57 | } 58 | func TestValidate_UniqueOperationNames_FragmentAndOperationNamedTheSame(t *testing.T) { 59 | testutil.ExpectPassesRule(t, graphql.UniqueOperationNamesRule, ` 60 | query Foo { 61 | ...Foo 62 | } 63 | fragment Foo on Type { 64 | field 65 | } 66 | `) 67 | } 68 | func TestValidate_UniqueOperationNames_MultipleOperationsOfSameName(t *testing.T) { 69 | testutil.ExpectFailsRule(t, graphql.UniqueOperationNamesRule, ` 70 | query Foo { 71 | fieldA 72 | } 73 | query Foo { 74 | fieldB 75 | } 76 | `, []gqlerrors.FormattedError{ 77 | testutil.RuleError(`There can only be one operation named "Foo".`, 2, 13, 5, 13), 78 | }) 79 | } 80 | func TestValidate_UniqueOperationNames_MultipleOperationsOfSameNameOfDifferentTypes_Mutation(t *testing.T) { 81 | testutil.ExpectFailsRule(t, graphql.UniqueOperationNamesRule, ` 82 | query Foo { 83 | fieldA 84 | } 85 | mutation Foo { 86 | fieldB 87 | } 88 | `, []gqlerrors.FormattedError{ 89 | testutil.RuleError(`There can only be one operation named "Foo".`, 2, 13, 5, 16), 90 | }) 91 | } 92 | 93 | func TestValidate_UniqueOperationNames_MultipleOperationsOfSameNameOfDifferentTypes_Subscription(t *testing.T) { 94 | testutil.ExpectFailsRule(t, graphql.UniqueOperationNamesRule, ` 95 | query Foo { 96 | fieldA 97 | } 98 | subscription Foo { 99 | fieldB 100 | } 101 | `, []gqlerrors.FormattedError{ 102 | testutil.RuleError(`There can only be one operation named "Foo".`, 2, 13, 5, 20), 103 | }) 104 | } 105 | 106 | func TestValidate_UniqueOperationNames_MultipleAnonymousOperations(t *testing.T) { 107 | testutil.ExpectFailsRule(t, graphql.UniqueOperationNamesRule, `{a}{b}`, []gqlerrors.FormattedError{ 108 | testutil.RuleError(`There can only be one operation named "".`, 1, 1, 1, 4), 109 | }) 110 | } 111 | -------------------------------------------------------------------------------- /rules_unique_variable_names_test.go: -------------------------------------------------------------------------------- 1 | package graphql_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/graphql-go/graphql" 7 | "github.com/graphql-go/graphql/gqlerrors" 8 | "github.com/graphql-go/graphql/testutil" 9 | ) 10 | 11 | func TestValidate_UniqueVariableNames_UniqueVariableNames(t *testing.T) { 12 | testutil.ExpectPassesRule(t, graphql.UniqueVariableNamesRule, ` 13 | query A($x: Int, $y: String) { __typename } 14 | query B($x: String, $y: Int) { __typename } 15 | `) 16 | } 17 | func TestValidate_UniqueVariableNames_DuplicateVariableNames(t *testing.T) { 18 | testutil.ExpectFailsRule(t, graphql.UniqueVariableNamesRule, ` 19 | query A($x: Int, $x: Int, $x: String) { __typename } 20 | query B($x: String, $x: Int) { __typename } 21 | query C($x: Int, $x: Int) { __typename } 22 | `, []gqlerrors.FormattedError{ 23 | testutil.RuleError(`There can only be one variable named "x".`, 2, 16, 2, 25), 24 | testutil.RuleError(`There can only be one variable named "x".`, 2, 16, 2, 34), 25 | testutil.RuleError(`There can only be one variable named "x".`, 3, 16, 3, 28), 26 | testutil.RuleError(`There can only be one variable named "x".`, 4, 16, 4, 25), 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /rules_variables_are_input_types_test.go: -------------------------------------------------------------------------------- 1 | package graphql_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/graphql-go/graphql" 7 | "github.com/graphql-go/graphql/gqlerrors" 8 | "github.com/graphql-go/graphql/testutil" 9 | ) 10 | 11 | func TestValidate_VariablesAreInputTypes_(t *testing.T) { 12 | testutil.ExpectPassesRule(t, graphql.VariablesAreInputTypesRule, ` 13 | query Foo($a: String, $b: [Boolean!]!, $c: ComplexInput) { 14 | field(a: $a, b: $b, c: $c) 15 | } 16 | `) 17 | } 18 | func TestValidate_VariablesAreInputTypes_1(t *testing.T) { 19 | testutil.ExpectFailsRule(t, graphql.VariablesAreInputTypesRule, ` 20 | query Foo($a: Dog, $b: [[CatOrDog!]]!, $c: Pet) { 21 | field(a: $a, b: $b, c: $c) 22 | } 23 | `, []gqlerrors.FormattedError{ 24 | testutil.RuleError(`Variable "$a" cannot be non-input type "Dog".`, 2, 21), 25 | testutil.RuleError(`Variable "$b" cannot be non-input type "[[CatOrDog!]]!".`, 2, 30), 26 | testutil.RuleError(`Variable "$c" cannot be non-input type "Pet".`, 2, 50), 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /scalars_parse_test.go: -------------------------------------------------------------------------------- 1 | package graphql_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "time" 7 | 8 | "github.com/graphql-go/graphql" 9 | "github.com/graphql-go/graphql/language/ast" 10 | ) 11 | 12 | func TestTypeSystem_Scalar_ParseValueOutputDateTime(t *testing.T) { 13 | t1, _ := time.Parse(time.RFC3339, "2017-07-23T03:46:56.647Z") 14 | tests := []dateTimeSerializationTest{ 15 | {nil, nil}, 16 | {"", nil}, 17 | {(*string)(nil), nil}, 18 | {"2017-07-23", nil}, 19 | {"2017-07-23T03:46:56.647Z", t1}, 20 | } 21 | for _, test := range tests { 22 | val := graphql.DateTime.ParseValue(test.Value) 23 | if val != test.Expected { 24 | reflectedValue := reflect.ValueOf(test.Value) 25 | t.Fatalf("failed DateTime.ParseValue(%v(%v)), expected: %v, got %v", reflectedValue.Type(), test.Value, test.Expected, val) 26 | } 27 | } 28 | } 29 | 30 | func TestTypeSystem_Scalar_ParseLiteralOutputDateTime(t *testing.T) { 31 | t1, _ := time.Parse(time.RFC3339, "2017-07-23T03:46:56.647Z") 32 | for name, testCase := range map[string]struct { 33 | Literal ast.Value 34 | Expected interface{} 35 | }{ 36 | "String": { 37 | Literal: &ast.StringValue{ 38 | Value: "2017-07-23T03:46:56.647Z", 39 | }, 40 | Expected: t1, 41 | }, 42 | "NotAString": { 43 | Literal: &ast.IntValue{}, 44 | Expected: nil, 45 | }, 46 | } { 47 | t.Run(name, func(t *testing.T) { 48 | parsed := graphql.DateTime.ParseLiteral(testCase.Literal) 49 | if parsed != testCase.Expected { 50 | t.Fatalf("failed DateTime.ParseLiteral(%T(%v)), expected: %v, got %v", testCase.Literal, testCase.Literal, parsed, testCase.Expected) 51 | } 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /scalars_serialization_test.go: -------------------------------------------------------------------------------- 1 | package graphql_test 2 | 3 | import ( 4 | "math" 5 | "reflect" 6 | "testing" 7 | "time" 8 | 9 | "github.com/graphql-go/graphql" 10 | ) 11 | 12 | type intSerializationTest struct { 13 | Value interface{} 14 | Expected interface{} 15 | } 16 | 17 | type float64SerializationTest struct { 18 | Value interface{} 19 | Expected interface{} 20 | } 21 | 22 | type stringSerializationTest struct { 23 | Value interface{} 24 | Expected string 25 | } 26 | 27 | type dateTimeSerializationTest struct { 28 | Value interface{} 29 | Expected interface{} 30 | } 31 | 32 | type boolSerializationTest struct { 33 | Value interface{} 34 | Expected bool 35 | } 36 | 37 | func TestTypeSystem_Scalar_SerializesOutputInt(t *testing.T) { 38 | tests := []intSerializationTest{ 39 | {1, 1}, 40 | {0, 0}, 41 | {-1, -1}, 42 | {float32(0.1), 0}, 43 | {float32(1.1), 1}, 44 | {float32(-1.1), -1}, 45 | {float32(1e5), 100000}, 46 | {float32(math.MaxFloat32), nil}, 47 | {float64(0.1), 0}, 48 | {float64(1.1), 1}, 49 | {float64(-1.1), -1}, 50 | {float64(1e5), 100000}, 51 | {float64(math.MaxFloat32), nil}, 52 | {float64(math.MaxFloat64), nil}, 53 | // Maybe a safe Go/Javascript `int`, but bigger than 2^32, so not 54 | // representable as a GraphQL Int 55 | {9876504321, nil}, 56 | {-9876504321, nil}, 57 | // Too big to represent as an Int in Go, JavaScript or GraphQL 58 | {float64(1e100), nil}, 59 | {float64(-1e100), nil}, 60 | {"-1.1", -1}, 61 | {"one", nil}, 62 | {false, 0}, 63 | {true, 1}, 64 | {int8(1), 1}, 65 | {int16(1), 1}, 66 | {int32(1), 1}, 67 | {int64(1), 1}, 68 | {uint(1), 1}, 69 | // Maybe a safe Go `uint`, but bigger than 2^32, so not 70 | // representable as a GraphQL Int 71 | {uint(math.MaxInt32 + 1), nil}, 72 | {uint8(1), 1}, 73 | {uint16(1), 1}, 74 | {uint32(1), 1}, 75 | {uint32(math.MaxUint32), nil}, 76 | {uint64(1), 1}, 77 | {uint64(math.MaxInt32), math.MaxInt32}, 78 | {int64(math.MaxInt32) + int64(1), nil}, 79 | {int64(math.MinInt32) - int64(1), nil}, 80 | {uint64(math.MaxInt64) + uint64(1), nil}, 81 | {byte(127), 127}, 82 | {'世', int('世')}, 83 | // testing types that don't match a value in the array. 84 | {[]int{}, nil}, 85 | } 86 | 87 | for i, test := range tests { 88 | val := graphql.Int.Serialize(test.Value) 89 | if val != test.Expected { 90 | reflectedTestValue := reflect.ValueOf(test.Value) 91 | reflectedExpectedValue := reflect.ValueOf(test.Expected) 92 | reflectedValue := reflect.ValueOf(val) 93 | t.Fatalf("Failed test #%d - Int.Serialize(%v(%v)), expected: %v(%v), got %v(%v)", 94 | i, reflectedTestValue.Type(), test.Value, 95 | reflectedExpectedValue.Type(), test.Expected, 96 | reflectedValue.Type(), val, 97 | ) 98 | } 99 | } 100 | } 101 | 102 | func TestTypeSystem_Scalar_SerializesOutputFloat(t *testing.T) { 103 | tests := []float64SerializationTest{ 104 | {int(1), 1.0}, 105 | {int(0), 0.0}, 106 | {int(-1), -1.0}, 107 | {float32(0.1), float32(0.1)}, 108 | {float32(1.1), float32(1.1)}, 109 | {float32(-1.1), float32(-1.1)}, 110 | {float64(0.1), float64(0.1)}, 111 | {float64(1.1), float64(1.1)}, 112 | {float64(-1.1), float64(-1.1)}, 113 | {"-1.1", -1.1}, 114 | {"one", nil}, 115 | {false, 0.0}, 116 | {true, 1.0}, 117 | } 118 | 119 | for i, test := range tests { 120 | val := graphql.Float.Serialize(test.Value) 121 | if val != test.Expected { 122 | reflectedTestValue := reflect.ValueOf(test.Value) 123 | reflectedExpectedValue := reflect.ValueOf(test.Expected) 124 | reflectedValue := reflect.ValueOf(val) 125 | t.Fatalf("Failed test #%d - Float.Serialize(%v(%v)), expected: %v(%v), got %v(%v)", 126 | i, reflectedTestValue.Type(), test.Value, 127 | reflectedExpectedValue.Type(), test.Expected, 128 | reflectedValue.Type(), val, 129 | ) 130 | } 131 | } 132 | } 133 | 134 | func TestTypeSystem_Scalar_SerializesOutputStrings(t *testing.T) { 135 | tests := []stringSerializationTest{ 136 | {"string", "string"}, 137 | {int(1), "1"}, 138 | {float32(-1.1), "-1.1"}, 139 | {float64(-1.1), "-1.1"}, 140 | {true, "true"}, 141 | {false, "false"}, 142 | } 143 | 144 | for _, test := range tests { 145 | val := graphql.String.Serialize(test.Value) 146 | if val != test.Expected { 147 | reflectedValue := reflect.ValueOf(test.Value) 148 | t.Fatalf("Failed String.Serialize(%v(%v)), expected: %v, got %v", reflectedValue.Type(), test.Value, test.Expected, val) 149 | } 150 | } 151 | } 152 | 153 | func TestTypeSystem_Scalar_SerializesOutputBoolean(t *testing.T) { 154 | tests := []boolSerializationTest{ 155 | {"true", true}, 156 | {"false", false}, 157 | {"string", true}, 158 | {"", false}, 159 | {int(1), true}, 160 | {int(0), false}, 161 | {true, true}, 162 | {false, false}, 163 | } 164 | 165 | for _, test := range tests { 166 | val := graphql.Boolean.Serialize(test.Value) 167 | if val != test.Expected { 168 | reflectedValue := reflect.ValueOf(test.Value) 169 | t.Fatalf("Failed String.Boolean(%v(%v)), expected: %v, got %v", reflectedValue.Type(), test.Value, test.Expected, val) 170 | } 171 | } 172 | } 173 | 174 | func TestTypeSystem_Scalar_SerializeOutputDateTime(t *testing.T) { 175 | now := time.Now() 176 | nowString, err := now.MarshalText() 177 | if err != nil { 178 | t.Fatal(err) 179 | } 180 | 181 | tests := []dateTimeSerializationTest{ 182 | {"string", nil}, 183 | {int(1), nil}, 184 | {float32(-1.1), nil}, 185 | {float64(-1.1), nil}, 186 | {true, nil}, 187 | {false, nil}, 188 | {now, string(nowString)}, 189 | {&now, string(nowString)}, 190 | } 191 | 192 | for _, test := range tests { 193 | val := graphql.DateTime.Serialize(test.Value) 194 | if val != test.Expected { 195 | reflectedValue := reflect.ValueOf(test.Value) 196 | t.Fatalf("Failed DateTime.Serialize(%v(%v)), expected: %v, got %v", reflectedValue.Type(), test.Value, test.Expected, val) 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /schema-all-descriptions.graphql: -------------------------------------------------------------------------------- 1 | # File: schema-all-descriptions.graphql 2 | 3 | """single line scalar description""" 4 | scalar ScalarSingleLine 5 | 6 | """ 7 | multi line 8 | 9 | scalar description 10 | """ 11 | scalar ScalarMultiLine 12 | 13 | """single line object description""" 14 | type ObjectSingleLine { 15 | no_description: ID 16 | 17 | """single line field description""" 18 | single_line(a: ID, b: ID, c: ID, d: ID): ID 19 | 20 | """ 21 | multi line 22 | 23 | field description 24 | """ 25 | multi_line( 26 | a: ID 27 | 28 | """single line argument description""" 29 | b: ID 30 | 31 | """ 32 | multi line 33 | 34 | field description 35 | """ 36 | c: ID 37 | d: ID 38 | ): ID 39 | } 40 | 41 | """ 42 | multi line 43 | 44 | object description 45 | """ 46 | type ObjectMultiLine { 47 | foo: ID 48 | } 49 | 50 | """single line interface description""" 51 | interface InterfaceSingleLine { 52 | no_description: ID 53 | 54 | """single line field description""" 55 | single_line(a: ID, b: ID, c: ID, d: ID): ID 56 | 57 | """ 58 | multi line 59 | 60 | field description 61 | """ 62 | multi_line( 63 | a: ID 64 | 65 | """single line argument description""" 66 | b: ID 67 | 68 | """ 69 | multi line 70 | 71 | argument description 72 | """ 73 | c: ID 74 | d: ID 75 | ): ID 76 | } 77 | 78 | """ 79 | multi line 80 | 81 | interface description 82 | """ 83 | interface InterfaceMultiLine { 84 | foo: ID 85 | } 86 | 87 | """single line union description""" 88 | union UnionSingleLine = String | Int | Float | ID 89 | 90 | """ 91 | multi line 92 | 93 | union description 94 | """ 95 | union UnionSingleLine = String | Int | Float | ID 96 | 97 | """single line enum description""" 98 | enum EnumSingleLine { 99 | no_description 100 | 101 | """single line enum description""" 102 | single_line 103 | 104 | """ 105 | multi line 106 | 107 | enum description 108 | """ 109 | multi_line 110 | again_no_description 111 | } 112 | 113 | """ 114 | multi line 115 | 116 | enum description 117 | """ 118 | enum EnumMultiLine { 119 | foo 120 | } 121 | 122 | """single line input description""" 123 | input InputSingleLine { 124 | a: ID 125 | 126 | """single line argument description""" 127 | b: ID 128 | 129 | """ 130 | multi line 131 | 132 | argument description 133 | """ 134 | c: ID 135 | d: ID 136 | } 137 | 138 | """ 139 | multi line 140 | 141 | input description 142 | """ 143 | input InputMultiLine { 144 | foo: ID 145 | } 146 | 147 | """single line directive description""" 148 | directive @DirectiveSingleLine( 149 | a: ID 150 | 151 | """single line argument description""" 152 | b: ID 153 | 154 | """ 155 | multi line 156 | 157 | argument description 158 | """ 159 | c: ID 160 | d: ID 161 | ) on SCALAR 162 | 163 | """ 164 | multi line 165 | 166 | directive description 167 | """ 168 | directive @DirectiveMultiLine on SCALAR 169 | -------------------------------------------------------------------------------- /schema-kitchen-sink.graphql: -------------------------------------------------------------------------------- 1 | # Filename: schema-kitchen-sink.graphql 2 | 3 | schema { 4 | query: QueryType 5 | mutation: MutationType 6 | } 7 | 8 | type Foo implements Bar & Baz { 9 | one: Type 10 | two(argument: InputType!): Type 11 | three(argument: InputType, other: String): Int 12 | four(argument: String = "string"): String 13 | five(argument: [String] = ["string", "string"]): String 14 | six(argument: InputType = {key: "value"}): Type 15 | } 16 | 17 | type AnnotatedObject @onObject(arg: "value") { 18 | annotatedField(arg: Type = "default" @onArg): Type @onField 19 | } 20 | 21 | interface Bar { 22 | one: Type 23 | four(argument: String = "string"): String 24 | } 25 | 26 | interface AnnotatedInterface @onInterface { 27 | annotatedField(arg: Type @onArg): Type @onField 28 | } 29 | 30 | union Feed = Story | Article | Advert 31 | 32 | union AnnotatedUnion @onUnion = A | B 33 | 34 | scalar CustomScalar 35 | 36 | scalar AnnotatedScalar @onScalar 37 | 38 | enum Site { 39 | DESKTOP 40 | MOBILE 41 | } 42 | 43 | enum AnnotatedEnum @onEnum { 44 | ANNOTATED_VALUE @onEnumValue 45 | OTHER_VALUE 46 | } 47 | 48 | input InputType { 49 | key: String! 50 | answer: Int = 42 51 | } 52 | 53 | input AnnotatedInput @onInputObjectType { 54 | annotatedField: Type @onField 55 | } 56 | 57 | extend type Foo { 58 | seven(argument: [String]): Type 59 | } 60 | 61 | extend type Foo @onType {} 62 | 63 | type NoFields {} 64 | 65 | directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT 66 | 67 | directive @include(if: Boolean!) 68 | on FIELD 69 | | FRAGMENT_SPREAD 70 | | INLINE_FRAGMENT 71 | -------------------------------------------------------------------------------- /subscription.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/graphql-go/graphql/gqlerrors" 8 | "github.com/graphql-go/graphql/language/parser" 9 | "github.com/graphql-go/graphql/language/source" 10 | ) 11 | 12 | // SubscribeParams parameters for subscribing 13 | type SubscribeParams struct { 14 | Schema Schema 15 | RequestString string 16 | RootValue interface{} 17 | // ContextValue context.Context 18 | VariableValues map[string]interface{} 19 | OperationName string 20 | FieldResolver FieldResolveFn 21 | FieldSubscriber FieldResolveFn 22 | } 23 | 24 | // Subscribe performs a subscribe operation on the given query and schema 25 | // To finish a subscription you can simply close the channel from inside the `Subscribe` function 26 | // currently does not support extensions hooks 27 | func Subscribe(p Params) chan *Result { 28 | 29 | source := source.NewSource(&source.Source{ 30 | Body: []byte(p.RequestString), 31 | Name: "GraphQL request", 32 | }) 33 | 34 | // TODO run extensions hooks 35 | 36 | // parse the source 37 | AST, err := parser.Parse(parser.ParseParams{Source: source}) 38 | if err != nil { 39 | 40 | // merge the errors from extensions and the original error from parser 41 | return sendOneResultAndClose(&Result{ 42 | Errors: gqlerrors.FormatErrors(err), 43 | }) 44 | } 45 | 46 | // validate document 47 | validationResult := ValidateDocument(&p.Schema, AST, nil) 48 | 49 | if !validationResult.IsValid { 50 | // run validation finish functions for extensions 51 | return sendOneResultAndClose(&Result{ 52 | Errors: validationResult.Errors, 53 | }) 54 | 55 | } 56 | return ExecuteSubscription(ExecuteParams{ 57 | Schema: p.Schema, 58 | Root: p.RootObject, 59 | AST: AST, 60 | OperationName: p.OperationName, 61 | Args: p.VariableValues, 62 | Context: p.Context, 63 | }) 64 | } 65 | 66 | func sendOneResultAndClose(res *Result) chan *Result { 67 | resultChannel := make(chan *Result, 1) 68 | resultChannel <- res 69 | close(resultChannel) 70 | return resultChannel 71 | } 72 | 73 | // ExecuteSubscription is similar to graphql.Execute but returns a channel instead of a Result 74 | // currently does not support extensions 75 | func ExecuteSubscription(p ExecuteParams) chan *Result { 76 | 77 | if p.Context == nil { 78 | p.Context = context.Background() 79 | } 80 | 81 | var mapSourceToResponse = func(payload interface{}) *Result { 82 | return Execute(ExecuteParams{ 83 | Schema: p.Schema, 84 | Root: payload, 85 | AST: p.AST, 86 | OperationName: p.OperationName, 87 | Args: p.Args, 88 | Context: p.Context, 89 | }) 90 | } 91 | var resultChannel = make(chan *Result) 92 | go func() { 93 | defer close(resultChannel) 94 | defer func() { 95 | if err := recover(); err != nil { 96 | e, ok := err.(error) 97 | if !ok { 98 | return 99 | } 100 | resultChannel <- &Result{ 101 | Errors: gqlerrors.FormatErrors(e), 102 | } 103 | } 104 | return 105 | }() 106 | 107 | exeContext, err := buildExecutionContext(buildExecutionCtxParams{ 108 | Schema: p.Schema, 109 | Root: p.Root, 110 | AST: p.AST, 111 | OperationName: p.OperationName, 112 | Args: p.Args, 113 | Context: p.Context, 114 | }) 115 | 116 | if err != nil { 117 | resultChannel <- &Result{ 118 | Errors: gqlerrors.FormatErrors(err), 119 | } 120 | 121 | return 122 | } 123 | 124 | operationType, err := getOperationRootType(p.Schema, exeContext.Operation) 125 | if err != nil { 126 | resultChannel <- &Result{ 127 | Errors: gqlerrors.FormatErrors(err), 128 | } 129 | 130 | return 131 | } 132 | 133 | fields := collectFields(collectFieldsParams{ 134 | ExeContext: exeContext, 135 | RuntimeType: operationType, 136 | SelectionSet: exeContext.Operation.GetSelectionSet(), 137 | }) 138 | 139 | responseNames := []string{} 140 | for name := range fields { 141 | responseNames = append(responseNames, name) 142 | } 143 | responseName := responseNames[0] 144 | fieldNodes := fields[responseName] 145 | fieldNode := fieldNodes[0] 146 | fieldName := fieldNode.Name.Value 147 | fieldDef := getFieldDef(p.Schema, operationType, fieldName) 148 | 149 | if fieldDef == nil { 150 | resultChannel <- &Result{ 151 | Errors: gqlerrors.FormatErrors(fmt.Errorf("the subscription field %q is not defined", fieldName)), 152 | } 153 | 154 | return 155 | } 156 | 157 | resolveFn := fieldDef.Subscribe 158 | 159 | if resolveFn == nil { 160 | resultChannel <- &Result{ 161 | Errors: gqlerrors.FormatErrors(fmt.Errorf("the subscription function %q is not defined", fieldName)), 162 | } 163 | return 164 | } 165 | fieldPath := &ResponsePath{ 166 | Key: responseName, 167 | } 168 | 169 | args := getArgumentValues(fieldDef.Args, fieldNode.Arguments, exeContext.VariableValues) 170 | info := ResolveInfo{ 171 | FieldName: fieldName, 172 | FieldASTs: fieldNodes, 173 | Path: fieldPath, 174 | ReturnType: fieldDef.Type, 175 | ParentType: operationType, 176 | Schema: p.Schema, 177 | Fragments: exeContext.Fragments, 178 | RootValue: exeContext.Root, 179 | Operation: exeContext.Operation, 180 | VariableValues: exeContext.VariableValues, 181 | } 182 | 183 | fieldResult, err := resolveFn(ResolveParams{ 184 | Source: p.Root, 185 | Args: args, 186 | Info: info, 187 | Context: p.Context, 188 | }) 189 | if err != nil { 190 | resultChannel <- &Result{ 191 | Errors: gqlerrors.FormatErrors(err), 192 | } 193 | 194 | return 195 | } 196 | 197 | if fieldResult == nil { 198 | resultChannel <- &Result{ 199 | Errors: gqlerrors.FormatErrors(fmt.Errorf("no field result")), 200 | } 201 | 202 | return 203 | } 204 | 205 | switch fieldResult.(type) { 206 | case chan interface{}: 207 | sub := fieldResult.(chan interface{}) 208 | for { 209 | select { 210 | case <-p.Context.Done(): 211 | return 212 | 213 | case res, more := <-sub: 214 | if !more { 215 | return 216 | } 217 | resultChannel <- mapSourceToResponse(res) 218 | } 219 | } 220 | default: 221 | resultChannel <- mapSourceToResponse(fieldResult) 222 | return 223 | } 224 | }() 225 | 226 | // return a result channel 227 | return resultChannel 228 | } 229 | -------------------------------------------------------------------------------- /suggested_list_internal_test.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestSuggestionList_ReturnsResultsWhenInputIsEmpty(t *testing.T) { 9 | expected := []string{"a"} 10 | result := suggestionList("", []string{"a"}) 11 | if !reflect.DeepEqual(expected, result) { 12 | t.Fatalf("Expected %v, got: %v", expected, result) 13 | } 14 | } 15 | func TestSuggestionList_ReturnsEmptyArrayWhenThereAreNoOptions(t *testing.T) { 16 | expected := []string{} 17 | result := suggestionList("input", []string{}) 18 | if !reflect.DeepEqual(expected, result) { 19 | t.Fatalf("Expected %v, got: %v", expected, result) 20 | } 21 | } 22 | func TestSuggestionList_ReturnsOptionsSortedBasedOnSimilarity(t *testing.T) { 23 | expected := []string{"abc", "ab"} 24 | result := suggestionList("abc", []string{"a", "ab", "abc"}) 25 | if !reflect.DeepEqual(expected, result) { 26 | t.Fatalf("Expected %v, got: %v", expected, result) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /testutil/introspection_query.go: -------------------------------------------------------------------------------- 1 | package testutil 2 | 3 | var IntrospectionQuery = ` 4 | query IntrospectionQuery { 5 | __schema { 6 | queryType { name } 7 | mutationType { name } 8 | subscriptionType { name } 9 | types { 10 | ...FullType 11 | } 12 | directives { 13 | name 14 | description 15 | locations 16 | args { 17 | ...InputValue 18 | } 19 | # deprecated, but included for coverage till removed 20 | onOperation 21 | onFragment 22 | onField 23 | } 24 | } 25 | } 26 | 27 | fragment FullType on __Type { 28 | kind 29 | name 30 | description 31 | fields(includeDeprecated: true) { 32 | name 33 | description 34 | args { 35 | ...InputValue 36 | } 37 | type { 38 | ...TypeRef 39 | } 40 | isDeprecated 41 | deprecationReason 42 | } 43 | inputFields { 44 | ...InputValue 45 | } 46 | interfaces { 47 | ...TypeRef 48 | } 49 | enumValues(includeDeprecated: true) { 50 | name 51 | description 52 | isDeprecated 53 | deprecationReason 54 | } 55 | possibleTypes { 56 | ...TypeRef 57 | } 58 | } 59 | 60 | fragment InputValue on __InputValue { 61 | name 62 | description 63 | type { ...TypeRef } 64 | defaultValue 65 | } 66 | 67 | fragment TypeRef on __Type { 68 | kind 69 | name 70 | ofType { 71 | kind 72 | name 73 | ofType { 74 | kind 75 | name 76 | ofType { 77 | kind 78 | name 79 | ofType { 80 | kind 81 | name 82 | ofType { 83 | kind 84 | name 85 | ofType { 86 | kind 87 | name 88 | ofType { 89 | kind 90 | name 91 | } 92 | } 93 | } 94 | } 95 | } 96 | } 97 | } 98 | } 99 | ` 100 | -------------------------------------------------------------------------------- /testutil/subscription.go: -------------------------------------------------------------------------------- 1 | package testutil 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "errors" 8 | "strconv" 9 | "testing" 10 | 11 | "github.com/graphql-go/graphql" 12 | ) 13 | 14 | // TestResponse models the expected response 15 | type TestResponse struct { 16 | Data string 17 | Errors []string 18 | } 19 | 20 | // TestSubscription is a GraphQL test case to be used with RunSubscribe. 21 | type TestSubscription struct { 22 | Name string 23 | Schema graphql.Schema 24 | Query string 25 | OperationName string 26 | Variables map[string]interface{} 27 | ExpectedResults []TestResponse 28 | } 29 | 30 | // RunSubscribes runs the given GraphQL subscription test cases as subtests. 31 | func RunSubscribes(t *testing.T, tests []*TestSubscription) { 32 | for i, test := range tests { 33 | if test.Name == "" { 34 | test.Name = strconv.Itoa(i + 1) 35 | } 36 | 37 | t.Run(test.Name, func(t *testing.T) { 38 | RunSubscribe(t, test) 39 | }) 40 | } 41 | } 42 | 43 | // RunSubscribe runs a single GraphQL subscription test case. 44 | func RunSubscribe(t *testing.T, test *TestSubscription) { 45 | ctx, cancel := context.WithCancel(context.Background()) 46 | defer cancel() 47 | 48 | c := graphql.Subscribe(graphql.Params{ 49 | Context: ctx, 50 | OperationName: test.OperationName, 51 | RequestString: test.Query, 52 | VariableValues: test.Variables, 53 | Schema: test.Schema, 54 | }) 55 | // if err != nil { 56 | // if err.Error() != test.ExpectedErr.Error() { 57 | // t.Fatalf("unexpected error: got %+v, want %+v", err, test.ExpectedErr) 58 | // } 59 | 60 | // return 61 | // } 62 | 63 | var results []*graphql.Result 64 | for res := range c { 65 | t.Log(pretty(res)) 66 | results = append(results, res) 67 | } 68 | 69 | for i, expected := range test.ExpectedResults { 70 | if len(results)-1 < i { 71 | t.Error(errors.New("not enough results, expected results are more than actual results")) 72 | return 73 | } 74 | res := results[i] 75 | 76 | var errs []string 77 | for _, err := range res.Errors { 78 | errs = append(errs, err.Message) 79 | } 80 | checkErrorStrings(t, expected.Errors, errs) 81 | if expected.Data == "" { 82 | continue 83 | } 84 | 85 | got, err := json.MarshalIndent(res.Data, "", " ") 86 | if err != nil { 87 | t.Fatalf("got: invalid JSON: %s; raw: %s", err, got) 88 | } 89 | 90 | if err != nil { 91 | t.Fatal(err) 92 | } 93 | want, err := formatJSON(expected.Data) 94 | if err != nil { 95 | t.Fatalf("got: invalid JSON: %s; raw: %s", err, res.Data) 96 | } 97 | 98 | if !bytes.Equal(got, want) { 99 | t.Logf("got: %s", got) 100 | t.Logf("want: %s", want) 101 | t.Fail() 102 | } 103 | } 104 | } 105 | 106 | func checkErrorStrings(t *testing.T, expected, actual []string) { 107 | expectedCount, actualCount := len(expected), len(actual) 108 | 109 | if expectedCount != actualCount { 110 | t.Fatalf("unexpected number of errors: want `%d`, got `%d`", expectedCount, actualCount) 111 | } 112 | 113 | if expectedCount > 0 { 114 | for i, want := range expected { 115 | got := actual[i] 116 | 117 | if got != want { 118 | t.Fatalf("unexpected error: got `%+v`, want `%+v`", got, want) 119 | } 120 | } 121 | 122 | // Return because we're done checking. 123 | return 124 | } 125 | 126 | for _, err := range actual { 127 | t.Errorf("unexpected error: '%s'", err) 128 | } 129 | } 130 | 131 | func formatJSON(data string) ([]byte, error) { 132 | var v interface{} 133 | if err := json.Unmarshal([]byte(data), &v); err != nil { 134 | return nil, err 135 | } 136 | formatted, err := json.MarshalIndent(v, "", " ") 137 | if err != nil { 138 | return nil, err 139 | } 140 | return formatted, nil 141 | } 142 | 143 | func pretty(x interface{}) string { 144 | got, err := json.MarshalIndent(x, "", " ") 145 | if err != nil { 146 | panic(err) 147 | } 148 | return string(got) 149 | } 150 | -------------------------------------------------------------------------------- /type_comparators_internal_test.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIsEqualType_SameReferenceAreEqual(t *testing.T) { 8 | if !isEqualType(String, String) { 9 | t.Fatalf("Expected same reference to be equal") 10 | } 11 | } 12 | 13 | func TestIsEqualType_IntAndFloatAreNotEqual(t *testing.T) { 14 | if isEqualType(Int, Float) { 15 | t.Fatalf("Expected GraphQLInt and GraphQLFloat to not equal") 16 | } 17 | } 18 | 19 | func TestIsEqualType_ListsOfSameTypeAreEqual(t *testing.T) { 20 | if !isEqualType(NewList(Int), NewList(Int)) { 21 | t.Fatalf("Expected lists of same type are equal") 22 | } 23 | } 24 | 25 | func TestIsEqualType_ListsAreNotEqualToItem(t *testing.T) { 26 | if isEqualType(NewList(Int), Int) { 27 | t.Fatalf("Expected lists are not equal to item") 28 | } 29 | } 30 | 31 | func TestIsEqualType_NonNullOfSameTypeAreEqual(t *testing.T) { 32 | if !isEqualType(NewNonNull(Int), NewNonNull(Int)) { 33 | t.Fatalf("Expected non-null of same type are equal") 34 | } 35 | } 36 | func TestIsEqualType_NonNullIsNotEqualToNullable(t *testing.T) { 37 | if isEqualType(NewNonNull(Int), Int) { 38 | t.Fatalf("Expected non-null is not equal to nullable") 39 | } 40 | } 41 | 42 | func testSchemaForIsTypeSubTypeOfTest(t *testing.T, fields Fields) *Schema { 43 | schema, err := NewSchema(SchemaConfig{ 44 | Query: NewObject(ObjectConfig{ 45 | Name: "Query", 46 | Fields: fields, 47 | }), 48 | }) 49 | if err != nil { 50 | t.Fatalf("Invalid schema: %v", err) 51 | } 52 | return &schema 53 | } 54 | 55 | func TestIsTypeSubTypeOf_SameReferenceIsSubtype(t *testing.T) { 56 | schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ 57 | "field": &Field{Type: String}, 58 | }) 59 | if !isTypeSubTypeOf(schema, String, String) { 60 | t.Fatalf("Expected same reference is subtype") 61 | } 62 | } 63 | func TestIsTypeSubTypeOf_IntIsNotSubtypeOfFloat(t *testing.T) { 64 | schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ 65 | "field": &Field{Type: String}, 66 | }) 67 | if isTypeSubTypeOf(schema, Int, Float) { 68 | t.Fatalf("Expected int is not subtype of float") 69 | } 70 | } 71 | func TestIsTypeSubTypeOf_NonNullIsSubtypeOfNullable(t *testing.T) { 72 | schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ 73 | "field": &Field{Type: String}, 74 | }) 75 | if !isTypeSubTypeOf(schema, NewNonNull(Int), Int) { 76 | t.Fatalf("Expected non-null is subtype of nullable") 77 | } 78 | } 79 | func TestIsTypeSubTypeOf_NullableIsNotSubtypeOfNonNull(t *testing.T) { 80 | schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ 81 | "field": &Field{Type: String}, 82 | }) 83 | if isTypeSubTypeOf(schema, Int, NewNonNull(Int)) { 84 | t.Fatalf("Expected nullable is not subtype of non-null") 85 | } 86 | } 87 | func TestIsTypeSubTypeOf_ItemIsNotSubTypeOfList(t *testing.T) { 88 | schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ 89 | "field": &Field{Type: String}, 90 | }) 91 | if isTypeSubTypeOf(schema, Int, NewList(Int)) { 92 | t.Fatalf("Expected item is not subtype of list") 93 | } 94 | } 95 | func TestIsTypeSubTypeOf_ListIsNotSubtypeOfItem(t *testing.T) { 96 | schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ 97 | "field": &Field{Type: String}, 98 | }) 99 | if isTypeSubTypeOf(schema, NewList(Int), Int) { 100 | t.Fatalf("Expected list is not subtype of item") 101 | } 102 | } 103 | 104 | func TestIsTypeSubTypeOf_MemberIsSubtypeOfUnion(t *testing.T) { 105 | memberType := NewObject(ObjectConfig{ 106 | Name: "Object", 107 | IsTypeOf: func(p IsTypeOfParams) bool { 108 | return true 109 | }, 110 | Fields: Fields{ 111 | "field": &Field{Type: String}, 112 | }, 113 | }) 114 | unionType := NewUnion(UnionConfig{ 115 | Name: "Union", 116 | Types: []*Object{memberType}, 117 | }) 118 | schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ 119 | "field": &Field{Type: unionType}, 120 | }) 121 | if !isTypeSubTypeOf(schema, memberType, unionType) { 122 | t.Fatalf("Expected member is subtype of union") 123 | } 124 | } 125 | 126 | func TestIsTypeSubTypeOf_ImplementationIsSubtypeOfInterface(t *testing.T) { 127 | ifaceType := NewInterface(InterfaceConfig{ 128 | Name: "Interface", 129 | Fields: Fields{ 130 | "field": &Field{Type: String}, 131 | }, 132 | }) 133 | implType := NewObject(ObjectConfig{ 134 | Name: "Object", 135 | IsTypeOf: func(p IsTypeOfParams) bool { 136 | return true 137 | }, 138 | Interfaces: []*Interface{ifaceType}, 139 | Fields: Fields{ 140 | "field": &Field{Type: String}, 141 | }, 142 | }) 143 | schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ 144 | "field": &Field{Type: implType}, 145 | }) 146 | if !isTypeSubTypeOf(schema, implType, ifaceType) { 147 | t.Fatalf("Expected implementation is subtype of interface") 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "github.com/graphql-go/graphql/gqlerrors" 5 | ) 6 | 7 | // type Schema interface{} 8 | 9 | // Result has the response, errors and extensions from the resolved schema 10 | type Result struct { 11 | Data interface{} `json:"data"` 12 | Errors []gqlerrors.FormattedError `json:"errors,omitempty"` 13 | Extensions map[string]interface{} `json:"extensions,omitempty"` 14 | } 15 | 16 | // HasErrors just a simple function to help you decide if the result has errors or not 17 | func (r *Result) HasErrors() bool { 18 | return len(r.Errors) > 0 19 | } 20 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "encoding" 5 | "fmt" 6 | "reflect" 7 | "strings" 8 | ) 9 | 10 | const TAG = "json" 11 | 12 | // can't take recursive slice type 13 | // e.g 14 | // type Person struct{ 15 | // Friends []Person 16 | // } 17 | // it will throw panic stack-overflow 18 | func BindFields(obj interface{}) Fields { 19 | t := reflect.TypeOf(obj) 20 | v := reflect.ValueOf(obj) 21 | fields := make(map[string]*Field) 22 | 23 | if t.Kind() == reflect.Ptr { 24 | t = t.Elem() 25 | v = v.Elem() 26 | } 27 | 28 | for i := 0; i < t.NumField(); i++ { 29 | field := t.Field(i) 30 | 31 | tag := extractTag(field.Tag) 32 | if tag == "-" { 33 | continue 34 | } 35 | 36 | fieldType := field.Type 37 | 38 | if fieldType.Kind() == reflect.Ptr { 39 | fieldType = fieldType.Elem() 40 | } 41 | 42 | var graphType Output 43 | if fieldType.Kind() == reflect.Struct { 44 | itf := v.Field(i).Interface() 45 | if _, ok := itf.(encoding.TextMarshaler); ok { 46 | fieldType = reflect.TypeOf("") 47 | goto nonStruct 48 | } 49 | 50 | structFields := BindFields(itf) 51 | 52 | if tag == "" { 53 | fields = appendFields(fields, structFields) 54 | continue 55 | } else { 56 | graphType = NewObject(ObjectConfig{ 57 | Name: tag, 58 | Fields: structFields, 59 | }) 60 | } 61 | } 62 | 63 | nonStruct: 64 | if tag == "" { 65 | continue 66 | } 67 | 68 | if graphType == nil { 69 | graphType = getGraphType(fieldType) 70 | } 71 | fields[tag] = &Field{ 72 | Type: graphType, 73 | Resolve: func(p ResolveParams) (interface{}, error) { 74 | return extractValue(tag, p.Source), nil 75 | }, 76 | } 77 | } 78 | return fields 79 | } 80 | 81 | func getGraphType(tipe reflect.Type) Output { 82 | kind := tipe.Kind() 83 | switch kind { 84 | case reflect.String: 85 | return String 86 | case reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64: 87 | return Int 88 | case reflect.Float32, reflect.Float64: 89 | return Float 90 | case reflect.Bool: 91 | return Boolean 92 | case reflect.Slice: 93 | return getGraphList(tipe) 94 | } 95 | return String 96 | } 97 | 98 | func getGraphList(tipe reflect.Type) *List { 99 | if tipe.Kind() == reflect.Slice { 100 | switch tipe.Elem().Kind() { 101 | case reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64: 102 | return NewList(Int) 103 | case reflect.Bool: 104 | return NewList(Boolean) 105 | case reflect.Float32, reflect.Float64: 106 | return NewList(Float) 107 | case reflect.String: 108 | return NewList(String) 109 | } 110 | } 111 | // finally bind object 112 | t := reflect.New(tipe.Elem()) 113 | name := strings.Replace(fmt.Sprint(tipe.Elem()), ".", "_", -1) 114 | obj := NewObject(ObjectConfig{ 115 | Name: name, 116 | Fields: BindFields(t.Elem().Interface()), 117 | }) 118 | return NewList(obj) 119 | } 120 | 121 | func appendFields(dest, origin Fields) Fields { 122 | for key, value := range origin { 123 | dest[key] = value 124 | } 125 | return dest 126 | } 127 | 128 | func extractValue(originTag string, obj interface{}) interface{} { 129 | val := reflect.Indirect(reflect.ValueOf(obj)) 130 | 131 | for j := 0; j < val.NumField(); j++ { 132 | field := val.Type().Field(j) 133 | found := originTag == extractTag(field.Tag) 134 | if field.Type.Kind() == reflect.Struct { 135 | itf := val.Field(j).Interface() 136 | 137 | if str, ok := itf.(encoding.TextMarshaler); ok && found { 138 | byt, _ := str.MarshalText() 139 | return string(byt) 140 | } 141 | 142 | res := extractValue(originTag, itf) 143 | if res != nil { 144 | return res 145 | } 146 | } 147 | 148 | if found { 149 | return reflect.Indirect(val.Field(j)).Interface() 150 | } 151 | } 152 | return nil 153 | } 154 | 155 | func extractTag(tag reflect.StructTag) string { 156 | t := tag.Get(TAG) 157 | if t != "" { 158 | t = strings.Split(t, ",")[0] 159 | } 160 | return t 161 | } 162 | 163 | // lazy way of binding args 164 | func BindArg(obj interface{}, tags ...string) FieldConfigArgument { 165 | v := reflect.Indirect(reflect.ValueOf(obj)) 166 | var config = make(FieldConfigArgument) 167 | for i := 0; i < v.NumField(); i++ { 168 | field := v.Type().Field(i) 169 | 170 | mytag := extractTag(field.Tag) 171 | if inArray(tags, mytag) { 172 | config[mytag] = &ArgumentConfig{ 173 | Type: getGraphType(field.Type), 174 | } 175 | } 176 | } 177 | return config 178 | } 179 | 180 | func inArray(slice interface{}, item interface{}) bool { 181 | s := reflect.ValueOf(slice) 182 | if s.Kind() != reflect.Slice { 183 | panic("inArray() given a non-slice type") 184 | } 185 | 186 | for i := 0; i < s.Len(); i++ { 187 | if reflect.DeepEqual(item, s.Index(i).Interface()) { 188 | return true 189 | } 190 | } 191 | return false 192 | } 193 | -------------------------------------------------------------------------------- /util_test.go: -------------------------------------------------------------------------------- 1 | package graphql_test 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "reflect" 7 | "testing" 8 | "time" 9 | 10 | "github.com/graphql-go/graphql" 11 | "github.com/graphql-go/graphql/testutil" 12 | ) 13 | 14 | type Person struct { 15 | Human 16 | Name string `json:"name"` 17 | Home Address `json:"home"` 18 | Hobbies []string `json:"hobbies"` 19 | Friends []Friend `json:"friends"` 20 | } 21 | 22 | type Human struct { 23 | Alive bool `json:"alive,omitempty"` 24 | Age int `json:"age"` 25 | Weight float64 `json:"weight"` 26 | DoB time.Time `json:"dob"` 27 | } 28 | 29 | type Friend struct { 30 | Name string `json:"name"` 31 | Address string `json:"address"` 32 | } 33 | 34 | type Address struct { 35 | Street string `json:"street"` 36 | City string `json:"city"` 37 | Test string `json:",omitempty"` 38 | } 39 | 40 | var personSource = Person{ 41 | Human: Human{ 42 | Age: 24, 43 | Weight: 70.1, 44 | Alive: true, 45 | DoB: time.Date(2019, 01, 01, 01, 01, 01, 0, time.UTC), 46 | }, 47 | Name: "John Doe", 48 | Home: Address{ 49 | Street: "Jl. G1", 50 | City: "Jakarta", 51 | }, 52 | Friends: friendSource, 53 | Hobbies: []string{"eat", "sleep", "code"}, 54 | } 55 | 56 | var friendSource = []Friend{ 57 | {Name: "Arief", Address: "palembang"}, 58 | {Name: "Al", Address: "semarang"}, 59 | } 60 | 61 | func TestBindFields(t *testing.T) { 62 | // create person type based on Person struct 63 | personType := graphql.NewObject(graphql.ObjectConfig{ 64 | Name: "Person", 65 | // pass empty Person struct to bind all of it's fields 66 | Fields: graphql.BindFields(Person{}), 67 | }) 68 | fields := graphql.Fields{ 69 | "person": &graphql.Field{ 70 | Type: personType, 71 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 72 | return personSource, nil 73 | }, 74 | }, 75 | } 76 | rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: fields} 77 | schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)} 78 | schema, err := graphql.NewSchema(schemaConfig) 79 | if err != nil { 80 | log.Fatalf("failed to create new schema, error: %v", err) 81 | } 82 | 83 | // Query 84 | query := ` 85 | { 86 | person{ 87 | name, 88 | dob, 89 | home{street,city}, 90 | friends{name,address}, 91 | age, 92 | weight, 93 | alive, 94 | hobbies 95 | } 96 | } 97 | ` 98 | params := graphql.Params{Schema: schema, RequestString: query} 99 | r := graphql.Do(params) 100 | if len(r.Errors) > 0 { 101 | log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors) 102 | } 103 | 104 | rJSON, _ := json.Marshal(r) 105 | data := struct { 106 | Data struct { 107 | Person Person `json:"person"` 108 | } `json:"data"` 109 | }{} 110 | err = json.Unmarshal(rJSON, &data) 111 | if err != nil { 112 | log.Fatalf("failed to unmarshal. error: %v", err) 113 | } 114 | 115 | newPerson := data.Data.Person 116 | if !reflect.DeepEqual(newPerson, personSource) { 117 | t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(personSource, newPerson)) 118 | } 119 | } 120 | 121 | func TestBindArg(t *testing.T) { 122 | var friendObj = graphql.NewObject(graphql.ObjectConfig{ 123 | Name: "friend", 124 | Fields: graphql.BindFields(Friend{}), 125 | }) 126 | 127 | fields := graphql.Fields{ 128 | "friend": &graphql.Field{ 129 | Type: friendObj, 130 | //it can be added more than one since it's a slice 131 | Args: graphql.BindArg(Friend{}, "name"), 132 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 133 | if name, ok := p.Args["name"].(string); ok { 134 | for _, friend := range friendSource { 135 | if friend.Name == name { 136 | return friend, nil 137 | } 138 | } 139 | } 140 | return nil, nil 141 | }, 142 | }, 143 | } 144 | rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: fields} 145 | schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)} 146 | schema, err := graphql.NewSchema(schemaConfig) 147 | if err != nil { 148 | log.Fatalf("failed to create new schema, error: %v", err) 149 | } 150 | 151 | // Query 152 | query := ` 153 | { 154 | friend(name:"Arief"){ 155 | address 156 | } 157 | } 158 | ` 159 | params := graphql.Params{Schema: schema, RequestString: query} 160 | r := graphql.Do(params) 161 | if len(r.Errors) > 0 { 162 | log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors) 163 | } 164 | 165 | rJSON, _ := json.Marshal(r) 166 | 167 | data := struct { 168 | Data struct { 169 | Friend Friend `json:"friend"` 170 | } `json:"data"` 171 | }{} 172 | err = json.Unmarshal(rJSON, &data) 173 | if err != nil { 174 | log.Fatalf("failed to unmarshal. error: %v", err) 175 | } 176 | 177 | expectedAddress := "palembang" 178 | newFriend := data.Data.Friend 179 | if newFriend.Address != expectedAddress { 180 | t.Fatalf("Unexpected result, expected address to be %s but got %s", expectedAddress, newFriend.Address) 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /validator_test.go: -------------------------------------------------------------------------------- 1 | package graphql_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/graphql-go/graphql" 7 | "github.com/graphql-go/graphql/gqlerrors" 8 | "github.com/graphql-go/graphql/language/ast" 9 | "github.com/graphql-go/graphql/language/location" 10 | "github.com/graphql-go/graphql/language/parser" 11 | "github.com/graphql-go/graphql/language/source" 12 | "github.com/graphql-go/graphql/testutil" 13 | ) 14 | 15 | func expectValid(t *testing.T, schema *graphql.Schema, queryString string) { 16 | source := source.NewSource(&source.Source{ 17 | Body: []byte(queryString), 18 | Name: "GraphQL request", 19 | }) 20 | AST, err := parser.Parse(parser.ParseParams{Source: source}) 21 | if err != nil { 22 | t.Fatalf("Unexpected error: %v", err) 23 | } 24 | validationResult := graphql.ValidateDocument(schema, AST, nil) 25 | 26 | if !validationResult.IsValid || len(validationResult.Errors) > 0 { 27 | t.Fatalf("Unexpected error: %v", validationResult.Errors) 28 | } 29 | 30 | } 31 | 32 | func TestValidator_SupportsFullValidation_ValidatesQueries(t *testing.T) { 33 | 34 | expectValid(t, testutil.TestSchema, ` 35 | query { 36 | catOrDog { 37 | ... on Cat { 38 | furColor 39 | } 40 | ... on Dog { 41 | isHousetrained 42 | } 43 | } 44 | } 45 | `) 46 | } 47 | 48 | // NOTE: experimental 49 | func TestValidator_SupportsFullValidation_ValidatesUsingACustomTypeInfo(t *testing.T) { 50 | 51 | // This TypeInfo will never return a valid field. 52 | typeInfo := graphql.NewTypeInfo(&graphql.TypeInfoConfig{ 53 | Schema: testutil.TestSchema, 54 | FieldDefFn: func(schema *graphql.Schema, parentType graphql.Type, fieldAST *ast.Field) *graphql.FieldDefinition { 55 | return nil 56 | }, 57 | }) 58 | 59 | ast := testutil.TestParse(t, ` 60 | query { 61 | catOrDog { 62 | ... on Cat { 63 | furColor 64 | } 65 | ... on Dog { 66 | isHousetrained 67 | } 68 | } 69 | } 70 | `) 71 | 72 | errors := graphql.VisitUsingRules(testutil.TestSchema, typeInfo, ast, graphql.SpecifiedRules) 73 | 74 | expectedErrors := []gqlerrors.FormattedError{ 75 | { 76 | Message: `Cannot query field "catOrDog" on type "QueryRoot". Did you mean "catOrDog"?`, 77 | Locations: []location.SourceLocation{ 78 | {Line: 3, Column: 9}, 79 | }, 80 | }, 81 | { 82 | Message: `Cannot query field "furColor" on type "Cat". Did you mean "furColor"?`, 83 | Locations: []location.SourceLocation{ 84 | {Line: 5, Column: 13}, 85 | }, 86 | }, 87 | { 88 | Message: `Cannot query field "isHousetrained" on type "Dog". Did you mean "isHousetrained"?`, 89 | Locations: []location.SourceLocation{ 90 | {Line: 8, Column: 13}, 91 | }, 92 | }, 93 | } 94 | if !testutil.EqualFormattedErrors(expectedErrors, errors) { 95 | t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedErrors, errors)) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /values_test.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import "testing" 4 | 5 | func TestIsIterable(t *testing.T) { 6 | if !isIterable([]int{}) { 7 | t.Fatal("expected isIterable to return true for a slice, got false") 8 | } 9 | if !isIterable([]int{}) { 10 | t.Fatal("expected isIterable to return true for an array, got false") 11 | } 12 | if isIterable(1) { 13 | t.Fatal("expected isIterable to return false for an int, got true") 14 | } 15 | if isIterable(nil) { 16 | t.Fatal("expected isIterable to return false for nil, got true") 17 | } 18 | } 19 | --------------------------------------------------------------------------------