├── .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 | [](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 [](https://circleci.com/gh/graphql-go/graphql/tree/master) [](https://pkg.go.dev/github.com/graphql-go/graphql) [](https://coveralls.io/github/graphql-go/graphql?branch=master) [](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 |
25 |
26 |
27 |
28 |
Add task
29 |
30 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
54 |
55 |
56 |
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 |
--------------------------------------------------------------------------------