pax_global_header00006660000000000000000000000064143500273170014514gustar00rootroot0000000000000052 comment=3951ad47b72439d4488df8c952b5ecf240269def graphql-go-1.5.0/000077500000000000000000000000001435002731700135605ustar00rootroot00000000000000graphql-go-1.5.0/.github/000077500000000000000000000000001435002731700151205ustar00rootroot00000000000000graphql-go-1.5.0/.github/workflows/000077500000000000000000000000001435002731700171555ustar00rootroot00000000000000graphql-go-1.5.0/.github/workflows/codeql-analysis.yml000066400000000000000000000044361435002731700227770ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ master ] pull_request: # The branches below must be a subset of the branches above branches: [ master ] schedule: - cron: '16 17 * * 5' jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'go' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Learn more about CodeQL language support at https://git.io/codeql-language-support steps: - name: Checkout repository uses: actions/checkout@v2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v1 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 graphql-go-1.5.0/.gitignore000066400000000000000000000001541435002731700155500ustar00rootroot00000000000000/.idea /.vscode /internal/validation/testdata/graphql-js /internal/validation/testdata/node_modules /vendor graphql-go-1.5.0/.golangci.yml000066400000000000000000000010151435002731700161410ustar00rootroot00000000000000run: timeout: 5m linters-settings: gofmt: simplify: true govet: check-shadowing: true enable-all: true disable: - fieldalignment - deepequalerrors # remove later linters: disable-all: true enable: - deadcode - gofmt - gosimple - govet - ineffassign - exportloopref - structcheck - staticcheck - unconvert - unused - varcheck - misspell - goimports issues: exclude-rules: - linters: - unused path: "graphql_test.go"graphql-go-1.5.0/.semaphore/000077500000000000000000000000001435002731700156215ustar00rootroot00000000000000graphql-go-1.5.0/.semaphore/semaphore.yml000066400000000000000000000013621435002731700203310ustar00rootroot00000000000000version: v1.0 name: Go agent: machine: type: e1-standard-2 os_image: ubuntu2004 blocks: - name: Style Check task: jobs: - name: fmt commands: - sem-version go 1.17 - checkout - ./scripts/golangci_install.sh -b $(go env GOPATH)/bin v1.42.1 - export PATH=$(go env GOPATH)/bin:$PATH - golangci-lint run ./... - name: Test & Build task: prologue: commands: - sem-version go 1.17 - export PATH=$(go env GOPATH)/bin:$PATH - checkout - go version jobs: - name: Test commands: - go test ./... - name: Build commands: - go build -v . graphql-go-1.5.0/CHANGELOG.md000066400000000000000000000010041435002731700153640ustar00rootroot00000000000000CHANGELOG [v1.1.0](https://github.com/graph-gophers/graphql-go/releases/tag/v1.1.0) Release v1.1.0 * [FEATURE] Add types package #437 * [FEATURE] Expose `packer.Unmarshaler` as `decode.Unmarshaler` to the public #450 * [FEATURE] Add location fields to type definitions #454 * [FEATURE] `errors.Errorf` preserves original error similar to `fmt.Errorf` #456 * [BUGFIX] Fix duplicated __typename in response (fixes #369) #443 [v1.0.0](https://github.com/graph-gophers/graphql-go/releases/tag/v1.0.0) Initial release graphql-go-1.5.0/CONTRIBUTING.md000066400000000000000000000011561435002731700160140ustar00rootroot00000000000000## Contributing - With issues: - Use the search tool before opening a new issue. - Please provide source code and commit sha if you found a bug. - Review existing issues and provide feedback or react to them. - With pull requests: - Open your pull request against `master` - Your pull request should have no more than two commits, if not you should squash them. - It should pass all tests in the available continuous integrations systems such as TravisCI. - You should add/modify tests to cover your proposed code changes. - If your pull request contains a new feature, please document it on the README.graphql-go-1.5.0/LICENSE000066400000000000000000000024171435002731700145710ustar00rootroot00000000000000Copyright (c) 2016 Richard Musiol. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. graphql-go-1.5.0/README.md000066400000000000000000000166101435002731700150430ustar00rootroot00000000000000# graphql-go [![Sourcegraph](https://sourcegraph.com/github.com/graph-gophers/graphql-go/-/badge.svg)](https://sourcegraph.com/github.com/graph-gophers/graphql-go?badge) [![Build Status](https://graph-gophers.semaphoreci.com/badges/graphql-go/branches/master.svg?style=shields)](https://graph-gophers.semaphoreci.com/projects/graphql-go) [![GoDoc](https://godoc.org/github.com/graph-gophers/graphql-go?status.svg)](https://godoc.org/github.com/graph-gophers/graphql-go)

The goal of this project is to provide full support of the [GraphQL draft specification](https://facebook.github.io/graphql/draft) with a set of idiomatic, easy to use Go packages. While still under heavy development (`internal` APIs are almost certainly subject to change), this library is safe for production use. ## Features - minimal API - support for `context.Context` - support for the `OpenTelemetry` and `OpenTracing` standards - schema type-checking against resolvers - resolvers are matched to the schema based on method sets (can resolve a GraphQL schema with a Go interface or Go struct). - handles panics in resolvers - parallel execution of resolvers - subscriptions - [sample WS transport](https://github.com/graph-gophers/graphql-transport-ws) ## Roadmap We're trying out the GitHub Project feature to manage `graphql-go`'s [development roadmap](https://github.com/graph-gophers/graphql-go/projects/1). Feedback is welcome and appreciated. ## (Some) Documentation ### Getting started In order to run a simple GraphQL server locally create a `main.go` file with the following content: ```go package main import ( "log" "net/http" graphql "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/relay" ) type query struct{} func (_ *query) Hello() string { return "Hello, world!" } func main() { s := ` type Query { hello: String! } ` schema := graphql.MustParseSchema(s, &query{}) http.Handle("/query", &relay.Handler{Schema: schema}) log.Fatal(http.ListenAndServe(":8080", nil)) } ``` Then run the file with `go run main.go`. To test: ```sh curl -XPOST -d '{"query": "{ hello }"}' localhost:8080/query ``` For more realistic usecases check our [examples section](https://github.com/graph-gophers/graphql-go/wiki/Examples). ### Resolvers A resolver must have one method or field for each field of the GraphQL type it resolves. The method or field name has to be [exported](https://golang.org/ref/spec#Exported_identifiers) and match the schema's field's name in a non-case-sensitive way. You can use struct fields as resolvers by using `SchemaOpt: UseFieldResolvers()`. For example, ``` opts := []graphql.SchemaOpt{graphql.UseFieldResolvers()} schema := graphql.MustParseSchema(s, &query{}, opts...) ``` When using `UseFieldResolvers` schema option, a struct field will be used *only* when: - there is no method for a struct field - a struct field does not implement an interface method - a struct field does not have arguments The method has up to two arguments: - Optional `context.Context` argument. - Mandatory `*struct { ... }` argument if the corresponding GraphQL field has arguments. The names of the struct fields have to be [exported](https://golang.org/ref/spec#Exported_identifiers) and have to match the names of the GraphQL arguments in a non-case-sensitive way. The method has up to two results: - The GraphQL field's value as determined by the resolver. - Optional `error` result. Example for a simple resolver method: ```go func (r *helloWorldResolver) Hello() string { return "Hello world!" } ``` The following signature is also allowed: ```go func (r *helloWorldResolver) Hello(ctx context.Context) (string, error) { return "Hello world!", nil } ``` ### Schema Options - `UseStringDescriptions()` enables the usage of double quoted and triple quoted. When this is not enabled, comments are parsed as descriptions instead. - `UseFieldResolvers()` specifies whether to use struct field resolvers. - `MaxDepth(n int)` specifies the maximum field nesting depth in a query. The default is 0 which disables max depth checking. - `MaxParallelism(n int)` specifies the maximum number of resolvers per request allowed to run in parallel. The default is 10. - `Tracer(tracer trace.Tracer)` is used to trace queries and fields. It defaults to `noop.Tracer`. - `Logger(logger log.Logger)` is used to log panics during query execution. It defaults to `exec.DefaultLogger`. - `PanicHandler(panicHandler errors.PanicHandler)` is used to transform panics into errors during query execution. It defaults to `errors.DefaultPanicHandler`. - `DisableIntrospection()` disables introspection queries. ### Custom Errors Errors returned by resolvers can include custom extensions by implementing the `ResolverError` interface: ```go type ResolverError interface { error Extensions() map[string]interface{} } ``` Example of a simple custom error: ```go type droidNotFoundError struct { Code string `json:"code"` Message string `json:"message"` } func (e droidNotFoundError) Error() string { return fmt.Sprintf("error [%s]: %s", e.Code, e.Message) } func (e droidNotFoundError) Extensions() map[string]interface{} { return map[string]interface{}{ "code": e.Code, "message": e.Message, } } ``` Which could produce a GraphQL error such as: ```go { "errors": [ { "message": "error [NotFound]: This is not the droid you are looking for", "path": [ "droid" ], "extensions": { "code": "NotFound", "message": "This is not the droid you are looking for" } } ], "data": null } ``` ### Tracing By default the library uses `noop.Tracer`. If you want to change that you can use the OpenTelemetry or the OpenTracing implementations, respectively: ```go // OpenTelemetry tracer package main import ( "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/example/starwars" otelgraphql "github.com/graph-gophers/graphql-go/trace/otel" "github.com/graph-gophers/graphql-go/trace/tracer" ) // ... _, err := graphql.ParseSchema(starwars.Schema, nil, graphql.Tracer(otelgraphql.DefaultTracer())) // ... ``` Alternatively you can pass an existing trace.Tracer instance: ```go tr := otel.Tracer("example") _, err = graphql.ParseSchema(starwars.Schema, nil, graphql.Tracer(&otelgraphql.Tracer{Tracer: tr})) ``` ```go // OpenTracing tracer package main import ( "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/example/starwars" "github.com/graph-gophers/graphql-go/trace/opentracing" "github.com/graph-gophers/graphql-go/trace/tracer" ) // ... _, err := graphql.ParseSchema(starwars.Schema, nil, graphql.Tracer(opentracing.Tracer{})) // ... ``` If you need to implement a custom tracer the library would accept any tracer which implements the interface below: ```go type Tracer interface { TraceQuery(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, varTypes map[string]*introspection.Type) (context.Context, func([]*errors.QueryError)) TraceField(ctx context.Context, label, typeName, fieldName string, trivial bool, args map[string]interface{}) (context.Context, func(*errors.QueryError)) TraceValidation(context.Context) func([]*errors.QueryError) } ``` ### [Examples](https://github.com/graph-gophers/graphql-go/wiki/Examples) graphql-go-1.5.0/SECURITY.md000066400000000000000000000013611435002731700153520ustar00rootroot00000000000000# Security Policy ## Supported Versions We always try to maintain the library secure and suggest our users to upgrade to the latest stable version. We realize that sometimes this is not possible. | Version | Supported | | ------- | ------------------ | | 1.x | :white_check_mark: | | < 1.0 | :x: | ## MaxDepth If you are using the `graphql.MaxDepth` schema option, make sure that you upgrade to version v1.3.0 or higher due to a bug causing security vulnerability in earlier versions. ## Reporting a Vulnerability If you find a security vulnerability with this library, please, DO NOT submit a pull request right away. Please, report the issue to @pavelnikolov and/or @tony in the Gophers Slack in a private message. graphql-go-1.5.0/decode/000077500000000000000000000000001435002731700150035ustar00rootroot00000000000000graphql-go-1.5.0/decode/decode.go000066400000000000000000000007571435002731700165660ustar00rootroot00000000000000package decode // Unmarshaler defines the api of Go types mapped to custom GraphQL scalar types type Unmarshaler interface { // ImplementsGraphQLType maps the implementing custom Go type // to the GraphQL scalar type in the schema. ImplementsGraphQLType(name string) bool // UnmarshalGraphQL is the custom unmarshaler for the implementing type // // This function will be called whenever you use the // custom GraphQL scalar type as an input UnmarshalGraphQL(input interface{}) error } graphql-go-1.5.0/docs/000077500000000000000000000000001435002731700145105ustar00rootroot00000000000000graphql-go-1.5.0/docs/img/000077500000000000000000000000001435002731700152645ustar00rootroot00000000000000graphql-go-1.5.0/docs/img/logo.png000066400000000000000000001721771435002731700167510ustar00rootroot00000000000000PNG  IHDR,ʽsRGB pHYs  =iTXtXML:com.adobe.xmp 2018:04:05 21:04:08 2018-03-28T15:56:49-04:00 2018-04-04T21:59:28-04:00 Pixelmator 3.7 adobe:docid:photoshop:5563f4c0-3874-11e8-a9f6-8d6dce19aad7 xmp.did:7c134ef2-c9d9-a04a-8074-010bf2d714fe xmp.iid:3a473a35-1b61-7548-b8c1-d37321342166 xmp.iid:2cea00a6-7265-0d42-8fa8-49801ffc3443 Adobe Photoshop CC 2017 (Windows) 2018-03-28T15:56:49-04:00 xmp.iid:7c134ef2-c9d9-a04a-8074-010bf2d714fe created Adobe Photoshop CC 2017 (Windows) / 2018-03-28T16:01:58-04:00 xmp.iid:72410e7c-2c06-c54b-aa85-be4234c69957 saved Adobe Photoshop CC 2017 (Windows) / 2018-04-04T21:59:28-04:00 xmp.iid:3a473a35-1b61-7548-b8c1-d37321342166 saved converted from application/vnd.adobe.photoshop to image/png derived converted from application/vnd.adobe.photoshop to image/png Adobe Photoshop CC 2017 (Windows) / 2018-04-04T21:59:28-04:00 xmp.iid:2cea00a6-7265-0d42-8fa8-49801ffc3443 saved adobe:docid:photoshop:ea9698db-3874-11e8-a9f6-8d6dce19aad7 xmp.did:7c134ef2-c9d9-a04a-8074-010bf2d714fe 300 229 1 image/png 72 5 2 1 72 sRGB IEC61966-2.1 adobe:docid:photoshop:a6ba20b8-337c-11e8-9a61-a538f610f253 xmp.did:ac00a4cc-1034-4276-87d5-15fd99ebc41b 3 (Q@IDATx]ˑK3 9Iߏb΂F>bYTD%$|䜹zoٙݻ#+=vBOOOOOuU*?2B۷4QQQ#q?)++vhhh󰰰ի~ᇩ?ca4Ҋ@zys1Ȥի(55.ϯƅOO>>MS<((ʕ+ܽ{w;^b*zJ_~yH0`>p̙^{R3æC0.%jXEO[=pwƬك>Xo>8R j{h՘_ař *IF9@Trss@P+;J3,HʖI$dO/:p),(/c$su W_SO=%'N6ZRjWjkO}ODy76% lOneIa]]z^q/$gMbb,c[d͚52?0+ IP{b>edڭ$V@j%999N7 (otBI`~+`n~>ݜW 2DO,Z0MiM2NmǶqN*Tn~Iz4̊F%pXH7Uĉk$s 14e¬| T~Ae$''ndUzb+h 8ۡ@NE\@=w=u;QUAزӧ|B'Y]&٫NO0̊@=?.YK+U2X|L:)IzAىL6ƌի m=p@2ywH=/)-ȯ*o+sw$JK$oYWCdZI9ie.8"YR#0FFUjRLHmuUV6}BzR> V"AȬXiӦ.yG$2j`~ nedu1NOI)arr(J[N.|F8[ M+"D͛72{}t5>_lWdݺu4pjǯ9@<}eK~2i5[>HmyLXpaH14DF8)⏡v}K/=?dᘣ]\W^`W셿f.k]\ Uԟ@?=nR\Uy >$w}W~uEcr|"ʹ}e7dF֓R**6~p;Rj׮ jf0^leq<Uؚ$(e}GĶԨQDd=.+ pwƌ ^5oNy}8{o$ "py}[=PΟڹ 6,10k]aN6oެlWYr$T2ke77@%ow[֙oR ?PH &M.`.I&_B/BJ, nݺomݺuy@ :u(ޜDG>{xxo3a,[AfT0,{SNǏo€!HT!1B+U:mf;SWElB ٛu~O~dZl;?XƱ8 ۰aÓ˗/7U>5io 0W2 scf;*i{ Lc\{[N?~23~ etZ1CQ s $sC%T.=*q`X5_WTl**њ!QlLcԾPقqpYԼh B\.?`x `-n^QozBuf{r @MRXѳH4 yIcL,=>FŸcbدgֵۡnXX&MHFԻ;iiiRkl۶mra˺(ׁ!齄UzX}"ΘUϟ K`v/+~lna m(]+n/"s,ĹMUN1@2D"A}P;mKHь ޽{ +YYj)v?S{!3#j"=I5i=(=,]f6*K  2d9T6@Q㍋c=䫤e\JZ[̕qZn׿%;v؛<$..N,X |]KFEx@ڶmѵZa`)S\B>1Q:ݩ`XE@gqY]Ú}n0,Fvfo _K=?ʃ޽{m˖-J1;%i\ ?1Hu4jDAF:"? xvZQKon.agi2&NR,S뮻NF6A60C#/!6KrL* ^phBSGϞ(J8qFp"T}K2~/gdH(j۾`j)!`W4p#Rmlz̚5K;d"bLl֭[EOVl(zgq=*P!AA!$;Wܭ J.F?+.9EƀhQAge:aR j!28Z]=ڮ%>|^`c8NDax0}l2.NDTSOSdӏkhT[|6>i6;AqBE?$K W\Ut-sn~9Rexzx T윕8o#bv~LF0&zl{1S3uwT4o<ˠ~ȩJ~\ /t6EF )!*?GH Cx Ծs LɆv+ӾZ!m1 o@c9` z_"Y{w9]?i$yg#iZZvՅ}~Ͽ~%^^,7md? 敌 wǑC%eCA8"뀱%H~G50(7 rVO"M6X U|ȱ_{n =LGveaCDzkP{^G3o`bd#zgLE`}H(q$Z!E\A9~ >\[y ݾDCX(m1V~ y}Q!~q\q_N;<"DݻXIL Rp[KJz [qq,JYCxm(Mc26ꁿ6`RtF4()K{X:~< GϹ&0-?s<4ڋמG_BP0pBˬO`/TE33Wz))[胸lF#=֒i!l۬w W6x7>6<2~? /e4}:j>l5yaLTC4!Ż*6IX]w:Kg-;kuVC|ǹ¼e"(t`V ս o feCòD&6m4u'_:*m23ׄe#!λq$BqtT4J|QoѲ?QY'_ً$/?c?tf])Ǐ9VEp. -W1wgR?qq" f|gOh808B¹vT0zlGy5Tϱ0,VFI`vmV"zfWbg:IiIQ`kt.  sF?ڵkeCÀ r$2`R/D \qyGcbRk(1 CmZ@5<l!#y- Ĝ%7 6KMWZ锷6!cQ2R?/i3* k'.?zKV7`ݴLZ@`cr ZLdg$`\tRleX 3|eZE|8=]0*£Gt~wf V(_ďK$ t[rU I!وQfY2fߛ2tX1zdzC3q\_JF51=j쿈K\Xz HNie׀Wi{vJu0;vSY)ܙ*/(L|5yFa`F1媫rh+AI gjGѡdw{`T]95b?@hL$݌cil[흥/s7e_Rɝ/ryX"Ұ3{STah {D~ D{cм\b@ %YƯR{r$紽 VN&5i;ŢGn`5i ɪUh} E ,SYSRE~E |HPHd;rޕ:s;%y>\,< C|A0vC D;5k/IXfvHϋ.Q5|+կvӬ{8Kr3|'/\X|(fb79 |7BguO34lP@Deq<.F5nx:o92+!=p#|6P-F2Vewo_L=O\dCvV[Udfa0X4IHWR@96kCc͐sIBmaٶr X>.[r9e{?ù>yon&y%!i *'ڒ%XBkz9R0M) aR*̫EaR6S3%}YYIo|uzL:U܎L= $E  gZH$1NFZ'7J9:'9~-z=OpC޶a=:fK ?V^c]$y[3$)u9(СCM0np֗壏>௖jIud@N#1=BäjK6v[DN///#pRMe|˵n?b%%lLfa*qcLiS?1}eܬY$"QF "j؇ob E*mfD:wTw)쏡y` % 0\/l]+a pŊnxxL# 1K _iaX4:""H6%_}}l1x f{@kžli?qBffT/bpB.{RmEl(èWˤ_'f^)n5Y. m4Βm>7wa{tlxBbY gyAaՂvZxꉕoEw3N$3RrMc' [J!c1lX}xe#ž7l9Pʚ c634l]3W={W+du70H};)| b֊&<.^ x5O/^\Em2+y_j%KmSIQ*"{F%/O^/|#ikKKs[2u֞:T哸9e҆"f4֌7{^P+sVIg_6:]1a\qQmi2j88"`Xߧ+7H'0LӮGʼ35{gڰ0{ \ЁEjFys2 >k@a SE\?BSYf̊xd*3% CB\Kf@An< f` ª `u >Ҕ-?Uc}`QK|ljL!YR:!q W&žWze'EcAJ\3fp2$EfB|npq^m6 vNc}>%1Fth/~%;^|m7Ckc|}wlJv1,@Z"`Vmk:R;:SE*5M.jFb_|VrUmI*uPf7.?jra8=Y -PU[[Y:Wj!u $"3ʆpR~:!lIes>uN_qAJHAئWP\Cf`Tɇԙdo [$ƖTyYJ:7KJU_wMy䒘[,gvfE5lLaJ7?ZO'@I H"S'HX&NaU|Su4j ?_m2r'd:i qq.ܜPtef0r ؓb[%ةڻVX+q^9PN-ᄦjKI2xݍ7ި̔{Id}7tky؏Z_gjH Z%Pe!DN$oآT^ 0r`Y yI*L=;$YhU;"Sj\KDaMz-&pZ'>m/#hUs + U0O)k+#ł߉\15ʩ ewI.cЇyxjJq`2U/O!sy`KXIW8 `O gw'T'+,EBɩ9lpwwh57Jĝ+0RZB*|Xޡ}O/D< 0xp/2[ݮVyZ}?ե]-wH&*PxǗ_m(fWX$P\ b+rJ0CYWwU0Īd2'$ǠDw$VM叙`ׄ KڊYэX+mE0crLa,#egM'ɪԲ5U%aW a_RڱuMބ] IG:k$kߒa T3XFwNͥZ4u>}VIrWANbC*$*?F*G`i9Llvst*?R*hI~|_y8_;/7{TuE#M%£~!ĬU׮]U];%nb)Hs04H}F༥nB7A(_6Fe`nK[WNK|I_.Kvc#+}CFD+[g13^kДvLP6,}{GVǼ C[>jkFڼrpIЮhZa1CjՑ}*5(B5m~YԷ;mY:Q̭-O+ P M>>MEňFغK@&CUIU xB͒&rd0P FtVʘҳRTJ&0)Ww]]}P{9(/X@< { DgFQR>6Sޟ=_(;FpIfX^1-reX fֻwoGbϯa%DKx4w3nWGnIQ\SA s%l8&>CPINhR RIٌBz[*">!p& OpjOU?2)C擑.JE &up-bTҎa劘n'4f~dztADYK^Lc+cَMɰh_-/3\}8_^tu.~IQvb&(Yеm^%>sS?r\ >cf81O?cLt< >Źci=;~IJBlu} FP3}$Qw qVH a$GBzWʩ ,ŔTȜY=Pw߉k:4QnXxplR;ʰ 0aI/:ٱ$py+|ši/Q?<,)/kyO!~XoRmyK5e;hOG{OKPC~֙ _0*AS8171oȂf`XyWJ, ;b;x`~}Fc//qTQwcj0 (ۦGK*76QE \H,&W(OYf)J!$%Hld]!qܖ6C - !L%ѿ7o Ucsկlcޓp@WF[EsmlzMYSlحcs2a vUr9a Dw i׸&ȊBCx9^=;;HWgfU;Q+-/s"9Ȃ6bRZ=]›Q]d{j*C7U3)&^C`I۰J#)ʚ8!c|M}w̩vޜŮĥ2vX0awH|o,yCR$'wR3,λ9"Dlnd@%6KUž۱c`&P:̞@pܲ $for5wL~XjVu?0{2 [f Gd(eJ-_(p4vuUڋ"WȨV#ڊ gc2)^!0oeq&"j7 _>p+bm=a, ff6mӆMhذZH"~`Gr 9C0rmǑL":wNP޸Ҷmۯ"4fZfUoٶUn leE+`Kϐo];.>|ɈSfEjuy~AHCB ᑃh5/Gf!ud cƄ!PݣA:%.}6!tzzC@oѹ[Kˮz >el& %ׅ<R܉þjD2`)0:-#1eA)(:܁+m;R{ڵKa9bdp{3IXVVxC@H{[9~ORj0k{2۾P7>8F-tY^T\+(R{$*-҄+D^gltK2o!+$s)X9]h >D!xx!+E<0!xÙ> &DV nI*޼> ܕ*;0+E]dg y 6^BBh0hAAGȔ=AcXȑR]^m䱴㸢$F\%:eI1m4̓gal ;qq G0FWYs0\`Blumߗ L@di ^(O^Wˣ@HaM?;=#{BWҶI EgՁ$y YW<|R<׬k3YCHiчQroRtjР:=AE G}LAlnSeEvsN;"+m +tr vn$=`|R=+`Zνˤ`)YIf%{ Fȥ$^@fJޫWA9C,{Ozv?:ttԗugQ&b>i@`hG4i~m$Lk8iD\ ޘ/⼖`&M0"[J(A?^#6&A̖l̊za= hy˰o3P Yd>թ_ vZxΙQgH¸RH~^kUJ--vï/WۘT} C?D,,8YnQJŕV%`(IǤ5' hN,K٪Ԋm^!0VWL짤)h,"Uf5WcR|AA!1a Y=)?>KKQ`_}!lg&0֕5%,bhJː DJ󶍋. +k.w-/j-_3&ͶGȼ:/hׯ /B.c6==SIUTIH vjU\(w ԷUVաOmx8~U;)|CCz*Ys-(Ve R_xz}K!㿸UFv&.A/^HJe/[GEEC&mQ*ydA!PdIۡ a+0D`]!Aw?`t (9TDDKL4bGKP(y(HIw8M awY9Er#h=1 Bȓ=oːvnm~ noUBT 阭_U.H۳S۷V2L ƿ] zIMN\9'Ƒzݡ]D2|̊`?4#jK`b~]?V. L 0tt]1 ۈO;LFYU0gAY{pKPBҤ5 G%?GҚ`00Q@amEl4hi0h,aR6 Rc"!puN!Sher }תXzfv+&hcJݽSr2%AAJ4R?O.3\m!x]^VGhk&SNx qތJJn~f_n̤"qj~NќGf>aDш<@7oX挽p4^Mp4vU˦ a֍Mmٽ{w*$up4~:TK}ۊ!p2ĥnfL\t - IH?>XðqC`nqE990s!@[LJC ԟpaj{ѝgԗ񍌆7`ξجy/@1)UcR{BYIbm%sIAY2)qx6mTl;dp}Wa&/jqLV}~9!}ZooP5֯ESwƌI\^gם3sԂeq2}t1݉'1Q0opȦD|0%@IDAT+_-rZ^cX>S _T~R;:$R!Yqט(&0n3"SƵP0q||g(K{Y2,!rcꥇRƸւ/ &H[C";2tPVH5pb -bZ_=$Me<lھWž)c8PqgmPk73tDpSdrTxYsо}{Ue> V MEVǢCY, 1oVjp3L}U]JјD?0s)AlDת+A:Ow7x8dVIS|O*I@HHE'}P`FcD,uN|dV!i~=ֶ)eћc?aڠ//sy 8O4F:DE7'R=/hp69 Cҵh}X@RIIIs lE5qubP'/Kt# xŊ Ez5ra%3 A*HQXq2/ֱ܂/;MTR A|0nѾ bU yƗ:@At q.E$ռʈ TH2`I +}ء؋33dt8;r%v-ĆѾ+bX,ج7!GLn^fZ ߛqC8>iAǀium@d)0Bv_|j/t|L͘ 2bR|dL4DᝄbRRxn;-%abIjUw= @SsbWdXǑ&f=WI~Ov# I4D|"dضp-U*aE>7H喷X18#Y6?Q*8U>aګo82Ғ_O fɘs|՟`Lۆ5p Um8)9h!lH߯=V[|ϕ>ӡkIR0VX!U=|!1|sJ OˣG~1/3J֞s& I@SUB@$P>p"m+Kp_.3_3m`VL/^`w;Qݧ^P5 TDC6}"0m jmf/T T=ġ"n%Pbq5%* +EUfL-[9Qw:ҶaHF;0,O PKHs0vJ XY?oTsb1CUcKU?/HVkjՒ"?b"("|bβ:!0)C7 P&e(KAGqŊ+QފJج=|H@``f&44D٥bb]]obJφfY&ˁ SRb*hi0pbX @Q(j*:#81z ?Q#VD Dcǎ9U`V1c0$pۚ7g˿f$`w6B?*F=GV>T@9s%8;&S}l( ?^"A-Z5 ёV_[kTw= S qyrQhc]ү3N8C+  Œw<|\secqly{r@ x5VZAmA1)Y&MA1 IĜlVUy8EӶmQ]~R. X9w -!Y9sR#ghq(y2#/pqH+dI'dD{rV%b[{c&Sdo3Ku;0  _rձ1ÂTg8(##Cvf#Q]f?%_r-eyTdLRcՠ-Ny`eR%sN#ﲔmPG%;C Dh}ӿ^C N?gTcRG&@,Jug4Iܾͩ \(w7 D'uCeEUU:$:ӿ N0 c9[ \ KJtV664[{x$s_|P5YM |"gL5=oG 7XJWNK`P!N\ z,5OZv%X ^f^2ǬTI/'e`VbB:8E(."JcT` Fň*fR' =٘*zlN8 6A+OWTmr294-paqȊw.C Q)kxܓ}Du*RnƌU OE煞=)]Nb8iz=bX@rC1LOfJU3Eũ%h'ZZ>ŁL!HަM]Ve}ZYnAſz-y.MECà \An2|v8s?q^wPweʥ6'5Pyj}hew 4ĸ`K`LˬkJOIk >uhYf|GUOC;wt6Y^i~#4 K[תjSO=eKIzm2Àheϗ_~ ! Cu^!^P1ni}++fm%MEEJGD\/H?=%H1z1 fO5qÇ-' _HDsY^Z= *?ym9yeY wYTJ=ك-lI-zO{%æ(˧:NP"C8PtrJ(I!i+!6pSNKcR%9Ui 9-MtUlLApfQw8^mLJ' V)A2m]dX@f+vNsU2,b^z%{WKz9ݓ0LKK1c/iUwͰBS@ nA/gS$ {g; 9e4 ڙUXޅ!=T< ş?%)`Y.|xƨ' 4s1\1۝n<3;&.rzA=s+Vmej4wBj/*JȬ܍֩]J#}I p"ٿ5]!a땹:h1gU)_i 8H- ~͛q 7ݠsv0JX5R!vsPu!g S|Ű I֦$JSp6& B)%/E&Ul*bKTYITt:4-V\#۞igX2鄓jqUٹYP GTf43S1]}U4NJ!1OUoy,:: .ǘ7#m-W~-]4I.8&tU{;fXXiҞ$^_>ϝFg  $ghqE! APA.&24h`ٞ0U3y!@"5;TXftoyreX|Qg)~QHLU}Q;J:UQuHSuѰM.HG #>6Ѷܓr| 'wԊaq暒cY԰aC_z-SVmŸj2k?^|.r7d5 VRV5mOJ3Tsc>â=Az˶U>z' ս!h eVc=@xU_x*MFXSUҧ6i~ipOs!T ǔ H:!:1' T}$ʁarWXv#pa02SZFuu($jYBgd3aZ*,`5KJMbX\ ?&J(r+}q~rOhP!WBE)ү fO!$ `ynD7>}.çB"|6V!e8x97n&M| D(3xçcUH}K;/vb$fu8m )!J|jȲ7K f=>HYM/J.>mƍ)_!HQʺS)-Ы2Ȭ NhѾxԞWe]Rkڊ@x5=uQ%+HaܰMie>ՠQ]<#TxaMP>YM!9&mvvBg02Y ƵwbD )5ڴTM-oo)eC,w ΋IF46V4~IVdVÆ 3ZlZ}vDyS:b!G8QD8ʇF8[Ǭ[]${)iTi9tfP[V!WJXf10~ZНpa<52,yX1~L?1K+p 0(5SŰn8yg Bd!DqGw qه,&0Fݛx`ʒt% a8'D [+S0+M4D'f0ս~P_KRq*SAUåu7Z6Ll~, NŰ|,e+v#_!21s~Ĩ\GRpvLxgh՛8KN: ipbE)<| fi !!ή |<4L%"Yψ>eR K\tkW\QΙ8QԾ!̌W e [ta Z׳bJT"#G*)Fßxe9@׸VQxf0N$ve O3(<;mt VI' O@.F7;OպzR9wpe[OgHL)^=:Lz#ԑ@+NUr_Kț3b/IKPlJu0/eYKm lȗPΌbjdwt͕WHޗʡ/?0XNrq>D֧ﲌ(}U@)r.Sn@L*Y 蒻2Th7ޥԈ<7K8Gu&JҞ]~~Ru{;HQr[Dk/kڪ5b[e e͒к a!@R3uZTsN jIê\2=AP Ká 3v;gѲzSW!Ӌ85W%-V4^X>}KhprpU7 qkՃz0[rr\^uslI"F`Xd-b`V)9oÂ;tv :D"kV6$~~zvӧO%o,`cVTc8E˴'ȢF]8\IA_,Uwi-knQRsdw۔ qY6`opIWT j+Ϭf}2)y{=uΩ6 >mbrQ|tT8hqZv,oH{.;1N?9dvfv8"aM[xOͫ-2RruDתrnߤ,|&͍|avt8a2ABH9奦ȑzVd$nQʺ*_BTP84߃!2O>Y~hc2e>)}w?(/oF 3V~7Gv?'-͌ |``Y8F%a@{.>N=1/7=wL:#.MZr gЗ6խ$xKR6I`m% Dt0Uq/ gcuz1'!N֏M+B ~~s02mP  vep`Xu?=:&bˑԸqcYjRC`@U`B#][qu6LD mm֥B IOo>|.2Z@^QlOٰlE5fjZƚGXd7<@j_ySJ^)Q .a33R;0N-k٘>83Gl&+uxY`%x<݇8l6Whfv5.ũ Gv |F(WJ͐6G_Խ6”./Nyx61J=J>*r%9ڵn:P>nT p9)Ir%/a;hȍKU$ B'];$+>QFE00K`thr:Ez..pd O{W4{ZұQAhL8>!]>aV x<`EΙ| kɪn&mނP}oز`VMZZ©Ś!-N|Hg0}'JOC~~AN@5 Ĉ\cjx*꠿%58|l4 ?󡞝z%ɜ6"(8q LO(7s?TDh'|TUgdK;'c@)F ]mH柌mdݣcL%'*3*|aB'ҝ$@b·dr* S'a|ǭLwYornK˔u*Goe%79>C.peː$|"qiE{%k׮_~E-X m6N3oڠ\D4wn*=<(m\F~I6 GD2nu1Y{NKIZvG(>f11#+,6VtAQxW TD1ETPNTBE@z`c6֝gϞ'Oׇq^)cƝ?#jŅ*Z7{̛7ϻ, G-O ptgl6 b}!72oOſjΗn|B^\͌AvJ[&TP<čk=(6#B5?j|PZ![2 O4τ 9MQI;߳0jk25g"9$Q*½x)Aj&.P"="jkIMl45644I%bB,Qp=-cȌuFŢgC\{J8 8&oGYF۩̘1CƎ+x! (I)2jòj%vm~/_eP $c9V15rZ? 2xu*߲dII=JȰ]|xVL&;Sjvn*poj[D( 2&n2Sϙ5C*zQ?*y]_Hu\ e|/kD@2BR&0@?^htl$1i #v6rCaLkIÀ}.?Oȁ(~IB(zxh aBgWt7N  `v.u,`V "\l!@!xlAv&*>>7)ީ*v,)3Qn9HCo'ghGB)Xt$1Qٵv{M$kLx֌ܪ,Id fLZ' egMA+usCn9e'Y?㇂aA.D?~H9$Ұ7}T(bި+f%p 򵫼 ;aS$kFk#3ȸu@n&&l?&KW8-f㭡QI۹0}^QLȸ}ѶHpcŠ]ܕA xzFݵRD{9&׎D[VV>?fF]1,SdY1^G 9oJZʖ,uGc\X⫳8ģ>*4¬NfM吲ˠmp#)MhhKvT_ymd܇,f="YFr  z >$Ȧ0TM~PBfуH)TJJ1l`U*c;J\!eOO#$ҿ~DU46JEzbyZzMvN5PǚS{v7=92seH\ jDxxW mPC"''g0_r'@C 8oG3T۪뇗ġv8C_q.w=D#;i6%?| *mwmy/lz[{f F ':Ckщ*!ۆd8+ oy X`9~4 |2 '$b76,j1ҋ $8 6w`1L1r`j:ԉ`BO=hKSy;/V$lnŰh܂-aTN7J0"r~N0cݩ'OU9ro'RP"W{|!tMŊR c|HFzy98+/HYw)%A^ ۢn; }0Λ#erb[uQL^sU͙ef͚%m1qԨQ|*,*Wi-'E^ yv1ip9be$vv0,ݝaDz(\L9rG"5 i_JY ʖ U\&87|vJaU //)a rbC.ឬќ{gT?AJxǨH|1[6K}I߿ [w\'78;CG+5p'"~D0Ly|Ř2) v^i[ UxΝZj{'?\tE2b2dʬ.G`!׉d!yaPgU5׮R 9\%D]m۶?⽋";JsQ e8$ԳHa#%4WSFW_̅^Dm/~*o~^1J)Rں92f!z:ۤO: z}}q1*fkeHdv"HY84p&hMc0iII/Aٲu{]vDxp0x}H4 IsPcG!z}iϺi@՚I>Zٵe&wmO~XޝA<^!>9qIJ}!>+ve4Vt"\ f <~KzbK^xd{` qMmV N_<0e=v:8oQ Ӳ207vس6;/g^SV3I4/I?#14#>cRˬCv'"3+'u<̩*ҕqi ԃȰ?D=f\8}T0Q\n\I5d}KjQ*קnX$5Q'9T WVY?ä!xϖ/нgBIM\Ƥ0ڣgÁz5ߑbZJ;)MaC] qݿQmasyUwaqΜ9aVoG:Wm^+ u((z=VLUvVmTLq"AV@y;EHb >]uJ#,p>,)G lm%P 7o*A G@c&+tsTlS9Iρ٦R45= nV wuo#C%*k'f]uqOcy`mY6ؖa0o1Xp;< VcAhKn Wb;Ȱ '¾} ˆ7=+op4mZF;rDQ_I QL37LY4"{BP@NY殒t")ysd.7Fut/O⵾ MezTr [۰1QR^+%VKqW&XμkoLSǟIwx^-b8Xܦ0&ߑ~#úk20PVk˜ΣJgۆ<#B0>$q%*cNu\Q:\;$_2{ʤ3(0Tkd|KlX'\-͋L+//NւҘ3kٚ̓j4QR=oݽD?9EIitx2A>ZJ[y{Uz̟?_;k`;Y]%8[5D0 m Q0Ae̓wE1; XY;޾}Եg 3=!N0A^l^vr' }m"]r{[D:YI[KgTbVfLJJ7וapO\~yTfǺ3ˡ7e+amN[Oq}Khi4n7.}6'x׵i ´ݺ ,4B1^Gx-; w:ywACJuV]џ#p8K/eEՒO(`-@jt7F )\~೓hhH]WEҵl(07Wx YpHlkAly 05ti RwEe&7c/Oġ`cu>wYRVVj>Ъ4;UM SZLRfx!^¼O[VZ/qVg%(b!I= -wWWI /R 4W.c=~woT]o[('\+֭45UAUB-ں~ %,^d Ԍ-h65Ixa=Iv42~􆉲 ^`<#=A,xy⭎q, |ϱ?mrTJs%l7sga[ۖ]\= /_IEr-]ZпαDw{/G<﫷dT)8H~~qƉRO0KyڶdV'jVE)-ktž|2,Y>C-dTU&Af 9 _F蘍5 v.=~cjrUq`zk ! 쁸fX+x*A$ℱ@u[2# {l08,=qB"2zb TkWtσpe%ޣDT5t^G$C#kά'bǎ_5HUVYX4GIS01z ~fG`OB H܏9 oS̱GdTdn7gVDlL4ΤtC4ˆJB8UGv& 5YeF"qS~ąMwc׼l\)}[me8p }?dZen]Ȇ 0m~(:bH'Đ"S:I,1«J) Q`d>7j8LKCr _a[+_(3-l`0a9O;/,ndbYqM2wR[$zRcVO4 N-(lWxf%t"|gІv_cC2~@u@#>XQG{2X! AN&=t~m@R˄35ybH'ƞsL_%㮑UwC'ý{O2: ڧ2, -.W5_F\~1Sq6 #FQ|0#dac=.O?*\a䎎û;#(!q& 3WQIg|"[-X f8x2O.oMVO>|D˯eǤ`%Aq';rUR "vgnRaN,cJ5kܰK7~;Ee%Jj݅[P1Z4Α- =PHYoP1,[-l】E)'bjP԰5"O.=r 듷$wf&Leoj+>n D$qW~ twLKI,ڭ3/;3zh}q>s=0#rC⹔yaa2/dC@IDATdd̙3U]b$|X'A03'F1QUsnwBK/ݿa#eȝ\v2oPLeۢj^Oxȟ),Ei gPao(Cg}.CYh tF M> \Sxک7`&$4zH7ed# >ՕRΗ.51%;DDp%aQ,0i] G:2yP au$8[f B=J51`t*~64m90G;}m(k]use5WK;d(!ɍ 9M%H-RJF:M:EL΁ѹĎx˜Sz\{>[yiy¶Jȑ?z-n8nL"t0UhѢE2c L^/"k.c.@4ۿjUiW+1&H1Az,=Hd|!u 9OVɰ}1y ~e#d=ׯ6ZO..%(ѸcTbjb,%(0[oCv ::s8A`n0yg_d970< ⭃za&y >+5MdnroEG {PbaxxñWlDy ^ǭh—}z" nO܃X'Ħ!q!p;̱R,xb{e~ vF+ܝDU92AS)9tIjr°x_dRWXP"7*E&:u!?~N-ո 95Rp¬r x2Lb b,?cc1#o,\Pv1܉&.:t$oR}Nf RfLܷGUg]:2,HY]v L+۩sf ?Ix,_#^AouI!1л[>>;X)7Hձ[ZR-X6Vک t,O v q$JZ'Jx=4~׉PY?@IkepP.Vl1+P8ؔ,H!+CQP~&ŏv)(b4?Zq$6؝n[͜%olϔޑoijSsW5 xa=p*0kB54ƐLU@=qb\v MI6x8qq# = BUBnΆ͜aP0N=E'9ٗ]$kK 땍H }?LA jH|TbPNX5T^/O.zb@[2<)KE>/2\sr/䐫ޒ)_.tj?UA 'fM{ؼ mۖub%v9$K2*;_=*Q?D9;_Vv J%F$)~haЖxxzgP|t2}HIAOo8r!ϑ4R:ϕj+.G$;"L2v,2TB }r6HFӣOPL_[E}1;Nץ?p8JGx!6DN6 a"} dYЌQ*BL)TFH,)cSԆYҨSG)|%݇: ;BV{ ;|)Jw{ې Jdmjf+dɞ͈P/ytp CFYY-˳_"׬ڸCo+sdisOYBR'⵱f}9d_0f\iiQ3$YZ64r$ Ĥ)ˠ DhsچRR ˚_EVڝP3GD,aF3dlր^USS?gzfG\h ]Ɲ{8nMyb+Yp06VB3c=,JT1哢1'.a]{I:DЮ9*F*Jvn'lUdH*>ѽ1*֨ c.Uq(>2zȴدIcD8Q4***=H|^P]$S o-Whw߆yr嗫m5?H\K+ƕM5rRKc ]3+%Q!|RùB\3f(5+?ʐ7P( Dn(Uxm0=_#Nd0*ڶdC3шN{<]8OEH׮S"vhLAM됲l0#^ǃ`h JޔE-5I#ncTwo"ǞCLWlv}Knˠ6Oov[E`]ԛ%) F}C- 7HQ LO1ZVP8G+V@jS %[*&.S~F0ă2#[6e>ŋP4k1[0s=Ͽ3 ."l)#y?%<>j`S-M>Qb[AAֿ@D$?" Z!C%KTE}D`cT9 /l[_uH 6SmV4!GJs&`a:6ɀ!H-R x5hYXn7_/R>PV:P0lX %Y-'^]HjH_|G7l.;bxL*) J$v3Wl gT;%Y(iSؙQPF;8={64&{}Rx`2 6J{RvDk կipCb; 8{pk& 3 g> lvAWj\іe#_6E+-l3!'H=8r6h]`FGK>L=_D$RGz fb<`_ =4֐LBmz^^D200 S&uRET,[qmr5ɋ HkG#ȰhTj$ߙg܌ Ql5KKI¢gN3&7kE'g'Ύ D B0Q^,^7 j.]rPG* () qvzb޽ [2_̩C,XEkԖpԚ*oS5i6)C՜֯ۤB敆mHh\JuIÆ5! Hr;CO|G@q=Nb}ZAsUJ3шT~8]AP9K92H nҫD{a3s~.[S-GxPL?e\L+V[ITIo6,n? HH_ \6 v>FӳxkV<D˙&i15#NaQ:dr&8//N7כ.B|G7|@E&k-Fvڨ`tk&JARgBS.?~uMyx(s daм}Ǻݦ=}?З ^ I<,[IօU.{+C-+L=-q⭵R}%FY}=H -ҎK+:Ab> ߇ [, _x^ǰoY.KҨE MDƻ[}v,>ęíoIy2:]6((#(|.f4n=P0tӆM|1L8.D,pt]2U(BP1,eD{ԑG{qa:`'-PS8x [Hp_lVn@*QB:É-wM1׷"莣m(oX- _3$.UO? ::[}Q%)eYt" &V]ZNraWiF'$(B,gH| zr3A&vg}j+;@Ѧ֖zriYepuvw^UeJ*~!}–+0p:jPk_pPZÚoޮ_o'=Uvw-7¨hjW-dq Ȅ!54|ZGe R T%K9T Nďj!ti;TMEY:9Re"W"]V+n=xkg%i٩lY2)Z%lm_slɴt@ȢxO&EJޚ)`#;bS5lVanU!~ilgd{3d^*ɫ/V@[vs@8e=zW__gyw6M==䓢%[/**ڠ㴟yfiLLdVD3 0UˁL#'L<+L{k*/~6»̬\t;FPe,c6/Wz]ptQi!b#ܮ Wj`>HX 8D:1,n'ǮkĂ%2*aBS9D ãMP+yQ̒"-`Gr?O9}~ٟ RJRX\,Ӽ>_BU,a?p} I:*˥|HJLL~5J+, +L S@GK(V \/ֽ7%%E12,bI}?sʿi9,Vw+^; 0I5@,3QʆF/RU T܇~qJ1~gA[$JQǂx6r3nj HH =|mf^Χ9*ۍ` -vۻ")IILĄ@jtW;'9)wh{ZɤGuU:ɻF)`9]fNJ(V$A|MS JM{^V̊@^W@2LP ^3FΒsPr4t=gNmPO-b!M)|D\gb `'{i)zȬ=d$v`󾖳ˏ {a1)hD/մU/׻ Q5ja{+ m;{+TmekdK,Tʢbq"p;`FJO_K!rJ -O/ A @u#I^;h{ڿ\y%5AlRՁnz*tau]oMCJyn.̪υŏ)E(oeHP>ֽy!}x F3Q{˩7$ҥȬ`(4!{YP&F^;7%imC=U4ϴzs|JZm!O=T[5gڎ5{ Xv8Aߗk!eͳki Cm7ba9 p7ʎ}RvncO5%G+$d%ct#\=!)ҟrGdYFen!ӢD"q3ӧH52mtCWSWö,!gkʴX#5)wsrӮj" R.dC ֪HrL/ysC- cH[,h*님Vb`) ;C NGUvYd! L[#VAͯi[-x^yޱ`7nl 75醷~)q0zmYӒoǜlQT,cb%w<@;#ʘN'P"'^㮓^FX(`TwaDQvI_=Ѓ4M1-+$񶌘 OAHAxp>."Գr8YPjM0ܩd3L,20+%~PFLFBs2hvݺu3 b2;gvFx}ԯ}VDr*IܼM|DĘ.N:l{SNZ;be~k;R-,]EKn} msdu @0s&O@~ߧ='uD_4m}ډS^0s. nZvEгf*DOG5C_ڡ,JRXۮ-7)fDHALDtp,ėtSp< W"W&)Z:r]̓ZfҐ T1vbӽOr,:k_]hvoT>?A*mgOD DHH+=66K[BdFLv"2iw@׬Y%ɰ,zn}jl[^Kk tY2vkψU#Y(WU@X11k Bpd3 ~ ?|-$! [#+MDMTG77}ɠ Xj)6* `3Ԡ At5JRxMMLk+kկ?mM6.̓Yp/Tˊah7.ܯ 8M@L-c -G$&I$lcN]x4I6BBZfm34 %3p^3$j8.].1axZdi!tTe[d1iܩ`Zk׶P~ q {ll_Eq#v F{rǪU O@3v6J->i4|Ha+ As[5NO.]$Ad@)򐡞pG;ݻwWLB۪=1Z'Z+#]$O:M;@V ";mBFFC}ÎݰT@N_V'+1Ń v,MHy~W ~tILi6)K #`';IY^{A2$<%zx.I )?Z^ !Bv}_;Bnpm1]#xJQtL`[)IIZz}rI.:b!x!QcDF]OHꫳ%qfaFXYqI$e29?x하2MwsbGJoM"%#dϤŗw 1aa`V(bhoZcƆa@ˡk2&<&Z L}8r5wJ/&- 2EI a#>-.x F-+jY dǕ-+11/!ؚߖڼ톮yC_+>@0 2~OL9aXPviFl}q ='L2$q 8j"`Z/aA,q]2-)g H^5Qu@4dF;v*ѭkoLdD_ ߊF5vCBo" fg@4C97 l u4 P5H[rZH&"E'[*01=.W9L:x] M *i.9_)S% Rڬu ӣWzK;v@!c#˖ :|Z؇P 8!E moEhۊBp$#|_~O;.#wU9v]6ź~$5;GJnn], zBsd'R0;wIV{Zn!RVM% cIFM"7u9˴p)I.x4Ý!0&T?YϾF(A\WQ|bJ'$*>(M GbZT=,$f یqK*J-^ h&K4%,3gRH_ekN5X1c w=|SR>C\odcBS;I"(\Dsad7x,<ډ44~Ni@ejWXٵ510MQ*,hF5l99k/p' e55C㪥@Y#-&xփ#anr[Uu@lNA ؖv CB1ے!n)--͛,Wͪf82K]._R2ߜf{ tWZZ$Ͳv&vH7i\tb6W^Ao+R-GNB-G}5wA3t@k&L$aD8a=*D?^t1TçAtIURS-Vc1LKC{=˻XCc$tTӶ6*"B .ZRy@t}y2hJdR;ú=9L\zywU1^Z `hwl'J]WJ,1hZHeGnjs~ޘϞ3&huU]]*vJԵ|d p'NB~vզ=` X'Xu%JJ-@S`1 F}A2uԁ\nnSS_*[.v5-豩2Q_p})l,W.0w7H鱣uR:fKك7ˎ.W􌚉Epة eqR3Ŧ#g}.CDzs;.p3Pd*vVV իiPLtjKDhK\d,Ç3j^u0"Q%(ס{+OHgUUG]pўsv{6z !e4A݁\F)A6\FV"g`UEDIᄑR(L.cP4n_V H5oEzW-[ʁCDϺH)VFCIP^DڸFÕr@O}MwD̋||,g qmoxq]M.z]RxRc>eQzWcx'#Rv/!9?![?(Tج:/+5\$>ʸn56>tCm\#h R$%H0>OV=q" 8 6*WXP`b4RwQ:;A:Gw9C׶r) 1( >; Kko%6 8!/)=λXp4 ofɖ G[AYYYC裏Zm@tgE^N|L 8q\Ɉ4 Lae3UlQ6)G d(jO؈Aqu^¬d f+U*JWdUc( ڈ핂nzOa"y}1+I)P ^ %Ƌb­Rj3k;P]]>T fƸ\3γ0@uo݌xYNNM4^ni"Æ nA~mBTw>A[._.%K!b3X߫DjL[='ѐh:SQQdooNwެ2 ]ė?pAmcYf2:Bc2rXȩD߶Q/ I(aU}!7`9JkQedz 90~Z|n\Dg78wU&}ᅒͷcuYgy Ǽfq9wv/x 7fL= ?~!șlDF"R ,F)Ut )#bر V OBzCiK)>H+ ئgdB0]U)[k[Btr{O=ܣtUú){EAX^3 ,Oz?ݏ%{{(ܧvȠ?n$NՄF8_lT9j; UͲ~7?$] /"hshV w^{*'vQoG- X*N̤aͰhj@q@Pkqc?͐%B8U]w܆Wh0,ay晞u3UyLR7l_ YwСj[Sq-"l[ T5>+?":\#ZYfy4n Do$ C֭\UU X8RGw+uD[:կh+B 7HjO;=cpȤIgƊbzLJ'.&»͖B6cw׉6GR_zdm)l4v^>s;x.;VxFf%aR\)ar-gqUۘ3sFI!-1A-V̌Lj3i^ y(fZp՝O{^&apr Qh?I:u< ϸAj~"nQ!%wVwQi(0{f|p9{LH4hfŬJ<[ҁimUϒ3i]M4`QPTJ8i31p ¼oO3^$#>b^p̰VQ*}#S1& S 1Yan5t71PYv=Br&ͫڣve\nhM|0W6?^PVLL[ /2'I¡3RE4oDXBR~)/LX,Ul *jax餝@|舸SU>^ s{Iʰ!Ps={h*/RFuPy*X4Գ%k[H E $3f7[$&JoR-9 Z.G%j1gic(T_(dx7kPc`\ yBTtK?a j;8W̺j1=nt_7 W|vբ@>q%FBb-˴9Pu\v[NyxX9US`,#?|-)1Quc7CM1IⳓLJr1sLO@V90x#{FFWð(-yԙ;{TAe(֩J5` o$ - %[>}̻v:Z7cK #wY̊l uNć&äӛ;9u/Q/ R=B4=.&+nFURyn-IdH`3]!b&߀!R*chVF"E$<4Nu+͔ zT^1MJuǘ"O yczF̓rS:ѿ<4bhFu½)+*ehJ9EziV)'P(qKi&K#?UKb>(1LD# J;vg)e1a|KV6#ӆN/'.թl6+`02q"ƞ~g%+Sgt+ZgL0>6g 80K{۷b,B;u4Z%YYU!wR#b~Nb"Mpp} ]8_:]"B6w& :ZR]6f@?ܹ6abwޢ3YYZL01Af% Fߕtϋ/(#&/XލwEO"ܰT]]K2rz!@sr W-i( F& )Hc(Q03p&Tq220hWH0X R ,WqD;vJ[#Zkѭ tyǴݾK,O];N P:u {?wѽ)T.))Ͼ-mLhYv?k[: G9tAfx iɅ 9HUunګ4 诓̪% Bv!Ja}D|D`G09eo {PI-ή%_>QA*{~TLOW, -Nn,7$0$iB;@2Q11[TJs[*%#i^et_(uVx~~W_Ue(IoW.3dTA嵕QUNb.PF=P1bd`m>P-fR& !EDE9Hwv;_4pe6Mu, YBTa/*Y E-R7eB M{%:\x ɠ3_ W$QT}0QKգʔ9!a`EJuA{bJE};FF3H7r6|4ehbH1ioLoк@BG\Icdd54QzazHkKHTQtER_\c%}5" %<2YP;sam<*r.kZsBNw=$Iz9M&p"#6B1 7޻sv+sq! QP A*gXQ  hR*3LJVqeeMV%l ݌^\')xO~éޱߑ8| j}xzV9rIP.M=Qz m-oVgo%B7|Lz2+,GhrJbiX/ d7Du8%<° 6&GfEcUaGIշ;۽vbdT-.xBb 3ަƌZ~wJ4R4S {UM{o5ӑ?"΀{a~`Rоa_ðx9cD!V!)eҍ+U2U~]G+5 [UQm&/~&ܼ1֫67]{0j!ȕ䑊1mL]3cW.S{kL' 7JT;&@rJF{Li\UhcY'\qN**Ik@ԩr-Og`C{t30!  d1_.n%LypDU=*^) $r+E#,ya AZ@hxp;,]$]ci eTkn %?ڥ'$<6X R@RT݅0ćW 6;[B+?|[J"-3= Fd+Z|c k ti4\&D?&LܪsQ$XvHHPG[c~*Ā ʖ A,C xc"=Q*$< Gi-ۂ9ۣ}عdxۥmaB2U߷!P1K9 L%_l_>I ()J!B"dOv3ШjU*vь⢢)O`0Cx2$U)N 2\@D7R )tij07*XXn:T6 z[6+w@\-n:2hwRaCwI lo &ח8L3l.Rb: It y|5"30fWJ42 hh7CVE""MƵ Άn[ݻn[Xll7S~YzEHul0+hĤ;JAݘ%אu<Q;m|Ew;{JZ*xe׺ +ȯ'7ⅆ(Ej_hI]-hkx?BYC!lD͊z#~'ũJsN6A+!,hg*'SDҌˋR\yESgjND:Z[ \WƚVqQh+{d64%ɞL ]* Oa/1pL`Pk׮mqk'';g8Ub릯+sLK<~$T] !PkPsk'-vW^`~jvq垍a"#)dm f1!WN|\҂E(wް#y+쨫2s->RG=~ڠ~ ڬOM|v0G4oUn1ʯr]-jo4iI=V +$<:$~l0 ?ޅ1t;#t6NLJ9 -uJdR tH*@`ՌRsM&"mB&&/ݮb=*(^ehQHi]GqmT gEl+ҴдÇf\'NJfSuRCj/GMLC[fYMIl],]Q7TfCĔhl[O)zV>z$Ugb G ȫYj8NfJ,$ 24>$Mwe&qdibh'@F{?qҩӺmrNY;Ģ(FG+?zH[ʹ^v%?/Š[|Xqwn4h ӪM!Xp\6`LC͙W, I" TtF,9)"ŴErRn? h#"O$cNw׈ `a<;+񓀆ʨ+<8*[#tUMcX |IT\t,tIwsܵ',_~I= m$_FlyZ[8A{FT:H9 $*ݬW=( ZY%s% Y2FA32LG,S7gj2}J 2t>/JLbl&!CYg}Zx^6$$ T[r9 s}u=VbTgysT 5˵ʈ;W_@:ES=`E#M.ddJ Q]WWg/ 0 { if v, ok := a[n-1].(error); ok { err = v } } return &QueryError{ Err: err, Message: fmt.Sprintf(format, a...), } } func (err *QueryError) Error() string { if err == nil { return "" } str := fmt.Sprintf("graphql: %s", err.Message) for _, loc := range err.Locations { str += fmt.Sprintf(" (line %d, column %d)", loc.Line, loc.Column) } return str } func (err *QueryError) Unwrap() error { if err == nil { return nil } return err.Err } var _ error = &QueryError{} graphql-go-1.5.0/errors/errors_test.go000066400000000000000000000020331435002731700177740ustar00rootroot00000000000000package errors import ( "io" "testing" ) // Is is simplified facsimile of the go 1.13 errors.Is to ensure QueryError is compatible func Is(err, target error) bool { for err != nil { if target == err { return true } switch e := err.(type) { case interface{ Unwrap() error }: err = e.Unwrap() default: break } } return false } func TestErrorf(t *testing.T) { cause := io.EOF t.Run("wrap error", func(t *testing.T) { err := Errorf("boom: %v", cause) if !Is(err, cause) { t.Fatalf("expected errors.Is to return true") } }) t.Run("handles nil", func(t *testing.T) { var err *QueryError if Is(err, cause) { t.Fatalf("expected errors.Is to return false") } }) t.Run("handle no arguments", func(t *testing.T) { err := Errorf("boom") if Is(err, cause) { t.Fatalf("expected errors.Is to return false") } }) t.Run("handle non-error argument arguments", func(t *testing.T) { err := Errorf("boom: %v", "shaka") if Is(err, cause) { t.Fatalf("expected errors.Is to return false") } }) } graphql-go-1.5.0/errors/panic_handler.go000066400000000000000000000010601435002731700202070ustar00rootroot00000000000000package errors import ( "context" ) // PanicHandler is the interface used to create custom panic errors that occur during query execution type PanicHandler interface { MakePanicError(ctx context.Context, value interface{}) *QueryError } // DefaultPanicHandler is the default PanicHandler type DefaultPanicHandler struct{} // MakePanicError creates a new QueryError from a panic that occurred during execution func (h *DefaultPanicHandler) MakePanicError(ctx context.Context, value interface{}) *QueryError { return Errorf("panic occurred: %v", value) } graphql-go-1.5.0/errors/panic_handler_test.go000066400000000000000000000011461435002731700212530ustar00rootroot00000000000000package errors import ( "context" "testing" ) func TestDefaultPanicHandler(t *testing.T) { handler := &DefaultPanicHandler{} qErr := handler.MakePanicError(context.Background(), "foo") if qErr == nil { t.Fatal("Panic error must not be nil") } const ( expectedMessage = "panic occurred: foo" expectedError = "graphql: " + expectedMessage ) if qErr.Error() != expectedError { t.Errorf("Unexpected panic error message: %q != %q", qErr.Error(), expectedError) } if qErr.Message != expectedMessage { t.Errorf("Unexpected panic QueryError.Message: %q != %q", qErr.Message, expectedMessage) } } graphql-go-1.5.0/example/000077500000000000000000000000001435002731700152135ustar00rootroot00000000000000graphql-go-1.5.0/example/apollo_federation/000077500000000000000000000000001435002731700207015ustar00rootroot00000000000000graphql-go-1.5.0/example/apollo_federation/README.md000066400000000000000000000011121435002731700221530ustar00rootroot00000000000000# Apollo Federation A simple example of integration with apollo federation as subgraph. Tested with Go v1.18, Node.js v16.14.2 and yarn 1.22.18. To run this server `go run ./example/apollo_federation/subgraph_one/server.go` `go run ./example/apollo_federation/subgraph_two/server.go` `cd example/apollo_federation/gateway` `yarn start` and go to localhost:4000 to interact Execute the query: ``` query { hello hi } ``` and you should see a result similar to this: ```json { "data": { "hello": "Hello from subgraph one!", "hi": "Hi from subgraph two!" } } ``` graphql-go-1.5.0/example/apollo_federation/gateway/000077500000000000000000000000001435002731700223425ustar00rootroot00000000000000graphql-go-1.5.0/example/apollo_federation/gateway/.gitignore000066400000000000000000000000311435002731700243240ustar00rootroot00000000000000/node_modules /yarn.lock graphql-go-1.5.0/example/apollo_federation/gateway/index.js000066400000000000000000000010421435002731700240040ustar00rootroot00000000000000const { ApolloServer } = require('apollo-server') const { ApolloGateway, IntrospectAndCompose } = require('@apollo/gateway'); const gateway = new ApolloGateway({ supergraphSdl: new IntrospectAndCompose({ subgraphs: [ { name: 'one', url: 'http://localhost:4001/query' }, { name: 'two', url: 'http://localhost:4002/query' }, ], }), }); const server = new ApolloServer({ gateway, subscriptions: false, }); server.listen().then(({ url }) => { console.log(`Server ready at ${url}`); }); graphql-go-1.5.0/example/apollo_federation/gateway/package.json000066400000000000000000000004421435002731700246300ustar00rootroot00000000000000{ "name": "apollo-federation-gateway", "version": "1.0.0", "description": "Graphql Federation", "main": "index.js", "scripts": { "start": "node index.js" }, "dependencies": { "@apollo/gateway": "^0.49.0", "apollo-server": "^2.21.1", "graphql": "^15.5.0" } } graphql-go-1.5.0/example/apollo_federation/subgraph_one/000077500000000000000000000000001435002731700233555ustar00rootroot00000000000000graphql-go-1.5.0/example/apollo_federation/subgraph_one/server.go000066400000000000000000000011041435002731700252060ustar00rootroot00000000000000package main import ( "log" "net/http" "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/relay" ) var schema = ` schema { query: Query } type Query { hello: String! } ` type resolver struct{} func (r *resolver) Hello() string { return "Hello from subgraph one!" } func main() { opts := []graphql.SchemaOpt{graphql.UseFieldResolvers(), graphql.MaxParallelism(20)} schema := graphql.MustParseSchema(schema, &resolver{}, opts...) http.Handle("/query", &relay.Handler{Schema: schema}) log.Fatal(http.ListenAndServe(":4001", nil)) } graphql-go-1.5.0/example/apollo_federation/subgraph_two/000077500000000000000000000000001435002731700234055ustar00rootroot00000000000000graphql-go-1.5.0/example/apollo_federation/subgraph_two/server.go000066400000000000000000000010731435002731700252430ustar00rootroot00000000000000package main import ( "log" "net/http" "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/relay" ) var schema = ` schema { query: Query } type Query { hi: String! } ` type resolver struct{} func (r *resolver) Hi() string { return "Hi from subgraph two!" } func main() { opts := []graphql.SchemaOpt{graphql.UseFieldResolvers(), graphql.MaxParallelism(20)} schema := graphql.MustParseSchema(schema, &resolver{}, opts...) http.Handle("/query", &relay.Handler{Schema: schema}) log.Fatal(http.ListenAndServe(":4002", nil)) } graphql-go-1.5.0/example/array_as_argument/000077500000000000000000000000001435002731700207165ustar00rootroot00000000000000graphql-go-1.5.0/example/array_as_argument/server.go000066400000000000000000000023161435002731700225550ustar00rootroot00000000000000package main import ( "log" "net/http" "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/relay" ) type query struct{} type IntInput struct { A int32 B int32 } func (_ *query) Reversed(args struct{ Values []string }) []string { result := make([]string, len(args.Values)) for i, value := range args.Values { for _, v := range value { result[i] = string(v) + result[i] } } return result } func (_ *query) Sums(args struct{ Values []IntInput }) []int32 { result := make([]int32, len(args.Values)) for i, value := range args.Values { result[i] = value.A + value.B } return result } func main() { s := ` input IntInput { a: Int! b: Int! } type Query { reversed(values: [String!]!): [String!]! sums(values: [IntInput!]!): [Int!]! } ` schema := graphql.MustParseSchema(s, &query{}) http.Handle("/query", &relay.Handler{Schema: schema}) log.Println("Listen in port :8080") log.Fatal(http.ListenAndServe(":8080", nil)) } /* * The following query query{ reversed(values:["hello", "hi"]) sums(values:[{a:2,b:3},{a:-10,b:-1}]) } * will return { "data": { "reversed": [ "olleh", "ih" ], "sums": [ 5, -11 ] } } */ graphql-go-1.5.0/example/caching/000077500000000000000000000000001435002731700166075ustar00rootroot00000000000000graphql-go-1.5.0/example/caching/cache/000077500000000000000000000000001435002731700176525ustar00rootroot00000000000000graphql-go-1.5.0/example/caching/cache/hint.go000066400000000000000000000036451435002731700211530ustar00rootroot00000000000000// Package cache implements caching of GraphQL requests by allowing resolvers to provide hints about their cacheability, // which can be used by the transport handlers (e.g. HTTP) to provide caching indicators in the response. package cache import ( "context" "fmt" "time" ) type ctxKey string const ( hintsKey ctxKey = "hints" ) type scope int // Cache control scopes. const ( ScopePublic scope = iota ScopePrivate ) const ( hintsBuffer = 20 ) // Hint defines a hint as to how long something should be cached for. type Hint struct { MaxAge *time.Duration Scope scope } // String resolves the HTTP Cache-Control value of the Hint. func (h Hint) String() string { var s string switch h.Scope { case ScopePublic: s = "public" case ScopePrivate: s = "private" } return fmt.Sprintf("%s, max-age=%d", s, int(h.MaxAge.Seconds())) } // TTL defines the cache duration. func TTL(d time.Duration) *time.Duration { return &d } // AddHint applies a caching hint to the request context. func AddHint(ctx context.Context, hint Hint) { c := hints(ctx) if c == nil { return } c <- hint } // Hintable extends the context with the ability to add cache hints. func Hintable(ctx context.Context) (hintCtx context.Context, hint <-chan Hint, done func()) { hints := make(chan Hint, hintsBuffer) h := make(chan Hint) go func() { h <- resolve(hints) }() done = func() { close(hints) } return context.WithValue(ctx, hintsKey, hints), h, done } func hints(ctx context.Context) chan Hint { h, ok := ctx.Value(hintsKey).(chan Hint) if !ok { return nil } return h } func resolve(hints <-chan Hint) Hint { var minAge *time.Duration s := ScopePublic for h := range hints { if h.Scope == ScopePrivate { s = h.Scope } if h.MaxAge != nil && (minAge == nil || *h.MaxAge < *minAge) { minAge = h.MaxAge } } if minAge == nil { var noCache time.Duration minAge = &noCache } return Hint{MaxAge: minAge, Scope: s} } graphql-go-1.5.0/example/caching/caching.go000066400000000000000000000014271435002731700205360ustar00rootroot00000000000000package caching import ( "context" "time" "github.com/graph-gophers/graphql-go/example/caching/cache" ) const Schema = ` schema { query: Query } type Query { hello(name: String!): String! me: UserProfile! } type UserProfile { name: String! } ` type Resolver struct{} func (r Resolver) Hello(ctx context.Context, args struct{ Name string }) string { cache.AddHint(ctx, cache.Hint{MaxAge: cache.TTL(1 * time.Hour), Scope: cache.ScopePublic}) return "Hello " + args.Name + "!" } func (r Resolver) Me(ctx context.Context) *UserProfile { cache.AddHint(ctx, cache.Hint{MaxAge: cache.TTL(1 * time.Minute), Scope: cache.ScopePrivate}) return &UserProfile{name: "World"} } type UserProfile struct { name string } func (p *UserProfile) Name() string { return p.name } graphql-go-1.5.0/example/caching/server/000077500000000000000000000000001435002731700201155ustar00rootroot00000000000000graphql-go-1.5.0/example/caching/server/server.go000066400000000000000000000077531435002731700217660ustar00rootroot00000000000000package main import ( "encoding/json" "fmt" "log" "net/http" "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/example/caching" "github.com/graph-gophers/graphql-go/example/caching/cache" ) var schema *graphql.Schema func init() { schema = graphql.MustParseSchema(caching.Schema, &caching.Resolver{}) } func main() { http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write(page) })) http.Handle("/query", &Handler{Schema: schema}) log.Fatal(http.ListenAndServe(":8080", nil)) } type Handler struct { Schema *graphql.Schema } func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { p, ok := h.parseRequest(w, r) if !ok { return } var response *graphql.Response var hint *cache.Hint if cacheable(r) { ctx, hints, done := cache.Hintable(r.Context()) response = h.Schema.Exec(ctx, p.Query, p.OperationName, p.Variables) done() v := <-hints hint = &v } else { response = h.Schema.Exec(r.Context(), p.Query, p.OperationName, p.Variables) } responseJSON, err := json.Marshal(response) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if hint != nil { w.Header().Set("Cache-Control", hint.String()) } w.Header().Set("Content-Type", "application/json") w.Write(responseJSON) } func (h *Handler) parseRequest(w http.ResponseWriter, r *http.Request) (params, bool) { var p params switch r.Method { case http.MethodGet: q := r.URL.Query() if p.Query = q.Get("query"); p.Query == "" { http.Error(w, "A non-empty 'query' parameter is required", http.StatusBadRequest) return params{}, false } p.OperationName = q.Get("operationName") if vars := q.Get("variables"); vars != "" { if err := json.Unmarshal([]byte(vars), &p.Variables); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return params{}, false } } return p, true case http.MethodPost: if err := json.NewDecoder(r.Body).Decode(&p); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return params{}, false } return p, true default: http.Error(w, fmt.Sprintf("unsupported HTTP method: %s", r.Method), http.StatusMethodNotAllowed) return params{}, false } } func cacheable(r *http.Request) bool { return r.Method == http.MethodGet } type params struct { Query string `json:"query"` OperationName string `json:"operationName"` Variables map[string]interface{} `json:"variables"` } var page = []byte(`
Loading...
`) graphql-go-1.5.0/example/customerrors/000077500000000000000000000000001435002731700177625ustar00rootroot00000000000000graphql-go-1.5.0/example/customerrors/server/000077500000000000000000000000001435002731700212705ustar00rootroot00000000000000graphql-go-1.5.0/example/customerrors/server/server.go000066400000000000000000000035711435002731700231330ustar00rootroot00000000000000package main import ( "log" "net/http" "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/example/customerrors" "github.com/graph-gophers/graphql-go/relay" ) var schema *graphql.Schema func init() { schema = graphql.MustParseSchema(customerrors.Schema, &customerrors.Resolver{}) } func main() { http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write(page) })) http.Handle("/query", &relay.Handler{Schema: schema}) log.Fatal(http.ListenAndServe(":8080", nil)) } var page = []byte(`
Loading...
`) graphql-go-1.5.0/example/customerrors/starwars.go000066400000000000000000000025561435002731700221670ustar00rootroot00000000000000package customerrors import ( "fmt" "github.com/graph-gophers/graphql-go" ) var Schema = ` schema { query: Query } type Query { droid(id: ID!): Droid! } # An autonomous mechanical character in the Star Wars universe type Droid { # The ID of the droid id: ID! # What others call this droid name: String! } ` type droid struct { ID graphql.ID Name string } var droids = []*droid{ {ID: "2000", Name: "C-3PO"}, {ID: "2001", Name: "R2-D2"}, } var droidData = make(map[graphql.ID]*droid) func init() { for _, d := range droids { droidData[d.ID] = d } } type Resolver struct{} func (r *Resolver) Droid(args struct{ ID graphql.ID }) (*droidResolver, error) { if d := droidData[args.ID]; d != nil { return &droidResolver{d: d}, nil } return nil, &droidNotFoundError{Code: "NotFound", Message: "This is not the droid you are looking for"} } type droidResolver struct { d *droid } func (r *droidResolver) ID() graphql.ID { return r.d.ID } func (r *droidResolver) Name() string { return r.d.Name } type droidNotFoundError struct { Code string `json:"code"` Message string `json:"message"` } func (e droidNotFoundError) Error() string { return fmt.Sprintf("error [%s]: %s", e.Code, e.Message) } func (e droidNotFoundError) Extensions() map[string]interface{} { return map[string]interface{}{ "code": e.Code, "message": e.Message, } } graphql-go-1.5.0/example/scalar_map/000077500000000000000000000000001435002731700173155ustar00rootroot00000000000000graphql-go-1.5.0/example/scalar_map/server.go000066400000000000000000000013021435002731700211460ustar00rootroot00000000000000package main import ( "fmt" "log" "net/http" graphql "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/example/scalar_map/types" "github.com/graph-gophers/graphql-go/relay" ) type Args struct { Name string Data types.Map } type mutation struct{} func (_ *mutation) Hello(args Args) string { fmt.Println(args) return "Args accept!" } func main() { s := ` scalar Map type Query {} type Mutation { hello( name: String! data: Map! ): String! } ` schema := graphql.MustParseSchema(s, &mutation{}) http.Handle("/query", &relay.Handler{Schema: schema}) log.Println("Listen in port :8080") log.Fatal(http.ListenAndServe(":8080", nil)) } graphql-go-1.5.0/example/scalar_map/types/000077500000000000000000000000001435002731700204615ustar00rootroot00000000000000graphql-go-1.5.0/example/scalar_map/types/map.go000066400000000000000000000004731435002731700215710ustar00rootroot00000000000000package types import "fmt" type Map map[string]interface{} func (Map) ImplementsGraphQLType(name string) bool { return name == "Map" } func (j *Map) UnmarshalGraphQL(input interface{}) error { json, ok := input.(map[string]interface{}) if !ok { return fmt.Errorf("wrong type") } *j = json return nil } graphql-go-1.5.0/example/scalar_time/000077500000000000000000000000001435002731700174765ustar00rootroot00000000000000graphql-go-1.5.0/example/scalar_time/server.go000066400000000000000000000010141435002731700213270ustar00rootroot00000000000000package main import ( "log" "net/http" "time" graphql "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/relay" ) type query struct{} func (_ *query) CurrentTime() graphql.Time { return graphql.Time{Time: time.Now()} } func main() { s := ` scalar Time type Query { currentTime: Time! } ` schema := graphql.MustParseSchema(s, &query{}) http.Handle("/query", &relay.Handler{Schema: schema}) log.Println("Listen in port :8080") log.Fatal(http.ListenAndServe(":8080", nil)) } graphql-go-1.5.0/example/social/000077500000000000000000000000001435002731700164655ustar00rootroot00000000000000graphql-go-1.5.0/example/social/README.md000066400000000000000000000003041435002731700177410ustar00rootroot00000000000000### Social App A simple example of how to use struct fields as resolvers instead of methods. To run this server `go run ./example/social/server/server.go` and go to localhost:9011 to interact graphql-go-1.5.0/example/social/introspect.json000066400000000000000000001223211435002731700215530ustar00rootroot00000000000000{ "__schema": { "directives": [ { "args": [ { "defaultValue": "\"No longer supported\"", "description": "Explains why this element was deprecated, usually also including a suggestion\nfor how to access supported similar data. Formatted in\n[Markdown](https://daringfireball.net/projects/markdown/).", "name": "reason", "type": { "kind": "SCALAR", "name": "String", "ofType": null } } ], "description": "Marks an element of a GraphQL schema as no longer supported.", "locations": [ "FIELD_DEFINITION", "ENUM_VALUE", "ARGUMENT_DEFINITION" ], "name": "deprecated" }, { "args": [ { "defaultValue": null, "description": "Included when true.", "name": "if", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } } } ], "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", "locations": [ "FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT" ], "name": "include" }, { "args": [ { "defaultValue": null, "description": "Skipped when true.", "name": "if", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } } } ], "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", "locations": [ "FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT" ], "name": "skip" }, { "args": [ { "defaultValue": null, "description": "The URL should point to a human-readable specification of the data format, serialization, and coercion rules.", "name": "url", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } } ], "description": "Provides a scalar specification URL for specifying the behavior of custom scalar types.", "locations": [ "SCALAR" ], "name": "specifiedBy" } ], "mutationType": null, "queryType": { "name": "Query" }, "subscriptionType": null, "types": [ { "description": null, "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "id", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "name", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "role", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "ENUM", "name": "Role", "ofType": null } } } ], "inputFields": null, "interfaces": null, "kind": "INTERFACE", "name": "Admin", "possibleTypes": [ { "kind": "OBJECT", "name": "User", "ofType": null } ] }, { "description": "The `Boolean` scalar type represents `true` or `false`.", "enumValues": null, "fields": null, "inputFields": null, "interfaces": null, "kind": "SCALAR", "name": "Boolean", "possibleTypes": null }, { "description": "The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).", "enumValues": null, "fields": null, "inputFields": null, "interfaces": null, "kind": "SCALAR", "name": "Float", "possibleTypes": null }, { "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", "enumValues": null, "fields": null, "inputFields": null, "interfaces": null, "kind": "SCALAR", "name": "ID", "possibleTypes": null }, { "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.", "enumValues": null, "fields": null, "inputFields": null, "interfaces": null, "kind": "SCALAR", "name": "Int", "possibleTypes": null }, { "description": null, "enumValues": null, "fields": null, "inputFields": [ { "defaultValue": null, "description": null, "name": "first", "type": { "kind": "SCALAR", "name": "Int", "ofType": null } }, { "defaultValue": null, "description": null, "name": "last", "type": { "kind": "SCALAR", "name": "Int", "ofType": null } } ], "interfaces": null, "kind": "INPUT_OBJECT", "name": "Pagination", "possibleTypes": null }, { "description": null, "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "name", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } } ], "inputFields": null, "interfaces": null, "kind": "INTERFACE", "name": "Person", "possibleTypes": [ { "kind": "OBJECT", "name": "User", "ofType": null } ] }, { "description": null, "enumValues": null, "fields": [ { "args": [ { "defaultValue": null, "description": null, "name": "id", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } } }, { "defaultValue": "ADMIN", "description": null, "name": "role", "type": { "kind": "ENUM", "name": "Role", "ofType": null } } ], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "admin", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "INTERFACE", "name": "Admin", "ofType": null } } }, { "args": [ { "defaultValue": null, "description": null, "name": "id", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } } } ], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "user", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "User", "ofType": null } } }, { "args": [ { "defaultValue": null, "description": null, "name": "text", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } } ], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "search", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "UNION", "name": "SearchResult", "ofType": null } } } } ], "inputFields": null, "interfaces": [], "kind": "OBJECT", "name": "Query", "possibleTypes": null }, { "description": null, "enumValues": [ { "deprecationReason": null, "description": null, "isDeprecated": false, "name": "ADMIN" }, { "deprecationReason": null, "description": null, "isDeprecated": false, "name": "USER" } ], "fields": null, "inputFields": null, "interfaces": null, "kind": "ENUM", "name": "Role", "possibleTypes": null }, { "description": null, "enumValues": null, "fields": null, "inputFields": null, "interfaces": null, "kind": "UNION", "name": "SearchResult", "possibleTypes": [ { "kind": "OBJECT", "name": "User", "ofType": null } ] }, { "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", "enumValues": null, "fields": null, "inputFields": null, "interfaces": null, "kind": "SCALAR", "name": "String", "possibleTypes": null }, { "description": null, "enumValues": null, "fields": null, "inputFields": null, "interfaces": null, "kind": "SCALAR", "name": "Time", "possibleTypes": null }, { "description": null, "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "id", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "name", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "email", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "role", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "ENUM", "name": "Role", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "phone", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "address", "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } } }, { "args": [ { "defaultValue": null, "description": null, "name": "page", "type": { "kind": "INPUT_OBJECT", "name": "Pagination", "ofType": null } } ], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "friends", "type": { "kind": "LIST", "name": null, "ofType": { "kind": "OBJECT", "name": "User", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "createdAt", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Time", "ofType": null } } } ], "inputFields": null, "interfaces": [ { "kind": "INTERFACE", "name": "Admin", "ofType": null }, { "kind": "INTERFACE", "name": "Person", "ofType": null } ], "kind": "OBJECT", "name": "User", "possibleTypes": null }, { "description": null, "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "sdl", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } } ], "inputFields": null, "interfaces": [], "kind": "OBJECT", "name": "_Service", "possibleTypes": null }, { "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior\nin ways field arguments will not suffice, such as conditionally including or\nskipping a field. Directives provide this by describing additional information\nto the executor.", "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "name", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "description", "type": { "kind": "SCALAR", "name": "String", "ofType": null } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "locations", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "ENUM", "name": "__DirectiveLocation", "ofType": null } } } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "args", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } } } } } ], "inputFields": null, "interfaces": [], "kind": "OBJECT", "name": "__Directive", "possibleTypes": null }, { "description": "A Directive can be adjacent to many parts of the GraphQL language, a\n__DirectiveLocation describes one such possible adjacencies.", "enumValues": [ { "deprecationReason": null, "description": "Location adjacent to a query operation.", "isDeprecated": false, "name": "QUERY" }, { "deprecationReason": null, "description": "Location adjacent to a mutation operation.", "isDeprecated": false, "name": "MUTATION" }, { "deprecationReason": null, "description": "Location adjacent to a subscription operation.", "isDeprecated": false, "name": "SUBSCRIPTION" }, { "deprecationReason": null, "description": "Location adjacent to a field.", "isDeprecated": false, "name": "FIELD" }, { "deprecationReason": null, "description": "Location adjacent to a fragment definition.", "isDeprecated": false, "name": "FRAGMENT_DEFINITION" }, { "deprecationReason": null, "description": "Location adjacent to a fragment spread.", "isDeprecated": false, "name": "FRAGMENT_SPREAD" }, { "deprecationReason": null, "description": "Location adjacent to an inline fragment.", "isDeprecated": false, "name": "INLINE_FRAGMENT" }, { "deprecationReason": null, "description": "Location adjacent to a schema definition.", "isDeprecated": false, "name": "SCHEMA" }, { "deprecationReason": null, "description": "Location adjacent to a scalar definition.", "isDeprecated": false, "name": "SCALAR" }, { "deprecationReason": null, "description": "Location adjacent to an object type definition.", "isDeprecated": false, "name": "OBJECT" }, { "deprecationReason": null, "description": "Location adjacent to a field definition.", "isDeprecated": false, "name": "FIELD_DEFINITION" }, { "deprecationReason": null, "description": "Location adjacent to an argument definition.", "isDeprecated": false, "name": "ARGUMENT_DEFINITION" }, { "deprecationReason": null, "description": "Location adjacent to an interface definition.", "isDeprecated": false, "name": "INTERFACE" }, { "deprecationReason": null, "description": "Location adjacent to a union definition.", "isDeprecated": false, "name": "UNION" }, { "deprecationReason": null, "description": "Location adjacent to an enum definition.", "isDeprecated": false, "name": "ENUM" }, { "deprecationReason": null, "description": "Location adjacent to an enum value definition.", "isDeprecated": false, "name": "ENUM_VALUE" }, { "deprecationReason": null, "description": "Location adjacent to an input object type definition.", "isDeprecated": false, "name": "INPUT_OBJECT" }, { "deprecationReason": null, "description": "Location adjacent to an input object field definition.", "isDeprecated": false, "name": "INPUT_FIELD_DEFINITION" } ], "fields": null, "inputFields": null, "interfaces": null, "kind": "ENUM", "name": "__DirectiveLocation", "possibleTypes": null }, { "description": "One possible value for a given Enum. Enum values are unique values, not a\nplaceholder for a string or numeric value. However an Enum value is returned in\na JSON response as a string.", "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "name", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "description", "type": { "kind": "SCALAR", "name": "String", "ofType": null } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "isDeprecated", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "deprecationReason", "type": { "kind": "SCALAR", "name": "String", "ofType": null } } ], "inputFields": null, "interfaces": [], "kind": "OBJECT", "name": "__EnumValue", "possibleTypes": null }, { "description": "Object and Interface types are described by a list of Fields, each of which has\na name, potentially a list of arguments, and a return type.", "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "name", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "description", "type": { "kind": "SCALAR", "name": "String", "ofType": null } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "args", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } } } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "type", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "isDeprecated", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "deprecationReason", "type": { "kind": "SCALAR", "name": "String", "ofType": null } } ], "inputFields": null, "interfaces": [], "kind": "OBJECT", "name": "__Field", "possibleTypes": null }, { "description": "Arguments provided to Fields or Directives and the input fields of an\nInputObject are represented as Input Values which describe their type and\noptionally a default value.", "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "name", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "description", "type": { "kind": "SCALAR", "name": "String", "ofType": null } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "type", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": "A GraphQL-formatted string representing the default value for this input value.", "isDeprecated": false, "name": "defaultValue", "type": { "kind": "SCALAR", "name": "String", "ofType": null } } ], "inputFields": null, "interfaces": [], "kind": "OBJECT", "name": "__InputValue", "possibleTypes": null }, { "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all\navailable types and directives on the server, as well as the entry points for\nquery, mutation, and subscription operations.", "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": "A list of all types supported by this server.", "isDeprecated": false, "name": "types", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } } } } }, { "args": [], "deprecationReason": null, "description": "The type that query operations will be rooted at.", "isDeprecated": false, "name": "queryType", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": "If this server supports mutation, the type that mutation operations will be rooted at.", "isDeprecated": false, "name": "mutationType", "type": { "kind": "OBJECT", "name": "__Type", "ofType": null } }, { "args": [], "deprecationReason": null, "description": "If this server support subscription, the type that subscription operations will be rooted at.", "isDeprecated": false, "name": "subscriptionType", "type": { "kind": "OBJECT", "name": "__Type", "ofType": null } }, { "args": [], "deprecationReason": null, "description": "A list of all directives supported by this server.", "isDeprecated": false, "name": "directives", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Directive", "ofType": null } } } } } ], "inputFields": null, "interfaces": [], "kind": "OBJECT", "name": "__Schema", "possibleTypes": null }, { "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of\ntypes in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that\ntype. Scalar types provide no information beyond a name and description, while\nEnum types provide their values. Object and Interface types provide the fields\nthey describe. Abstract types, Union and Interface, provide the Object types\npossible at runtime. List and NonNull types compose other types.", "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "kind", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "ENUM", "name": "__TypeKind", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "name", "type": { "kind": "SCALAR", "name": "String", "ofType": null } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "description", "type": { "kind": "SCALAR", "name": "String", "ofType": null } }, { "args": [ { "defaultValue": "false", "description": null, "name": "includeDeprecated", "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null } } ], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "fields", "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Field", "ofType": null } } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "interfaces", "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "possibleTypes", "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } } } }, { "args": [ { "defaultValue": "false", "description": null, "name": "includeDeprecated", "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null } } ], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "enumValues", "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__EnumValue", "ofType": null } } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "inputFields", "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "ofType", "type": { "kind": "OBJECT", "name": "__Type", "ofType": null } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "specifiedByURL", "type": { "kind": "SCALAR", "name": "String", "ofType": null } } ], "inputFields": null, "interfaces": [], "kind": "OBJECT", "name": "__Type", "possibleTypes": null }, { "description": "An enum describing what kind of type a given `__Type` is.", "enumValues": [ { "deprecationReason": null, "description": "Indicates this type is a scalar.", "isDeprecated": false, "name": "SCALAR" }, { "deprecationReason": null, "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", "isDeprecated": false, "name": "OBJECT" }, { "deprecationReason": null, "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", "isDeprecated": false, "name": "INTERFACE" }, { "deprecationReason": null, "description": "Indicates this type is a union. `possibleTypes` is a valid field.", "isDeprecated": false, "name": "UNION" }, { "deprecationReason": null, "description": "Indicates this type is an enum. `enumValues` is a valid field.", "isDeprecated": false, "name": "ENUM" }, { "deprecationReason": null, "description": "Indicates this type is an input object. `inputFields` is a valid field.", "isDeprecated": false, "name": "INPUT_OBJECT" }, { "deprecationReason": null, "description": "Indicates this type is a list. `ofType` is a valid field.", "isDeprecated": false, "name": "LIST" }, { "deprecationReason": null, "description": "Indicates this type is a non-null. `ofType` is a valid field.", "isDeprecated": false, "name": "NON_NULL" } ], "fields": null, "inputFields": null, "interfaces": null, "kind": "ENUM", "name": "__TypeKind", "possibleTypes": null } ] } } graphql-go-1.5.0/example/social/server/000077500000000000000000000000001435002731700177735ustar00rootroot00000000000000graphql-go-1.5.0/example/social/server/server.go000066400000000000000000000036331435002731700216350ustar00rootroot00000000000000package main import ( "log" "net/http" "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/example/social" "github.com/graph-gophers/graphql-go/relay" ) func main() { opts := []graphql.SchemaOpt{graphql.UseFieldResolvers(), graphql.MaxParallelism(20)} schema := graphql.MustParseSchema(social.Schema, &social.Resolver{}, opts...) http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write(page) })) http.Handle("/query", &relay.Handler{Schema: schema}) log.Fatal(http.ListenAndServe(":9011", nil)) } var page = []byte(`
Loading...
`) graphql-go-1.5.0/example/social/social.go000066400000000000000000000104231435002731700202660ustar00rootroot00000000000000package social import ( "context" "errors" "fmt" "strings" "time" "github.com/graph-gophers/graphql-go" ) const Schema = ` schema { query: Query } type Query { admin(id: ID!, role: Role = ADMIN): Admin! user(id: ID!): User! search(text: String!): [SearchResult]! } interface Admin { id: ID! name: String! role: Role! } interface Person { name: String! } scalar Time type User implements Admin & Person { id: ID! name: String! email: String! role: Role! phone: String! address: [String!] friends(page: Pagination): [User] createdAt: Time! } input Pagination { first: Int last: Int } enum Role { ADMIN USER } union SearchResult = User ` type page struct { First *float64 Last *float64 } type admin interface { ID() graphql.ID Name() string Role() string } type adminResolver struct { admin } func (r *adminResolver) ToUser() (*user, bool) { n, ok := r.admin.(user) return &n, ok } type searchResult struct { result interface{} } func (r *searchResult) ToUser() (*user, bool) { res, ok := r.result.(*user) return res, ok } type contact struct { Email string Phone string } type user struct { IDField string NameField string RoleField string Address *[]string FriendsField *[]*user CreatedAt graphql.Time contact } func (u user) ID() graphql.ID { return graphql.ID(u.IDField) } func (u user) Name() string { return u.NameField } func (u user) Role() string { return u.RoleField } func (u user) Friends(args struct{ Page *page }) (*[]*user, error) { var from int numFriends := len(*u.FriendsField) to := numFriends if args.Page != nil { if args.Page.First != nil { from = int(*args.Page.First) if from > numFriends { return nil, errors.New("not enough users") } } if args.Page.Last != nil { to = int(*args.Page.Last) if to == 0 || to > numFriends { to = numFriends } } } friends := (*u.FriendsField)[from:to] return &friends, nil } var users = []*user{ { IDField: "0x01", NameField: "Albus Dumbledore", RoleField: "ADMIN", Address: &[]string{"Office @ Hogwarts", "where Horcruxes are"}, CreatedAt: graphql.Time{Time: time.Now()}, contact: contact{ Email: "Albus@hogwarts.com", Phone: "000-000-0000", }, }, { IDField: "0x02", NameField: "Harry Potter", RoleField: "USER", Address: &[]string{"123 dorm room @ Hogwarts", "456 random place"}, CreatedAt: graphql.Time{Time: time.Now()}, contact: contact{ Email: "harry@hogwarts.com", Phone: "000-000-0001", }, }, { IDField: "0x03", NameField: "Hermione Granger", RoleField: "USER", Address: &[]string{"233 dorm room @ Hogwarts", "786 @ random place"}, CreatedAt: graphql.Time{Time: time.Now()}, contact: contact{ Email: "hermione@hogwarts.com", Phone: "000-000-0011", }, }, { IDField: "0x04", NameField: "Ronald Weasley", RoleField: "USER", Address: &[]string{"411 dorm room @ Hogwarts", "981 @ random place"}, CreatedAt: graphql.Time{Time: time.Now()}, contact: contact{ Email: "ronald@hogwarts.com", Phone: "000-000-0111", }, }, } var usersMap = make(map[string]*user) func init() { users[0].FriendsField = &[]*user{users[1]} users[1].FriendsField = &[]*user{users[0], users[2], users[3]} users[2].FriendsField = &[]*user{users[1], users[3]} users[3].FriendsField = &[]*user{users[1], users[2]} for _, usr := range users { usersMap[usr.IDField] = usr } } type Resolver struct{} func (r *Resolver) Admin(ctx context.Context, args struct { ID string Role string }) (*adminResolver, error) { if usr, ok := usersMap[args.ID]; ok { if usr.RoleField == args.Role { return &adminResolver{*usr}, nil } } err := fmt.Errorf("user with id=%s and role=%s does not exist", args.ID, args.Role) return nil, err } func (r *Resolver) User(ctx context.Context, args struct{ Id string }) (user, error) { if usr, ok := usersMap[args.Id]; ok { return *usr, nil } err := fmt.Errorf("user with id=%s does not exist", args.Id) return user{}, err } func (r *Resolver) Search(ctx context.Context, args struct{ Text string }) ([]*searchResult, error) { var result []*searchResult for _, usr := range users { if strings.Contains(usr.NameField, args.Text) { result = append(result, &searchResult{usr}) } } return result, nil } graphql-go-1.5.0/example/starwars/000077500000000000000000000000001435002731700170615ustar00rootroot00000000000000graphql-go-1.5.0/example/starwars/introspect.json000066400000000000000000001675041435002731700221630ustar00rootroot00000000000000{ "__schema": { "directives": [ { "args": [ { "defaultValue": "\"No longer supported\"", "description": "Explains why this element was deprecated, usually also including a suggestion\nfor how to access supported similar data. Formatted in\n[Markdown](https://daringfireball.net/projects/markdown/).", "name": "reason", "type": { "kind": "SCALAR", "name": "String", "ofType": null } } ], "description": "Marks an element of a GraphQL schema as no longer supported.", "locations": [ "FIELD_DEFINITION", "ENUM_VALUE", "ARGUMENT_DEFINITION" ], "name": "deprecated" }, { "args": [ { "defaultValue": null, "description": "Included when true.", "name": "if", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } } } ], "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", "locations": [ "FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT" ], "name": "include" }, { "args": [ { "defaultValue": null, "description": "Skipped when true.", "name": "if", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } } } ], "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", "locations": [ "FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT" ], "name": "skip" }, { "args": [ { "defaultValue": null, "description": "The URL should point to a human-readable specification of the data format, serialization, and coercion rules.", "name": "url", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } } ], "description": "Provides a scalar specification URL for specifying the behavior of custom scalar types.", "locations": [ "SCALAR" ], "name": "specifiedBy" } ], "mutationType": { "name": "Mutation" }, "queryType": { "name": "Query" }, "subscriptionType": null, "types": [ { "description": "The `Boolean` scalar type represents `true` or `false`.", "enumValues": null, "fields": null, "inputFields": null, "interfaces": null, "kind": "SCALAR", "name": "Boolean", "possibleTypes": null }, { "description": "A character from the Star Wars universe", "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": "The ID of the character", "isDeprecated": false, "name": "id", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": "The name of the character", "isDeprecated": false, "name": "name", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": "The friends of the character, or an empty list if they have none", "isDeprecated": false, "name": "friends", "type": { "kind": "LIST", "name": null, "ofType": { "kind": "INTERFACE", "name": "Character", "ofType": null } } }, { "args": [ { "defaultValue": null, "description": null, "name": "first", "type": { "kind": "SCALAR", "name": "Int", "ofType": null } }, { "defaultValue": null, "description": null, "name": "after", "type": { "kind": "SCALAR", "name": "ID", "ofType": null } } ], "deprecationReason": null, "description": "The friends of the character exposed as a connection with edges", "isDeprecated": false, "name": "friendsConnection", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "FriendsConnection", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": "The movies this character appears in", "isDeprecated": false, "name": "appearsIn", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "ENUM", "name": "Episode", "ofType": null } } } } } ], "inputFields": null, "interfaces": null, "kind": "INTERFACE", "name": "Character", "possibleTypes": [ { "kind": "OBJECT", "name": "Human", "ofType": null }, { "kind": "OBJECT", "name": "Droid", "ofType": null } ] }, { "description": "An autonomous mechanical character in the Star Wars universe", "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": "The ID of the droid", "isDeprecated": false, "name": "id", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": "What others call this droid", "isDeprecated": false, "name": "name", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": "This droid's friends, or an empty list if they have none", "isDeprecated": false, "name": "friends", "type": { "kind": "LIST", "name": null, "ofType": { "kind": "INTERFACE", "name": "Character", "ofType": null } } }, { "args": [ { "defaultValue": null, "description": null, "name": "first", "type": { "kind": "SCALAR", "name": "Int", "ofType": null } }, { "defaultValue": null, "description": null, "name": "after", "type": { "kind": "SCALAR", "name": "ID", "ofType": null } } ], "deprecationReason": null, "description": "The friends of the droid exposed as a connection with edges", "isDeprecated": false, "name": "friendsConnection", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "FriendsConnection", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": "The movies this droid appears in", "isDeprecated": false, "name": "appearsIn", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "ENUM", "name": "Episode", "ofType": null } } } } }, { "args": [], "deprecationReason": null, "description": "This droid's primary function", "isDeprecated": false, "name": "primaryFunction", "type": { "kind": "SCALAR", "name": "String", "ofType": null } } ], "inputFields": null, "interfaces": [ { "kind": "INTERFACE", "name": "Character", "ofType": null } ], "kind": "OBJECT", "name": "Droid", "possibleTypes": null }, { "description": "The episodes in the Star Wars trilogy", "enumValues": [ { "deprecationReason": null, "description": "Star Wars Episode IV: A New Hope, released in 1977.", "isDeprecated": false, "name": "NEWHOPE" }, { "deprecationReason": null, "description": "Star Wars Episode V: The Empire Strikes Back, released in 1980.", "isDeprecated": false, "name": "EMPIRE" }, { "deprecationReason": null, "description": "Star Wars Episode VI: Return of the Jedi, released in 1983.", "isDeprecated": false, "name": "JEDI" } ], "fields": null, "inputFields": null, "interfaces": null, "kind": "ENUM", "name": "Episode", "possibleTypes": null }, { "description": "The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).", "enumValues": null, "fields": null, "inputFields": null, "interfaces": null, "kind": "SCALAR", "name": "Float", "possibleTypes": null }, { "description": "A connection object for a character's friends", "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": "The total number of friends", "isDeprecated": false, "name": "totalCount", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Int", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": "The edges for each of the character's friends.", "isDeprecated": false, "name": "edges", "type": { "kind": "LIST", "name": null, "ofType": { "kind": "OBJECT", "name": "FriendsEdge", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": "A list of the friends, as a convenience when edges are not needed.", "isDeprecated": false, "name": "friends", "type": { "kind": "LIST", "name": null, "ofType": { "kind": "INTERFACE", "name": "Character", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": "Information for paginating this connection", "isDeprecated": false, "name": "pageInfo", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "PageInfo", "ofType": null } } } ], "inputFields": null, "interfaces": [], "kind": "OBJECT", "name": "FriendsConnection", "possibleTypes": null }, { "description": "An edge object for a character's friends", "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": "A cursor used for pagination", "isDeprecated": false, "name": "cursor", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": "The character represented by this friendship edge", "isDeprecated": false, "name": "node", "type": { "kind": "INTERFACE", "name": "Character", "ofType": null } } ], "inputFields": null, "interfaces": [], "kind": "OBJECT", "name": "FriendsEdge", "possibleTypes": null }, { "description": "A humanoid creature from the Star Wars universe", "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": "The ID of the human", "isDeprecated": false, "name": "id", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": "What this human calls themselves", "isDeprecated": false, "name": "name", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } }, { "args": [ { "defaultValue": "METER", "description": null, "name": "unit", "type": { "kind": "ENUM", "name": "LengthUnit", "ofType": null } } ], "deprecationReason": null, "description": "Height in the preferred unit, default is meters", "isDeprecated": false, "name": "height", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": "Mass in kilograms, or null if unknown", "isDeprecated": false, "name": "mass", "type": { "kind": "SCALAR", "name": "Float", "ofType": null } }, { "args": [], "deprecationReason": null, "description": "This human's friends, or an empty list if they have none", "isDeprecated": false, "name": "friends", "type": { "kind": "LIST", "name": null, "ofType": { "kind": "INTERFACE", "name": "Character", "ofType": null } } }, { "args": [ { "defaultValue": null, "description": null, "name": "first", "type": { "kind": "SCALAR", "name": "Int", "ofType": null } }, { "defaultValue": null, "description": null, "name": "after", "type": { "kind": "SCALAR", "name": "ID", "ofType": null } } ], "deprecationReason": null, "description": "The friends of the human exposed as a connection with edges", "isDeprecated": false, "name": "friendsConnection", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "FriendsConnection", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": "The movies this human appears in", "isDeprecated": false, "name": "appearsIn", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "ENUM", "name": "Episode", "ofType": null } } } } }, { "args": [], "deprecationReason": null, "description": "A list of starships this person has piloted, or an empty list if none", "isDeprecated": false, "name": "starships", "type": { "kind": "LIST", "name": null, "ofType": { "kind": "OBJECT", "name": "Starship", "ofType": null } } } ], "inputFields": null, "interfaces": [ { "kind": "INTERFACE", "name": "Character", "ofType": null } ], "kind": "OBJECT", "name": "Human", "possibleTypes": null }, { "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", "enumValues": null, "fields": null, "inputFields": null, "interfaces": null, "kind": "SCALAR", "name": "ID", "possibleTypes": null }, { "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.", "enumValues": null, "fields": null, "inputFields": null, "interfaces": null, "kind": "SCALAR", "name": "Int", "possibleTypes": null }, { "description": "Units of height", "enumValues": [ { "deprecationReason": null, "description": "The standard unit around the world", "isDeprecated": false, "name": "METER" }, { "deprecationReason": null, "description": "Primarily used in the United States", "isDeprecated": false, "name": "FOOT" } ], "fields": null, "inputFields": null, "interfaces": null, "kind": "ENUM", "name": "LengthUnit", "possibleTypes": null }, { "description": "The mutation type, represents all updates we can make to our data", "enumValues": null, "fields": [ { "args": [ { "defaultValue": null, "description": null, "name": "episode", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "ENUM", "name": "Episode", "ofType": null } } }, { "defaultValue": null, "description": null, "name": "review", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "INPUT_OBJECT", "name": "ReviewInput", "ofType": null } } } ], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "createReview", "type": { "kind": "OBJECT", "name": "Review", "ofType": null } } ], "inputFields": null, "interfaces": [], "kind": "OBJECT", "name": "Mutation", "possibleTypes": null }, { "description": "Information for paginating this connection", "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "startCursor", "type": { "kind": "SCALAR", "name": "ID", "ofType": null } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "endCursor", "type": { "kind": "SCALAR", "name": "ID", "ofType": null } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "hasNextPage", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } } } ], "inputFields": null, "interfaces": [], "kind": "OBJECT", "name": "PageInfo", "possibleTypes": null }, { "description": "The query type, represents all of the entry points into our object graph", "enumValues": null, "fields": [ { "args": [ { "defaultValue": "NEWHOPE", "description": null, "name": "episode", "type": { "kind": "ENUM", "name": "Episode", "ofType": null } } ], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "hero", "type": { "kind": "INTERFACE", "name": "Character", "ofType": null } }, { "args": [ { "defaultValue": null, "description": null, "name": "episode", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "ENUM", "name": "Episode", "ofType": null } } } ], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "reviews", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "OBJECT", "name": "Review", "ofType": null } } } }, { "args": [ { "defaultValue": null, "description": null, "name": "text", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } } ], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "search", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "UNION", "name": "SearchResult", "ofType": null } } } }, { "args": [ { "defaultValue": null, "description": null, "name": "id", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } } } ], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "character", "type": { "kind": "INTERFACE", "name": "Character", "ofType": null } }, { "args": [ { "defaultValue": null, "description": null, "name": "id", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } } } ], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "droid", "type": { "kind": "OBJECT", "name": "Droid", "ofType": null } }, { "args": [ { "defaultValue": null, "description": null, "name": "id", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } } } ], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "human", "type": { "kind": "OBJECT", "name": "Human", "ofType": null } }, { "args": [ { "defaultValue": null, "description": null, "name": "id", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } } } ], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "starship", "type": { "kind": "OBJECT", "name": "Starship", "ofType": null } } ], "inputFields": null, "interfaces": [], "kind": "OBJECT", "name": "Query", "possibleTypes": null }, { "description": "Represents a review for a movie", "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": "The number of stars this review gave, 1-5", "isDeprecated": false, "name": "stars", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Int", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": "Comment about the movie", "isDeprecated": false, "name": "commentary", "type": { "kind": "SCALAR", "name": "String", "ofType": null } } ], "inputFields": null, "interfaces": [], "kind": "OBJECT", "name": "Review", "possibleTypes": null }, { "description": "The input object sent when someone is creating a new review", "enumValues": null, "fields": null, "inputFields": [ { "defaultValue": null, "description": "0-5 stars", "name": "stars", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Int", "ofType": null } } }, { "defaultValue": null, "description": "Comment about the movie, optional", "name": "commentary", "type": { "kind": "SCALAR", "name": "String", "ofType": null } } ], "interfaces": null, "kind": "INPUT_OBJECT", "name": "ReviewInput", "possibleTypes": null }, { "description": null, "enumValues": null, "fields": null, "inputFields": null, "interfaces": null, "kind": "UNION", "name": "SearchResult", "possibleTypes": [ { "kind": "OBJECT", "name": "Human", "ofType": null }, { "kind": "OBJECT", "name": "Droid", "ofType": null }, { "kind": "OBJECT", "name": "Starship", "ofType": null } ] }, { "description": null, "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": "The ID of the starship", "isDeprecated": false, "name": "id", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": "The name of the starship", "isDeprecated": false, "name": "name", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } }, { "args": [ { "defaultValue": "METER", "description": null, "name": "unit", "type": { "kind": "ENUM", "name": "LengthUnit", "ofType": null } } ], "deprecationReason": null, "description": "Length of the starship, along the longest axis", "isDeprecated": false, "name": "length", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } } } ], "inputFields": null, "interfaces": [], "kind": "OBJECT", "name": "Starship", "possibleTypes": null }, { "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", "enumValues": null, "fields": null, "inputFields": null, "interfaces": null, "kind": "SCALAR", "name": "String", "possibleTypes": null }, { "description": null, "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "sdl", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } } ], "inputFields": null, "interfaces": [], "kind": "OBJECT", "name": "_Service", "possibleTypes": null }, { "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior\nin ways field arguments will not suffice, such as conditionally including or\nskipping a field. Directives provide this by describing additional information\nto the executor.", "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "name", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "description", "type": { "kind": "SCALAR", "name": "String", "ofType": null } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "locations", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "ENUM", "name": "__DirectiveLocation", "ofType": null } } } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "args", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } } } } } ], "inputFields": null, "interfaces": [], "kind": "OBJECT", "name": "__Directive", "possibleTypes": null }, { "description": "A Directive can be adjacent to many parts of the GraphQL language, a\n__DirectiveLocation describes one such possible adjacencies.", "enumValues": [ { "deprecationReason": null, "description": "Location adjacent to a query operation.", "isDeprecated": false, "name": "QUERY" }, { "deprecationReason": null, "description": "Location adjacent to a mutation operation.", "isDeprecated": false, "name": "MUTATION" }, { "deprecationReason": null, "description": "Location adjacent to a subscription operation.", "isDeprecated": false, "name": "SUBSCRIPTION" }, { "deprecationReason": null, "description": "Location adjacent to a field.", "isDeprecated": false, "name": "FIELD" }, { "deprecationReason": null, "description": "Location adjacent to a fragment definition.", "isDeprecated": false, "name": "FRAGMENT_DEFINITION" }, { "deprecationReason": null, "description": "Location adjacent to a fragment spread.", "isDeprecated": false, "name": "FRAGMENT_SPREAD" }, { "deprecationReason": null, "description": "Location adjacent to an inline fragment.", "isDeprecated": false, "name": "INLINE_FRAGMENT" }, { "deprecationReason": null, "description": "Location adjacent to a schema definition.", "isDeprecated": false, "name": "SCHEMA" }, { "deprecationReason": null, "description": "Location adjacent to a scalar definition.", "isDeprecated": false, "name": "SCALAR" }, { "deprecationReason": null, "description": "Location adjacent to an object type definition.", "isDeprecated": false, "name": "OBJECT" }, { "deprecationReason": null, "description": "Location adjacent to a field definition.", "isDeprecated": false, "name": "FIELD_DEFINITION" }, { "deprecationReason": null, "description": "Location adjacent to an argument definition.", "isDeprecated": false, "name": "ARGUMENT_DEFINITION" }, { "deprecationReason": null, "description": "Location adjacent to an interface definition.", "isDeprecated": false, "name": "INTERFACE" }, { "deprecationReason": null, "description": "Location adjacent to a union definition.", "isDeprecated": false, "name": "UNION" }, { "deprecationReason": null, "description": "Location adjacent to an enum definition.", "isDeprecated": false, "name": "ENUM" }, { "deprecationReason": null, "description": "Location adjacent to an enum value definition.", "isDeprecated": false, "name": "ENUM_VALUE" }, { "deprecationReason": null, "description": "Location adjacent to an input object type definition.", "isDeprecated": false, "name": "INPUT_OBJECT" }, { "deprecationReason": null, "description": "Location adjacent to an input object field definition.", "isDeprecated": false, "name": "INPUT_FIELD_DEFINITION" } ], "fields": null, "inputFields": null, "interfaces": null, "kind": "ENUM", "name": "__DirectiveLocation", "possibleTypes": null }, { "description": "One possible value for a given Enum. Enum values are unique values, not a\nplaceholder for a string or numeric value. However an Enum value is returned in\na JSON response as a string.", "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "name", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "description", "type": { "kind": "SCALAR", "name": "String", "ofType": null } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "isDeprecated", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "deprecationReason", "type": { "kind": "SCALAR", "name": "String", "ofType": null } } ], "inputFields": null, "interfaces": [], "kind": "OBJECT", "name": "__EnumValue", "possibleTypes": null }, { "description": "Object and Interface types are described by a list of Fields, each of which has\na name, potentially a list of arguments, and a return type.", "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "name", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "description", "type": { "kind": "SCALAR", "name": "String", "ofType": null } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "args", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } } } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "type", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "isDeprecated", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "deprecationReason", "type": { "kind": "SCALAR", "name": "String", "ofType": null } } ], "inputFields": null, "interfaces": [], "kind": "OBJECT", "name": "__Field", "possibleTypes": null }, { "description": "Arguments provided to Fields or Directives and the input fields of an\nInputObject are represented as Input Values which describe their type and\noptionally a default value.", "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "name", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "description", "type": { "kind": "SCALAR", "name": "String", "ofType": null } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "type", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": "A GraphQL-formatted string representing the default value for this input value.", "isDeprecated": false, "name": "defaultValue", "type": { "kind": "SCALAR", "name": "String", "ofType": null } } ], "inputFields": null, "interfaces": [], "kind": "OBJECT", "name": "__InputValue", "possibleTypes": null }, { "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all\navailable types and directives on the server, as well as the entry points for\nquery, mutation, and subscription operations.", "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": "A list of all types supported by this server.", "isDeprecated": false, "name": "types", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } } } } }, { "args": [], "deprecationReason": null, "description": "The type that query operations will be rooted at.", "isDeprecated": false, "name": "queryType", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": "If this server supports mutation, the type that mutation operations will be rooted at.", "isDeprecated": false, "name": "mutationType", "type": { "kind": "OBJECT", "name": "__Type", "ofType": null } }, { "args": [], "deprecationReason": null, "description": "If this server support subscription, the type that subscription operations will be rooted at.", "isDeprecated": false, "name": "subscriptionType", "type": { "kind": "OBJECT", "name": "__Type", "ofType": null } }, { "args": [], "deprecationReason": null, "description": "A list of all directives supported by this server.", "isDeprecated": false, "name": "directives", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Directive", "ofType": null } } } } } ], "inputFields": null, "interfaces": [], "kind": "OBJECT", "name": "__Schema", "possibleTypes": null }, { "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of\ntypes in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that\ntype. Scalar types provide no information beyond a name and description, while\nEnum types provide their values. Object and Interface types provide the fields\nthey describe. Abstract types, Union and Interface, provide the Object types\npossible at runtime. List and NonNull types compose other types.", "enumValues": null, "fields": [ { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "kind", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "ENUM", "name": "__TypeKind", "ofType": null } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "name", "type": { "kind": "SCALAR", "name": "String", "ofType": null } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "description", "type": { "kind": "SCALAR", "name": "String", "ofType": null } }, { "args": [ { "defaultValue": "false", "description": null, "name": "includeDeprecated", "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null } } ], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "fields", "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Field", "ofType": null } } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "interfaces", "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "possibleTypes", "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } } } }, { "args": [ { "defaultValue": "false", "description": null, "name": "includeDeprecated", "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null } } ], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "enumValues", "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__EnumValue", "ofType": null } } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "inputFields", "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } } } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "ofType", "type": { "kind": "OBJECT", "name": "__Type", "ofType": null } }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, "name": "specifiedByURL", "type": { "kind": "SCALAR", "name": "String", "ofType": null } } ], "inputFields": null, "interfaces": [], "kind": "OBJECT", "name": "__Type", "possibleTypes": null }, { "description": "An enum describing what kind of type a given `__Type` is.", "enumValues": [ { "deprecationReason": null, "description": "Indicates this type is a scalar.", "isDeprecated": false, "name": "SCALAR" }, { "deprecationReason": null, "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", "isDeprecated": false, "name": "OBJECT" }, { "deprecationReason": null, "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", "isDeprecated": false, "name": "INTERFACE" }, { "deprecationReason": null, "description": "Indicates this type is a union. `possibleTypes` is a valid field.", "isDeprecated": false, "name": "UNION" }, { "deprecationReason": null, "description": "Indicates this type is an enum. `enumValues` is a valid field.", "isDeprecated": false, "name": "ENUM" }, { "deprecationReason": null, "description": "Indicates this type is an input object. `inputFields` is a valid field.", "isDeprecated": false, "name": "INPUT_OBJECT" }, { "deprecationReason": null, "description": "Indicates this type is a list. `ofType` is a valid field.", "isDeprecated": false, "name": "LIST" }, { "deprecationReason": null, "description": "Indicates this type is a non-null. `ofType` is a valid field.", "isDeprecated": false, "name": "NON_NULL" } ], "fields": null, "inputFields": null, "interfaces": null, "kind": "ENUM", "name": "__TypeKind", "possibleTypes": null } ] } } graphql-go-1.5.0/example/starwars/server/000077500000000000000000000000001435002731700203675ustar00rootroot00000000000000graphql-go-1.5.0/example/starwars/server/server.go000066400000000000000000000035551435002731700222340ustar00rootroot00000000000000package main import ( "log" "net/http" "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/example/starwars" "github.com/graph-gophers/graphql-go/relay" ) var schema *graphql.Schema func init() { schema = graphql.MustParseSchema(starwars.Schema, &starwars.Resolver{}) } func main() { http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write(page) })) http.Handle("/query", &relay.Handler{Schema: schema}) log.Fatal(http.ListenAndServe(":8080", nil)) } var page = []byte(`
Loading...
`) graphql-go-1.5.0/example/starwars/starwars.go000066400000000000000000000346371435002731700212730ustar00rootroot00000000000000// Package starwars provides a example schema and resolver based on Star Wars characters. // // Source: https://github.com/graphql/graphql.github.io/blob/source/site/_core/swapiSchema.js package starwars import ( "encoding/base64" "fmt" "strconv" "strings" graphql "github.com/graph-gophers/graphql-go" ) var Schema = ` schema { query: Query mutation: Mutation } # The query type, represents all of the entry points into our object graph type Query { hero(episode: Episode = NEWHOPE): Character reviews(episode: Episode!): [Review]! search(text: String!): [SearchResult]! character(id: ID!): Character droid(id: ID!): Droid human(id: ID!): Human starship(id: ID!): Starship } # The mutation type, represents all updates we can make to our data type Mutation { createReview(episode: Episode!, review: ReviewInput!): Review } # The episodes in the Star Wars trilogy enum Episode { # Star Wars Episode IV: A New Hope, released in 1977. NEWHOPE # Star Wars Episode V: The Empire Strikes Back, released in 1980. EMPIRE # Star Wars Episode VI: Return of the Jedi, released in 1983. JEDI } # A character from the Star Wars universe interface Character { # The ID of the character id: ID! # The name of the character name: String! # The friends of the character, or an empty list if they have none friends: [Character] # The friends of the character exposed as a connection with edges friendsConnection(first: Int, after: ID): FriendsConnection! # The movies this character appears in appearsIn: [Episode!]! } # Units of height enum LengthUnit { # The standard unit around the world METER # Primarily used in the United States FOOT } # A humanoid creature from the Star Wars universe type Human implements Character { # The ID of the human id: ID! # What this human calls themselves name: String! # Height in the preferred unit, default is meters height(unit: LengthUnit = METER): Float! # Mass in kilograms, or null if unknown mass: Float # This human's friends, or an empty list if they have none friends: [Character] # The friends of the human exposed as a connection with edges friendsConnection(first: Int, after: ID): FriendsConnection! # The movies this human appears in appearsIn: [Episode!]! # A list of starships this person has piloted, or an empty list if none starships: [Starship] } # An autonomous mechanical character in the Star Wars universe type Droid implements Character { # The ID of the droid id: ID! # What others call this droid name: String! # This droid's friends, or an empty list if they have none friends: [Character] # The friends of the droid exposed as a connection with edges friendsConnection(first: Int, after: ID): FriendsConnection! # The movies this droid appears in appearsIn: [Episode!]! # This droid's primary function primaryFunction: String } # A connection object for a character's friends type FriendsConnection { # The total number of friends totalCount: Int! # The edges for each of the character's friends. edges: [FriendsEdge] # A list of the friends, as a convenience when edges are not needed. friends: [Character] # Information for paginating this connection pageInfo: PageInfo! } # An edge object for a character's friends type FriendsEdge { # A cursor used for pagination cursor: ID! # The character represented by this friendship edge node: Character } # Information for paginating this connection type PageInfo { startCursor: ID endCursor: ID hasNextPage: Boolean! } # Represents a review for a movie type Review { # The number of stars this review gave, 1-5 stars: Int! # Comment about the movie commentary: String } # The input object sent when someone is creating a new review input ReviewInput { # 0-5 stars stars: Int! # Comment about the movie, optional commentary: String } type Starship { # The ID of the starship id: ID! # The name of the starship name: String! # Length of the starship, along the longest axis length(unit: LengthUnit = METER): Float! } union SearchResult = Human | Droid | Starship ` type human struct { ID graphql.ID Name string Friends []graphql.ID AppearsIn []string Height float64 Mass int Starships []graphql.ID } var humans = []*human{ { ID: "1000", Name: "Luke Skywalker", Friends: []graphql.ID{"1002", "1003", "2000", "2001"}, AppearsIn: []string{"NEWHOPE", "EMPIRE", "JEDI"}, Height: 1.72, Mass: 77, Starships: []graphql.ID{"3001", "3003"}, }, { ID: "1001", Name: "Darth Vader", Friends: []graphql.ID{"1004"}, AppearsIn: []string{"NEWHOPE", "EMPIRE", "JEDI"}, Height: 2.02, Mass: 136, Starships: []graphql.ID{"3002"}, }, { ID: "1002", Name: "Han Solo", Friends: []graphql.ID{"1000", "1003", "2001"}, AppearsIn: []string{"NEWHOPE", "EMPIRE", "JEDI"}, Height: 1.8, Mass: 80, Starships: []graphql.ID{"3000", "3003"}, }, { ID: "1003", Name: "Leia Organa", Friends: []graphql.ID{"1000", "1002", "2000", "2001"}, AppearsIn: []string{"NEWHOPE", "EMPIRE", "JEDI"}, Height: 1.5, Mass: 49, }, { ID: "1004", Name: "Wilhuff Tarkin", Friends: []graphql.ID{"1001"}, AppearsIn: []string{"NEWHOPE"}, Height: 1.8, Mass: 0, }, } var humanData = make(map[graphql.ID]*human) func init() { for _, h := range humans { humanData[h.ID] = h } } type droid struct { ID graphql.ID Name string Friends []graphql.ID AppearsIn []string PrimaryFunction string } var droids = []*droid{ { ID: "2000", Name: "C-3PO", Friends: []graphql.ID{"1000", "1002", "1003", "2001"}, AppearsIn: []string{"NEWHOPE", "EMPIRE", "JEDI"}, PrimaryFunction: "Protocol", }, { ID: "2001", Name: "R2-D2", Friends: []graphql.ID{"1000", "1002", "1003"}, AppearsIn: []string{"NEWHOPE", "EMPIRE", "JEDI"}, PrimaryFunction: "Astromech", }, } var droidData = make(map[graphql.ID]*droid) func init() { for _, d := range droids { droidData[d.ID] = d } } type starship struct { ID graphql.ID Name string Length float64 } var starships = []*starship{ { ID: "3000", Name: "Millennium Falcon", Length: 34.37, }, { ID: "3001", Name: "X-Wing", Length: 12.5, }, { ID: "3002", Name: "TIE Advanced x1", Length: 9.2, }, { ID: "3003", Name: "Imperial shuttle", Length: 20, }, } var starshipData = make(map[graphql.ID]*starship) func init() { for _, s := range starships { starshipData[s.ID] = s } } type review struct { stars int32 commentary *string } var reviews = make(map[string][]*review) type Resolver struct{} func (r *Resolver) Hero(args struct{ Episode string }) *characterResolver { if args.Episode == "EMPIRE" { return &characterResolver{&humanResolver{humanData["1000"]}} } return &characterResolver{&droidResolver{droidData["2001"]}} } func (r *Resolver) Reviews(args struct{ Episode string }) []*reviewResolver { var l []*reviewResolver for _, review := range reviews[args.Episode] { l = append(l, &reviewResolver{review}) } return l } func (r *Resolver) Search(args struct{ Text string }) []*searchResultResolver { var l []*searchResultResolver for _, h := range humans { if strings.Contains(h.Name, args.Text) { l = append(l, &searchResultResolver{&humanResolver{h}}) } } for _, d := range droids { if strings.Contains(d.Name, args.Text) { l = append(l, &searchResultResolver{&droidResolver{d}}) } } for _, s := range starships { if strings.Contains(s.Name, args.Text) { l = append(l, &searchResultResolver{&starshipResolver{s}}) } } return l } func (r *Resolver) Character(args struct{ ID graphql.ID }) *characterResolver { if h := humanData[args.ID]; h != nil { return &characterResolver{&humanResolver{h}} } if d := droidData[args.ID]; d != nil { return &characterResolver{&droidResolver{d}} } return nil } func (r *Resolver) Human(args struct{ ID graphql.ID }) *humanResolver { if h := humanData[args.ID]; h != nil { return &humanResolver{h} } return nil } func (r *Resolver) Droid(args struct{ ID graphql.ID }) *droidResolver { if d := droidData[args.ID]; d != nil { return &droidResolver{d} } return nil } func (r *Resolver) Starship(args struct{ ID graphql.ID }) *starshipResolver { if s := starshipData[args.ID]; s != nil { return &starshipResolver{s} } return nil } func (r *Resolver) CreateReview(args *struct { Episode string Review *reviewInput }) *reviewResolver { review := &review{ stars: args.Review.Stars, commentary: args.Review.Commentary, } reviews[args.Episode] = append(reviews[args.Episode], review) return &reviewResolver{review} } type friendsConnectionArgs struct { First *int32 After *graphql.ID } type character interface { ID() graphql.ID Name() string Friends() *[]*characterResolver FriendsConnection(friendsConnectionArgs) (*friendsConnectionResolver, error) AppearsIn() []string } type characterResolver struct { character } func (r *characterResolver) ToHuman() (*humanResolver, bool) { c, ok := r.character.(*humanResolver) return c, ok } func (r *characterResolver) ToDroid() (*droidResolver, bool) { c, ok := r.character.(*droidResolver) return c, ok } type humanResolver struct { h *human } func (r *humanResolver) ID() graphql.ID { return r.h.ID } func (r *humanResolver) Name() string { return r.h.Name } func (r *humanResolver) Height(args struct{ Unit string }) float64 { return convertLength(r.h.Height, args.Unit) } func (r *humanResolver) Mass() *float64 { if r.h.Mass == 0 { return nil } f := float64(r.h.Mass) return &f } func (r *humanResolver) Friends() *[]*characterResolver { return resolveCharacters(r.h.Friends) } func (r *humanResolver) FriendsConnection(args friendsConnectionArgs) (*friendsConnectionResolver, error) { return newFriendsConnectionResolver(r.h.Friends, args) } func (r *humanResolver) AppearsIn() []string { return r.h.AppearsIn } func (r *humanResolver) Starships() *[]*starshipResolver { l := make([]*starshipResolver, len(r.h.Starships)) for i, id := range r.h.Starships { l[i] = &starshipResolver{starshipData[id]} } return &l } type droidResolver struct { d *droid } func (r *droidResolver) ID() graphql.ID { return r.d.ID } func (r *droidResolver) Name() string { return r.d.Name } func (r *droidResolver) Friends() *[]*characterResolver { return resolveCharacters(r.d.Friends) } func (r *droidResolver) FriendsConnection(args friendsConnectionArgs) (*friendsConnectionResolver, error) { return newFriendsConnectionResolver(r.d.Friends, args) } func (r *droidResolver) AppearsIn() []string { return r.d.AppearsIn } func (r *droidResolver) PrimaryFunction() *string { if r.d.PrimaryFunction == "" { return nil } return &r.d.PrimaryFunction } type starshipResolver struct { s *starship } func (r *starshipResolver) ID() graphql.ID { return r.s.ID } func (r *starshipResolver) Name() string { return r.s.Name } func (r *starshipResolver) Length(args struct{ Unit string }) float64 { return convertLength(r.s.Length, args.Unit) } type searchResultResolver struct { result interface{} } func (r *searchResultResolver) ToHuman() (*humanResolver, bool) { res, ok := r.result.(*humanResolver) return res, ok } func (r *searchResultResolver) ToDroid() (*droidResolver, bool) { res, ok := r.result.(*droidResolver) return res, ok } func (r *searchResultResolver) ToStarship() (*starshipResolver, bool) { res, ok := r.result.(*starshipResolver) return res, ok } func convertLength(meters float64, unit string) float64 { switch unit { case "METER": return meters case "FOOT": return meters * 3.28084 default: panic("invalid unit") } } func resolveCharacters(ids []graphql.ID) *[]*characterResolver { var characters []*characterResolver for _, id := range ids { if c := resolveCharacter(id); c != nil { characters = append(characters, c) } } return &characters } func resolveCharacter(id graphql.ID) *characterResolver { if h, ok := humanData[id]; ok { return &characterResolver{&humanResolver{h}} } if d, ok := droidData[id]; ok { return &characterResolver{&droidResolver{d}} } return nil } type reviewResolver struct { r *review } func (r *reviewResolver) Stars() int32 { return r.r.stars } func (r *reviewResolver) Commentary() *string { return r.r.commentary } type friendsConnectionResolver struct { ids []graphql.ID from int to int } func newFriendsConnectionResolver(ids []graphql.ID, args friendsConnectionArgs) (*friendsConnectionResolver, error) { from := 0 if args.After != nil { b, err := base64.StdEncoding.DecodeString(string(*args.After)) if err != nil { return nil, err } i, err := strconv.Atoi(strings.TrimPrefix(string(b), "cursor")) if err != nil { return nil, err } from = i } to := len(ids) if args.First != nil { to = from + int(*args.First) if to > len(ids) { to = len(ids) } } return &friendsConnectionResolver{ ids: ids, from: from, to: to, }, nil } func (r *friendsConnectionResolver) TotalCount() int32 { return int32(len(r.ids)) } func (r *friendsConnectionResolver) Edges() *[]*friendsEdgeResolver { l := make([]*friendsEdgeResolver, r.to-r.from) for i := range l { l[i] = &friendsEdgeResolver{ cursor: encodeCursor(r.from + i), id: r.ids[r.from+i], } } return &l } func (r *friendsConnectionResolver) Friends() *[]*characterResolver { return resolveCharacters(r.ids[r.from:r.to]) } func (r *friendsConnectionResolver) PageInfo() *pageInfoResolver { return &pageInfoResolver{ startCursor: encodeCursor(r.from), endCursor: encodeCursor(r.to - 1), hasNextPage: r.to < len(r.ids), } } func encodeCursor(i int) graphql.ID { return graphql.ID(base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("cursor%d", i+1)))) } type friendsEdgeResolver struct { cursor graphql.ID id graphql.ID } func (r *friendsEdgeResolver) Cursor() graphql.ID { return r.cursor } func (r *friendsEdgeResolver) Node() *characterResolver { return resolveCharacter(r.id) } type pageInfoResolver struct { startCursor graphql.ID endCursor graphql.ID hasNextPage bool } func (r *pageInfoResolver) StartCursor() *graphql.ID { return &r.startCursor } func (r *pageInfoResolver) EndCursor() *graphql.ID { return &r.endCursor } func (r *pageInfoResolver) HasNextPage() bool { return r.hasNextPage } type reviewInput struct { Stars int32 Commentary *string } graphql-go-1.5.0/go.mod000066400000000000000000000002671435002731700146730ustar00rootroot00000000000000module github.com/graph-gophers/graphql-go go 1.13 require ( github.com/opentracing/opentracing-go v1.2.0 go.opentelemetry.io/otel v1.6.3 go.opentelemetry.io/otel/trace v1.6.3 ) graphql-go-1.5.0/go.sum000066400000000000000000000046231435002731700147200ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= go.opentelemetry.io/otel v1.6.3 h1:FLOfo8f9JzFVFVyU+MSRJc2HdEAXQgm7pIv2uFKRSZE= go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI= go.opentelemetry.io/otel/trace v1.6.3 h1:IqN4L+5b0mPNjdXIiZ90Ni4Bl5BRkDQywePLWemd9bc= go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= graphql-go-1.5.0/gqltesting/000077500000000000000000000000001435002731700157415ustar00rootroot00000000000000graphql-go-1.5.0/gqltesting/subscriptions.go000066400000000000000000000052161435002731700212030ustar00rootroot00000000000000package gqltesting import ( "bytes" "context" "encoding/json" "strconv" "testing" graphql "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/errors" ) // TestResponse models the expected response type TestResponse struct { Data json.RawMessage Errors []*errors.QueryError } // TestSubscription is a GraphQL test case to be used with RunSubscribe. type TestSubscription struct { Name string Schema *graphql.Schema Query string OperationName string Variables map[string]interface{} ExpectedResults []TestResponse ExpectedErr error } // RunSubscribes runs the given GraphQL subscription test cases as subtests. func RunSubscribes(t *testing.T, tests []*TestSubscription) { for i, test := range tests { if test.Name == "" { test.Name = strconv.Itoa(i + 1) } t.Run(test.Name, func(t *testing.T) { RunSubscribe(t, test) }) } } // RunSubscribe runs a single GraphQL subscription test case. func RunSubscribe(t *testing.T, test *TestSubscription) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() c, err := test.Schema.Subscribe(ctx, test.Query, test.OperationName, test.Variables) if err != nil { if err.Error() != test.ExpectedErr.Error() { t.Fatalf("unexpected error: got %+v, want %+v", err, test.ExpectedErr) } return } var results []*graphql.Response for res := range c { results = append(results, res.(*graphql.Response)) } for i, expected := range test.ExpectedResults { res := results[i] checkErrorStrings(t, expected.Errors, res.Errors) resData, err := res.Data.MarshalJSON() if err != nil { t.Fatal(err) } got, err := formatJSON(resData) if err != nil { t.Fatalf("got: invalid JSON: %s; raw: %s", err, resData) } expectedData, err := expected.Data.MarshalJSON() if err != nil { t.Fatal(err) } want, err := formatJSON(expectedData) if err != nil { t.Fatalf("got: invalid JSON: %s; raw: %s", err, expectedData) } if !bytes.Equal(got, want) { t.Logf("got: %s", got) t.Logf("want: %s", want) t.Fail() } } } func checkErrorStrings(t *testing.T, expected, actual []*errors.QueryError) { expectedCount, actualCount := len(expected), len(actual) if expectedCount != actualCount { t.Fatalf("unexpected number of errors: want %d, got %d", expectedCount, actualCount) } if expectedCount > 0 { for i, want := range expected { got := actual[i] if got.Error() != want.Error() { t.Fatalf("unexpected error: got %+v, want %+v", got, want) } } // Return because we're done checking. return } for _, err := range actual { t.Errorf("unexpected error: '%s'", err) } } graphql-go-1.5.0/gqltesting/testing.go000066400000000000000000000050631435002731700177510ustar00rootroot00000000000000package gqltesting import ( "bytes" "context" "encoding/json" "fmt" "reflect" "sort" "strconv" "testing" graphql "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/errors" ) // Test is a GraphQL test case to be used with RunTest(s). type Test struct { Context context.Context Schema *graphql.Schema Query string OperationName string Variables map[string]interface{} ExpectedResult string ExpectedErrors []*errors.QueryError RawResponse bool } // RunTests runs the given GraphQL test cases as subtests. func RunTests(t *testing.T, tests []*Test) { if len(tests) == 1 { RunTest(t, tests[0]) return } for i, test := range tests { t.Run(strconv.Itoa(i+1), func(t *testing.T) { RunTest(t, test) }) } } // RunTest runs a single GraphQL test case. func RunTest(t *testing.T, test *Test) { if test.Context == nil { test.Context = context.Background() } result := test.Schema.Exec(test.Context, test.Query, test.OperationName, test.Variables) checkErrors(t, test.ExpectedErrors, result.Errors) if test.ExpectedResult == "" { if result.Data != nil { t.Fatalf("got: %s, want: null", result.Data) } return } // Verify JSON to avoid red herring errors. var got []byte if test.RawResponse { value, err := result.Data.MarshalJSON() if err != nil { t.Fatalf("got: unable to marshal JSON response: %s", err) } got = value } else { value, err := formatJSON(result.Data) if err != nil { t.Fatalf("got: invalid JSON: %s", err) } got = value } want, err := formatJSON([]byte(test.ExpectedResult)) if err != nil { t.Fatalf("want: invalid JSON: %s", err) } if !bytes.Equal(got, want) { t.Logf("got: %s", got) t.Logf("want: %s", want) t.Fail() } } func formatJSON(data []byte) ([]byte, error) { var v interface{} if err := json.Unmarshal(data, &v); err != nil { return nil, err } formatted, err := json.Marshal(v) if err != nil { return nil, err } return formatted, nil } func checkErrors(t *testing.T, want, got []*errors.QueryError) { sortErrors(want) sortErrors(got) // Clear the underlying error before the DeepEqual check. It's too // much to ask the tester to include the raw failing error. for _, err := range got { err.Err = nil } if !reflect.DeepEqual(got, want) { t.Fatalf("unexpected error: got %+v, want %+v", got, want) } } func sortErrors(errors []*errors.QueryError) { if len(errors) <= 1 { return } sort.Slice(errors, func(i, j int) bool { return fmt.Sprintf("%s", errors[i].Path) < fmt.Sprintf("%s", errors[j].Path) }) } graphql-go-1.5.0/graphql.go000066400000000000000000000261631435002731700155550ustar00rootroot00000000000000package graphql import ( "context" "encoding/json" "fmt" "time" "github.com/graph-gophers/graphql-go/errors" "github.com/graph-gophers/graphql-go/internal/common" "github.com/graph-gophers/graphql-go/internal/exec" "github.com/graph-gophers/graphql-go/internal/exec/resolvable" "github.com/graph-gophers/graphql-go/internal/exec/selected" "github.com/graph-gophers/graphql-go/internal/query" "github.com/graph-gophers/graphql-go/internal/schema" "github.com/graph-gophers/graphql-go/internal/validation" "github.com/graph-gophers/graphql-go/introspection" "github.com/graph-gophers/graphql-go/log" "github.com/graph-gophers/graphql-go/trace/noop" "github.com/graph-gophers/graphql-go/trace/tracer" "github.com/graph-gophers/graphql-go/types" ) // ParseSchema parses a GraphQL schema and attaches the given root resolver. It returns an error if // the Go type signature of the resolvers does not match the schema. If nil is passed as the // resolver, then the schema can not be executed, but it may be inspected (e.g. with ToJSON). func ParseSchema(schemaString string, resolver interface{}, opts ...SchemaOpt) (*Schema, error) { s := &Schema{ schema: schema.New(), maxParallelism: 10, tracer: noop.Tracer{}, logger: &log.DefaultLogger{}, panicHandler: &errors.DefaultPanicHandler{}, } for _, opt := range opts { opt(s) } if s.validationTracer == nil { if t, ok := s.tracer.(tracer.ValidationTracer); ok { s.validationTracer = t } else { s.validationTracer = &validationBridgingTracer{tracer: tracer.LegacyNoopValidationTracer{}} //nolint:staticcheck } } if err := schema.Parse(s.schema, schemaString, s.useStringDescriptions); err != nil { return nil, err } if err := s.validateSchema(); err != nil { return nil, err } r, err := resolvable.ApplyResolver(s.schema, resolver) if err != nil { return nil, err } s.res = r return s, nil } // MustParseSchema calls ParseSchema and panics on error. func MustParseSchema(schemaString string, resolver interface{}, opts ...SchemaOpt) *Schema { s, err := ParseSchema(schemaString, resolver, opts...) if err != nil { panic(err) } return s } // Schema represents a GraphQL schema with an optional resolver. type Schema struct { schema *types.Schema res *resolvable.Schema maxDepth int maxParallelism int tracer tracer.Tracer validationTracer tracer.ValidationTracer logger log.Logger panicHandler errors.PanicHandler useStringDescriptions bool disableIntrospection bool subscribeResolverTimeout time.Duration } func (s *Schema) ASTSchema() *types.Schema { return s.schema } // SchemaOpt is an option to pass to ParseSchema or MustParseSchema. type SchemaOpt func(*Schema) // UseStringDescriptions enables the usage of double quoted and triple quoted // strings as descriptions as per the June 2018 spec // https://facebook.github.io/graphql/June2018/. When this is not enabled, // comments are parsed as descriptions instead. func UseStringDescriptions() SchemaOpt { return func(s *Schema) { s.useStringDescriptions = true } } // UseFieldResolvers specifies whether to use struct field resolvers func UseFieldResolvers() SchemaOpt { return func(s *Schema) { s.schema.UseFieldResolvers = true } } // MaxDepth specifies the maximum field nesting depth in a query. The default is 0 which disables max depth checking. func MaxDepth(n int) SchemaOpt { return func(s *Schema) { s.maxDepth = n } } // MaxParallelism specifies the maximum number of resolvers per request allowed to run in parallel. The default is 10. func MaxParallelism(n int) SchemaOpt { return func(s *Schema) { s.maxParallelism = n } } // Tracer is used to trace queries and fields. It defaults to tracer.Noop. func Tracer(t tracer.Tracer) SchemaOpt { return func(s *Schema) { s.tracer = t } } // ValidationTracer is used to trace validation errors. It defaults to tracer.LegacyNoopValidationTracer. // Deprecated: context is needed to support tracing correctly. Use a Tracer which implements tracer.ValidationTracer. func ValidationTracer(tracer tracer.LegacyValidationTracer) SchemaOpt { //nolint:staticcheck return func(s *Schema) { s.validationTracer = &validationBridgingTracer{tracer: tracer} } } // Logger is used to log panics during query execution. It defaults to exec.DefaultLogger. func Logger(logger log.Logger) SchemaOpt { return func(s *Schema) { s.logger = logger } } // PanicHandler is used to customize the panic errors during query execution. // It defaults to errors.DefaultPanicHandler. func PanicHandler(panicHandler errors.PanicHandler) SchemaOpt { return func(s *Schema) { s.panicHandler = panicHandler } } // DisableIntrospection disables introspection queries. func DisableIntrospection() SchemaOpt { return func(s *Schema) { s.disableIntrospection = true } } // SubscribeResolverTimeout is an option to control the amount of time // we allow for a single subscribe message resolver to complete it's job // before it times out and returns an error to the subscriber. func SubscribeResolverTimeout(timeout time.Duration) SchemaOpt { return func(s *Schema) { s.subscribeResolverTimeout = timeout } } // Response represents a typical response of a GraphQL server. It may be encoded to JSON directly or // it may be further processed to a custom response type, for example to include custom error data. // Errors are intentionally serialized first based on the advice in https://github.com/facebook/graphql/commit/7b40390d48680b15cb93e02d46ac5eb249689876#diff-757cea6edf0288677a9eea4cfc801d87R107 type Response struct { Errors []*errors.QueryError `json:"errors,omitempty"` Data json.RawMessage `json:"data,omitempty"` Extensions map[string]interface{} `json:"extensions,omitempty"` } // Validate validates the given query with the schema. func (s *Schema) Validate(queryString string) []*errors.QueryError { return s.ValidateWithVariables(queryString, nil) } // ValidateWithVariables validates the given query with the schema and the input variables. func (s *Schema) ValidateWithVariables(queryString string, variables map[string]interface{}) []*errors.QueryError { doc, qErr := query.Parse(queryString) if qErr != nil { return []*errors.QueryError{qErr} } return validation.Validate(s.schema, doc, variables, s.maxDepth) } // Exec executes the given query with the schema's resolver. It panics if the schema was created // without a resolver. If the context get cancelled, no further resolvers will be called and a // the context error will be returned as soon as possible (not immediately). func (s *Schema) Exec(ctx context.Context, queryString string, operationName string, variables map[string]interface{}) *Response { if !s.res.Resolver.IsValid() { panic("schema created without resolver, can not exec") } return s.exec(ctx, queryString, operationName, variables, s.res) } func (s *Schema) exec(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, res *resolvable.Schema) *Response { doc, qErr := query.Parse(queryString) if qErr != nil { return &Response{Errors: []*errors.QueryError{qErr}} } validationFinish := s.validationTracer.TraceValidation(ctx) errs := validation.Validate(s.schema, doc, variables, s.maxDepth) validationFinish(errs) if len(errs) != 0 { return &Response{Errors: errs} } op, err := getOperation(doc, operationName) if err != nil { return &Response{Errors: []*errors.QueryError{errors.Errorf("%s", err)}} } // If the optional "operationName" POST parameter is not provided then // use the query's operation name for improved tracing. if operationName == "" { operationName = op.Name.Name } // Subscriptions are not valid in Exec. Use schema.Subscribe() instead. if op.Type == query.Subscription { return &Response{Errors: []*errors.QueryError{{Message: "graphql-ws protocol header is missing"}}} } if op.Type == query.Mutation { if _, ok := s.schema.EntryPoints["mutation"]; !ok { return &Response{Errors: []*errors.QueryError{{Message: "no mutations are offered by the schema"}}} } } // Fill in variables with the defaults from the operation if variables == nil { variables = make(map[string]interface{}, len(op.Vars)) } for _, v := range op.Vars { if _, ok := variables[v.Name.Name]; !ok && v.Default != nil { variables[v.Name.Name] = v.Default.Deserialize(nil) } } r := &exec.Request{ Request: selected.Request{ Doc: doc, Vars: variables, Schema: s.schema, DisableIntrospection: s.disableIntrospection, }, Limiter: make(chan struct{}, s.maxParallelism), Tracer: s.tracer, Logger: s.logger, PanicHandler: s.panicHandler, } varTypes := make(map[string]*introspection.Type) for _, v := range op.Vars { t, err := common.ResolveType(v.Type, s.schema.Resolve) if err != nil { return &Response{Errors: []*errors.QueryError{err}} } varTypes[v.Name.Name] = introspection.WrapType(t) } traceCtx, finish := s.tracer.TraceQuery(ctx, queryString, operationName, variables, varTypes) data, errs := r.Execute(traceCtx, res, op) finish(errs) return &Response{ Data: data, Errors: errs, } } func (s *Schema) validateSchema() error { // https://graphql.github.io/graphql-spec/June2018/#sec-Root-Operation-Types // > The query root operation type must be provided and must be an Object type. if err := validateRootOp(s.schema, "query", true); err != nil { return err } // > The mutation root operation type is optional; if it is not provided, the service does not support mutations. // > If it is provided, it must be an Object type. if err := validateRootOp(s.schema, "mutation", false); err != nil { return err } // > Similarly, the subscription root operation type is also optional; if it is not provided, the service does not // > support subscriptions. If it is provided, it must be an Object type. if err := validateRootOp(s.schema, "subscription", false); err != nil { return err } return nil } type validationBridgingTracer struct { tracer tracer.LegacyValidationTracer //nolint:staticcheck } func (t *validationBridgingTracer) TraceValidation(context.Context) func([]*errors.QueryError) { return t.tracer.TraceValidation() } func validateRootOp(s *types.Schema, name string, mandatory bool) error { t, ok := s.EntryPoints[name] if !ok { if mandatory { return fmt.Errorf("root operation %q must be defined", name) } return nil } if t.Kind() != "OBJECT" { return fmt.Errorf("root operation %q must be an OBJECT", name) } return nil } func getOperation(document *types.ExecutableDefinition, operationName string) (*types.OperationDefinition, error) { if len(document.Operations) == 0 { return nil, fmt.Errorf("no operations in query document") } if operationName == "" { if len(document.Operations) > 1 { return nil, fmt.Errorf("more than one operation in query document and no operation name given") } for _, op := range document.Operations { return op, nil // return the one and only operation } } op := document.Operations.Get(operationName) if op == nil { return nil, fmt.Errorf("no operation with name %q", operationName) } return op, nil } graphql-go-1.5.0/graphql_test.go000066400000000000000000002427251435002731700166200ustar00rootroot00000000000000package graphql_test import ( "context" "errors" "fmt" "sync" "testing" "time" "github.com/graph-gophers/graphql-go" gqlerrors "github.com/graph-gophers/graphql-go/errors" "github.com/graph-gophers/graphql-go/example/starwars" "github.com/graph-gophers/graphql-go/gqltesting" "github.com/graph-gophers/graphql-go/introspection" "github.com/graph-gophers/graphql-go/trace/tracer" ) type helloWorldResolver1 struct{} func (r *helloWorldResolver1) Hello() string { return "Hello world!" } type helloWorldResolver2 struct{} func (r *helloWorldResolver2) Hello(ctx context.Context) (string, error) { return "Hello world!", nil } type helloSnakeResolver1 struct{} func (r *helloSnakeResolver1) HelloHTML() string { return "Hello snake!" } func (r *helloSnakeResolver1) SayHello(args struct{ FullName string }) string { return "Hello " + args.FullName + "!" } type helloSnakeResolver2 struct{} func (r *helloSnakeResolver2) HelloHTML(ctx context.Context) (string, error) { return "Hello snake!", nil } func (r *helloSnakeResolver2) SayHello(ctx context.Context, args struct{ FullName string }) (string, error) { return "Hello " + args.FullName + "!", nil } type theNumberResolver struct { number int32 } func (r *theNumberResolver) TheNumber() int32 { return r.number } func (r *theNumberResolver) ChangeTheNumber(args struct{ NewNumber int32 }) *theNumberResolver { r.number = args.NewNumber return r } type timeResolver struct{} func (r *timeResolver) AddHour(args struct{ Time graphql.Time }) graphql.Time { return graphql.Time{Time: args.Time.Add(time.Hour)} } type echoResolver struct{} func (r *echoResolver) Echo(args struct{ Value *string }) *string { return args.Value } var starwarsSchema = graphql.MustParseSchema(starwars.Schema, &starwars.Resolver{}) type ResolverError interface { error Extensions() map[string]interface{} } type resolverNotFoundError struct { Code string `json:"code"` Message string `json:"message"` } func (e resolverNotFoundError) Error() string { return fmt.Sprintf("Error [%s]: %s", e.Code, e.Message) } func (e resolverNotFoundError) Extensions() map[string]interface{} { return map[string]interface{}{ "code": e.Code, "message": e.Message, } } type findDroidResolver struct{} func (r *findDroidResolver) FindDroid(ctx context.Context) (string, error) { return "", resolverNotFoundError{ Code: "NotFound", Message: "This is not the droid you are looking for", } } var ( droidNotFoundError = resolverNotFoundError{ Code: "NotFound", Message: "This is not the droid you are looking for", } quoteError = errors.New("Bleep bloop") r2d2 = &droidResolver{name: "R2-D2"} c3po = &droidResolver{name: "C-3PO"} notFoundDroid = &droidResolver{err: droidNotFoundError} ) type findDroidsResolver struct{} func (r *findDroidsResolver) FindDroids(ctx context.Context) []*droidResolver { return []*droidResolver{r2d2, notFoundDroid, c3po} } func (r *findDroidsResolver) FindNilDroids(ctx context.Context) *[]*droidResolver { return &[]*droidResolver{r2d2, nil, c3po} } type findDroidOrHumanResolver struct{} func (r *findDroidOrHumanResolver) FindHuman(ctx context.Context) (*string, error) { human := "human" return &human, nil } func (r *findDroidOrHumanResolver) FindDroid(ctx context.Context) (*droidResolver, error) { return nil, notFoundDroid.err } type droidResolver struct { name string err error } func (d *droidResolver) Name() (string, error) { if d.err != nil { return "", d.err } return d.name, nil } func (d *droidResolver) Quotes() ([]string, error) { switch d.name { case r2d2.name: return nil, quoteError case c3po.name: return []string{"We're doomed!", "R2-D2, where are you?"}, nil } return nil, nil } type discussPlanResolver struct{} func (r *discussPlanResolver) DismissVader(ctx context.Context) (string, error) { return "", errors.New("I find your lack of faith disturbing") } func TestHelloWorld(t *testing.T) { t.Parallel() gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { hello: String! } `, &helloWorldResolver1{}), Query: ` { hello } `, ExpectedResult: ` { "hello": "Hello world!" } `, }, { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { hello: String! } `, &helloWorldResolver2{}), Query: ` { hello } `, ExpectedResult: ` { "hello": "Hello world!" } `, }, }) } func TestHelloSnake(t *testing.T) { t.Parallel() gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { hello_html: String! } `, &helloSnakeResolver1{}), Query: ` { hello_html } `, ExpectedResult: ` { "hello_html": "Hello snake!" } `, }, { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { hello_html: String! } `, &helloSnakeResolver2{}), Query: ` { hello_html } `, ExpectedResult: ` { "hello_html": "Hello snake!" } `, }, }) } func TestHelloSnakeArguments(t *testing.T) { t.Parallel() gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { say_hello(full_name: String!): String! } `, &helloSnakeResolver1{}), Query: ` { say_hello(full_name: "Rob Pike") } `, ExpectedResult: ` { "say_hello": "Hello Rob Pike!" } `, }, { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { say_hello(full_name: String!): String! } `, &helloSnakeResolver2{}), Query: ` { say_hello(full_name: "Rob Pike") } `, ExpectedResult: ` { "say_hello": "Hello Rob Pike!" } `, }, }) } func TestRootOperations_invalidSchema(t *testing.T) { type args struct { Schema string } type want struct { Error string } testTable := map[string]struct { Args args Want want }{ "Empty schema": { Want: want{Error: `root operation "query" must be defined`}, }, "Query declared by schema, but type not present": { Args: args{ Schema: ` schema { query: Query } `, }, Want: want{Error: `graphql: type "Query" not found`}, }, "Query as incorrect type": { Args: args{ Schema: ` schema { query: String } `, }, Want: want{Error: `root operation "query" must be an OBJECT`}, }, "Query with custom name, schema omitted": { Args: args{ Schema: ` type QueryType { hello: String! } `, }, Want: want{Error: `root operation "query" must be defined`}, }, "Mutation as incorrect type": { Args: args{ Schema: ` schema { query: Query mutation: String } type Query { thing: String } `, }, Want: want{Error: `root operation "mutation" must be an OBJECT`}, }, "Mutation declared by schema, but type not present": { Args: args{ Schema: ` schema { query: Query mutation: Mutation } type Query { hello: String! } `, }, Want: want{Error: `graphql: type "Mutation" not found`}, }, } for name, tt := range testTable { tt := tt t.Run(name, func(t *testing.T) { t.Parallel() _, err := graphql.ParseSchema(tt.Args.Schema, nil) if err == nil || err.Error() != tt.Want.Error { t.Logf("got: %v", err) t.Logf("want: %s", tt.Want.Error) t.Fail() } }) } } func TestRootOperations_validSchema(t *testing.T) { type resolver struct { helloSaidResolver helloWorldResolver1 theNumberResolver } gqltesting.RunTests(t, []*gqltesting.Test{ { // Query only, default name with `schema` omitted Schema: graphql.MustParseSchema(` type Query { hello: String! } `, &resolver{}), Query: `{ hello }`, ExpectedResult: `{"hello": "Hello world!"}`, }, { // Query only, default name with `schema` present Schema: graphql.MustParseSchema(` schema { query: Query } type Query { hello: String! } `, &resolver{}), Query: `{ hello }`, ExpectedResult: `{"hello": "Hello world!"}`, }, { // Query only, custom name Schema: graphql.MustParseSchema(` schema { query: QueryType } type QueryType { hello: String! } `, &resolver{}), Query: `{ hello }`, ExpectedResult: `{"hello": "Hello world!"}`, }, { // Query+Mutation, default names with `schema` omitted Schema: graphql.MustParseSchema(` type Query { hello: String! } type Mutation { changeTheNumber(newNumber: Int!): ChangedNumber! } type ChangedNumber { theNumber: Int! } `, &resolver{}), Query: ` mutation { changeTheNumber(newNumber: 1) { theNumber } } `, ExpectedResult: `{"changeTheNumber": {"theNumber": 1}}`, }, { // Query+Mutation, custom names Schema: graphql.MustParseSchema(` schema { query: QueryType mutation: MutationType } type QueryType { hello: String! } type MutationType { changeTheNumber(newNumber: Int!): ChangedNumber! } type ChangedNumber { theNumber: Int! } `, &resolver{}), Query: ` mutation { changeTheNumber(newNumber: 1) { theNumber } } `, ExpectedResult: `{"changeTheNumber": {"theNumber": 1}}`, }, { // Mutation with custom name, schema omitted Schema: graphql.MustParseSchema(` type Query { hello: String! } type MutationType { changeTheNumber(newNumber: Int!): ChangedNumber! } type ChangedNumber { theNumber: Int! } `, &resolver{}), Query: ` mutation { changeTheNumber(newNumber: 1) { theNumber } } `, ExpectedErrors: []*gqlerrors.QueryError{{Message: "no mutations are offered by the schema"}}, }, { // Explicit schema without mutation field Schema: graphql.MustParseSchema(` schema { query: Query } type Query { hello: String! } type Mutation { changeTheNumber(newNumber: Int!): ChangedNumber! } type ChangedNumber { theNumber: Int! } `, &resolver{}), Query: ` mutation { changeTheNumber(newNumber: 1) { theNumber } } `, ExpectedErrors: []*gqlerrors.QueryError{{Message: "no mutations are offered by the schema"}}, }, }) } func TestBasic(t *testing.T) { gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: starwarsSchema, Query: ` { hero { id name friends { name } } } `, ExpectedResult: ` { "hero": { "id": "2001", "name": "R2-D2", "friends": [ { "name": "Luke Skywalker" }, { "name": "Han Solo" }, { "name": "Leia Organa" } ] } } `, }, }) } type testEmbeddedStructResolver struct{} func (_ *testEmbeddedStructResolver) Course() courseResolver { return courseResolver{ CourseMeta: CourseMeta{ Name: "Biology", Timestamps: Timestamps{CreatedAt: "yesterday", UpdatedAt: "today"}, }, Instructor: Instructor{Name: "Socrates"}, } } type courseResolver struct { CourseMeta Instructor Instructor } type CourseMeta struct { Name string Timestamps } type Instructor struct { Name string } type Timestamps struct { CreatedAt string UpdatedAt string } func TestEmbeddedStruct(t *testing.T) { gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { course: Course! } type Course { name: String! createdAt: String! updatedAt: String! instructor: Instructor! } type Instructor { name: String! } `, &testEmbeddedStructResolver{}, graphql.UseFieldResolvers()), Query: ` { course{ name createdAt updatedAt instructor { name } } } `, ExpectedResult: ` { "course": { "name": "Biology", "createdAt": "yesterday", "updatedAt": "today", "instructor": { "name":"Socrates" } } } `, }, }) } type testNilInterfaceResolver struct{} func (r *testNilInterfaceResolver) A() interface{ Z() int32 } { return nil } func (r *testNilInterfaceResolver) B() (interface{ Z() int32 }, error) { return nil, errors.New("x") } func (r *testNilInterfaceResolver) C() (interface{ Z() int32 }, error) { return nil, nil } func TestNilInterface(t *testing.T) { t.Parallel() gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { a: T b: T c: T } type T { z: Int! } `, &testNilInterfaceResolver{}), Query: ` { a { z } b { z } c { z } } `, ExpectedResult: ` { "a": null, "b": null, "c": null } `, ExpectedErrors: []*gqlerrors.QueryError{ { Message: "x", Path: []interface{}{"b"}, ResolverError: errors.New("x"), }, }, }, }) } func TestErrorPropagationInLists(t *testing.T) { t.Parallel() gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { findDroids: [Droid!]! } type Droid { name: String! } `, &findDroidsResolver{}), Query: ` { findDroids { name } } `, ExpectedResult: ` null `, ExpectedErrors: []*gqlerrors.QueryError{ { Message: droidNotFoundError.Error(), Path: []interface{}{"findDroids", 1, "name"}, ResolverError: droidNotFoundError, Extensions: map[string]interface{}{"code": droidNotFoundError.Code, "message": droidNotFoundError.Message}, }, }, }, { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { findDroids: [Droid]! } type Droid { name: String! } `, &findDroidsResolver{}), Query: ` { findDroids { name } } `, ExpectedResult: ` { "findDroids": [ { "name": "R2-D2" }, null, { "name": "C-3PO" } ] } `, ExpectedErrors: []*gqlerrors.QueryError{ { Message: droidNotFoundError.Error(), Path: []interface{}{"findDroids", 1, "name"}, ResolverError: droidNotFoundError, Extensions: map[string]interface{}{"code": droidNotFoundError.Code, "message": droidNotFoundError.Message}, }, }, }, { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { findNilDroids: [Droid!] } type Droid { name: String! } `, &findDroidsResolver{}), Query: ` { findNilDroids { name } } `, ExpectedResult: ` { "findNilDroids": null } `, ExpectedErrors: []*gqlerrors.QueryError{ { Message: `graphql: got nil for non-null "Droid"`, Path: []interface{}{"findNilDroids", 1}, }, }, }, { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { findNilDroids: [Droid] } type Droid { name: String! } `, &findDroidsResolver{}), Query: ` { findNilDroids { name } } `, ExpectedResult: ` { "findNilDroids": [ { "name": "R2-D2" }, null, { "name": "C-3PO" } ] } `, }, { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { findDroids: [Droid]! } type Droid { quotes: [String!]! } `, &findDroidsResolver{}), Query: ` { findDroids { quotes } } `, ExpectedResult: ` { "findDroids": [ null, { "quotes": [] }, { "quotes": [ "We're doomed!", "R2-D2, where are you?" ] } ] } `, ExpectedErrors: []*gqlerrors.QueryError{ { Message: quoteError.Error(), ResolverError: quoteError, Path: []interface{}{"findDroids", 0, "quotes"}, }, }, }, { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { findNilDroids: [Droid!] } type Droid { name: String! quotes: [String!]! } `, &findDroidsResolver{}), Query: ` { findNilDroids { name quotes } } `, ExpectedResult: ` { "findNilDroids": null } `, ExpectedErrors: []*gqlerrors.QueryError{ { Message: quoteError.Error(), ResolverError: quoteError, Path: []interface{}{"findNilDroids", 0, "quotes"}, }, { Message: `graphql: got nil for non-null "Droid"`, Path: []interface{}{"findNilDroids", 1}, }, }, }, }) } func TestErrorWithExtensions(t *testing.T) { t.Parallel() gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { FindDroid: Droid! FindHuman: String } type Droid { Name: String! } `, &findDroidOrHumanResolver{}), Query: ` { FindDroid { Name } FindHuman } `, ExpectedResult: ` null `, ExpectedErrors: []*gqlerrors.QueryError{ { Message: droidNotFoundError.Error(), Path: []interface{}{"FindDroid"}, ResolverError: droidNotFoundError, Extensions: map[string]interface{}{"code": droidNotFoundError.Code, "message": droidNotFoundError.Message}, }, }, }, }) } func TestErrorWithNoExtensions(t *testing.T) { t.Parallel() err := errors.New("I find your lack of faith disturbing") gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { DismissVader: String! } `, &discussPlanResolver{}), Query: ` { DismissVader } `, ExpectedResult: ` null `, ExpectedErrors: []*gqlerrors.QueryError{ { Message: err.Error(), Path: []interface{}{"DismissVader"}, ResolverError: err, Extensions: nil, }, }, }, }) } func TestArguments(t *testing.T) { gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: starwarsSchema, Query: ` { human(id: "1000") { name height } } `, ExpectedResult: ` { "human": { "name": "Luke Skywalker", "height": 1.72 } } `, }, { Schema: starwarsSchema, Query: ` { human(id: "1000") { name height(unit: FOOT) } } `, ExpectedResult: ` { "human": { "name": "Luke Skywalker", "height": 5.6430448 } } `, }, }) } func TestAliases(t *testing.T) { gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: starwarsSchema, Query: ` { empireHero: hero(episode: EMPIRE) { name } jediHero: hero(episode: JEDI) { name } } `, ExpectedResult: ` { "empireHero": { "name": "Luke Skywalker" }, "jediHero": { "name": "R2-D2" } } `, }, }) } func TestFragments(t *testing.T) { gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: starwarsSchema, Query: ` { leftComparison: hero(episode: EMPIRE) { ...comparisonFields ...height } rightComparison: hero(episode: JEDI) { ...comparisonFields ...height } } fragment comparisonFields on Character { name appearsIn friends { name } } fragment height on Human { height } `, ExpectedResult: ` { "leftComparison": { "name": "Luke Skywalker", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ], "friends": [ { "name": "Han Solo" }, { "name": "Leia Organa" }, { "name": "C-3PO" }, { "name": "R2-D2" } ], "height": 1.72 }, "rightComparison": { "name": "R2-D2", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ], "friends": [ { "name": "Luke Skywalker" }, { "name": "Han Solo" }, { "name": "Leia Organa" } ] } } `, }, }) } func TestVariables(t *testing.T) { gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: starwarsSchema, Query: ` query HeroNameAndFriends($episode: Episode) { hero(episode: $episode) { name } } `, Variables: map[string]interface{}{ "episode": "JEDI", }, ExpectedResult: ` { "hero": { "name": "R2-D2" } } `, }, { Schema: starwarsSchema, Query: ` query HeroNameAndFriends($episode: Episode) { hero(episode: $episode) { name } } `, Variables: map[string]interface{}{ "episode": "EMPIRE", }, ExpectedResult: ` { "hero": { "name": "Luke Skywalker" } } `, }, { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { echo(value: String): String } `, &echoResolver{}), Query: ` query Echo($value:String = "default"){ echo(value:$value) } `, ExpectedResult: ` { "echo": "default" } `, }, }) } func TestSkipDirective(t *testing.T) { gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: starwarsSchema, Query: ` query Hero($episode: Episode, $withoutFriends: Boolean!) { hero(episode: $episode) { name friends @skip(if: $withoutFriends) { name } } } `, Variables: map[string]interface{}{ "episode": "JEDI", "withoutFriends": true, }, ExpectedResult: ` { "hero": { "name": "R2-D2" } } `, }, { Schema: starwarsSchema, Query: ` query Hero($episode: Episode, $withoutFriends: Boolean!) { hero(episode: $episode) { name friends @skip(if: $withoutFriends) { name } } } `, Variables: map[string]interface{}{ "episode": "JEDI", "withoutFriends": false, }, ExpectedResult: ` { "hero": { "name": "R2-D2", "friends": [ { "name": "Luke Skywalker" }, { "name": "Han Solo" }, { "name": "Leia Organa" } ] } } `, }, }) } func TestIncludeDirective(t *testing.T) { gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: starwarsSchema, Query: ` query Hero($episode: Episode, $withFriends: Boolean!) { hero(episode: $episode) { name ...friendsFragment @include(if: $withFriends) } } fragment friendsFragment on Character { friends { name } } `, Variables: map[string]interface{}{ "episode": "JEDI", "withFriends": false, }, ExpectedResult: ` { "hero": { "name": "R2-D2" } } `, }, { Schema: starwarsSchema, Query: ` query Hero($episode: Episode, $withFriends: Boolean!) { hero(episode: $episode) { name ...friendsFragment @include(if: $withFriends) } } fragment friendsFragment on Character { friends { name } } `, Variables: map[string]interface{}{ "episode": "JEDI", "withFriends": true, }, ExpectedResult: ` { "hero": { "name": "R2-D2", "friends": [ { "name": "Luke Skywalker" }, { "name": "Han Solo" }, { "name": "Leia Organa" } ] } } `, }, }) } type testDeprecatedDirectiveResolver struct{} func (r *testDeprecatedDirectiveResolver) A() int32 { return 0 } func (r *testDeprecatedDirectiveResolver) B() int32 { return 0 } func (r *testDeprecatedDirectiveResolver) C() int32 { return 0 } func TestDeprecatedDirective(t *testing.T) { t.Parallel() gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { a: Int! b: Int! @deprecated c: Int! @deprecated(reason: "We don't like it") } `, &testDeprecatedDirectiveResolver{}), Query: ` { __type(name: "Query") { fields { name } allFields: fields(includeDeprecated: true) { name isDeprecated deprecationReason } } } `, ExpectedResult: ` { "__type": { "fields": [ { "name": "a" } ], "allFields": [ { "name": "a", "isDeprecated": false, "deprecationReason": null }, { "name": "b", "isDeprecated": true, "deprecationReason": "No longer supported" }, { "name": "c", "isDeprecated": true, "deprecationReason": "We don't like it" } ] } } `, }, { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { } enum Test { A B @deprecated C @deprecated(reason: "We don't like it") } `, &testDeprecatedDirectiveResolver{}), Query: ` { __type(name: "Test") { enumValues { name } allEnumValues: enumValues(includeDeprecated: true) { name isDeprecated deprecationReason } } } `, ExpectedResult: ` { "__type": { "enumValues": [ { "name": "A" } ], "allEnumValues": [ { "name": "A", "isDeprecated": false, "deprecationReason": null }, { "name": "B", "isDeprecated": true, "deprecationReason": "No longer supported" }, { "name": "C", "isDeprecated": true, "deprecationReason": "We don't like it" } ] } } `, }, }) } func TestSpecifiedByDirective(t *testing.T) { gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { } scalar UUID @specifiedBy( url: "https://tools.ietf.org/html/rfc4122" ) `, &struct{}{}), Query: ` query { __type(name: "UUID") { name specifiedByURL } } `, Variables: map[string]interface{}{}, ExpectedResult: ` { "__type": { "name": "UUID", "specifiedByURL": "https://tools.ietf.org/html/rfc4122" } } `, }, }) } type testBadEnumResolver struct{} func (r *testBadEnumResolver) Hero() *testBadEnumCharacterResolver { return &testBadEnumCharacterResolver{} } type testBadEnumCharacterResolver struct{} func (r *testBadEnumCharacterResolver) Name() string { return "Spock" } func (r *testBadEnumCharacterResolver) AppearsIn() []string { return []string{"STAR_TREK"} } func TestUnknownType(t *testing.T) { gqltesting.RunTest(t, &gqltesting.Test{ Schema: starwarsSchema, Query: ` query TypeInfo { __type(name: "unknown-type") { name } } `, ExpectedResult: ` { "__type": null } `, }) } func TestEnums(t *testing.T) { gqltesting.RunTests(t, []*gqltesting.Test{ // Valid input enum supplied in query text { Schema: starwarsSchema, Query: ` query HeroForEpisode { hero(episode: EMPIRE) { name } } `, ExpectedResult: ` { "hero": { "name": "Luke Skywalker" } } `, }, // Invalid input enum supplied in query text { Schema: starwarsSchema, Query: ` query HeroForEpisode { hero(episode: WRATH_OF_KHAN) { name } } `, ExpectedErrors: []*gqlerrors.QueryError{ { Message: "Argument \"episode\" has invalid value WRATH_OF_KHAN.\nExpected type \"Episode\", found WRATH_OF_KHAN.", Locations: []gqlerrors.Location{{Column: 20, Line: 3}}, Rule: "ArgumentsOfCorrectType", }, }, }, // Valid input enum supplied in variables { Schema: starwarsSchema, Query: ` query HeroForEpisode($episode: Episode!) { hero(episode: $episode) { name } } `, Variables: map[string]interface{}{"episode": "JEDI"}, ExpectedResult: ` { "hero": { "name": "R2-D2" } } `, }, // Invalid input enum supplied in variables { Schema: starwarsSchema, Query: ` query HeroForEpisode($episode: Episode!) { hero(episode: $episode) { name } } `, Variables: map[string]interface{}{"episode": "FINAL_FRONTIER"}, ExpectedErrors: []*gqlerrors.QueryError{ { Message: "Variable \"episode\" has invalid value FINAL_FRONTIER.\nExpected type \"Episode\", found FINAL_FRONTIER.", Locations: []gqlerrors.Location{{Column: 26, Line: 2}}, Rule: "VariablesOfCorrectType", }, }, }, // Valid enum value in response { Schema: starwarsSchema, Query: ` query Hero { hero { name appearsIn } } `, ExpectedResult: ` { "hero": { "name": "R2-D2", "appearsIn": ["NEWHOPE","EMPIRE","JEDI"] } } `, }, // Invalid enum value in response { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { hero: Character } enum Episode { NEWHOPE EMPIRE JEDI } type Character { name: String! appearsIn: [Episode!]! } `, &testBadEnumResolver{}), Query: ` query Hero { hero { name appearsIn } } `, ExpectedResult: `{ "hero": null }`, ExpectedErrors: []*gqlerrors.QueryError{ { Message: "Invalid value STAR_TREK.\nExpected type Episode, found STAR_TREK.", Path: []interface{}{"hero", "appearsIn", 0}, }, }, }, }) } type testDeprecatedArgsResolver struct{} func (r *testDeprecatedArgsResolver) A(args struct{ B *string }) int32 { return 0 } func TestDeprecatedArgs(t *testing.T) { graphql.MustParseSchema(` schema { query: Query } type Query { a(b: String @deprecated): Int! } `, &testDeprecatedArgsResolver{}) } func TestInlineFragments(t *testing.T) { gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: starwarsSchema, Query: ` query HeroForEpisode($episode: Episode!) { hero(episode: $episode) { name ... on Droid { primaryFunction } ... on Human { height } } } `, Variables: map[string]interface{}{ "episode": "JEDI", }, ExpectedResult: ` { "hero": { "name": "R2-D2", "primaryFunction": "Astromech" } } `, }, { Schema: starwarsSchema, Query: ` query HeroForEpisode($episode: Episode!) { hero(episode: $episode) { name ... on Droid { primaryFunction } ... on Human { height } } } `, Variables: map[string]interface{}{ "episode": "EMPIRE", }, ExpectedResult: ` { "hero": { "name": "Luke Skywalker", "height": 1.72 } } `, }, { Schema: starwarsSchema, Query: ` query CharacterSearch { search(text: "C-3PO") { ... on Character { name } } } `, ExpectedResult: ` { "search": [ { "name": "C-3PO" } ] } `, }, { Schema: starwarsSchema, Query: ` query CharacterSearch { hero { ... on Character { ... on Human { name } ... on Droid { name } } } } `, ExpectedResult: ` { "hero": { "name": "R2-D2" } } `, }, { Schema: socialSchema, Query: ` query { admin(id: "0x01") { ... on User { email } ... on Person { name } } } `, ExpectedResult: ` { "admin": { "email": "Albus@hogwarts.com", "name": "Albus Dumbledore" } } `, }, }) } func TestTypeName(t *testing.T) { gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: starwarsSchema, Query: ` { search(text: "an") { __typename ... on Human { name } ... on Droid { name } ... on Starship { name } } } `, ExpectedResult: ` { "search": [ { "__typename": "Human", "name": "Han Solo" }, { "__typename": "Human", "name": "Leia Organa" }, { "__typename": "Starship", "name": "TIE Advanced x1" } ] } `, }, { Schema: starwarsSchema, Query: ` { human(id: "1000") { __typename name } } `, ExpectedResult: ` { "human": { "__typename": "Human", "name": "Luke Skywalker" } } `, }, { Schema: starwarsSchema, Query: ` { hero { __typename name ... on Character { ...Droid name __typename } } } fragment Droid on Droid { name __typename } `, RawResponse: true, ExpectedResult: `{"hero":{"__typename":"Droid","name":"R2-D2"}}`, }, }) } func TestConnections(t *testing.T) { gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: starwarsSchema, Query: ` { hero { name friendsConnection { totalCount pageInfo { startCursor endCursor hasNextPage } edges { cursor node { name } } } } } `, ExpectedResult: ` { "hero": { "name": "R2-D2", "friendsConnection": { "totalCount": 3, "pageInfo": { "startCursor": "Y3Vyc29yMQ==", "endCursor": "Y3Vyc29yMw==", "hasNextPage": false }, "edges": [ { "cursor": "Y3Vyc29yMQ==", "node": { "name": "Luke Skywalker" } }, { "cursor": "Y3Vyc29yMg==", "node": { "name": "Han Solo" } }, { "cursor": "Y3Vyc29yMw==", "node": { "name": "Leia Organa" } } ] } } } `, }, { Schema: starwarsSchema, Query: ` { hero { name friendsConnection(first: 1, after: "Y3Vyc29yMQ==") { totalCount pageInfo { startCursor endCursor hasNextPage } edges { cursor node { name } } } }, moreFriends: hero { name friendsConnection(first: 1, after: "Y3Vyc29yMg==") { totalCount pageInfo { startCursor endCursor hasNextPage } edges { cursor node { name } } } } } `, ExpectedResult: ` { "hero": { "name": "R2-D2", "friendsConnection": { "totalCount": 3, "pageInfo": { "startCursor": "Y3Vyc29yMg==", "endCursor": "Y3Vyc29yMg==", "hasNextPage": true }, "edges": [ { "cursor": "Y3Vyc29yMg==", "node": { "name": "Han Solo" } } ] } }, "moreFriends": { "name": "R2-D2", "friendsConnection": { "totalCount": 3, "pageInfo": { "startCursor": "Y3Vyc29yMw==", "endCursor": "Y3Vyc29yMw==", "hasNextPage": false }, "edges": [ { "cursor": "Y3Vyc29yMw==", "node": { "name": "Leia Organa" } } ] } } } `, }, }) } func TestMutation(t *testing.T) { gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: starwarsSchema, Query: ` { reviews(episode: JEDI) { stars commentary } } `, ExpectedResult: ` { "reviews": [] } `, }, { Schema: starwarsSchema, Query: ` mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) { createReview(episode: $ep, review: $review) { stars commentary } } `, Variables: map[string]interface{}{ "ep": "JEDI", "review": map[string]interface{}{ "stars": 5, "commentary": "This is a great movie!", }, }, ExpectedResult: ` { "createReview": { "stars": 5, "commentary": "This is a great movie!" } } `, }, { Schema: starwarsSchema, Query: ` mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) { createReview(episode: $ep, review: $review) { stars commentary } } `, Variables: map[string]interface{}{ "ep": "EMPIRE", "review": map[string]interface{}{ "stars": float64(4), }, }, ExpectedResult: ` { "createReview": { "stars": 4, "commentary": null } } `, }, { Schema: starwarsSchema, Query: ` { reviews(episode: JEDI) { stars commentary } } `, ExpectedResult: ` { "reviews": [{ "stars": 5, "commentary": "This is a great movie!" }] } `, }, }) } func TestIntrospection(t *testing.T) { gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: starwarsSchema, Query: ` { __schema { types { name } } } `, ExpectedResult: ` { "__schema": { "types": [ { "name": "Boolean" }, { "name": "Character" }, { "name": "Droid" }, { "name": "Episode" }, { "name": "Float" }, { "name": "FriendsConnection" }, { "name": "FriendsEdge" }, { "name": "Human" }, { "name": "ID" }, { "name": "Int" }, { "name": "LengthUnit" }, { "name": "Mutation" }, { "name": "PageInfo" }, { "name": "Query" }, { "name": "Review" }, { "name": "ReviewInput" }, { "name": "SearchResult" }, { "name": "Starship" }, { "name": "String" }, { "name": "_Service" }, { "name": "__Directive" }, { "name": "__DirectiveLocation" }, { "name": "__EnumValue" }, { "name": "__Field" }, { "name": "__InputValue" }, { "name": "__Schema" }, { "name": "__Type" }, { "name": "__TypeKind" } ] } } `, }, { Schema: starwarsSchema, Query: ` { __schema { queryType { name } } } `, ExpectedResult: ` { "__schema": { "queryType": { "name": "Query" } } } `, }, { Schema: starwarsSchema, Query: ` { a: __type(name: "Droid") { name kind interfaces { name } possibleTypes { name } }, b: __type(name: "Character") { name kind interfaces { name } possibleTypes { name } } c: __type(name: "SearchResult") { name kind interfaces { name } possibleTypes { name } } } `, ExpectedResult: ` { "a": { "name": "Droid", "kind": "OBJECT", "interfaces": [ { "name": "Character" } ], "possibleTypes": null }, "b": { "name": "Character", "kind": "INTERFACE", "interfaces": null, "possibleTypes": [ { "name": "Human" }, { "name": "Droid" } ] }, "c": { "name": "SearchResult", "kind": "UNION", "interfaces": null, "possibleTypes": [ { "name": "Human" }, { "name": "Droid" }, { "name": "Starship" } ] } } `, }, { Schema: starwarsSchema, Query: ` { __type(name: "Droid") { name fields { name args { name type { name } defaultValue } type { name kind } } } } `, ExpectedResult: ` { "__type": { "name": "Droid", "fields": [ { "name": "id", "args": [], "type": { "name": null, "kind": "NON_NULL" } }, { "name": "name", "args": [], "type": { "name": null, "kind": "NON_NULL" } }, { "name": "friends", "args": [], "type": { "name": null, "kind": "LIST" } }, { "name": "friendsConnection", "args": [ { "name": "first", "type": { "name": "Int" }, "defaultValue": null }, { "name": "after", "type": { "name": "ID" }, "defaultValue": null } ], "type": { "name": null, "kind": "NON_NULL" } }, { "name": "appearsIn", "args": [], "type": { "name": null, "kind": "NON_NULL" } }, { "name": "primaryFunction", "args": [], "type": { "name": "String", "kind": "SCALAR" } } ] } } `, }, { Schema: starwarsSchema, Query: ` { __type(name: "Episode") { enumValues { name } } } `, ExpectedResult: ` { "__type": { "enumValues": [ { "name": "NEWHOPE" }, { "name": "EMPIRE" }, { "name": "JEDI" } ] } } `, }, { Schema: starwarsSchema, Query: ` { __schema { directives { name description locations args { name description type { kind ofType { kind name } } } } } } `, ExpectedResult: ` { "__schema": { "directives": [ { "name": "deprecated", "description": "Marks an element of a GraphQL schema as no longer supported.", "locations": [ "FIELD_DEFINITION", "ENUM_VALUE", "ARGUMENT_DEFINITION" ], "args": [ { "name": "reason", "description": "Explains why this element was deprecated, usually also including a suggestion\nfor how to access supported similar data. Formatted in\n[Markdown](https://daringfireball.net/projects/markdown/).", "type": { "kind": "SCALAR", "ofType": null } } ] }, { "name": "include", "description": "Directs the executor to include this field or fragment only when the ` + "`" + `if` + "`" + ` argument is true.", "locations": [ "FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT" ], "args": [ { "name": "if", "description": "Included when true.", "type": { "kind": "NON_NULL", "ofType": { "kind": "SCALAR", "name": "Boolean" } } } ] }, { "name": "skip", "description": "Directs the executor to skip this field or fragment when the ` + "`" + `if` + "`" + ` argument is true.", "locations": [ "FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT" ], "args": [ { "name": "if", "description": "Skipped when true.", "type": { "kind": "NON_NULL", "ofType": { "kind": "SCALAR", "name": "Boolean" } } } ] }, { "name": "specifiedBy", "description": "Provides a scalar specification URL for specifying the behavior of custom scalar types.", "locations": [ "SCALAR" ], "args": [ { "name": "url", "description": "The URL should point to a human-readable specification of the data format, serialization, and coercion rules.", "type": { "kind": "NON_NULL", "ofType": { "kind": "SCALAR", "name": "String" } } } ] } ] } } `, }, }) } var starwarsSchemaNoIntrospection = graphql.MustParseSchema(starwars.Schema, &starwars.Resolver{}, []graphql.SchemaOpt{graphql.DisableIntrospection()}...) func TestIntrospectionDisableIntrospection(t *testing.T) { gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: starwarsSchemaNoIntrospection, Query: ` { __schema { types { name } } } `, ExpectedResult: ` { } `, }, { Schema: starwarsSchemaNoIntrospection, Query: ` { __schema { queryType { name } } } `, ExpectedResult: ` { } `, }, { Schema: starwarsSchemaNoIntrospection, Query: ` { a: __type(name: "Droid") { name kind interfaces { name } possibleTypes { name } }, b: __type(name: "Character") { name kind interfaces { name } possibleTypes { name } } c: __type(name: "SearchResult") { name kind interfaces { name } possibleTypes { name } } } `, ExpectedResult: ` { } `, }, { Schema: starwarsSchemaNoIntrospection, Query: ` { __type(name: "Droid") { name fields { name args { name type { name } defaultValue } type { name kind } } } } `, ExpectedResult: ` { } `, }, { Schema: starwarsSchemaNoIntrospection, Query: ` { __type(name: "Episode") { enumValues { name } } } `, ExpectedResult: ` { } `, }, { Schema: starwarsSchemaNoIntrospection, Query: ` { __schema { directives { name description locations args { name description type { kind ofType { kind name } } } } } } `, ExpectedResult: ` { } `, }, { Schema: starwarsSchemaNoIntrospection, Query: ` { search(text: "an") { __typename ... on Human { name } ... on Droid { name } ... on Starship { name } } } `, ExpectedResult: ` { "search": [ { "__typename": "Human", "name": "Han Solo" }, { "__typename": "Human", "name": "Leia Organa" }, { "__typename": "Starship", "name": "TIE Advanced x1" } ] } `, }, }) } func TestMutationOrder(t *testing.T) { t.Parallel() gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: graphql.MustParseSchema(` schema { query: Query mutation: Mutation } type Query { theNumber: Int! } type Mutation { changeTheNumber(newNumber: Int!): Query } `, &theNumberResolver{}), Query: ` mutation { first: changeTheNumber(newNumber: 1) { theNumber } second: changeTheNumber(newNumber: 3) { theNumber } third: changeTheNumber(newNumber: 2) { theNumber } } `, ExpectedResult: ` { "first": { "theNumber": 1 }, "second": { "theNumber": 3 }, "third": { "theNumber": 2 } } `, }, }) } func TestTime(t *testing.T) { t.Parallel() gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { addHour(time: Time = "2001-02-03T04:05:06Z"): Time! } scalar Time `, &timeResolver{}), Query: ` query($t: Time!) { a: addHour(time: $t) b: addHour } `, Variables: map[string]interface{}{ "t": time.Date(2000, 2, 3, 4, 5, 6, 0, time.UTC), }, ExpectedResult: ` { "a": "2000-02-03T05:05:06Z", "b": "2001-02-03T05:05:06Z" } `, }, }) } type resolverWithUnexportedMethod struct{} func (r *resolverWithUnexportedMethod) changeTheNumber(args struct{ NewNumber int32 }) int32 { return args.NewNumber } func TestUnexportedMethod(t *testing.T) { t.Parallel() _, err := graphql.ParseSchema(` schema { mutation: Mutation } type Mutation { changeTheNumber(newNumber: Int!): Int! } `, &resolverWithUnexportedMethod{}) if err == nil { t.Error("error expected") } } type resolverWithUnexportedField struct{} func (r *resolverWithUnexportedField) ChangeTheNumber(args struct{ newNumber int32 }) int32 { return args.newNumber } func TestUnexportedField(t *testing.T) { t.Parallel() _, err := graphql.ParseSchema(` schema { mutation: Mutation } type Mutation { changeTheNumber(newNumber: Int!): Int! } `, &resolverWithUnexportedField{}) if err == nil { t.Error("error expected") } } type StringEnum string const ( EnumOption1 StringEnum = "Option1" EnumOption2 StringEnum = "Option2" ) type IntEnum int const ( IntEnum0 IntEnum = iota IntEnum1 ) func (e IntEnum) String() string { switch int(e) { case 0: return "Int0" case 1: return "Int1" default: return "IntN" } } func (IntEnum) ImplementsGraphQLType(name string) bool { return name == "IntEnum" } func (e *IntEnum) UnmarshalGraphQL(input interface{}) error { if str, ok := input.(string); ok { switch str { case "Int0": *e = IntEnum(0) case "Int1": *e = IntEnum(1) default: *e = IntEnum(-1) } return nil } return fmt.Errorf("wrong type for IntEnum: %T", input) } type inputResolver struct{} func (r *inputResolver) Int(args struct{ Value int32 }) int32 { return args.Value } func (r *inputResolver) Float(args struct{ Value float64 }) float64 { return args.Value } func (r *inputResolver) String(args struct{ Value string }) string { return args.Value } func (r *inputResolver) Boolean(args struct{ Value bool }) bool { return args.Value } func (r *inputResolver) Nullable(args struct{ Value *int32 }) *int32 { return args.Value } func (r *inputResolver) List(args struct{ Value []*struct{ V int32 } }) []int32 { l := make([]int32, len(args.Value)) for i, entry := range args.Value { l[i] = entry.V } return l } func (r *inputResolver) NullableList(args struct{ Value *[]*struct{ V int32 } }) *[]*int32 { if args.Value == nil { return nil } l := make([]*int32, len(*args.Value)) for i, entry := range *args.Value { if entry != nil { l[i] = &entry.V } } return &l } func (r *inputResolver) StringEnumValue(args struct{ Value string }) string { return args.Value } func (r *inputResolver) NullableStringEnumValue(args struct{ Value *string }) *string { return args.Value } func (r *inputResolver) StringEnum(args struct{ Value StringEnum }) StringEnum { return args.Value } func (r *inputResolver) NullableStringEnum(args struct{ Value *StringEnum }) *StringEnum { return args.Value } func (r *inputResolver) IntEnumValue(args struct{ Value string }) string { return args.Value } func (r *inputResolver) NullableIntEnumValue(args struct{ Value *string }) *string { return args.Value } func (r *inputResolver) IntEnum(args struct{ Value IntEnum }) IntEnum { return args.Value } func (r *inputResolver) NullableIntEnum(args struct{ Value *IntEnum }) *IntEnum { return args.Value } type recursive struct { Next *recursive } func (r *inputResolver) Recursive(args struct{ Value *recursive }) int32 { n := int32(0) v := args.Value for v != nil { v = v.Next n++ } return n } func (r *inputResolver) ID(args struct{ Value graphql.ID }) graphql.ID { return args.Value } func TestInput(t *testing.T) { t.Parallel() coercionSchema := graphql.MustParseSchema(` schema { query: Query } type Query { int(value: Int!): Int! float(value: Float!): Float! string(value: String!): String! boolean(value: Boolean!): Boolean! nullable(value: Int): Int list(value: [Input!]!): [Int!]! nullableList(value: [Input]): [Int] stringEnumValue(value: StringEnum!): StringEnum! nullableStringEnumValue(value: StringEnum): StringEnum stringEnum(value: StringEnum!): StringEnum! nullableStringEnum(value: StringEnum): StringEnum intEnumValue(value: IntEnum!): IntEnum! nullableIntEnumValue(value: IntEnum): IntEnum intEnum(value: IntEnum!): IntEnum! nullableIntEnum(value: IntEnum): IntEnum recursive(value: RecursiveInput!): Int! id(value: ID!): ID! } input Input { v: Int! } input RecursiveInput { next: RecursiveInput } enum StringEnum { Option1 Option2 } enum IntEnum { Int0 Int1 } `, &inputResolver{}) gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: coercionSchema, Query: ` { int(value: 42) float1: float(value: 42) float2: float(value: 42.5) string(value: "foo") boolean(value: true) nullable1: nullable(value: 42) nullable2: nullable(value: null) list1: list(value: [{v: 41}, {v: 42}, {v: 43}]) list2: list(value: {v: 42}) nullableList1: nullableList(value: [{v: 41}, null, {v: 43}]) nullableList2: nullableList(value: null) stringEnumValue(value: Option1) nullableStringEnumValue1: nullableStringEnum(value: Option1) nullableStringEnumValue2: nullableStringEnum(value: null) stringEnum(value: Option2) nullableStringEnum1: nullableStringEnum(value: Option2) nullableStringEnum2: nullableStringEnum(value: null) intEnumValue(value: Int1) nullableIntEnumValue1: nullableIntEnumValue(value: Int1) nullableIntEnumValue2: nullableIntEnumValue(value: null) intEnum(value: Int1) nullableIntEnum1: nullableIntEnum(value: Int1) nullableIntEnum2: nullableIntEnum(value: null) recursive(value: {next: {next: {}}}) intID: id(value: 1234) strID: id(value: "1234") } `, ExpectedResult: ` { "int": 42, "float1": 42, "float2": 42.5, "string": "foo", "boolean": true, "nullable1": 42, "nullable2": null, "list1": [41, 42, 43], "list2": [42], "nullableList1": [41, null, 43], "nullableList2": null, "stringEnumValue": "Option1", "nullableStringEnumValue1": "Option1", "nullableStringEnumValue2": null, "stringEnum": "Option2", "nullableStringEnum1": "Option2", "nullableStringEnum2": null, "intEnumValue": "Int1", "nullableIntEnumValue1": "Int1", "nullableIntEnumValue2": null, "intEnum": "Int1", "nullableIntEnum1": "Int1", "nullableIntEnum2": null, "recursive": 3, "intID": "1234", "strID": "1234" } `, }, }) } type inputArgumentsHello struct{} type inputArgumentsScalarMismatch1 struct{} type inputArgumentsScalarMismatch2 struct{} type inputArgumentsObjectMismatch1 struct{} type inputArgumentsObjectMismatch2 struct{} type inputArgumentsObjectMismatch3 struct{} type fieldNameMismatch struct{} type helloInput struct { Name string } type helloOutput struct { Name string } func (*fieldNameMismatch) Hello() helloOutput { return helloOutput{} } type helloInputMismatch struct { World string } func (r *inputArgumentsHello) Hello(args struct{ Input *helloInput }) string { return "Hello " + args.Input.Name + "!" } func (r *inputArgumentsScalarMismatch1) Hello(name string) string { return "Hello " + name + "!" } func (r *inputArgumentsScalarMismatch2) Hello(args struct{ World string }) string { return "Hello " + args.World + "!" } func (r *inputArgumentsObjectMismatch1) Hello(in helloInput) string { return "Hello " + in.Name + "!" } func (r *inputArgumentsObjectMismatch2) Hello(args struct{ Input *helloInputMismatch }) string { return "Hello " + args.Input.World + "!" } func (r *inputArgumentsObjectMismatch3) Hello(args struct{ Input *struct{ Thing string } }) string { return "Hello " + args.Input.Thing + "!" } func TestInputArguments_failSchemaParsing(t *testing.T) { type args struct { Resolver interface{} Schema string Opts []graphql.SchemaOpt } type want struct { Error string } testTable := map[string]struct { Args args Want want }{ "Non-input type used with field arguments": { Args: args{ Resolver: &inputArgumentsHello{}, Schema: ` schema { query: Query } type Query { hello(input: HelloInput): String! } type HelloInput { name: String } `, }, Want: want{Error: "field \"Input\": type of kind OBJECT can not be used as input\n\tused by (*graphql_test.inputArgumentsHello).Hello"}, }, "Missing Args Wrapper for scalar input": { Args: args{ Resolver: &inputArgumentsScalarMismatch1{}, Schema: ` schema { query: Query } type Query { hello(name: String): String! } input HelloInput { name: String } `, }, Want: want{Error: "expected struct or pointer to struct, got string (hint: missing `args struct { ... }` wrapper for field arguments?)\n\tused by (*graphql_test.inputArgumentsScalarMismatch1).Hello"}, }, "Mismatching field name for scalar input": { Args: args{ Resolver: &inputArgumentsScalarMismatch2{}, Schema: ` schema { query: Query } type Query { hello(name: String): String! } `, }, Want: want{Error: "struct { World string } does not define field \"name\" (hint: missing `args struct { ... }` wrapper for field arguments, or missing field on input struct)\n\tused by (*graphql_test.inputArgumentsScalarMismatch2).Hello"}, }, "Missing Args Wrapper for Input type": { Args: args{ Resolver: &inputArgumentsObjectMismatch1{}, Schema: ` schema { query: Query } type Query { hello(input: HelloInput): String! } input HelloInput { name: String } `, }, Want: want{Error: "graphql_test.helloInput does not define field \"input\" (hint: missing `args struct { ... }` wrapper for field arguments, or missing field on input struct)\n\tused by (*graphql_test.inputArgumentsObjectMismatch1).Hello"}, }, "Input struct missing field": { Args: args{ Resolver: &inputArgumentsObjectMismatch2{}, Schema: ` schema { query: Query } type Query { hello(input: HelloInput): String! } input HelloInput { name: String } `, }, Want: want{Error: "field \"Input\": *graphql_test.helloInputMismatch does not define field \"name\" (hint: missing `args struct { ... }` wrapper for field arguments, or missing field on input struct)\n\tused by (*graphql_test.inputArgumentsObjectMismatch2).Hello"}, }, "Inline Input struct missing field": { Args: args{ Resolver: &inputArgumentsObjectMismatch3{}, Schema: ` schema { query: Query } type Query { hello(input: HelloInput): String! } input HelloInput { name: String } `, }, Want: want{Error: "field \"Input\": *struct { Thing string } does not define field \"name\" (hint: missing `args struct { ... }` wrapper for field arguments, or missing field on input struct)\n\tused by (*graphql_test.inputArgumentsObjectMismatch3).Hello"}, }, "Struct field name inclusion": { Args: args{ Resolver: &fieldNameMismatch{}, Opts: []graphql.SchemaOpt{graphql.UseFieldResolvers()}, Schema: ` type Query { hello(): HelloOutput! } type HelloOutput { name: Int } `, }, Want: want{Error: "string is not a pointer\n\tused by (graphql_test.helloOutput).Name\n\tused by (*graphql_test.fieldNameMismatch).Hello"}, }, } for name, tt := range testTable { tt := tt t.Run(name, func(t *testing.T) { t.Parallel() _, err := graphql.ParseSchema(tt.Args.Schema, tt.Args.Resolver, tt.Args.Opts...) if err == nil || err.Error() != tt.Want.Error { t.Log("Schema parsing error mismatch") t.Logf("got: %s", err) t.Logf("exp: %s", tt.Want.Error) t.Fail() } }) } } func TestComposedFragments(t *testing.T) { gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: starwarsSchema, Query: ` { composed: hero(episode: EMPIRE) { name ...friendsNames ...friendsIds } } fragment friendsNames on Character { name friends { name } } fragment friendsIds on Character { name friends { id } } `, ExpectedResult: ` { "composed": { "name": "Luke Skywalker", "friends": [ { "id": "1002", "name": "Han Solo" }, { "id": "1003", "name": "Leia Organa" }, { "id": "2000", "name": "C-3PO" }, { "id": "2001", "name": "R2-D2" } ] } } `, }, }) } var ( exampleError = fmt.Errorf("This is an error") nilChildErrorString = `graphql: got nil for non-null "Child"` ) type childResolver struct{} func (r *childResolver) TriggerError() (string, error) { return "This will never be returned to the client", exampleError } func (r *childResolver) NoError() string { return "no error" } func (r *childResolver) Child() *childResolver { return &childResolver{} } func (r *childResolver) NilChild() *childResolver { return nil } func TestErrorPropagation(t *testing.T) { t.Parallel() gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { noError: String! triggerError: String! } `, &childResolver{}), Query: ` { noError triggerError } `, ExpectedResult: ` null `, ExpectedErrors: []*gqlerrors.QueryError{ { Message: exampleError.Error(), ResolverError: exampleError, Path: []interface{}{"triggerError"}, }, }, }, { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { noError: String! child: Child } type Child { noError: String! triggerError: String! } `, &childResolver{}), Query: ` { noError child { noError triggerError } } `, ExpectedResult: ` { "noError": "no error", "child": null } `, ExpectedErrors: []*gqlerrors.QueryError{ { Message: exampleError.Error(), ResolverError: exampleError, Path: []interface{}{"child", "triggerError"}, }, }, }, { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { noError: String! child: Child } type Child { noError: String! triggerError: String! child: Child! } `, &childResolver{}), Query: ` { noError child { noError child { noError triggerError } } } `, ExpectedResult: ` { "noError": "no error", "child": null } `, ExpectedErrors: []*gqlerrors.QueryError{ { Message: exampleError.Error(), ResolverError: exampleError, Path: []interface{}{"child", "child", "triggerError"}, }, }, }, { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { noError: String! child: Child } type Child { noError: String! triggerError: String! child: Child } `, &childResolver{}), Query: ` { noError child { noError child { noError triggerError } } } `, ExpectedResult: ` { "noError": "no error", "child": { "noError": "no error", "child": null } } `, ExpectedErrors: []*gqlerrors.QueryError{ { Message: exampleError.Error(), ResolverError: exampleError, Path: []interface{}{"child", "child", "triggerError"}, }, }, }, { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { noError: String! child: Child! } type Child { noError: String! nilChild: Child! } `, &childResolver{}), Query: ` { noError child { nilChild { noError } } } `, ExpectedResult: ` null `, ExpectedErrors: []*gqlerrors.QueryError{ { Message: nilChildErrorString, Path: []interface{}{"child", "nilChild"}, }, }, }, { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { noError: String! child: Child } type Child { noError: String! nilChild: Child! } `, &childResolver{}), Query: ` { noError child { noError nilChild { noError } } } `, ExpectedResult: ` { "noError": "no error", "child": null } `, ExpectedErrors: []*gqlerrors.QueryError{ { Message: nilChildErrorString, Path: []interface{}{"child", "nilChild"}, }, }, }, { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { child: Child } type Child { triggerError: String! child: Child nilChild: Child! } `, &childResolver{}), Query: ` { child { child { triggerError child { nilChild { triggerError } } } } } `, ExpectedResult: ` { "child": { "child": null } } `, ExpectedErrors: []*gqlerrors.QueryError{ { Message: nilChildErrorString, Path: []interface{}{"child", "child", "child", "nilChild"}, }, { Message: exampleError.Error(), ResolverError: exampleError, Path: []interface{}{"child", "child", "triggerError"}, }, }, }, { Schema: graphql.MustParseSchema(` schema { query: Query } type Query { child: Child } type Child { noError: String! child: Child! nilChild: Child! } `, &childResolver{}), Query: ` { child { child { nilChild { noError } } } } `, ExpectedResult: ` { "child": null } `, ExpectedErrors: []*gqlerrors.QueryError{ { Message: nilChildErrorString, Path: []interface{}{"child", "child", "nilChild"}, }, }, }, }) } type assertionResolver struct{} func (r *assertionResolver) ToHuman() (*struct{ Name string }, bool) { return &struct{ Name string }{Name: "Luke Skywalker"}, true } type assertionQueryResolver struct{} func (*assertionQueryResolver) Character() *assertionResolver { return &assertionResolver{} } type badAssertionResolver struct{} func (r *badAssertionResolver) ToHuman(ctx context.Context) (*struct{ Name string }, bool) { return &struct{ Name string }{Name: "Luke Skywalker"}, true } type badAssertionQueryResolver struct{} func (*badAssertionQueryResolver) Character() *badAssertionResolver { return &badAssertionResolver{} } func TestTypeAssertions(t *testing.T) { assertionSchema := ` schema { query: Query } type Query { character: Character! } type Human { name: String! } union Character = Human ` query := ` query { character { ... on Human { name } } } ` gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: graphql.MustParseSchema(assertionSchema, &assertionQueryResolver{}, graphql.UseFieldResolvers()), Query: query, ExpectedResult: ` { "character": { "name": "Luke Skywalker" } } `, }, }) } func TestPanicTypeAssertionArguments(t *testing.T) { panicMessage := `*graphql_test.badAssertionResolver does not resolve "Character": method "ToHuman" should't have any arguments used by (*graphql_test.badAssertionQueryResolver).Character` defer func() { r := recover() if r == nil { t.Fatal("expected schema parse to panic") } if r.(error).Error() != panicMessage { t.Logf("got: %s", r) t.Logf("want: %s", panicMessage) t.Fail() } }() schema := ` schema { query: Query } type Query { character: Character! } type Human { name: String! } union Character = Human ` graphql.MustParseSchema(schema, &badAssertionQueryResolver{}, graphql.UseFieldResolvers()) } type ambiguousResolver struct { Name string // ambiguous University } type University struct { Name string // ambiguous } func TestPanicAmbiguity(t *testing.T) { panicMessage := `*graphql_test.ambiguousResolver does not resolve "Query": ambiguous field "name"` defer func() { r := recover() if r == nil { t.Fatal("expected schema parse to panic") } if r.(error).Error() != panicMessage { t.Logf("got: %s", r) t.Logf("want: %s", panicMessage) t.Fail() } }() schema := ` schema { query: Query } type Query { name: String! university: University! } type University { name: String! } ` graphql.MustParseSchema(schema, &ambiguousResolver{}, graphql.UseFieldResolvers()) } func TestSchema_Exec_without_resolver(t *testing.T) { t.Parallel() type args struct { Query string Schema string } type want struct { Panic interface{} } testTable := []struct { Name string Args args Want want }{ { Name: "schema_without_resolver_errors", Args: args{ Query: ` query { hero { id name friends { name } } } `, Schema: starwars.Schema, }, Want: want{Panic: "schema created without resolver, can not exec"}, }, } for _, tt := range testTable { t.Run(tt.Name, func(t *testing.T) { s := graphql.MustParseSchema(tt.Args.Schema, nil) defer func() { r := recover() if r == nil { t.Fatal("expected query to panic") } if r != tt.Want.Panic { t.Logf("got: %s", r) t.Logf("want: %s", tt.Want.Panic) t.Fail() } }() _ = s.Exec(context.Background(), tt.Args.Query, "", map[string]interface{}{}) }) } } type subscriptionsInExecResolver struct{} func (r *subscriptionsInExecResolver) AppUpdated() <-chan string { return make(chan string) } func TestSubscriptions_In_Exec(t *testing.T) { r := &struct { *helloResolver *subscriptionsInExecResolver }{ helloResolver: &helloResolver{}, subscriptionsInExecResolver: &subscriptionsInExecResolver{}, } gqltesting.RunTest(t, &gqltesting.Test{ Schema: graphql.MustParseSchema(` type Query { hello: String! } type Subscription { appUpdated : String! } `, r), Query: ` subscription { appUpdated } `, ExpectedErrors: []*gqlerrors.QueryError{ { Message: "graphql-ws protocol header is missing", }, }, }) } type nilPointerReturnValue struct{} func (r *nilPointerReturnValue) Value() *string { return nil } type nilPointerReturnResolver struct{} func (r *nilPointerReturnResolver) PointerReturn() *nilPointerReturnValue { return &nilPointerReturnValue{} } func TestPointerReturnForNonNull(t *testing.T) { gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: graphql.MustParseSchema(` type Query { pointerReturn: PointerReturnValue } type PointerReturnValue { value: Hello! } enum Hello { WORLD } `, &nilPointerReturnResolver{}), Query: ` query { pointerReturn { value } } `, ExpectedResult: ` { "pointerReturn": null } `, ExpectedErrors: []*gqlerrors.QueryError{ { Message: `graphql: got nil for non-null "Hello"`, Path: []interface{}{"pointerReturn", "value"}, }, }, }, }) } type nullableInput struct { String graphql.NullString Int graphql.NullInt Bool graphql.NullBool Time graphql.NullTime Float graphql.NullFloat } type nullableResult struct { String string Int string Bool string Time string Float string } type nullableResolver struct { } func (r *nullableResolver) TestNullables(args struct { Input *nullableInput }) nullableResult { var res nullableResult if args.Input.String.Set { if args.Input.String.Value == nil { res.String = "" } else { res.String = *args.Input.String.Value } } if args.Input.Int.Set { if args.Input.Int.Value == nil { res.Int = "" } else { res.Int = fmt.Sprintf("%d", *args.Input.Int.Value) } } if args.Input.Float.Set { if args.Input.Float.Value == nil { res.Float = "" } else { res.Float = fmt.Sprintf("%.2f", *args.Input.Float.Value) } } if args.Input.Bool.Set { if args.Input.Bool.Value == nil { res.Bool = "" } else { res.Bool = fmt.Sprintf("%t", *args.Input.Bool.Value) } } if args.Input.Time.Set { if args.Input.Time.Value == nil { res.Time = "" } else { res.Time = args.Input.Time.Value.Format(time.RFC3339) } } return res } func TestNullable(t *testing.T) { schema := ` scalar Time input MyInput { string: String int: Int float: Float bool: Boolean time: Time } type Result { string: String! int: String! float: String! bool: String! time: String! } type Query { testNullables(input: MyInput): Result! } ` gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: graphql.MustParseSchema(schema, &nullableResolver{}, graphql.UseFieldResolvers()), Query: ` query { testNullables(input: { string: "test" int: 1234 float: 42.42 bool: true time: "2021-01-02T15:04:05Z" }) { string int float bool time } } `, ExpectedResult: ` { "testNullables": { "string": "test", "int": "1234", "float": "42.42", "bool": "true", "time": "2021-01-02T15:04:05Z" } } `, }, { Schema: graphql.MustParseSchema(schema, &nullableResolver{}, graphql.UseFieldResolvers()), Query: ` query { testNullables(input: { string: null int: null float: null bool: null time: null }) { string int float bool time } } `, ExpectedResult: ` { "testNullables": { "string": "", "int": "", "float": "", "bool": "", "time": "" } } `, }, { Schema: graphql.MustParseSchema(schema, &nullableResolver{}, graphql.UseFieldResolvers()), Query: ` query { testNullables(input: {}) { string int float bool time } } `, ExpectedResult: ` { "testNullables": { "string": "", "int": "", "float": "", "bool": "", "time": "" } } `, }, }) } type testTracer struct { mu *sync.Mutex fields []fieldTrace queries []queryTrace } type fieldTrace struct { label string typeName string fieldName string isTrivial bool args map[string]interface{} err *gqlerrors.QueryError } type queryTrace struct { document string opName string variables map[string]interface{} varTypes map[string]*introspection.Type errors []*gqlerrors.QueryError } func (t *testTracer) TraceField(ctx context.Context, label, typeName, fieldName string, trivial bool, args map[string]interface{}) (context.Context, func(*gqlerrors.QueryError)) { return ctx, func(qe *gqlerrors.QueryError) { t.mu.Lock() defer t.mu.Unlock() ft := fieldTrace{ label: label, typeName: typeName, fieldName: fieldName, isTrivial: trivial, args: args, err: qe, } t.fields = append(t.fields, ft) } } func (t *testTracer) TraceQuery(ctx context.Context, document string, opName string, vars map[string]interface{}, varTypes map[string]*introspection.Type) (context.Context, func([]*gqlerrors.QueryError)) { return ctx, func(qe []*gqlerrors.QueryError) { t.mu.Lock() defer t.mu.Unlock() qt := queryTrace{ document: document, opName: opName, variables: vars, varTypes: varTypes, errors: qe, } t.queries = append(t.queries, qt) } } var _ tracer.Tracer = (*testTracer)(nil) func TestTracer(t *testing.T) { t.Parallel() tt := &testTracer{mu: &sync.Mutex{}} schema, err := graphql.ParseSchema(starwars.Schema, &starwars.Resolver{}, graphql.Tracer(tt)) if err != nil { t.Fatalf("graphql.ParseSchema: %s", err) } ctx := context.Background() doc := ` query TestTracer($id: ID!) { HanSolo: human(id: $id) { __typename name } } ` opName := "TestTracer" variables := map[string]interface{}{ "id": "1002", } _ = schema.Exec(ctx, doc, opName, variables) tt.mu.Lock() defer tt.mu.Unlock() if len(tt.queries) != 1 { t.Fatalf("expected one query trace, but got %d: %#v", len(tt.queries), tt.queries) } qt := tt.queries[0] if qt.document != doc { t.Errorf("mismatched query trace document:\nwant: %q\ngot : %q", doc, qt.document) } if qt.opName != opName { t.Errorf("mismated query trace operationName:\nwant: %q\ngot : %q", opName, qt.opName) } expectedFieldTraces := []fieldTrace{ {fieldName: "human", typeName: "Query"}, {fieldName: "__typename", typeName: "Human"}, {fieldName: "name", typeName: "Human"}, } checkFieldTraces(t, expectedFieldTraces, tt.fields) } func checkFieldTraces(t *testing.T, want, have []fieldTrace) { if len(want) != len(have) { t.Errorf("mismatched field traces: expected %d but got %d: %#v", len(want), len(have), have) } type comparison struct { want fieldTrace have fieldTrace } m := map[string]comparison{} for _, ft := range want { m[ft.fieldName] = comparison{want: ft} } for _, ft := range have { c := m[ft.fieldName] c.have = ft m[ft.fieldName] = c } for _, c := range m { if err := stringsEqual(c.want.fieldName, c.have.fieldName); err != "" { t.Error("mismatched field name:", err) } if err := stringsEqual(c.want.typeName, c.have.typeName); err != "" { t.Error("mismatched field parent type:", err) } } } func stringsEqual(want, have string) string { if want != have { return fmt.Sprintf("mismatched values:\nwant: %q\nhave: %q", want, have) } return "" } type queryVarResolver struct{} type filterArgs struct { Required string Optional *string } type filterSearchResults struct { Match *string } func (r *queryVarResolver) Search(ctx context.Context, args *struct{ Filter filterArgs }) []filterSearchResults { return []filterSearchResults{} } func TestQueryVariablesValidation(t *testing.T) { gqltesting.RunTests(t, []*gqltesting.Test{{ Schema: graphql.MustParseSchema(` input SearchFilter { required: String! optional: String } type SearchResults { match: String } type Query { search(filter: SearchFilter!): [SearchResults!]! }`, &queryVarResolver{}, graphql.UseFieldResolvers()), Query: ` query { search(filter: {}) { match } }`, ExpectedErrors: []*gqlerrors.QueryError{{ Message: "Argument \"filter\" has invalid value {}.\nIn field \"required\": Expected \"String!\", found null.", Locations: []gqlerrors.Location{{Line: 3, Column: 27}}, Rule: "ArgumentsOfCorrectType", }}, }, { Schema: graphql.MustParseSchema(` input SearchFilter { required: String! optional: String } type SearchResults { match: String } type Query { search(filter: SearchFilter!): [SearchResults!]! }`, &queryVarResolver{}, graphql.UseFieldResolvers()), Query: ` query q($filter: SearchFilter!) { search(filter: $filter) { match } }`, Variables: map[string]interface{}{"filter": map[string]interface{}{}}, ExpectedErrors: []*gqlerrors.QueryError{{ Message: "Variable \"required\" has invalid value null.\nExpected type \"String!\", found null.", Locations: []gqlerrors.Location{{Line: 3, Column: 5}}, Rule: "VariablesOfCorrectType", }}, }}) } type interfaceImplementingInterfaceResolver struct{} type interfaceImplementingInterfaceExample struct { A string B string C bool } func (r *interfaceImplementingInterfaceResolver) Hey() *interfaceImplementingInterfaceExample { return &interfaceImplementingInterfaceExample{ A: "testing", B: "test", C: true, } } func TestInterfaceImplementingInterface(t *testing.T) { gqltesting.RunTests(t, []*gqltesting.Test{{ Schema: graphql.MustParseSchema(` interface A { a: String! } interface B implements A { a: String! b: String! } interface C implements B & A { a: String! b: String! c: Boolean! } type ABC implements C { a: String! b: String! c: Boolean! } type Query { hey: ABC }`, &interfaceImplementingInterfaceResolver{}, graphql.UseFieldResolvers(), graphql.UseFieldResolvers()), Query: `query {hey { a b c }}`, ExpectedResult: ` { "hey": { "a": "testing", "b": "test", "c": true } } `, }}) } func TestCircularFragmentMaxDepth(t *testing.T) { withMaxDepth := graphql.MustParseSchema(starwars.Schema, &starwars.Resolver{}, graphql.MaxDepth(2)) gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: withMaxDepth, Query: ` query { ...X } fragment X on Query { ...Y } fragment Y on Query { ...X } `, ExpectedErrors: []*gqlerrors.QueryError{{ Message: `Cannot spread fragment "X" within itself via Y.`, Rule: "NoFragmentCycles", Locations: []gqlerrors.Location{ {Line: 7, Column: 20}, {Line: 10, Column: 20}, }, }}, }, }) } func TestQueryService(t *testing.T) { t.Parallel() schemaString := ` schema { query: Query } type Query { hello: String! }` gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: graphql.MustParseSchema(schemaString, &helloWorldResolver1{}), Query: ` { _service{ sdl } } `, ExpectedResult: ` { "_service": { "sdl": "\n\tschema {\n\t\tquery: Query\n\t}\n\n\ttype Query {\n\t\thello: String!\n\t}" } } `, }, }) } graphql-go-1.5.0/id.go000066400000000000000000000011061435002731700145010ustar00rootroot00000000000000package graphql import ( "fmt" "strconv" ) // ID represents GraphQL's "ID" scalar type. A custom type may be used instead. type ID string func (ID) ImplementsGraphQLType(name string) bool { return name == "ID" } func (id *ID) UnmarshalGraphQL(input interface{}) error { var err error switch input := input.(type) { case string: *id = ID(input) case int32: *id = ID(strconv.Itoa(int(input))) default: err = fmt.Errorf("wrong type for ID: %T", input) } return err } func (id ID) MarshalJSON() ([]byte, error) { return strconv.AppendQuote(nil, string(id)), nil } graphql-go-1.5.0/internal/000077500000000000000000000000001435002731700153745ustar00rootroot00000000000000graphql-go-1.5.0/internal/common/000077500000000000000000000000001435002731700166645ustar00rootroot00000000000000graphql-go-1.5.0/internal/common/blockstring.go000066400000000000000000000056401435002731700215410ustar00rootroot00000000000000// MIT License // // Copyright (c) 2019 GraphQL Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // // This implementation has been adapted from the graphql-js reference implementation // https://github.com/graphql/graphql-js/blob/5eb7c4ded7ceb83ac742149cbe0dae07a8af9a30/src/language/blockString.js // which is released under the MIT License above. package common import ( "strings" ) // Produces the value of a block string from its parsed raw value, similar to // CoffeeScript's block string, Python's docstring trim or Ruby's strip_heredoc. // // This implements the GraphQL spec's BlockStringValue() static algorithm. func blockString(raw string) string { lines := strings.Split(raw, "\n") // Remove common indentation from all lines except the first (which has none) ind := blockStringIndentation(lines) if ind > 0 { for i := 1; i < len(lines); i++ { l := lines[i] if len(l) < ind { lines[i] = "" continue } lines[i] = l[ind:] } } // Remove leading and trailing blank lines trimStart := 0 for i := 0; i < len(lines) && isBlank(lines[i]); i++ { trimStart++ } lines = lines[trimStart:] trimEnd := 0 for i := len(lines) - 1; i > 0 && isBlank(lines[i]); i-- { trimEnd++ } lines = lines[:len(lines)-trimEnd] return strings.Join(lines, "\n") } func blockStringIndentation(lines []string) int { var commonIndent *int for i := 1; i < len(lines); i++ { l := lines[i] indent := leadingWhitespace(l) if indent == len(l) { // don't consider blank/empty lines continue } if indent == 0 { return 0 } if commonIndent == nil || indent < *commonIndent { commonIndent = &indent } } if commonIndent == nil { return 0 } return *commonIndent } func isBlank(s string) bool { return len(s) == 0 || leadingWhitespace(s) == len(s) } func leadingWhitespace(s string) int { i := 0 for _, r := range s { if r != '\t' && r != ' ' { break } i++ } return i } graphql-go-1.5.0/internal/common/directive.go000066400000000000000000000006331435002731700211730ustar00rootroot00000000000000package common import "github.com/graph-gophers/graphql-go/types" func ParseDirectives(l *Lexer) types.DirectiveList { var directives types.DirectiveList for l.Peek() == '@' { l.ConsumeToken('@') d := &types.Directive{} d.Name = l.ConsumeIdentWithLoc() d.Name.Loc.Column-- if l.Peek() == '(' { d.Arguments = ParseArgumentList(l) } directives = append(directives, d) } return directives } graphql-go-1.5.0/internal/common/lexer.go000066400000000000000000000130531435002731700203340ustar00rootroot00000000000000package common import ( "bytes" "fmt" "strconv" "strings" "text/scanner" "github.com/graph-gophers/graphql-go/errors" "github.com/graph-gophers/graphql-go/types" ) type syntaxError string type Lexer struct { sc *scanner.Scanner next rune comment bytes.Buffer useStringDescriptions bool } type Ident struct { Name string Loc errors.Location } func NewLexer(s string, useStringDescriptions bool) *Lexer { sc := &scanner.Scanner{ Mode: scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats | scanner.ScanStrings, } sc.Init(strings.NewReader(s)) l := Lexer{sc: sc, useStringDescriptions: useStringDescriptions} l.sc.Error = l.CatchScannerError return &l } func (l *Lexer) CatchSyntaxError(f func()) (errRes *errors.QueryError) { defer func() { if err := recover(); err != nil { if err, ok := err.(syntaxError); ok { errRes = errors.Errorf("syntax error: %s", err) errRes.Locations = []errors.Location{l.Location()} return } panic(err) } }() f() return } func (l *Lexer) Peek() rune { return l.next } // ConsumeWhitespace consumes whitespace and tokens equivalent to whitespace (e.g. commas and comments). // // Consumed comment characters will build the description for the next type or field encountered. // The description is available from `DescComment()`, and will be reset every time `ConsumeWhitespace()` is // executed unless l.useStringDescriptions is set. func (l *Lexer) ConsumeWhitespace() { l.comment.Reset() for { l.next = l.sc.Scan() if l.next == ',' { // Similar to white space and line terminators, commas (',') are used to improve the // legibility of source text and separate lexical tokens but are otherwise syntactically and // semantically insignificant within GraphQL documents. // // http://facebook.github.io/graphql/draft/#sec-Insignificant-Commas continue } if l.next == '#' { // GraphQL source documents may contain single-line comments, starting with the '#' marker. // // A comment can contain any Unicode code point except `LineTerminator` so a comment always // consists of all code points starting with the '#' character up to but not including the // line terminator. l.consumeComment() continue } break } } // consumeDescription optionally consumes a description based on the June 2018 graphql spec if any are present. // // Single quote strings are also single line. Triple quote strings can be multi-line. Triple quote strings // whitespace trimmed on both ends. // If a description is found, consume any following comments as well // // http://facebook.github.io/graphql/June2018/#sec-Descriptions func (l *Lexer) consumeDescription() string { // If the next token is not a string, we don't consume it if l.next != scanner.String { return "" } // Triple quote string is an empty "string" followed by an open quote due to the way the parser treats strings as one token var desc string if l.sc.Peek() == '"' { desc = l.consumeTripleQuoteComment() } else { desc = l.consumeStringComment() } l.ConsumeWhitespace() return desc } func (l *Lexer) ConsumeIdent() string { name := l.sc.TokenText() l.ConsumeToken(scanner.Ident) return name } func (l *Lexer) ConsumeIdentWithLoc() types.Ident { loc := l.Location() name := l.sc.TokenText() l.ConsumeToken(scanner.Ident) return types.Ident{Name: name, Loc: loc} } func (l *Lexer) ConsumeKeyword(keyword string) { if l.next != scanner.Ident || l.sc.TokenText() != keyword { l.SyntaxError(fmt.Sprintf("unexpected %q, expecting %q", l.sc.TokenText(), keyword)) } l.ConsumeWhitespace() } func (l *Lexer) ConsumeLiteral() *types.PrimitiveValue { lit := &types.PrimitiveValue{Type: l.next, Text: l.sc.TokenText()} l.ConsumeWhitespace() return lit } func (l *Lexer) ConsumeToken(expected rune) { if l.next != expected { l.SyntaxError(fmt.Sprintf("unexpected %q, expecting %s", l.sc.TokenText(), scanner.TokenString(expected))) } l.ConsumeWhitespace() } func (l *Lexer) DescComment() string { comment := l.comment.String() desc := l.consumeDescription() if l.useStringDescriptions { return desc } return comment } func (l *Lexer) SyntaxError(message string) { panic(syntaxError(message)) } func (l *Lexer) Location() errors.Location { return errors.Location{ Line: l.sc.Line, Column: l.sc.Column, } } func (l *Lexer) consumeTripleQuoteComment() string { l.next = l.sc.Next() if l.next != '"' { panic("consumeTripleQuoteComment used in wrong context: no third quote?") } var buf bytes.Buffer var numQuotes int for { l.next = l.sc.Next() if l.next == '"' { numQuotes++ } else { numQuotes = 0 } buf.WriteRune(l.next) if numQuotes == 3 || l.next == scanner.EOF { break } } val := buf.String() val = val[:len(val)-numQuotes] return blockString(val) } func (l *Lexer) consumeStringComment() string { val, err := strconv.Unquote(l.sc.TokenText()) if err != nil { panic(err) } return val } // consumeComment consumes all characters from `#` to the first encountered line terminator. // The characters are appended to `l.comment`. func (l *Lexer) consumeComment() { if l.next != '#' { panic("consumeComment used in wrong context") } // TODO: count and trim whitespace so we can dedent any following lines. if l.sc.Peek() == ' ' { l.sc.Next() } if l.comment.Len() > 0 { l.comment.WriteRune('\n') } for { next := l.sc.Next() if next == '\r' || next == '\n' || next == scanner.EOF { break } l.comment.WriteRune(next) } } func (l *Lexer) CatchScannerError(s *scanner.Scanner, msg string) { l.SyntaxError(msg) } graphql-go-1.5.0/internal/common/lexer_test.go000066400000000000000000000066211435002731700213760ustar00rootroot00000000000000package common_test import ( "testing" "github.com/graph-gophers/graphql-go/internal/common" ) type consumeTestCase struct { description string definition string expected string // expected description failureExpected bool useStringDescriptions bool } // Note that these tests stop as soon as they parse the comments, so even though the rest of the file will fail to parse sometimes, the tests still pass var consumeTests = []consumeTestCase{{ description: "no string descriptions allowed in old mode", definition: ` # Comment line 1 #Comment line 2 ,,,,,, # Commas are insignificant "New style comments" type Hello { world: String! }`, expected: "Comment line 1\nComment line 2\nCommas are insignificant", useStringDescriptions: false, }, { description: "simple string descriptions allowed in new mode", definition: ` # Comment line 1 #Comment line 2 ,,,,,, # Commas are insignificant "New style comments" type Hello { world: String! }`, expected: "New style comments", useStringDescriptions: true, }, { description: "comment after description works", definition: ` # Comment line 1 #Comment line 2 ,,,,,, # Commas are insignificant type Hello { world: String! }`, expected: "", useStringDescriptions: true, }, { description: "triple quote descriptions allowed in new mode", definition: ` # Comment line 1 #Comment line 2 ,,,,,, # Commas are insignificant """ New style comments Another line """ type Hello { world: String! }`, expected: "New style comments\nAnother line", useStringDescriptions: true, }} func TestConsume(t *testing.T) { for _, test := range consumeTests { t.Run(test.description, func(t *testing.T) { lex := common.NewLexer(test.definition, test.useStringDescriptions) err := lex.CatchSyntaxError(func() { lex.ConsumeWhitespace() }) if test.failureExpected { if err == nil { t.Fatalf("schema should have been invalid; comment: %s", lex.DescComment()) } } else { if err != nil { t.Fatal(err) } } if test.expected != lex.DescComment() { t.Errorf("wrong description value:\nwant: %q\ngot : %q", test.expected, lex.DescComment()) } }) } } var multilineStringTests = []consumeTestCase{ { description: "Oneline strings are okay", definition: `"Hello World"`, expected: "", failureExpected: false, useStringDescriptions: true, }, { description: "Multiline strings are not allowed", definition: `"Hello World"`, expected: `graphql: syntax error: literal not terminated (line 1, column 1)`, failureExpected: true, useStringDescriptions: true, }, } func TestMultilineString(t *testing.T) { for _, test := range multilineStringTests { t.Run(test.description, func(t *testing.T) { lex := common.NewLexer(test.definition, test.useStringDescriptions) err := lex.CatchSyntaxError(func() { lex.ConsumeWhitespace() }) if test.failureExpected && err == nil { t.Fatalf("Test '%s' should fail", test.description) } else if test.failureExpected && err != nil { if test.expected != err.Error() { t.Fatalf("Test '%s' failed with wrong error: '%s'. Error should be: '%s'", test.description, err.Error(), test.expected) } } if !test.failureExpected && err != nil { t.Fatalf("Test '%s' failed with error: '%s'", test.description, err.Error()) } }) } } graphql-go-1.5.0/internal/common/literals.go000066400000000000000000000024561435002731700210410ustar00rootroot00000000000000package common import ( "text/scanner" "github.com/graph-gophers/graphql-go/types" ) func ParseLiteral(l *Lexer, constOnly bool) types.Value { loc := l.Location() switch l.Peek() { case '$': if constOnly { l.SyntaxError("variable not allowed") panic("unreachable") } l.ConsumeToken('$') return &types.Variable{Name: l.ConsumeIdent(), Loc: loc} case scanner.Int, scanner.Float, scanner.String, scanner.Ident: lit := l.ConsumeLiteral() if lit.Type == scanner.Ident && lit.Text == "null" { return &types.NullValue{Loc: loc} } lit.Loc = loc return lit case '-': l.ConsumeToken('-') lit := l.ConsumeLiteral() lit.Text = "-" + lit.Text lit.Loc = loc return lit case '[': l.ConsumeToken('[') var list []types.Value for l.Peek() != ']' { list = append(list, ParseLiteral(l, constOnly)) } l.ConsumeToken(']') return &types.ListValue{Values: list, Loc: loc} case '{': l.ConsumeToken('{') var fields []*types.ObjectField for l.Peek() != '}' { name := l.ConsumeIdentWithLoc() l.ConsumeToken(':') value := ParseLiteral(l, constOnly) fields = append(fields, &types.ObjectField{Name: name, Value: value}) } l.ConsumeToken('}') return &types.ObjectValue{Fields: fields, Loc: loc} default: l.SyntaxError("invalid value") panic("unreachable") } } graphql-go-1.5.0/internal/common/types.go000066400000000000000000000032601435002731700203600ustar00rootroot00000000000000package common import ( "github.com/graph-gophers/graphql-go/errors" "github.com/graph-gophers/graphql-go/types" ) func ParseType(l *Lexer) types.Type { t := parseNullType(l) if l.Peek() == '!' { l.ConsumeToken('!') return &types.NonNull{OfType: t} } return t } func parseNullType(l *Lexer) types.Type { if l.Peek() == '[' { l.ConsumeToken('[') ofType := ParseType(l) l.ConsumeToken(']') return &types.List{OfType: ofType} } return &types.TypeName{Ident: l.ConsumeIdentWithLoc()} } type Resolver func(name string) types.Type // ResolveType attempts to resolve a type's name against a resolving function. // This function is used when one needs to check if a TypeName exists in the resolver (typically a Schema). // // In the example below, ResolveType would be used to check if the resolving function // returns a valid type for Dimension: // // type Profile { // picture(dimensions: Dimension): Url // } // // ResolveType recursively unwraps List and NonNull types until a NamedType is reached. func ResolveType(t types.Type, resolver Resolver) (types.Type, *errors.QueryError) { switch t := t.(type) { case *types.List: ofType, err := ResolveType(t.OfType, resolver) if err != nil { return nil, err } return &types.List{OfType: ofType}, nil case *types.NonNull: ofType, err := ResolveType(t.OfType, resolver) if err != nil { return nil, err } return &types.NonNull{OfType: ofType}, nil case *types.TypeName: refT := resolver(t.Name) if refT == nil { err := errors.Errorf("Unknown type %q.", t.Name) err.Rule = "KnownTypeNames" err.Locations = []errors.Location{t.Loc} return nil, err } return refT, nil default: return t, nil } } graphql-go-1.5.0/internal/common/values.go000066400000000000000000000015321435002731700205130ustar00rootroot00000000000000package common import ( "github.com/graph-gophers/graphql-go/types" ) func ParseInputValue(l *Lexer) *types.InputValueDefinition { p := &types.InputValueDefinition{} p.Loc = l.Location() p.Desc = l.DescComment() p.Name = l.ConsumeIdentWithLoc() l.ConsumeToken(':') p.TypeLoc = l.Location() p.Type = ParseType(l) if l.Peek() == '=' { l.ConsumeToken('=') p.Default = ParseLiteral(l, true) } p.Directives = ParseDirectives(l) return p } func ParseArgumentList(l *Lexer) types.ArgumentList { var args types.ArgumentList l.ConsumeToken('(') for l.Peek() != ')' { name := l.ConsumeIdentWithLoc() l.ConsumeToken(':') value := ParseLiteral(l, false) directives := ParseDirectives(l) args = append(args, &types.Argument{ Name: name, Value: value, Directives: directives, }) } l.ConsumeToken(')') return args } graphql-go-1.5.0/internal/exec/000077500000000000000000000000001435002731700163205ustar00rootroot00000000000000graphql-go-1.5.0/internal/exec/exec.go000066400000000000000000000243131435002731700175760ustar00rootroot00000000000000package exec import ( "bytes" "context" "encoding/json" "fmt" "reflect" "sync" "time" "github.com/graph-gophers/graphql-go/errors" "github.com/graph-gophers/graphql-go/internal/exec/resolvable" "github.com/graph-gophers/graphql-go/internal/exec/selected" "github.com/graph-gophers/graphql-go/internal/query" "github.com/graph-gophers/graphql-go/log" "github.com/graph-gophers/graphql-go/trace/tracer" "github.com/graph-gophers/graphql-go/types" ) type Request struct { selected.Request Limiter chan struct{} Tracer tracer.Tracer Logger log.Logger PanicHandler errors.PanicHandler SubscribeResolverTimeout time.Duration } func (r *Request) handlePanic(ctx context.Context) { if value := recover(); value != nil { r.Logger.LogPanic(ctx, value) r.AddError(r.PanicHandler.MakePanicError(ctx, value)) } } type extensionser interface { Extensions() map[string]interface{} } func (r *Request) Execute(ctx context.Context, s *resolvable.Schema, op *types.OperationDefinition) ([]byte, []*errors.QueryError) { var out bytes.Buffer func() { defer r.handlePanic(ctx) sels := selected.ApplyOperation(&r.Request, s, op) r.execSelections(ctx, sels, nil, s, s.Resolver, &out, op.Type == query.Mutation) }() if err := ctx.Err(); err != nil { return nil, []*errors.QueryError{errors.Errorf("%s", err)} } return out.Bytes(), r.Errs } type fieldToExec struct { field *selected.SchemaField sels []selected.Selection resolver reflect.Value out *bytes.Buffer } func resolvedToNull(b *bytes.Buffer) bool { return bytes.Equal(b.Bytes(), []byte("null")) } func (r *Request) execSelections(ctx context.Context, sels []selected.Selection, path *pathSegment, s *resolvable.Schema, resolver reflect.Value, out *bytes.Buffer, serially bool) { async := !serially && selected.HasAsyncSel(sels) var fields []*fieldToExec collectFieldsToResolve(sels, s, resolver, &fields, make(map[string]*fieldToExec)) if async { var wg sync.WaitGroup wg.Add(len(fields)) for _, f := range fields { go func(f *fieldToExec) { defer wg.Done() defer r.handlePanic(ctx) f.out = new(bytes.Buffer) execFieldSelection(ctx, r, s, f, &pathSegment{path, f.field.Alias}, true) }(f) } wg.Wait() } else { for _, f := range fields { f.out = new(bytes.Buffer) execFieldSelection(ctx, r, s, f, &pathSegment{path, f.field.Alias}, true) } } out.WriteByte('{') for i, f := range fields { // If a non-nullable child resolved to null, an error was added to the // "errors" list in the response, so this field resolves to null. // If this field is non-nullable, the error is propagated to its parent. if _, ok := f.field.Type.(*types.NonNull); ok && resolvedToNull(f.out) { out.Reset() out.Write([]byte("null")) return } if i > 0 { out.WriteByte(',') } out.WriteByte('"') out.WriteString(f.field.Alias) out.WriteByte('"') out.WriteByte(':') out.Write(f.out.Bytes()) } out.WriteByte('}') } func collectFieldsToResolve(sels []selected.Selection, s *resolvable.Schema, resolver reflect.Value, fields *[]*fieldToExec, fieldByAlias map[string]*fieldToExec) { for _, sel := range sels { switch sel := sel.(type) { case *selected.SchemaField: field, ok := fieldByAlias[sel.Alias] if !ok { // validation already checked for conflict (TODO) field = &fieldToExec{field: sel, resolver: resolver} fieldByAlias[sel.Alias] = field *fields = append(*fields, field) } field.sels = append(field.sels, sel.Sels...) case *selected.TypenameField: _, ok := fieldByAlias[sel.Alias] if !ok { res := reflect.ValueOf(typeOf(sel, resolver)) f := s.FieldTypename f.TypeName = res.String() sf := &selected.SchemaField{ Field: f, Alias: sel.Alias, FixedResult: res, } field := &fieldToExec{field: sf, resolver: resolver} *fields = append(*fields, field) fieldByAlias[sel.Alias] = field } case *selected.TypeAssertion: out := resolver.Method(sel.MethodIndex).Call(nil) if !out[1].Bool() { continue } collectFieldsToResolve(sel.Sels, s, out[0], fields, fieldByAlias) default: panic("unreachable") } } } func typeOf(tf *selected.TypenameField, resolver reflect.Value) string { if len(tf.TypeAssertions) == 0 { return tf.Name } for name, a := range tf.TypeAssertions { out := resolver.Method(a.MethodIndex).Call(nil) if out[1].Bool() { return name } } return "" } func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f *fieldToExec, path *pathSegment, applyLimiter bool) { if applyLimiter { r.Limiter <- struct{}{} } var result reflect.Value var err *errors.QueryError traceCtx, finish := r.Tracer.TraceField(ctx, f.field.TraceLabel, f.field.TypeName, f.field.Name, !f.field.Async, f.field.Args) defer func() { finish(err) }() err = func() (err *errors.QueryError) { defer func() { if panicValue := recover(); panicValue != nil { r.Logger.LogPanic(ctx, panicValue) err = r.PanicHandler.MakePanicError(ctx, panicValue) err.Path = path.toSlice() } }() if f.field.FixedResult.IsValid() { result = f.field.FixedResult return nil } if err := traceCtx.Err(); err != nil { return errors.Errorf("%s", err) // don't execute any more resolvers if context got cancelled } res := f.resolver if f.field.UseMethodResolver() { var in []reflect.Value if f.field.HasContext { in = append(in, reflect.ValueOf(traceCtx)) } if f.field.ArgsPacker != nil { in = append(in, f.field.PackedArgs) } callOut := res.Method(f.field.MethodIndex).Call(in) result = callOut[0] if f.field.HasError && !callOut[1].IsNil() { resolverErr := callOut[1].Interface().(error) err := errors.Errorf("%s", resolverErr) err.Path = path.toSlice() err.ResolverError = resolverErr if ex, ok := callOut[1].Interface().(extensionser); ok { err.Extensions = ex.Extensions() } return err } } else { // TODO extract out unwrapping ptr logic to a common place if res.Kind() == reflect.Ptr { res = res.Elem() } result = res.FieldByIndex(f.field.FieldIndex) } return nil }() if applyLimiter { <-r.Limiter } if err != nil { // If an error occurred while resolving a field, it should be treated as though the field // returned null, and an error must be added to the "errors" list in the response. r.AddError(err) f.out.WriteString("null") return } r.execSelectionSet(traceCtx, f.sels, f.field.Type, path, s, result, f.out) } func (r *Request) execSelectionSet(ctx context.Context, sels []selected.Selection, typ types.Type, path *pathSegment, s *resolvable.Schema, resolver reflect.Value, out *bytes.Buffer) { t, nonNull := unwrapNonNull(typ) // a reflect.Value of a nil interface will show up as an Invalid value if resolver.Kind() == reflect.Invalid || ((resolver.Kind() == reflect.Ptr || resolver.Kind() == reflect.Interface) && resolver.IsNil()) { // If a field of a non-null type resolves to null (either because the // function to resolve the field returned null or because an error occurred), // add an error to the "errors" list in the response. if nonNull { err := errors.Errorf("graphql: got nil for non-null %q", t) err.Path = path.toSlice() r.AddError(err) } out.WriteString("null") return } switch t.(type) { case *types.ObjectTypeDefinition, *types.InterfaceTypeDefinition, *types.Union: r.execSelections(ctx, sels, path, s, resolver, out, false) return } // Any pointers or interfaces at this point should be non-nil, so we can get the actual value of them // for serialization if resolver.Kind() == reflect.Ptr || resolver.Kind() == reflect.Interface { resolver = resolver.Elem() } switch t := t.(type) { case *types.List: r.execList(ctx, sels, t, path, s, resolver, out) case *types.ScalarTypeDefinition: v := resolver.Interface() data, err := json.Marshal(v) if err != nil { panic(errors.Errorf("could not marshal %v: %s", v, err)) } out.Write(data) case *types.EnumTypeDefinition: var stringer fmt.Stringer = resolver if s, ok := resolver.Interface().(fmt.Stringer); ok { stringer = s } name := stringer.String() var valid bool for _, v := range t.EnumValuesDefinition { if v.EnumValue == name { valid = true break } } if !valid { err := errors.Errorf("Invalid value %s.\nExpected type %s, found %s.", name, t.Name, name) err.Path = path.toSlice() r.AddError(err) out.WriteString("null") return } out.WriteByte('"') out.WriteString(name) out.WriteByte('"') default: panic("unreachable") } } func (r *Request) execList(ctx context.Context, sels []selected.Selection, typ *types.List, path *pathSegment, s *resolvable.Schema, resolver reflect.Value, out *bytes.Buffer) { l := resolver.Len() entryouts := make([]bytes.Buffer, l) if selected.HasAsyncSel(sels) { // Limit the number of concurrent goroutines spawned as it can lead to large // memory spikes for large lists. concurrency := cap(r.Limiter) sem := make(chan struct{}, concurrency) for i := 0; i < l; i++ { sem <- struct{}{} go func(i int) { defer func() { <-sem }() defer r.handlePanic(ctx) r.execSelectionSet(ctx, sels, typ.OfType, &pathSegment{path, i}, s, resolver.Index(i), &entryouts[i]) }(i) } for i := 0; i < concurrency; i++ { sem <- struct{}{} } } else { for i := 0; i < l; i++ { r.execSelectionSet(ctx, sels, typ.OfType, &pathSegment{path, i}, s, resolver.Index(i), &entryouts[i]) } } _, listOfNonNull := typ.OfType.(*types.NonNull) out.WriteByte('[') for i, entryout := range entryouts { // If the list wraps a non-null type and one of the list elements // resolves to null, then the entire list resolves to null. if listOfNonNull && resolvedToNull(&entryout) { out.Reset() out.WriteString("null") return } if i > 0 { out.WriteByte(',') } out.Write(entryout.Bytes()) } out.WriteByte(']') } func unwrapNonNull(t types.Type) (types.Type, bool) { if nn, ok := t.(*types.NonNull); ok { return nn.OfType, true } return t, false } type pathSegment struct { parent *pathSegment value interface{} } func (p *pathSegment) toSlice() []interface{} { if p == nil { return nil } return append(p.parent.toSlice(), p.value) } graphql-go-1.5.0/internal/exec/packer/000077500000000000000000000000001435002731700175655ustar00rootroot00000000000000graphql-go-1.5.0/internal/exec/packer/packer.go000066400000000000000000000221021435002731700213560ustar00rootroot00000000000000package packer import ( "fmt" "math" "reflect" "strings" "github.com/graph-gophers/graphql-go/decode" "github.com/graph-gophers/graphql-go/errors" "github.com/graph-gophers/graphql-go/types" ) type packer interface { Pack(value interface{}) (reflect.Value, error) } type Builder struct { packerMap map[typePair]*packerMapEntry structPackers []*StructPacker } type typePair struct { graphQLType types.Type resolverType reflect.Type } type packerMapEntry struct { packer packer targets []*packer } func NewBuilder() *Builder { return &Builder{ packerMap: make(map[typePair]*packerMapEntry), } } func (b *Builder) Finish() error { for _, entry := range b.packerMap { for _, target := range entry.targets { *target = entry.packer } } for _, p := range b.structPackers { p.defaultStruct = reflect.New(p.structType).Elem() for _, f := range p.fields { if defaultVal := f.field.Default; defaultVal != nil { v, err := f.fieldPacker.Pack(defaultVal.Deserialize(nil)) if err != nil { return err } p.defaultStruct.FieldByIndex(f.fieldIndex).Set(v) } } } return nil } func (b *Builder) assignPacker(target *packer, schemaType types.Type, reflectType reflect.Type) error { k := typePair{schemaType, reflectType} ref, ok := b.packerMap[k] if !ok { ref = &packerMapEntry{} b.packerMap[k] = ref var err error ref.packer, err = b.makePacker(schemaType, reflectType) if err != nil { return err } } ref.targets = append(ref.targets, target) return nil } func (b *Builder) makePacker(schemaType types.Type, reflectType reflect.Type) (packer, error) { t, nonNull := unwrapNonNull(schemaType) if !nonNull { if reflectType.Kind() == reflect.Ptr { elemType := reflectType.Elem() addPtr := true if _, ok := t.(*types.InputObject); ok { elemType = reflectType // keep pointer for input objects addPtr = false } elem, err := b.makeNonNullPacker(t, elemType) if err != nil { return nil, err } return &nullPacker{ elemPacker: elem, valueType: reflectType, addPtr: addPtr, }, nil } else if isNullable(reflectType) { elemType := reflectType addPtr := false elem, err := b.makeNonNullPacker(t, elemType) if err != nil { return nil, err } return &nullPacker{ elemPacker: elem, valueType: reflectType, addPtr: addPtr, }, nil } else { return nil, fmt.Errorf("%s is not a pointer or a nullable type", reflectType) } } return b.makeNonNullPacker(t, reflectType) } func (b *Builder) makeNonNullPacker(schemaType types.Type, reflectType reflect.Type) (packer, error) { if u, ok := reflect.New(reflectType).Interface().(decode.Unmarshaler); ok { if !u.ImplementsGraphQLType(schemaType.String()) { return nil, fmt.Errorf("can not unmarshal %s into %s", schemaType, reflectType) } return &unmarshalerPacker{ ValueType: reflectType, }, nil } switch t := schemaType.(type) { case *types.ScalarTypeDefinition: return &ValuePacker{ ValueType: reflectType, }, nil case *types.EnumTypeDefinition: if reflectType.Kind() != reflect.String { return nil, fmt.Errorf("wrong type, expected %s", reflect.String) } return &ValuePacker{ ValueType: reflectType, }, nil case *types.InputObject: e, err := b.MakeStructPacker(t.Values, reflectType) if err != nil { return nil, err } return e, nil case *types.List: if reflectType.Kind() != reflect.Slice { return nil, fmt.Errorf("expected slice, got %s", reflectType) } p := &listPacker{ sliceType: reflectType, } if err := b.assignPacker(&p.elem, t.OfType, reflectType.Elem()); err != nil { return nil, err } return p, nil case *types.ObjectTypeDefinition, *types.InterfaceTypeDefinition, *types.Union: return nil, fmt.Errorf("type of kind %s can not be used as input", t.Kind()) default: panic("unreachable") } } func (b *Builder) MakeStructPacker(values []*types.InputValueDefinition, typ reflect.Type) (*StructPacker, error) { structType := typ usePtr := false if typ.Kind() == reflect.Ptr { structType = typ.Elem() usePtr = true } if structType.Kind() != reflect.Struct { return nil, fmt.Errorf("expected struct or pointer to struct, got %s (hint: missing `args struct { ... }` wrapper for field arguments?)", typ) } var fields []*structPackerField for _, v := range values { fe := &structPackerField{field: v} fx := func(n string) bool { return strings.EqualFold(stripUnderscore(n), stripUnderscore(v.Name.Name)) } sf, ok := structType.FieldByNameFunc(fx) if !ok { return nil, fmt.Errorf("%s does not define field %q (hint: missing `args struct { ... }` wrapper for field arguments, or missing field on input struct)", typ, v.Name.Name) } if sf.PkgPath != "" { return nil, fmt.Errorf("field %q must be exported", sf.Name) } fe.fieldIndex = sf.Index ft := v.Type if v.Default != nil { ft, _ = unwrapNonNull(ft) ft = &types.NonNull{OfType: ft} } if err := b.assignPacker(&fe.fieldPacker, ft, sf.Type); err != nil { return nil, fmt.Errorf("field %q: %s", sf.Name, err) } fields = append(fields, fe) } p := &StructPacker{ structType: structType, usePtr: usePtr, fields: fields, } b.structPackers = append(b.structPackers, p) return p, nil } type StructPacker struct { structType reflect.Type usePtr bool defaultStruct reflect.Value fields []*structPackerField } type structPackerField struct { field *types.InputValueDefinition fieldIndex []int fieldPacker packer } func (p *StructPacker) Pack(value interface{}) (reflect.Value, error) { if value == nil { return reflect.Value{}, errors.Errorf("got null for non-null") } values := value.(map[string]interface{}) v := reflect.New(p.structType) v.Elem().Set(p.defaultStruct) for _, f := range p.fields { if value, ok := values[f.field.Name.Name]; ok { packed, err := f.fieldPacker.Pack(value) if err != nil { return reflect.Value{}, err } v.Elem().FieldByIndex(f.fieldIndex).Set(packed) } } if !p.usePtr { return v.Elem(), nil } return v, nil } type listPacker struct { sliceType reflect.Type elem packer } func (e *listPacker) Pack(value interface{}) (reflect.Value, error) { list, ok := value.([]interface{}) if !ok { list = []interface{}{value} } v := reflect.MakeSlice(e.sliceType, len(list), len(list)) for i := range list { packed, err := e.elem.Pack(list[i]) if err != nil { return reflect.Value{}, err } v.Index(i).Set(packed) } return v, nil } type nullPacker struct { elemPacker packer valueType reflect.Type addPtr bool } func (p *nullPacker) Pack(value interface{}) (reflect.Value, error) { if value == nil && !isNullable(p.valueType) { return reflect.Zero(p.valueType), nil } v, err := p.elemPacker.Pack(value) if err != nil { return reflect.Value{}, err } if p.addPtr { ptr := reflect.New(p.valueType.Elem()) ptr.Elem().Set(v) return ptr, nil } return v, nil } type ValuePacker struct { ValueType reflect.Type } func (p *ValuePacker) Pack(value interface{}) (reflect.Value, error) { if value == nil { return reflect.Value{}, errors.Errorf("got null for non-null") } coerced, err := unmarshalInput(p.ValueType, value) if err != nil { return reflect.Value{}, fmt.Errorf("could not unmarshal %#v (%T) into %s: %s", value, value, p.ValueType, err) } return reflect.ValueOf(coerced), nil } type unmarshalerPacker struct { ValueType reflect.Type } func (p *unmarshalerPacker) Pack(value interface{}) (reflect.Value, error) { if value == nil && !isNullable(p.ValueType) { return reflect.Value{}, errors.Errorf("got null for non-null") } v := reflect.New(p.ValueType) if err := v.Interface().(decode.Unmarshaler).UnmarshalGraphQL(value); err != nil { return reflect.Value{}, err } return v.Elem(), nil } func unmarshalInput(typ reflect.Type, input interface{}) (interface{}, error) { if reflect.TypeOf(input) == typ { return input, nil } switch typ.Kind() { case reflect.Int32: switch input := input.(type) { case int: if input < math.MinInt32 || input > math.MaxInt32 { return nil, fmt.Errorf("not a 32-bit integer") } return int32(input), nil case float64: coerced := int32(input) if input < math.MinInt32 || input > math.MaxInt32 || float64(coerced) != input { return nil, fmt.Errorf("not a 32-bit integer") } return coerced, nil } case reflect.Float64: switch input := input.(type) { case int32: return float64(input), nil case int: return float64(input), nil } case reflect.String: if reflect.TypeOf(input).ConvertibleTo(typ) { return reflect.ValueOf(input).Convert(typ).Interface(), nil } } return nil, fmt.Errorf("incompatible type") } func unwrapNonNull(t types.Type) (types.Type, bool) { if nn, ok := t.(*types.NonNull); ok { return nn.OfType, true } return t, false } func stripUnderscore(s string) string { return strings.Replace(s, "_", "", -1) } // NullUnmarshaller is an unmarshaller that can handle a nil input type NullUnmarshaller interface { decode.Unmarshaler Nullable() } func isNullable(t reflect.Type) bool { _, ok := reflect.New(t).Interface().(NullUnmarshaller) return ok } graphql-go-1.5.0/internal/exec/resolvable/000077500000000000000000000000001435002731700204565ustar00rootroot00000000000000graphql-go-1.5.0/internal/exec/resolvable/meta.go000066400000000000000000000037761435002731700217500ustar00rootroot00000000000000package resolvable import ( "reflect" "github.com/graph-gophers/graphql-go/introspection" "github.com/graph-gophers/graphql-go/types" ) // Meta defines the details of the metadata schema for introspection. type Meta struct { FieldSchema Field FieldType Field FieldTypename Field FieldService Field Schema *Object Type *Object Service *Object } func newMeta(s *types.Schema) *Meta { var err error b := newBuilder(s) metaSchema := s.Types["__Schema"].(*types.ObjectTypeDefinition) so, err := b.makeObjectExec(metaSchema.Name, metaSchema.Fields, nil, false, reflect.TypeOf(&introspection.Schema{})) if err != nil { panic(err) } metaType := s.Types["__Type"].(*types.ObjectTypeDefinition) t, err := b.makeObjectExec(metaType.Name, metaType.Fields, nil, false, reflect.TypeOf(&introspection.Type{})) if err != nil { panic(err) } metaService := s.Types["_Service"].(*types.ObjectTypeDefinition) sv, err := b.makeObjectExec(metaService.Name, metaService.Fields, nil, false, reflect.TypeOf(&introspection.Service{})) if err != nil { panic(err) } if err := b.finish(); err != nil { panic(err) } fieldTypename := Field{ FieldDefinition: types.FieldDefinition{ Name: "__typename", Type: &types.NonNull{OfType: s.Types["String"]}, }, TraceLabel: "GraphQL field: __typename", } fieldSchema := Field{ FieldDefinition: types.FieldDefinition{ Name: "__schema", Type: s.Types["__Schema"], }, TraceLabel: "GraphQL field: __schema", } fieldType := Field{ FieldDefinition: types.FieldDefinition{ Name: "__type", Type: s.Types["__Type"], }, TraceLabel: "GraphQL field: __type", } fieldService := Field{ FieldDefinition: types.FieldDefinition{ Name: "_service", Type: s.Types["_Service"], }, TraceLabel: "GraphQL field: _service", } return &Meta{ FieldSchema: fieldSchema, FieldTypename: fieldTypename, FieldType: fieldType, FieldService: fieldService, Schema: so, Type: t, Service: sv, } } graphql-go-1.5.0/internal/exec/resolvable/resolvable.go000066400000000000000000000265141435002731700231530ustar00rootroot00000000000000package resolvable import ( "context" "fmt" "reflect" "strings" "github.com/graph-gophers/graphql-go/decode" "github.com/graph-gophers/graphql-go/internal/exec/packer" "github.com/graph-gophers/graphql-go/types" ) type Schema struct { *Meta types.Schema Query Resolvable Mutation Resolvable Subscription Resolvable Resolver reflect.Value } type Resolvable interface { isResolvable() } type Object struct { Name string Fields map[string]*Field TypeAssertions map[string]*TypeAssertion } type Field struct { types.FieldDefinition TypeName string MethodIndex int FieldIndex []int HasContext bool HasError bool ArgsPacker *packer.StructPacker ValueExec Resolvable TraceLabel string } func (f *Field) UseMethodResolver() bool { return len(f.FieldIndex) == 0 } type TypeAssertion struct { MethodIndex int TypeExec Resolvable } type List struct { Elem Resolvable } type Scalar struct{} func (*Object) isResolvable() {} func (*List) isResolvable() {} func (*Scalar) isResolvable() {} func ApplyResolver(s *types.Schema, resolver interface{}) (*Schema, error) { if resolver == nil { return &Schema{Meta: newMeta(s), Schema: *s}, nil } b := newBuilder(s) var query, mutation, subscription Resolvable if t, ok := s.EntryPoints["query"]; ok { if err := b.assignExec(&query, t, reflect.TypeOf(resolver)); err != nil { return nil, err } } if t, ok := s.EntryPoints["mutation"]; ok { if err := b.assignExec(&mutation, t, reflect.TypeOf(resolver)); err != nil { return nil, err } } if t, ok := s.EntryPoints["subscription"]; ok { if err := b.assignExec(&subscription, t, reflect.TypeOf(resolver)); err != nil { return nil, err } } if err := b.finish(); err != nil { return nil, err } return &Schema{ Meta: newMeta(s), Schema: *s, Resolver: reflect.ValueOf(resolver), Query: query, Mutation: mutation, Subscription: subscription, }, nil } type execBuilder struct { schema *types.Schema resMap map[typePair]*resMapEntry packerBuilder *packer.Builder } type typePair struct { graphQLType types.Type resolverType reflect.Type } type resMapEntry struct { exec Resolvable targets []*Resolvable } func newBuilder(s *types.Schema) *execBuilder { return &execBuilder{ schema: s, resMap: make(map[typePair]*resMapEntry), packerBuilder: packer.NewBuilder(), } } func (b *execBuilder) finish() error { for _, entry := range b.resMap { for _, target := range entry.targets { *target = entry.exec } } return b.packerBuilder.Finish() } func (b *execBuilder) assignExec(target *Resolvable, t types.Type, resolverType reflect.Type) error { k := typePair{t, resolverType} ref, ok := b.resMap[k] if !ok { ref = &resMapEntry{} b.resMap[k] = ref var err error ref.exec, err = b.makeExec(t, resolverType) if err != nil { return err } } ref.targets = append(ref.targets, target) return nil } func (b *execBuilder) makeExec(t types.Type, resolverType reflect.Type) (Resolvable, error) { var nonNull bool t, nonNull = unwrapNonNull(t) switch t := t.(type) { case *types.ObjectTypeDefinition: return b.makeObjectExec(t.Name, t.Fields, nil, nonNull, resolverType) case *types.InterfaceTypeDefinition: return b.makeObjectExec(t.Name, t.Fields, t.PossibleTypes, nonNull, resolverType) case *types.Union: return b.makeObjectExec(t.Name, nil, t.UnionMemberTypes, nonNull, resolverType) } if !nonNull { if resolverType.Kind() != reflect.Ptr { return nil, fmt.Errorf("%s is not a pointer", resolverType) } resolverType = resolverType.Elem() } switch t := t.(type) { case *types.ScalarTypeDefinition: return makeScalarExec(t, resolverType) case *types.EnumTypeDefinition: return &Scalar{}, nil case *types.List: if resolverType.Kind() != reflect.Slice { return nil, fmt.Errorf("%s is not a slice", resolverType) } e := &List{} if err := b.assignExec(&e.Elem, t.OfType, resolverType.Elem()); err != nil { return nil, err } return e, nil default: panic("invalid type: " + t.String()) } } func makeScalarExec(t *types.ScalarTypeDefinition, resolverType reflect.Type) (Resolvable, error) { implementsType := false switch r := reflect.New(resolverType).Interface().(type) { case *int32: implementsType = t.Name == "Int" case *float64: implementsType = t.Name == "Float" case *string: implementsType = t.Name == "String" case *bool: implementsType = t.Name == "Boolean" case decode.Unmarshaler: implementsType = r.ImplementsGraphQLType(t.Name) } if !implementsType { return nil, fmt.Errorf("can not use %s as %s", resolverType, t.Name) } return &Scalar{}, nil } func (b *execBuilder) makeObjectExec(typeName string, fields types.FieldsDefinition, possibleTypes []*types.ObjectTypeDefinition, nonNull bool, resolverType reflect.Type) (*Object, error) { if !nonNull { if resolverType.Kind() != reflect.Ptr && resolverType.Kind() != reflect.Interface { return nil, fmt.Errorf("%s is not a pointer or interface", resolverType) } } methodHasReceiver := resolverType.Kind() != reflect.Interface Fields := make(map[string]*Field) rt := unwrapPtr(resolverType) fieldsCount := fieldCount(rt, map[string]int{}) for _, f := range fields { var fieldIndex []int methodIndex := findMethod(resolverType, f.Name) if b.schema.UseFieldResolvers && methodIndex == -1 { if fieldsCount[strings.ToLower(stripUnderscore(f.Name))] > 1 { return nil, fmt.Errorf("%s does not resolve %q: ambiguous field %q", resolverType, typeName, f.Name) } fieldIndex = findField(rt, f.Name, []int{}) } if methodIndex == -1 && len(fieldIndex) == 0 { hint := "" if findMethod(reflect.PtrTo(resolverType), f.Name) != -1 { hint = " (hint: the method exists on the pointer type)" } return nil, fmt.Errorf("%s does not resolve %q: missing method for field %q%s", resolverType, typeName, f.Name, hint) } var m reflect.Method var sf reflect.StructField if methodIndex != -1 { m = resolverType.Method(methodIndex) } else { sf = rt.FieldByIndex(fieldIndex) } fe, err := b.makeFieldExec(typeName, f, m, sf, methodIndex, fieldIndex, methodHasReceiver) if err != nil { var resolverName string if methodIndex != -1 { resolverName = m.Name } else { resolverName = sf.Name } return nil, fmt.Errorf("%s\n\tused by (%s).%s", err, resolverType, resolverName) } Fields[f.Name] = fe } // Check type assertions when // 1) using method resolvers // 2) Or resolver is not an interface type typeAssertions := make(map[string]*TypeAssertion) if !b.schema.UseFieldResolvers || resolverType.Kind() != reflect.Interface { for _, impl := range possibleTypes { methodIndex := findMethod(resolverType, "To"+impl.Name) if methodIndex == -1 { return nil, fmt.Errorf("%s does not resolve %q: missing method %q to convert to %q", resolverType, typeName, "To"+impl.Name, impl.Name) } m := resolverType.Method(methodIndex) expectedIn := 0 if methodHasReceiver { expectedIn = 1 } if m.Type.NumIn() != expectedIn { return nil, fmt.Errorf("%s does not resolve %q: method %q should't have any arguments", resolverType, typeName, "To"+impl.Name) } if m.Type.NumOut() != 2 { return nil, fmt.Errorf("%s does not resolve %q: method %q should return a value and a bool indicating success", resolverType, typeName, "To"+impl.Name) } a := &TypeAssertion{ MethodIndex: methodIndex, } if err := b.assignExec(&a.TypeExec, impl, resolverType.Method(methodIndex).Type.Out(0)); err != nil { return nil, err } typeAssertions[impl.Name] = a } } return &Object{ Name: typeName, Fields: Fields, TypeAssertions: typeAssertions, }, nil } var contextType = reflect.TypeOf((*context.Context)(nil)).Elem() var errorType = reflect.TypeOf((*error)(nil)).Elem() func (b *execBuilder) makeFieldExec(typeName string, f *types.FieldDefinition, m reflect.Method, sf reflect.StructField, methodIndex int, fieldIndex []int, methodHasReceiver bool) (*Field, error) { var argsPacker *packer.StructPacker var hasError bool var hasContext bool // Validate resolver method only when there is one if methodIndex != -1 { in := make([]reflect.Type, m.Type.NumIn()) for i := range in { in[i] = m.Type.In(i) } if methodHasReceiver { in = in[1:] // first parameter is receiver } hasContext = len(in) > 0 && in[0] == contextType if hasContext { in = in[1:] } if len(f.Arguments) > 0 { if len(in) == 0 { return nil, fmt.Errorf("must have `args struct { ... }` argument for field arguments") } var err error argsPacker, err = b.packerBuilder.MakeStructPacker(f.Arguments, in[0]) if err != nil { return nil, err } in = in[1:] } if len(in) > 0 { return nil, fmt.Errorf("too many arguments") } maxNumOfReturns := 2 if m.Type.NumOut() < maxNumOfReturns-1 { return nil, fmt.Errorf("too few return values") } if m.Type.NumOut() > maxNumOfReturns { return nil, fmt.Errorf("too many return values") } hasError = m.Type.NumOut() == maxNumOfReturns if hasError { if m.Type.Out(maxNumOfReturns-1) != errorType { return nil, fmt.Errorf(`must have "error" as its last return value`) } } } fe := &Field{ FieldDefinition: *f, TypeName: typeName, MethodIndex: methodIndex, FieldIndex: fieldIndex, HasContext: hasContext, ArgsPacker: argsPacker, HasError: hasError, TraceLabel: fmt.Sprintf("GraphQL field: %s.%s", typeName, f.Name), } var out reflect.Type if methodIndex != -1 { out = m.Type.Out(0) sub, ok := b.schema.EntryPoints["subscription"] if ok && typeName == sub.TypeName() && out.Kind() == reflect.Chan { out = m.Type.Out(0).Elem() } } else { out = sf.Type } if err := b.assignExec(&fe.ValueExec, f.Type, out); err != nil { return nil, err } return fe, nil } func findMethod(t reflect.Type, name string) int { for i := 0; i < t.NumMethod(); i++ { if strings.EqualFold(stripUnderscore(name), stripUnderscore(t.Method(i).Name)) { return i } } return -1 } func findField(t reflect.Type, name string, index []int) []int { for i := 0; i < t.NumField(); i++ { field := t.Field(i) if field.Type.Kind() == reflect.Struct && field.Anonymous { newIndex := findField(field.Type, name, []int{i}) if len(newIndex) > 1 { return append(index, newIndex...) } } if strings.EqualFold(stripUnderscore(name), stripUnderscore(field.Name)) { return append(index, i) } } return index } // fieldCount helps resolve ambiguity when more than one embedded struct contains fields with the same name. func fieldCount(t reflect.Type, count map[string]int) map[string]int { if t.Kind() != reflect.Struct { return nil } for i := 0; i < t.NumField(); i++ { field := t.Field(i) fieldName := strings.ToLower(stripUnderscore(field.Name)) if field.Type.Kind() == reflect.Struct && field.Anonymous { count = fieldCount(field.Type, count) } else { if _, ok := count[fieldName]; !ok { count[fieldName] = 0 } count[fieldName]++ } } return count } func unwrapNonNull(t types.Type) (types.Type, bool) { if nn, ok := t.(*types.NonNull); ok { return nn.OfType, true } return t, false } func stripUnderscore(s string) string { return strings.Replace(s, "_", "", -1) } func unwrapPtr(t reflect.Type) reflect.Type { if t.Kind() == reflect.Ptr { return t.Elem() } return t } graphql-go-1.5.0/internal/exec/selected/000077500000000000000000000000001435002731700201105ustar00rootroot00000000000000graphql-go-1.5.0/internal/exec/selected/selected.go000066400000000000000000000167031435002731700222360ustar00rootroot00000000000000package selected import ( "fmt" "reflect" "sync" "github.com/graph-gophers/graphql-go/errors" "github.com/graph-gophers/graphql-go/internal/exec/packer" "github.com/graph-gophers/graphql-go/internal/exec/resolvable" "github.com/graph-gophers/graphql-go/internal/query" "github.com/graph-gophers/graphql-go/introspection" "github.com/graph-gophers/graphql-go/types" ) type Request struct { Schema *types.Schema Doc *types.ExecutableDefinition Vars map[string]interface{} Mu sync.Mutex Errs []*errors.QueryError DisableIntrospection bool } func (r *Request) AddError(err *errors.QueryError) { r.Mu.Lock() r.Errs = append(r.Errs, err) r.Mu.Unlock() } func ApplyOperation(r *Request, s *resolvable.Schema, op *types.OperationDefinition) []Selection { var obj *resolvable.Object switch op.Type { case query.Query: obj = s.Query.(*resolvable.Object) case query.Mutation: obj = s.Mutation.(*resolvable.Object) case query.Subscription: obj = s.Subscription.(*resolvable.Object) } return applySelectionSet(r, s, obj, op.Selections) } type Selection interface { isSelection() } type SchemaField struct { resolvable.Field Alias string Args map[string]interface{} PackedArgs reflect.Value Sels []Selection Async bool FixedResult reflect.Value } type TypeAssertion struct { resolvable.TypeAssertion Sels []Selection } type TypenameField struct { resolvable.Object Alias string } func (*SchemaField) isSelection() {} func (*TypeAssertion) isSelection() {} func (*TypenameField) isSelection() {} func applySelectionSet(r *Request, s *resolvable.Schema, e *resolvable.Object, sels []types.Selection) (flattenedSels []Selection) { for _, sel := range sels { switch sel := sel.(type) { case *types.Field: field := sel if skipByDirective(r, field.Directives) { continue } switch field.Name.Name { case "__typename": // __typename is available even though r.DisableIntrospection == true // because it is necessary when using union types and interfaces: https://graphql.org/learn/schema/#union-types flattenedSels = append(flattenedSels, &TypenameField{ Object: *e, Alias: field.Alias.Name, }) case "__schema": if !r.DisableIntrospection { flattenedSels = append(flattenedSels, &SchemaField{ Field: s.Meta.FieldSchema, Alias: field.Alias.Name, Sels: applySelectionSet(r, s, s.Meta.Schema, field.SelectionSet), Async: true, FixedResult: reflect.ValueOf(introspection.WrapSchema(r.Schema)), }) } case "__type": if !r.DisableIntrospection { p := packer.ValuePacker{ValueType: reflect.TypeOf("")} v, err := p.Pack(field.Arguments.MustGet("name").Deserialize(r.Vars)) if err != nil { r.AddError(errors.Errorf("%s", err)) return nil } var resolvedType *introspection.Type t, ok := r.Schema.Types[v.String()] if ok { resolvedType = introspection.WrapType(t) } flattenedSels = append(flattenedSels, &SchemaField{ Field: s.Meta.FieldType, Alias: field.Alias.Name, Sels: applySelectionSet(r, s, s.Meta.Type, field.SelectionSet), Async: true, FixedResult: reflect.ValueOf(resolvedType), }) } case "_service": if !r.DisableIntrospection { flattenedSels = append(flattenedSels, &SchemaField{ Field: s.Meta.FieldService, Alias: field.Alias.Name, Sels: applySelectionSet(r, s, s.Meta.Service, field.SelectionSet), Async: true, FixedResult: reflect.ValueOf(introspection.WrapService(r.Schema)), }) } default: fe := e.Fields[field.Name.Name] var args map[string]interface{} var packedArgs reflect.Value if fe.ArgsPacker != nil { args = make(map[string]interface{}) for _, arg := range field.Arguments { args[arg.Name.Name] = arg.Value.Deserialize(r.Vars) } var err error packedArgs, err = fe.ArgsPacker.Pack(args) if err != nil { r.AddError(errors.Errorf("%s", err)) return } } fieldSels := applyField(r, s, fe.ValueExec, field.SelectionSet) flattenedSels = append(flattenedSels, &SchemaField{ Field: *fe, Alias: field.Alias.Name, Args: args, PackedArgs: packedArgs, Sels: fieldSels, Async: fe.HasContext || fe.ArgsPacker != nil || fe.HasError || HasAsyncSel(fieldSels), }) } case *types.InlineFragment: frag := sel if skipByDirective(r, frag.Directives) { continue } flattenedSels = append(flattenedSels, applyFragment(r, s, e, &frag.Fragment)...) case *types.FragmentSpread: spread := sel if skipByDirective(r, spread.Directives) { continue } flattenedSels = append(flattenedSels, applyFragment(r, s, e, &r.Doc.Fragments.Get(spread.Name.Name).Fragment)...) default: panic("invalid type") } } return } func applyFragment(r *Request, s *resolvable.Schema, e *resolvable.Object, frag *types.Fragment) []Selection { if frag.On.Name != e.Name { t := r.Schema.Resolve(frag.On.Name) face, ok := t.(*types.InterfaceTypeDefinition) if !ok && frag.On.Name != "" { a, ok2 := e.TypeAssertions[frag.On.Name] if !ok2 { panic(fmt.Errorf("%q does not implement %q", frag.On, e.Name)) // TODO proper error handling } return []Selection{&TypeAssertion{ TypeAssertion: *a, Sels: applySelectionSet(r, s, a.TypeExec.(*resolvable.Object), frag.Selections), }} } if ok && len(face.PossibleTypes) > 0 { sels := []Selection{} for _, t := range face.PossibleTypes { if t.Name == e.Name { return applySelectionSet(r, s, e, frag.Selections) } if a, ok := e.TypeAssertions[t.Name]; ok { sels = append(sels, &TypeAssertion{ TypeAssertion: *a, Sels: applySelectionSet(r, s, a.TypeExec.(*resolvable.Object), frag.Selections), }) } } if len(sels) == 0 { panic(fmt.Errorf("%q does not implement %q", e.Name, frag.On)) // TODO proper error handling } return sels } } return applySelectionSet(r, s, e, frag.Selections) } func applyField(r *Request, s *resolvable.Schema, e resolvable.Resolvable, sels []types.Selection) []Selection { switch e := e.(type) { case *resolvable.Object: return applySelectionSet(r, s, e, sels) case *resolvable.List: return applyField(r, s, e.Elem, sels) case *resolvable.Scalar: return nil default: panic("unreachable") } } func skipByDirective(r *Request, directives types.DirectiveList) bool { if d := directives.Get("skip"); d != nil { p := packer.ValuePacker{ValueType: reflect.TypeOf(false)} v, err := p.Pack(d.Arguments.MustGet("if").Deserialize(r.Vars)) if err != nil { r.AddError(errors.Errorf("%s", err)) } if err == nil && v.Bool() { return true } } if d := directives.Get("include"); d != nil { p := packer.ValuePacker{ValueType: reflect.TypeOf(false)} v, err := p.Pack(d.Arguments.MustGet("if").Deserialize(r.Vars)) if err != nil { r.AddError(errors.Errorf("%s", err)) } if err == nil && !v.Bool() { return true } } return false } func HasAsyncSel(sels []Selection) bool { for _, sel := range sels { switch sel := sel.(type) { case *SchemaField: if sel.Async { return true } case *TypeAssertion: if HasAsyncSel(sel.Sels) { return true } case *TypenameField: // sync default: panic("unreachable") } } return false } graphql-go-1.5.0/internal/exec/subscribe.go000066400000000000000000000104321435002731700206300ustar00rootroot00000000000000package exec import ( "bytes" "context" "encoding/json" "fmt" "reflect" "time" "github.com/graph-gophers/graphql-go/errors" "github.com/graph-gophers/graphql-go/internal/exec/resolvable" "github.com/graph-gophers/graphql-go/internal/exec/selected" "github.com/graph-gophers/graphql-go/types" ) type Response struct { Data json.RawMessage Errors []*errors.QueryError } func (r *Request) Subscribe(ctx context.Context, s *resolvable.Schema, op *types.OperationDefinition) <-chan *Response { var result reflect.Value var f *fieldToExec var err *errors.QueryError func() { defer r.handlePanic(ctx) sels := selected.ApplyOperation(&r.Request, s, op) var fields []*fieldToExec collectFieldsToResolve(sels, s, s.Resolver, &fields, make(map[string]*fieldToExec)) // TODO: move this check into validation.Validate if len(fields) != 1 { err = errors.Errorf("%s", "can subscribe to at most one subscription at a time") return } f = fields[0] var in []reflect.Value if f.field.HasContext { in = append(in, reflect.ValueOf(ctx)) } if f.field.ArgsPacker != nil { in = append(in, f.field.PackedArgs) } callOut := f.resolver.Method(f.field.MethodIndex).Call(in) result = callOut[0] if f.field.HasError && !callOut[1].IsNil() { switch resolverErr := callOut[1].Interface().(type) { case *errors.QueryError: err = resolverErr case error: err = errors.Errorf("%s", resolverErr) err.ResolverError = resolverErr default: panic(fmt.Errorf("can only deal with *QueryError and error types, got %T", resolverErr)) } } }() // Handles the case where the locally executed func above panicked if len(r.Request.Errs) > 0 { return sendAndReturnClosed(&Response{Errors: r.Request.Errs}) } if f == nil { return sendAndReturnClosed(&Response{Errors: []*errors.QueryError{err}}) } if err != nil { if _, nonNullChild := f.field.Type.(*types.NonNull); nonNullChild { return sendAndReturnClosed(&Response{Errors: []*errors.QueryError{err}}) } return sendAndReturnClosed(&Response{Data: []byte(fmt.Sprintf(`{"%s":null}`, f.field.Alias)), Errors: []*errors.QueryError{err}}) } if ctxErr := ctx.Err(); ctxErr != nil { return sendAndReturnClosed(&Response{Errors: []*errors.QueryError{errors.Errorf("%s", ctxErr)}}) } c := make(chan *Response) // TODO: handle resolver nil channel better? if result.IsZero() { close(c) return c } go func() { for { // Check subscription context chosen, resp, ok := reflect.Select([]reflect.SelectCase{ { Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ctx.Done()), }, { Dir: reflect.SelectRecv, Chan: result, }, }) switch chosen { // subscription context done case 0: close(c) return // upstream received case 1: // upstream closed if !ok { close(c) return } subR := &Request{ Request: selected.Request{ Doc: r.Request.Doc, Vars: r.Request.Vars, Schema: r.Request.Schema, }, Limiter: r.Limiter, Tracer: r.Tracer, Logger: r.Logger, } var out bytes.Buffer func() { timeout := r.SubscribeResolverTimeout if timeout == 0 { timeout = time.Second } subCtx, cancel := context.WithTimeout(ctx, timeout) defer cancel() // resolve response func() { defer subR.handlePanic(subCtx) var buf bytes.Buffer subR.execSelectionSet(subCtx, f.sels, f.field.Type, &pathSegment{nil, f.field.Alias}, s, resp, &buf) propagateChildError := false if _, nonNullChild := f.field.Type.(*types.NonNull); nonNullChild && resolvedToNull(&buf) { propagateChildError = true } if !propagateChildError { out.WriteString(fmt.Sprintf(`{"%s":`, f.field.Alias)) out.Write(buf.Bytes()) out.WriteString(`}`) } }() if err := subCtx.Err(); err != nil { c <- &Response{Errors: []*errors.QueryError{errors.Errorf("%s", err)}} return } // Send response within timeout // TODO: maybe block until sent? select { case <-subCtx.Done(): case c <- &Response{Data: out.Bytes(), Errors: subR.Errs}: } }() } } }() return c } func sendAndReturnClosed(resp *Response) chan *Response { c := make(chan *Response, 1) c <- resp close(c) return c } graphql-go-1.5.0/internal/query/000077500000000000000000000000001435002731700165415ustar00rootroot00000000000000graphql-go-1.5.0/internal/query/query.go000066400000000000000000000073101435002731700202360ustar00rootroot00000000000000package query import ( "fmt" "text/scanner" "github.com/graph-gophers/graphql-go/errors" "github.com/graph-gophers/graphql-go/internal/common" "github.com/graph-gophers/graphql-go/types" ) const ( Query types.OperationType = "QUERY" Mutation types.OperationType = "MUTATION" Subscription types.OperationType = "SUBSCRIPTION" ) func Parse(queryString string) (*types.ExecutableDefinition, *errors.QueryError) { l := common.NewLexer(queryString, false) var execDef *types.ExecutableDefinition err := l.CatchSyntaxError(func() { execDef = parseExecutableDefinition(l) }) if err != nil { return nil, err } return execDef, nil } func parseExecutableDefinition(l *common.Lexer) *types.ExecutableDefinition { ed := &types.ExecutableDefinition{} l.ConsumeWhitespace() for l.Peek() != scanner.EOF { if l.Peek() == '{' { op := &types.OperationDefinition{Type: Query, Loc: l.Location()} op.Selections = parseSelectionSet(l) ed.Operations = append(ed.Operations, op) continue } loc := l.Location() switch x := l.ConsumeIdent(); x { case "query": op := parseOperation(l, Query) op.Loc = loc ed.Operations = append(ed.Operations, op) case "mutation": ed.Operations = append(ed.Operations, parseOperation(l, Mutation)) case "subscription": ed.Operations = append(ed.Operations, parseOperation(l, Subscription)) case "fragment": frag := parseFragment(l) frag.Loc = loc ed.Fragments = append(ed.Fragments, frag) default: l.SyntaxError(fmt.Sprintf(`unexpected %q, expecting "fragment"`, x)) } } return ed } func parseOperation(l *common.Lexer, opType types.OperationType) *types.OperationDefinition { op := &types.OperationDefinition{Type: opType} op.Name.Loc = l.Location() if l.Peek() == scanner.Ident { op.Name = l.ConsumeIdentWithLoc() } op.Directives = common.ParseDirectives(l) if l.Peek() == '(' { l.ConsumeToken('(') for l.Peek() != ')' { loc := l.Location() l.ConsumeToken('$') iv := common.ParseInputValue(l) iv.Loc = loc op.Vars = append(op.Vars, iv) } l.ConsumeToken(')') } op.Selections = parseSelectionSet(l) return op } func parseFragment(l *common.Lexer) *types.FragmentDefinition { f := &types.FragmentDefinition{} f.Name = l.ConsumeIdentWithLoc() l.ConsumeKeyword("on") f.On = types.TypeName{Ident: l.ConsumeIdentWithLoc()} f.Directives = common.ParseDirectives(l) f.Selections = parseSelectionSet(l) return f } func parseSelectionSet(l *common.Lexer) []types.Selection { var sels []types.Selection l.ConsumeToken('{') for l.Peek() != '}' { sels = append(sels, parseSelection(l)) } l.ConsumeToken('}') return sels } func parseSelection(l *common.Lexer) types.Selection { if l.Peek() == '.' { return parseSpread(l) } return parseFieldDef(l) } func parseFieldDef(l *common.Lexer) *types.Field { f := &types.Field{} f.Alias = l.ConsumeIdentWithLoc() f.Name = f.Alias if l.Peek() == ':' { l.ConsumeToken(':') f.Name = l.ConsumeIdentWithLoc() } if l.Peek() == '(' { f.Arguments = common.ParseArgumentList(l) } f.Directives = common.ParseDirectives(l) if l.Peek() == '{' { f.SelectionSetLoc = l.Location() f.SelectionSet = parseSelectionSet(l) } return f } func parseSpread(l *common.Lexer) types.Selection { loc := l.Location() l.ConsumeToken('.') l.ConsumeToken('.') l.ConsumeToken('.') f := &types.InlineFragment{Loc: loc} if l.Peek() == scanner.Ident { ident := l.ConsumeIdentWithLoc() if ident.Name != "on" { fs := &types.FragmentSpread{ Name: ident, Loc: loc, } fs.Directives = common.ParseDirectives(l) return fs } f.On = types.TypeName{Ident: l.ConsumeIdentWithLoc()} } f.Directives = common.ParseDirectives(l) f.Selections = parseSelectionSet(l) return f } graphql-go-1.5.0/internal/schema/000077500000000000000000000000001435002731700166345ustar00rootroot00000000000000graphql-go-1.5.0/internal/schema/meta.go000066400000000000000000000174531435002731700201230ustar00rootroot00000000000000package schema import ( "github.com/graph-gophers/graphql-go/types" ) func init() { _ = newMeta() } // newMeta initializes an instance of the meta Schema. func newMeta() *types.Schema { s := &types.Schema{ EntryPointNames: make(map[string]string), Types: make(map[string]types.NamedType), Directives: make(map[string]*types.DirectiveDefinition), } err := Parse(s, metaSrc, false) if err != nil { panic(err) } return s } var metaSrc = ` # The ` + "`" + `Int` + "`" + ` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. scalar Int # The ` + "`" + `Float` + "`" + ` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point). scalar Float # The ` + "`" + `String` + "`" + ` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text. scalar String # The ` + "`" + `Boolean` + "`" + ` scalar type represents ` + "`" + `true` + "`" + ` or ` + "`" + `false` + "`" + `. scalar Boolean # The ` + "`" + `ID` + "`" + ` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as ` + "`" + `"4"` + "`" + `) or integer (such as ` + "`" + `4` + "`" + `) input value will be accepted as an ID. scalar ID # Directs the executor to include this field or fragment only when the ` + "`" + `if` + "`" + ` argument is true. directive @include( # Included when true. if: Boolean! ) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT # Directs the executor to skip this field or fragment when the ` + "`" + `if` + "`" + ` argument is true. directive @skip( # Skipped when true. if: Boolean! ) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT # Marks an element of a GraphQL schema as no longer supported. directive @deprecated( # Explains why this element was deprecated, usually also including a suggestion # for how to access supported similar data. Formatted in # [Markdown](https://daringfireball.net/projects/markdown/). reason: String = "No longer supported" ) on FIELD_DEFINITION | ENUM_VALUE | ARGUMENT_DEFINITION # Provides a scalar specification URL for specifying the behavior of custom scalar types. directive @specifiedBy( # The URL should point to a human-readable specification of the data format, serialization, and coercion rules. url: String! ) on SCALAR # A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document. # # In some cases, you need to provide options to alter GraphQL's execution behavior # in ways field arguments will not suffice, such as conditionally including or # skipping a field. Directives provide this by describing additional information # to the executor. type __Directive { name: String! description: String locations: [__DirectiveLocation!]! args: [__InputValue!]! } # A Directive can be adjacent to many parts of the GraphQL language, a # __DirectiveLocation describes one such possible adjacencies. enum __DirectiveLocation { # Location adjacent to a query operation. QUERY # Location adjacent to a mutation operation. MUTATION # Location adjacent to a subscription operation. SUBSCRIPTION # Location adjacent to a field. FIELD # Location adjacent to a fragment definition. FRAGMENT_DEFINITION # Location adjacent to a fragment spread. FRAGMENT_SPREAD # Location adjacent to an inline fragment. INLINE_FRAGMENT # Location adjacent to a schema definition. SCHEMA # Location adjacent to a scalar definition. SCALAR # Location adjacent to an object type definition. OBJECT # Location adjacent to a field definition. FIELD_DEFINITION # Location adjacent to an argument definition. ARGUMENT_DEFINITION # Location adjacent to an interface definition. INTERFACE # Location adjacent to a union definition. UNION # Location adjacent to an enum definition. ENUM # Location adjacent to an enum value definition. ENUM_VALUE # Location adjacent to an input object type definition. INPUT_OBJECT # Location adjacent to an input object field definition. INPUT_FIELD_DEFINITION } # One possible value for a given Enum. Enum values are unique values, not a # placeholder for a string or numeric value. However an Enum value is returned in # a JSON response as a string. type __EnumValue { name: String! description: String isDeprecated: Boolean! deprecationReason: String } # Object and Interface types are described by a list of Fields, each of which has # a name, potentially a list of arguments, and a return type. type __Field { name: String! description: String args: [__InputValue!]! type: __Type! isDeprecated: Boolean! deprecationReason: String } # Arguments provided to Fields or Directives and the input fields of an # InputObject are represented as Input Values which describe their type and # optionally a default value. type __InputValue { name: String! description: String type: __Type! # A GraphQL-formatted string representing the default value for this input value. defaultValue: String } # A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all # available types and directives on the server, as well as the entry points for # query, mutation, and subscription operations. type __Schema { # A list of all types supported by this server. types: [__Type!]! # The type that query operations will be rooted at. queryType: __Type! # If this server supports mutation, the type that mutation operations will be rooted at. mutationType: __Type # If this server support subscription, the type that subscription operations will be rooted at. subscriptionType: __Type # A list of all directives supported by this server. directives: [__Directive!]! } # The fundamental unit of any GraphQL Schema is the type. There are many kinds of # types in GraphQL as represented by the ` + "`" + `__TypeKind` + "`" + ` enum. # # Depending on the kind of a type, certain fields describe information about that # type. Scalar types provide no information beyond a name and description, while # Enum types provide their values. Object and Interface types provide the fields # they describe. Abstract types, Union and Interface, provide the Object types # possible at runtime. List and NonNull types compose other types. type __Type { kind: __TypeKind! name: String description: String fields(includeDeprecated: Boolean = false): [__Field!] interfaces: [__Type!] possibleTypes: [__Type!] enumValues(includeDeprecated: Boolean = false): [__EnumValue!] inputFields: [__InputValue!] ofType: __Type specifiedByURL: String } # An enum describing what kind of type a given ` + "`" + `__Type` + "`" + ` is. enum __TypeKind { # Indicates this type is a scalar. SCALAR # Indicates this type is an object. ` + "`" + `fields` + "`" + ` and ` + "`" + `interfaces` + "`" + ` are valid fields. OBJECT # Indicates this type is an interface. ` + "`" + `fields` + "`" + ` and ` + "`" + `possibleTypes` + "`" + ` are valid fields. INTERFACE # Indicates this type is a union. ` + "`" + `possibleTypes` + "`" + ` is a valid field. UNION # Indicates this type is an enum. ` + "`" + `enumValues` + "`" + ` is a valid field. ENUM # Indicates this type is an input object. ` + "`" + `inputFields` + "`" + ` is a valid field. INPUT_OBJECT # Indicates this type is a list. ` + "`" + `ofType` + "`" + ` is a valid field. LIST # Indicates this type is a non-null. ` + "`" + `ofType` + "`" + ` is a valid field. NON_NULL } type _Service { sdl: String! } ` graphql-go-1.5.0/internal/schema/schema.go000066400000000000000000000413011435002731700204220ustar00rootroot00000000000000package schema import ( "fmt" "text/scanner" "github.com/graph-gophers/graphql-go/errors" "github.com/graph-gophers/graphql-go/internal/common" "github.com/graph-gophers/graphql-go/types" ) // New initializes an instance of Schema. func New() *types.Schema { s := &types.Schema{ EntryPointNames: make(map[string]string), Types: make(map[string]types.NamedType), Directives: make(map[string]*types.DirectiveDefinition), } m := newMeta() for n, t := range m.Types { s.Types[n] = t } for n, d := range m.Directives { s.Directives[n] = d } return s } func Parse(s *types.Schema, schemaString string, useStringDescriptions bool) error { l := common.NewLexer(schemaString, useStringDescriptions) err := l.CatchSyntaxError(func() { parseSchema(s, l) }) if err != nil { return err } if err := mergeExtensions(s); err != nil { return err } for _, t := range s.Types { if err := resolveNamedType(s, t); err != nil { return err } } for _, d := range s.Directives { for _, arg := range d.Arguments { t, err := common.ResolveType(arg.Type, s.Resolve) if err != nil { return err } arg.Type = t } } // https://graphql.github.io/graphql-spec/June2018/#sec-Root-Operation-Types // > While any type can be the root operation type for a GraphQL operation, the type system definition language can // > omit the schema definition when the query, mutation, and subscription root types are named Query, Mutation, // > and Subscription respectively. if len(s.EntryPointNames) == 0 { if _, ok := s.Types["Query"]; ok { s.EntryPointNames["query"] = "Query" } if _, ok := s.Types["Mutation"]; ok { s.EntryPointNames["mutation"] = "Mutation" } if _, ok := s.Types["Subscription"]; ok { s.EntryPointNames["subscription"] = "Subscription" } } s.EntryPoints = make(map[string]types.NamedType) for key, name := range s.EntryPointNames { t, ok := s.Types[name] if !ok { return errors.Errorf("type %q not found", name) } s.EntryPoints[key] = t } // Interface types need validation: https://spec.graphql.org/draft/#sec-Interfaces.Interfaces-Implementing-Interfaces for _, typeDef := range s.Types { switch t := typeDef.(type) { case *types.InterfaceTypeDefinition: for i, implements := range t.Interfaces { typ, ok := s.Types[implements.Name] if !ok { return errors.Errorf("interface %q not found", implements) } inteface, ok := typ.(*types.InterfaceTypeDefinition) if !ok { return errors.Errorf("type %q is not an interface", inteface) } for _, f := range inteface.Fields.Names() { if t.Fields.Get(f) == nil { return errors.Errorf("interface %q expects field %q but %q does not provide it", inteface.Name, f, t.Name) } } t.Interfaces[i] = inteface } default: continue } } for _, obj := range s.Objects { obj.Interfaces = make([]*types.InterfaceTypeDefinition, len(obj.InterfaceNames)) if err := resolveDirectives(s, obj.Directives, "OBJECT"); err != nil { return err } for _, field := range obj.Fields { if err := resolveDirectives(s, field.Directives, "FIELD_DEFINITION"); err != nil { return err } } for i, intfName := range obj.InterfaceNames { t, ok := s.Types[intfName] if !ok { return errors.Errorf("interface %q not found", intfName) } intf, ok := t.(*types.InterfaceTypeDefinition) if !ok { return errors.Errorf("type %q is not an interface", intfName) } for _, f := range intf.Fields.Names() { if obj.Fields.Get(f) == nil { return errors.Errorf("interface %q expects field %q but %q does not provide it", intfName, f, obj.Name) } } obj.Interfaces[i] = intf intf.PossibleTypes = append(intf.PossibleTypes, obj) } } for _, union := range s.Unions { if err := resolveDirectives(s, union.Directives, "UNION"); err != nil { return err } union.UnionMemberTypes = make([]*types.ObjectTypeDefinition, len(union.TypeNames)) for i, name := range union.TypeNames { t, ok := s.Types[name] if !ok { return errors.Errorf("object type %q not found", name) } obj, ok := t.(*types.ObjectTypeDefinition) if !ok { return errors.Errorf("type %q is not an object", name) } union.UnionMemberTypes[i] = obj } } for _, enum := range s.Enums { if err := resolveDirectives(s, enum.Directives, "ENUM"); err != nil { return err } for _, value := range enum.EnumValuesDefinition { if err := resolveDirectives(s, value.Directives, "ENUM_VALUE"); err != nil { return err } } } s.SchemaString = schemaString return nil } func ParseSchema(schemaString string, useStringDescriptions bool) (*types.Schema, error) { s := New() err := Parse(s, schemaString, useStringDescriptions) return s, err } func mergeExtensions(s *types.Schema) error { for _, ext := range s.Extensions { typ := s.Types[ext.Type.TypeName()] if typ == nil { return fmt.Errorf("trying to extend unknown type %q", ext.Type.TypeName()) } if typ.Kind() != ext.Type.Kind() { return fmt.Errorf("trying to extend type %q with type %q", typ.Kind(), ext.Type.Kind()) } switch og := typ.(type) { case *types.ObjectTypeDefinition: e := ext.Type.(*types.ObjectTypeDefinition) for _, field := range e.Fields { if og.Fields.Get(field.Name) != nil { return fmt.Errorf("extended field %q already exists", field.Name) } } og.Fields = append(og.Fields, e.Fields...) for _, en := range e.InterfaceNames { for _, on := range og.InterfaceNames { if on == en { return fmt.Errorf("interface %q implemented in the extension is already implemented in %q", on, og.Name) } } } og.InterfaceNames = append(og.InterfaceNames, e.InterfaceNames...) case *types.InputObject: e := ext.Type.(*types.InputObject) for _, field := range e.Values { if og.Values.Get(field.Name.Name) != nil { return fmt.Errorf("extended field %q already exists", field.Name) } } og.Values = append(og.Values, e.Values...) case *types.InterfaceTypeDefinition: e := ext.Type.(*types.InterfaceTypeDefinition) for _, field := range e.Fields { if og.Fields.Get(field.Name) != nil { return fmt.Errorf("extended field %s already exists", field.Name) } } og.Fields = append(og.Fields, e.Fields...) case *types.Union: e := ext.Type.(*types.Union) for _, en := range e.TypeNames { for _, on := range og.TypeNames { if on == en { return fmt.Errorf("union type %q already declared in %q", on, og.Name) } } } og.TypeNames = append(og.TypeNames, e.TypeNames...) case *types.EnumTypeDefinition: e := ext.Type.(*types.EnumTypeDefinition) for _, en := range e.EnumValuesDefinition { for _, on := range og.EnumValuesDefinition { if on.EnumValue == en.EnumValue { return fmt.Errorf("enum value %q already declared in %q", on.EnumValue, og.Name) } } } og.EnumValuesDefinition = append(og.EnumValuesDefinition, e.EnumValuesDefinition...) default: return fmt.Errorf(`unexpected %q, expecting "schema", "type", "enum", "interface", "union" or "input"`, og.TypeName()) } } return nil } func resolveNamedType(s *types.Schema, t types.NamedType) error { switch t := t.(type) { case *types.ObjectTypeDefinition: for _, f := range t.Fields { if err := resolveField(s, f); err != nil { return err } } case *types.InterfaceTypeDefinition: for _, f := range t.Fields { if err := resolveField(s, f); err != nil { return err } } if err := resolveDirectives(s, t.Directives, "INTERFACE"); err != nil { return err } case *types.InputObject: if err := resolveInputObject(s, t.Values); err != nil { return err } if err := resolveDirectives(s, t.Directives, "INPUT_OBJECT"); err != nil { return err } case *types.ScalarTypeDefinition: if err := resolveDirectives(s, t.Directives, "SCALAR"); err != nil { return err } } return nil } func resolveField(s *types.Schema, f *types.FieldDefinition) error { t, err := common.ResolveType(f.Type, s.Resolve) if err != nil { return err } f.Type = t if err := resolveDirectives(s, f.Directives, "FIELD_DEFINITION"); err != nil { return err } return resolveInputObject(s, f.Arguments) } func resolveDirectives(s *types.Schema, directives types.DirectiveList, loc string) error { alreadySeenNonRepeatable := make(map[string]struct{}) for _, d := range directives { dirName := d.Name.Name dd, ok := s.Directives[dirName] if !ok { return errors.Errorf("directive %q not found", dirName) } validLoc := false for _, l := range dd.Locations { if l == loc { validLoc = true break } } if !validLoc { return errors.Errorf("invalid location %q for directive %q (must be one of %v)", loc, dirName, dd.Locations) } for _, arg := range d.Arguments { if dd.Arguments.Get(arg.Name.Name) == nil { return errors.Errorf("invalid argument %q for directive %q", arg.Name.Name, dirName) } } for _, arg := range dd.Arguments { if _, ok := d.Arguments.Get(arg.Name.Name); !ok { d.Arguments = append(d.Arguments, &types.Argument{Name: arg.Name, Value: arg.Default}) } } if dd.Repeatable { continue } if _, seen := alreadySeenNonRepeatable[dirName]; seen { return errors.Errorf(`non repeatable directive %q can not be repeated. Consider adding "repeatable".`, dirName) } alreadySeenNonRepeatable[dirName] = struct{}{} } return nil } func resolveInputObject(s *types.Schema, values types.ArgumentsDefinition) error { for _, v := range values { t, err := common.ResolveType(v.Type, s.Resolve) if err != nil { return err } v.Type = t if err := resolveDirectives(s, v.Directives, "ARGUMENT_DEFINITION"); err != nil { return err } } return nil } func parseSchema(s *types.Schema, l *common.Lexer) { l.ConsumeWhitespace() for l.Peek() != scanner.EOF { desc := l.DescComment() switch x := l.ConsumeIdent(); x { case "schema": l.ConsumeToken('{') for l.Peek() != '}' { name := l.ConsumeIdent() l.ConsumeToken(':') typ := l.ConsumeIdent() s.EntryPointNames[name] = typ } l.ConsumeToken('}') case "type": obj := parseObjectDef(l) obj.Desc = desc s.Types[obj.Name] = obj s.Objects = append(s.Objects, obj) case "interface": iface := parseInterfaceDef(l) iface.Desc = desc s.Types[iface.Name] = iface case "union": union := parseUnionDef(l) union.Desc = desc s.Types[union.Name] = union s.Unions = append(s.Unions, union) case "enum": enum := parseEnumDef(l) enum.Desc = desc s.Types[enum.Name] = enum s.Enums = append(s.Enums, enum) case "input": input := parseInputDef(l) input.Desc = desc s.Types[input.Name] = input case "scalar": loc := l.Location() name := l.ConsumeIdent() directives := common.ParseDirectives(l) s.Types[name] = &types.ScalarTypeDefinition{Name: name, Desc: desc, Directives: directives, Loc: loc} case "directive": directive := parseDirectiveDef(l) directive.Desc = desc s.Directives[directive.Name] = directive case "extend": parseExtension(s, l) default: // TODO: Add support for type extensions. l.SyntaxError(fmt.Sprintf(`unexpected %q, expecting "schema", "type", "enum", "interface", "union", "input", "scalar" or "directive"`, x)) } } } func parseObjectDef(l *common.Lexer) *types.ObjectTypeDefinition { object := &types.ObjectTypeDefinition{Loc: l.Location(), Name: l.ConsumeIdent()} for { if l.Peek() == '{' { break } if l.Peek() == '@' { object.Directives = common.ParseDirectives(l) continue } if l.Peek() != scanner.Ident { break } l.ConsumeKeyword("implements") for l.Peek() != '{' && l.Peek() != '@' { if l.Peek() == '&' { l.ConsumeToken('&') } object.InterfaceNames = append(object.InterfaceNames, l.ConsumeIdent()) } } l.ConsumeToken('{') object.Fields = parseFieldsDef(l) l.ConsumeToken('}') return object } func parseInterfaceDef(l *common.Lexer) *types.InterfaceTypeDefinition { i := &types.InterfaceTypeDefinition{Loc: l.Location(), Name: l.ConsumeIdent()} if l.Peek() == scanner.Ident { l.ConsumeKeyword("implements") i.Interfaces = append(i.Interfaces, &types.InterfaceTypeDefinition{Name: l.ConsumeIdent()}) for l.Peek() == '&' { l.ConsumeToken('&') i.Interfaces = append(i.Interfaces, &types.InterfaceTypeDefinition{Name: l.ConsumeIdent()}) } } i.Directives = common.ParseDirectives(l) l.ConsumeToken('{') i.Fields = parseFieldsDef(l) l.ConsumeToken('}') return i } func parseUnionDef(l *common.Lexer) *types.Union { union := &types.Union{Loc: l.Location(), Name: l.ConsumeIdent()} union.Directives = common.ParseDirectives(l) l.ConsumeToken('=') union.TypeNames = []string{l.ConsumeIdent()} for l.Peek() == '|' { l.ConsumeToken('|') union.TypeNames = append(union.TypeNames, l.ConsumeIdent()) } return union } func parseInputDef(l *common.Lexer) *types.InputObject { i := &types.InputObject{} i.Loc = l.Location() i.Name = l.ConsumeIdent() i.Directives = common.ParseDirectives(l) l.ConsumeToken('{') for l.Peek() != '}' { i.Values = append(i.Values, common.ParseInputValue(l)) } l.ConsumeToken('}') return i } func parseEnumDef(l *common.Lexer) *types.EnumTypeDefinition { enum := &types.EnumTypeDefinition{Loc: l.Location(), Name: l.ConsumeIdent()} enum.Directives = common.ParseDirectives(l) l.ConsumeToken('{') for l.Peek() != '}' { v := &types.EnumValueDefinition{ Desc: l.DescComment(), Loc: l.Location(), EnumValue: l.ConsumeIdent(), Directives: common.ParseDirectives(l), } enum.EnumValuesDefinition = append(enum.EnumValuesDefinition, v) } l.ConsumeToken('}') return enum } func parseDirectiveDef(l *common.Lexer) *types.DirectiveDefinition { l.ConsumeToken('@') loc := l.Location() d := &types.DirectiveDefinition{Name: l.ConsumeIdent(), Loc: loc} if l.Peek() == '(' { l.ConsumeToken('(') for l.Peek() != ')' { v := common.ParseInputValue(l) d.Arguments = append(d.Arguments, v) } l.ConsumeToken(')') } switch x := l.ConsumeIdent(); x { case "on": // no-op; Go doesn't fallthrough by default case "repeatable": d.Repeatable = true l.ConsumeKeyword("on") default: l.SyntaxError(fmt.Sprintf(`unexpected %q, expecting "on" or "repeatable"`, x)) } for { loc := l.ConsumeIdent() if _, ok := legalDirectiveLocationNames[loc]; !ok { l.SyntaxError(fmt.Sprintf("%q is not a legal directive location (options: %v)", loc, legalDirectiveLocationNames)) } d.Locations = append(d.Locations, loc) if l.Peek() != '|' { break } l.ConsumeToken('|') } return d } func parseExtension(s *types.Schema, l *common.Lexer) { loc := l.Location() switch x := l.ConsumeIdent(); x { case "schema": l.ConsumeToken('{') for l.Peek() != '}' { name := l.ConsumeIdent() l.ConsumeToken(':') typ := l.ConsumeIdent() s.EntryPointNames[name] = typ } l.ConsumeToken('}') case "type": obj := parseObjectDef(l) s.Extensions = append(s.Extensions, &types.Extension{Type: obj, Loc: loc}) case "interface": iface := parseInterfaceDef(l) s.Extensions = append(s.Extensions, &types.Extension{Type: iface, Loc: loc}) case "union": union := parseUnionDef(l) s.Extensions = append(s.Extensions, &types.Extension{Type: union, Loc: loc}) case "enum": enum := parseEnumDef(l) s.Extensions = append(s.Extensions, &types.Extension{Type: enum, Loc: loc}) case "input": input := parseInputDef(l) s.Extensions = append(s.Extensions, &types.Extension{Type: input, Loc: loc}) default: // TODO: Add ScalarTypeDefinition when adding directives l.SyntaxError(fmt.Sprintf(`unexpected %q, expecting "schema", "type", "enum", "interface", "union" or "input"`, x)) } } func parseFieldsDef(l *common.Lexer) types.FieldsDefinition { var fields types.FieldsDefinition for l.Peek() != '}' { f := &types.FieldDefinition{} f.Desc = l.DescComment() f.Loc = l.Location() f.Name = l.ConsumeIdent() if l.Peek() == '(' { l.ConsumeToken('(') for l.Peek() != ')' { f.Arguments = append(f.Arguments, common.ParseInputValue(l)) } l.ConsumeToken(')') } l.ConsumeToken(':') f.Type = common.ParseType(l) f.Directives = common.ParseDirectives(l) fields = append(fields, f) } return fields } var legalDirectiveLocationNames = map[string]struct{}{ "SCHEMA": {}, "SCALAR": {}, "OBJECT": {}, "FIELD_DEFINITION": {}, "ARGUMENT_DEFINITION": {}, "INTERFACE": {}, "UNION": {}, "ENUM": {}, "ENUM_VALUE": {}, "INPUT_OBJECT": {}, "INPUT_FIELD_DEFINITION": {}, "QUERY": {}, "MUTATION": {}, "SUBSCRIPTION": {}, "FIELD": {}, "FRAGMENT_DEFINITION": {}, "FRAGMENT_SPREAD": {}, "INLINE_FRAGMENT": {}, "VARIABLE_DEFINITION": {}, } graphql-go-1.5.0/internal/schema/schema_internal_test.go000066400000000000000000000244041435002731700233620ustar00rootroot00000000000000package schema import ( "reflect" "testing" "github.com/graph-gophers/graphql-go/errors" "github.com/graph-gophers/graphql-go/internal/common" "github.com/graph-gophers/graphql-go/types" ) func TestParseInterfaceDef(t *testing.T) { type testCase struct { description string definition string expected *types.InterfaceTypeDefinition err *errors.QueryError } tests := []testCase{{ description: "Parses simple interface", definition: "Greeting { field: String }", expected: &types.InterfaceTypeDefinition{ Name: "Greeting", Loc: errors.Location{Line: 1, Column: 1}, Fields: types.FieldsDefinition{&types.FieldDefinition{Name: "field"}}}, }} for _, test := range tests { t.Run(test.description, func(t *testing.T) { var actual *types.InterfaceTypeDefinition lex := setup(t, test.definition) parse := func() { actual = parseInterfaceDef(lex) } err := lex.CatchSyntaxError(parse) compareErrors(t, test.err, err) compareInterfaces(t, test.expected, actual) }) } } // TestParseObjectDef tests the logic for parsing object types from the schema definition as // written in `parseObjectDef()`. func TestParseObjectDef(t *testing.T) { type testCase struct { description string definition string expected *types.ObjectTypeDefinition err *errors.QueryError } tests := []testCase{{ description: "Parses type inheriting single interface", definition: "Hello implements World { field: String }", expected: &types.ObjectTypeDefinition{Name: "Hello", Loc: errors.Location{Line: 1, Column: 1}, InterfaceNames: []string{"World"}}, }, { description: "Parses type inheriting multiple interfaces", definition: "Hello implements Wo & rld { field: String }", expected: &types.ObjectTypeDefinition{Name: "Hello", Loc: errors.Location{Line: 1, Column: 1}, InterfaceNames: []string{"Wo", "rld"}}, }, { description: "Parses type inheriting multiple interfaces with leading ampersand", definition: "Hello implements & Wo & rld { field: String }", expected: &types.ObjectTypeDefinition{Name: "Hello", Loc: errors.Location{Line: 1, Column: 1}, InterfaceNames: []string{"Wo", "rld"}}, }, { description: "Allows legacy SDL interfaces", definition: "Hello implements Wo, rld { field: String }", expected: &types.ObjectTypeDefinition{Name: "Hello", Loc: errors.Location{Line: 1, Column: 1}, InterfaceNames: []string{"Wo", "rld"}}, }} for _, test := range tests { t.Run(test.description, func(t *testing.T) { var actual *types.ObjectTypeDefinition lex := setup(t, test.definition) parse := func() { actual = parseObjectDef(lex) } err := lex.CatchSyntaxError(parse) compareErrors(t, test.err, err) compareObjects(t, test.expected, actual) }) } } func TestParseUnionDef(t *testing.T) { type testCase struct { description string definition string expected *types.Union err *errors.QueryError } tests := []testCase{ { description: "Parses a union", definition: "Foo = Bar | Qux | Quux", expected: &types.Union{ Name: "Foo", TypeNames: []string{"Bar", "Qux", "Quux"}, Loc: errors.Location{Line: 1, Column: 1}, }, }, } for _, test := range tests { t.Run(test.description, func(t *testing.T) { var actual *types.Union lex := setup(t, test.definition) parse := func() { actual = parseUnionDef(lex) } err := lex.CatchSyntaxError(parse) compareErrors(t, test.err, err) compareUnions(t, test.expected, actual) }) } } func TestParseEnumDef(t *testing.T) { type testCase struct { description string definition string expected *types.EnumTypeDefinition err *errors.QueryError } tests := []testCase{ { description: "parses EnumTypeDefinition on single line", definition: "Foo { BAR QUX }", expected: &types.EnumTypeDefinition{ Name: "Foo", EnumValuesDefinition: []*types.EnumValueDefinition{ { EnumValue: "BAR", Loc: errors.Location{Line: 1, Column: 7}, }, { EnumValue: "QUX", Loc: errors.Location{Line: 1, Column: 11}, }, }, Loc: errors.Location{Line: 1, Column: 1}, }, }, { description: "parses EnumtypeDefinition with new lines", definition: `Foo { BAR QUX }`, expected: &types.EnumTypeDefinition{ Name: "Foo", EnumValuesDefinition: []*types.EnumValueDefinition{ { EnumValue: "BAR", Loc: errors.Location{Line: 2, Column: 5}, }, { EnumValue: "QUX", Loc: errors.Location{Line: 3, Column: 5}, }, }, Loc: errors.Location{Line: 1, Column: 1}, }, }, } for _, test := range tests { t.Run(test.description, func(t *testing.T) { var actual *types.EnumTypeDefinition lex := setup(t, test.definition) parse := func() { actual = parseEnumDef(lex) } err := lex.CatchSyntaxError(parse) compareErrors(t, test.err, err) compareEnumTypeDefs(t, test.expected, actual) }) } } func TestParseDirectiveDef(t *testing.T) { type testCase struct { description string definition string expected *types.DirectiveDefinition err *errors.QueryError } tests := []*testCase{ { description: "parses DirectiveDefinition", definition: "@Foo on FIELD", expected: &types.DirectiveDefinition{ Name: "Foo", Loc: errors.Location{Line: 1, Column: 2}, Locations: []string{"FIELD"}, }, }, } for _, test := range tests { t.Run(test.description, func(t *testing.T) { var actual *types.DirectiveDefinition lex := setup(t, test.definition) parse := func() { actual = parseDirectiveDef(lex) } err := lex.CatchSyntaxError(parse) compareErrors(t, test.err, err) compareDirectiveDefinitions(t, test.expected, actual) }) } } func TestParseInputDef(t *testing.T) { type testCase struct { description string definition string expected *types.InputObject err *errors.QueryError } tests := []testCase{ { description: "parses an input object type definition", definition: "Foo { qux: String }", expected: &types.InputObject{ Name: "Foo", Values: nil, Loc: errors.Location{Line: 1, Column: 1}, }, }, } for _, test := range tests { t.Run(test.description, func(t *testing.T) { var actual *types.InputObject lex := setup(t, test.definition) parse := func() { actual = parseInputDef(lex) } err := lex.CatchSyntaxError(parse) compareErrors(t, test.err, err) compareInputObjectTypeDefinition(t, test.expected, actual) }) } } func compareDirectiveDefinitions(t *testing.T, expected *types.DirectiveDefinition, actual *types.DirectiveDefinition) { t.Helper() if expected.Name != actual.Name { t.Fatalf("wrong DirectiveDefinition name: want %q, got %q", expected.Name, actual.Name) } if !reflect.DeepEqual(expected.Locations, actual.Locations) { t.Errorf("wrong DirectiveDefinition locations: want %v, got %v", expected.Locations, actual.Locations) } compareLoc(t, "DirectiveDefinition", expected.Loc, actual.Loc) } func compareInputObjectTypeDefinition(t *testing.T, expected, actual *types.InputObject) { t.Helper() if expected.Name != actual.Name { t.Fatalf("wrong InputObject name: want %q, got %q", expected.Name, actual.Name) } compareLoc(t, "InputObjectTypeDefinition", expected.Loc, actual.Loc) } func compareEnumTypeDefs(t *testing.T, expected, actual *types.EnumTypeDefinition) { t.Helper() if expected.Name != actual.Name { t.Fatalf("wrong EnumTypeDefinition name: want %q, got %q", expected.Name, actual.Name) } compareLoc(t, "EnumValueDefinition", expected.Loc, actual.Loc) for i, definition := range expected.EnumValuesDefinition { expectedValue, expectedLoc := definition.EnumValue, definition.Loc actualDef := actual.EnumValuesDefinition[i] if expectedValue != actualDef.EnumValue { t.Fatalf("wrong EnumValue: want %q, got %q", expectedValue, actualDef.EnumValue) } compareLoc(t, "EnumValue "+expectedValue, expectedLoc, actualDef.Loc) } } func compareLoc(t *testing.T, typeName string, expected, actual errors.Location) { t.Helper() if expected != actual { t.Errorf("wrong location on %s: want %v, got %v", typeName, expected, actual) } } func compareErrors(t *testing.T, expected, actual *errors.QueryError) { t.Helper() switch { case expected != nil && actual != nil: if expected.Message != actual.Message { t.Fatalf("wanted error message %q, got %q", expected.Message, actual.Message) } // TODO: Check error locations are as expected. case expected != nil && actual == nil: t.Fatalf("missing expected error: %q", expected) case expected == nil && actual != nil: t.Fatalf("got unexpected error: %q", actual) } } func compareInterfaces(t *testing.T, expected, actual *types.InterfaceTypeDefinition) { t.Helper() if expected.Name != actual.Name { t.Errorf("wrong interface name: want %q, got %q", expected.Name, actual.Name) } compareLoc(t, "InterfaceTypeDefinition", expected.Loc, actual.Loc) if len(expected.Fields) != len(actual.Fields) { t.Fatalf("wanted %d field definitions, got %d", len(expected.Fields), len(actual.Fields)) } for i, f := range expected.Fields { if f.Name != actual.Fields[i].Name { t.Errorf("fields[%d]: wrong field name: want %q, got %q", i, f.Name, actual.Fields[i].Name) } } } func compareUnions(t *testing.T, expected, actual *types.Union) { t.Helper() if expected.Name != actual.Name { t.Errorf("wrong object name: want %q, got %q", expected.Name, actual.Name) } if !reflect.DeepEqual(expected, actual) { t.Errorf("wrong type names: want %v, got %v", expected.TypeNames, actual.TypeNames) } } func compareObjects(t *testing.T, expected, actual *types.ObjectTypeDefinition) { t.Helper() if expected.Name != actual.Name { t.Errorf("wrong object name: want %q, got %q", expected.Name, actual.Name) } if len(expected.InterfaceNames) != len(actual.InterfaceNames) { t.Fatalf( "wrong number of interface names: want %s, got %s", expected.InterfaceNames, actual.InterfaceNames, ) } for i, expectedName := range expected.InterfaceNames { actualName := actual.InterfaceNames[i] if expectedName != actualName { t.Errorf("wrong interface name: want %q, got %q", expectedName, actualName) } } } func setup(t *testing.T, def string) *common.Lexer { t.Helper() lex := common.NewLexer(def, false) lex.ConsumeWhitespace() return lex } graphql-go-1.5.0/internal/schema/schema_test.go000066400000000000000000000771411435002731700214740ustar00rootroot00000000000000package schema_test import ( "fmt" "strings" "testing" "github.com/graph-gophers/graphql-go/internal/schema" "github.com/graph-gophers/graphql-go/types" ) func TestParse(t *testing.T) { for _, test := range []struct { name string sdl string useStringDescriptions bool validateError func(err error) error validateSchema func(s *types.Schema) error }{ { name: "Parses interface definition", sdl: "interface Greeting { message: String! }", validateSchema: func(s *types.Schema) error { const typeName = "Greeting" typ, ok := s.Types[typeName].(*types.InterfaceTypeDefinition) if !ok { return fmt.Errorf("interface %q not found", typeName) } if want, have := 1, len(typ.Fields); want != have { return fmt.Errorf("invalid number of fields: want %d, have %d", want, have) } const fieldName = "message" if typ.Fields[0].Name != fieldName { return fmt.Errorf("field %q not found", fieldName) } return nil }, }, { name: "Parses implementing type without providing required fields", sdl: ` interface Greeting { message: String! } type Welcome implements Greeting { }`, validateError: func(err error) error { if err == nil { return fmt.Errorf("want error, have ") } if want, have := `graphql: interface "Greeting" expects field "message" but "Welcome" does not provide it`, err.Error(); want != have { return fmt.Errorf("unexpected error: want %q, have %q", want, have) } return nil }, }, { name: "Parses type with description string", sdl: ` "Single line description." type Type { field: String }`, useStringDescriptions: true, validateSchema: func(s *types.Schema) error { const typeName = "Type" typ, ok := s.Types[typeName].(*types.ObjectTypeDefinition) if !ok { return fmt.Errorf("type %q not found", typeName) } if want, have := "Single line description.", typ.Description(); want != have { return fmt.Errorf("invalid description: want %q, have %q", want, have) } return nil }, }, { name: "Parses type with simple multi-line 'BlockString' description", sdl: ` """ Multi-line description. """ type Type { field: String }`, useStringDescriptions: true, validateSchema: func(s *types.Schema) error { const typeName = "Type" typ, ok := s.Types[typeName].(*types.ObjectTypeDefinition) if !ok { return fmt.Errorf("type %q not found", typeName) } if want, have := "Multi-line description.", typ.Description(); want != have { return fmt.Errorf("invalid description: want %q, have %q", want, have) } return nil }, }, { name: "Parses type with empty multi-line 'BlockString' description", sdl: ` """ """ type Type { field: String }`, useStringDescriptions: true, validateSchema: func(s *types.Schema) error { const typeName = "Type" typ, ok := s.Types[typeName].(*types.ObjectTypeDefinition) if !ok { return fmt.Errorf("type %q not found", typeName) } if want, have := "", typ.Description(); want != have { return fmt.Errorf("invalid description: want %q, have %q", want, have) } return nil }, }, { name: "Parses type with multi-line 'BlockString' description", sdl: ` """ First line of the description. Second line of the description. query { code { example } } Notes: * First note * Second note """ type Type { field: String }`, useStringDescriptions: true, validateSchema: func(s *types.Schema) error { const typeName = "Type" typ, ok := s.Types[typeName].(*types.ObjectTypeDefinition) if !ok { return fmt.Errorf("type %q not found", typeName) } want := "First line of the description.\n\nSecond line of the description.\n\n\tquery {\n\t\tcode {\n\t\t\texample\n\t\t}\n\t}\n\nNotes:\n\n * First note\n * Second note" if have := typ.Description(); want != have { return fmt.Errorf("invalid description: want %q, have %q", want, have) } return nil }, }, { name: "Parses type with un-indented multi-line 'BlockString' description", sdl: ` """ First line of the description. Second line of the description. """ type Type { field: String }`, useStringDescriptions: true, validateSchema: func(s *types.Schema) error { const typeName = "Type" typ, ok := s.Types[typeName].(*types.ObjectTypeDefinition) if !ok { return fmt.Errorf("type %q not found", typeName) } want := "First line of the description.\n\nSecond line of the description." if have := typ.Description(); want != have { return fmt.Errorf("invalid description: want %q, have %q", want, have) } return nil }, }, { name: "Parses type with space-indented multi-line 'BlockString' description", sdl: ` """ First line of the description. Second line of the description. query { code { example } } """ type Type { field: String }`, useStringDescriptions: true, validateSchema: func(s *types.Schema) error { const typeName = "Type" typ, ok := s.Types[typeName].(*types.ObjectTypeDefinition) if !ok { return fmt.Errorf("type %q not found", typeName) } want := "First line of the description.\n\nSecond line of the description.\n\n query {\n code {\n example\n }\n }" if have := typ.Description(); want != have { return fmt.Errorf("invalid description: want %q, have %q", want, have) } return nil }, }, { name: "Parses type with multi-line 'BlockString' description and ignores comments", sdl: ` """ Multi-line description with ignored comments. """ # This comment should be ignored. type Type { field: String }`, useStringDescriptions: true, validateSchema: func(s *types.Schema) error { const typeName = "Type" typ, ok := s.Types[typeName].(*types.ObjectTypeDefinition) if !ok { return fmt.Errorf("type %q not found", typeName) } if want, have := "Multi-line description with ignored comments.", typ.Description(); want != have { return fmt.Errorf("invalid description: want %q, have %q", want, have) } return nil }, }, { name: "Parses type invalid syntax", sdl: ` type U = T `, validateError: func(err error) error { msg := `graphql: syntax error: unexpected "=", expecting "{" (line 2, column 11)` if err == nil || err.Error() != msg { return fmt.Errorf("expected error %q, but got %q", msg, err) } return nil }, }, { name: "Description is correctly parsed for non-described types", sdl: ` "Some description." scalar MyInt type Type { field: String }`, useStringDescriptions: true, validateSchema: func(s *types.Schema) error { typ, ok := s.Types["Type"] if !ok { return fmt.Errorf("type %q not found", "Type") } if want, have := "", typ.Description(); want != have { return fmt.Errorf("description does not match: want %q, have %q ", want, have) } return nil }, }, { name: "Multi-line comment is correctly parsed", sdl: ` # Multi-line # comment. " This description should be ignored. " scalar MyInt type Type { field: String }`, validateSchema: func(s *types.Schema) error { typ, ok := s.Types["MyInt"] if !ok { return fmt.Errorf("scalar %q not found", "MyInt") } if want, have := "Multi-line\ncomment.", typ.Description(); want != have { return fmt.Errorf("description does not match: want %q, have %q ", want, have) } typ, ok = s.Types["Type"] if !ok { return fmt.Errorf("type %q not found", "Type") } if want, have := "", typ.Description(); want != have { return fmt.Errorf("description does not match: want %q, have %q ", want, have) } return nil }, }, { name: "Default Root schema", sdl: ` type Query { hello: String! } type Mutation { concat(a: String!, b: String!): String! } `, validateSchema: func(s *types.Schema) error { typq, ok := s.Types["Query"].(*types.ObjectTypeDefinition) if !ok { return fmt.Errorf("type %q not found", "Query") } helloField := typq.Fields.Get("hello") if helloField == nil { return fmt.Errorf("field %q not found", "hello") } if helloField.Type.String() != "String!" { return fmt.Errorf("field %q has an invalid type: %q", "hello", helloField.Type.String()) } typm, ok := s.Types["Mutation"].(*types.ObjectTypeDefinition) if !ok { return fmt.Errorf("type %q not found", "Mutation") } concatField := typm.Fields.Get("concat") if concatField == nil { return fmt.Errorf("field %q not found", "concat") } if concatField.Type.String() != "String!" { return fmt.Errorf("field %q has an invalid type: %q", "concat", concatField.Type.String()) } if len(concatField.Arguments) != 2 || concatField.Arguments[0] == nil || concatField.Arguments[1] == nil || concatField.Arguments[0].Type.String() != "String!" || concatField.Arguments[1].Type.String() != "String!" { return fmt.Errorf("field %q has an invalid args: %+v", "concat", concatField.Arguments) } return nil }, }, { name: "Extend type", sdl: ` type Query { hello: String! } extend type Query { world: String! }`, validateSchema: func(s *types.Schema) error { typ, ok := s.Types["Query"].(*types.ObjectTypeDefinition) if !ok { return fmt.Errorf("type %q not found", "Query") } helloField := typ.Fields.Get("hello") if helloField == nil { return fmt.Errorf("field %q not found", "hello") } if helloField.Type.String() != "String!" { return fmt.Errorf("field %q has an invalid type: %q", "hello", helloField.Type.String()) } worldField := typ.Fields.Get("world") if worldField == nil { return fmt.Errorf("field %q not found", "world") } if worldField.Type.String() != "String!" { return fmt.Errorf("field %q has an invalid type: %q", "world", worldField.Type.String()) } return nil }, }, { name: "Extend schema", sdl: ` schema { query: Query } type Query { hello: String! } extend schema { mutation: Mutation } type Mutation { concat(a: String!, b: String!): String! } `, validateSchema: func(s *types.Schema) error { typq, ok := s.Types["Query"].(*types.ObjectTypeDefinition) if !ok { return fmt.Errorf("type %q not found", "Query") } helloField := typq.Fields.Get("hello") if helloField == nil { return fmt.Errorf("field %q not found", "hello") } if helloField.Type.String() != "String!" { return fmt.Errorf("field %q has an invalid type: %q", "hello", helloField.Type.String()) } typm, ok := s.Types["Mutation"].(*types.ObjectTypeDefinition) if !ok { return fmt.Errorf("type %q not found", "Mutation") } concatField := typm.Fields.Get("concat") if concatField == nil { return fmt.Errorf("field %q not found", "concat") } if concatField.Type.String() != "String!" { return fmt.Errorf("field %q has an invalid type: %q", "concat", concatField.Type.String()) } if len(concatField.Arguments) != 2 || concatField.Arguments[0] == nil || concatField.Arguments[1] == nil || concatField.Arguments[0].Type.String() != "String!" || concatField.Arguments[1].Type.String() != "String!" { return fmt.Errorf("field %q has an invalid args: %+v", "concat", concatField.Arguments) } return nil }, }, { name: "Extend type with interface implementation", sdl: ` interface Named { name: String! } type Product { id: ID! } extend type Product implements Named { name: String! }`, validateSchema: func(s *types.Schema) error { typ, ok := s.Types["Product"].(*types.ObjectTypeDefinition) if !ok { return fmt.Errorf("type %q not found", "Product") } idField := typ.Fields.Get("id") if idField == nil { return fmt.Errorf("field %q not found", "id") } if idField.Type.String() != "ID!" { return fmt.Errorf("field %q has an invalid type: %q", "id", idField.Type.String()) } nameField := typ.Fields.Get("name") if nameField == nil { return fmt.Errorf("field %q not found", "name") } if nameField.Type.String() != "String!" { return fmt.Errorf("field %q has an invalid type: %q", "name", nameField.Type.String()) } ifc, ok := s.Types["Named"].(*types.InterfaceTypeDefinition) if !ok { return fmt.Errorf("type %q not found", "Named") } nameField = ifc.Fields.Get("name") if nameField == nil { return fmt.Errorf("field %q not found", "name") } if nameField.Type.String() != "String!" { return fmt.Errorf("field %q has an invalid type: %q", "name", nameField.Type.String()) } return nil }, }, { name: "Extend union type", sdl: ` type Named { name: String! } type Numbered { num: Int! } union Item = Named | Numbered type Coloured { Colour: String! } extend union Item = Coloured `, validateSchema: func(s *types.Schema) error { typ, ok := s.Types["Item"].(*types.Union) if !ok { return fmt.Errorf("type %q not found", "Item") } if len(typ.UnionMemberTypes) != 3 { return fmt.Errorf("Expected 3 possible types, but instead got %d types", len(typ.UnionMemberTypes)) } posible := map[string]struct{}{ "Coloured": {}, "Named": {}, "Numbered": {}, } for _, pt := range typ.UnionMemberTypes { if _, ok := posible[pt.Name]; !ok { return fmt.Errorf("Unexpected possible type %q", pt.Name) } } return nil }, }, { name: "Extend enum type", sdl: ` enum Currencies{ AUD USD EUR } extend enum Currencies { BGN GBP } `, validateSchema: func(s *types.Schema) error { typ, ok := s.Types["Currencies"].(*types.EnumTypeDefinition) if !ok { return fmt.Errorf("enum %q not found", "Currencies") } if len(typ.EnumValuesDefinition) != 5 { return fmt.Errorf("Expected 5 enum values, but instead got %d types", len(typ.EnumValuesDefinition)) } posible := map[string]struct{}{ "AUD": {}, "USD": {}, "EUR": {}, "BGN": {}, "GBP": {}, } for _, v := range typ.EnumValuesDefinition { if _, ok := posible[v.EnumValue]; !ok { return fmt.Errorf("Unexpected enum value %q", v.EnumValue) } } return nil }, }, { name: "Extend incompatible type", sdl: ` type Query { hello: String! } extend interface Query { name: String! }`, validateError: func(err error) error { msg := `trying to extend type "OBJECT" with type "INTERFACE"` if err == nil || err.Error() != msg { return fmt.Errorf("expected error %q, but got %q", msg, err) } return nil }, }, { name: "Extend type already implements an interface", sdl: ` interface Named { name: String! } type Product implements Named { id: ID! name: String! } extend type Product implements Named { }`, validateError: func(err error) error { msg := `interface "Named" implemented in the extension is already implemented in "Product"` if err == nil || err.Error() != msg { return fmt.Errorf("expected error %q, but got %q", msg, err) } return nil }, }, { name: "Extend union already contains type", sdl: ` type Named { name: String! } type Numbered { num: Int! } union Item = Named | Numbered type Coloured { Colour: String! } extend union Item = Coloured | Named `, validateError: func(err error) error { msg := `union type "Named" already declared in "Item"` if err == nil || err.Error() != msg { return fmt.Errorf("expected error %q, but got %q", msg, err) } return nil }, }, { name: "Extend union contains type", sdl: ` type Named { name: String! } type Numbered { num: Int! } union Item = Named | Numbered type Coloured { Colour: String! } extend union Item = Coloured `, validateSchema: func(s *types.Schema) error { typ, ok := s.Types["Item"].(*types.Union) if !ok { return fmt.Errorf("type %q not found", "Item") } if len(typ.UnionMemberTypes) != 3 { return fmt.Errorf("Expected 3 possible types, but instead got %d types", len(typ.UnionMemberTypes)) } posible := map[string]struct{}{ "Coloured": {}, "Named": {}, "Numbered": {}, } for _, pt := range typ.UnionMemberTypes { if _, ok := posible[pt.Name]; !ok { return fmt.Errorf("Unexpected possible type %q", pt.Name) } } return nil }, }, { name: "Extend input", sdl: ` input Product { id: ID! name: String! } extend input Product { category: Category! tags: [String!]! = ["sale", "shoes"] } input Category { id: ID! name: String! } `, validateSchema: func(s *types.Schema) error { typ, ok := s.Types["Product"].(*types.InputObject) if !ok { return fmt.Errorf("type %q not found", "Product") } if len(typ.Values) != 4 { return fmt.Errorf("Expected 4 fields, but instead got %d types", len(typ.Values)) } posible := map[string]struct{}{ "id": {}, "name": {}, "category": {}, "tags": {}, } for _, pt := range typ.Values { if _, ok := posible[pt.Name.Name]; !ok { return fmt.Errorf("Unexpected possible type %q", pt.Name) } } categoryField := typ.Values.Get("category") if categoryField == nil { return fmt.Errorf("field %q not found", "category") } if categoryField.Type.String() != "Category!" { return fmt.Errorf("expected type %q, but got %q", "Category!", categoryField.Type.String()) } if categoryField.Type.Kind() != "NON_NULL" { return fmt.Errorf("expected kind %q, but got %q", "NON_NULL", categoryField.Type.Kind()) } return nil }, }, { name: "Extend enum value already exists", sdl: ` enum Currencies{ AUD USD EUR } extend enum Currencies { AUD }`, validateError: func(err error) error { msg := `enum value "AUD" already declared in "Currencies"` if err == nil || err.Error() != msg { return fmt.Errorf("expected error %q, but got %q", msg, err) } return nil }, }, { name: "Extend input field already exists", sdl: ` input Product{ name: String! } extend input Product { name: String! }`, validateError: func(err error) error { msg := `extended field {"name" {'\x06' '\x05'}} already exists` if err == nil || err.Error() != msg { return fmt.Errorf("expected error %q, but got %q", msg, err) } return nil }, }, { name: "Extend field already exists", sdl: ` interface Named { name: String! } type Product implements Named { id: ID! name: String! } extend type Product { name: String! }`, validateError: func(err error) error { msg := `extended field "name" already exists` if err == nil || err.Error() != msg { return fmt.Errorf("expected error %q, but got %q", msg, err) } return nil }, }, { name: "Extend interface type", sdl: ` interface Product { id: ID! name: String! } extend interface Product { category: String! } `, validateSchema: func(s *types.Schema) error { typ, ok := s.Types["Product"].(*types.InterfaceTypeDefinition) if !ok { return fmt.Errorf("type %q not found", "Product") } if len(typ.Fields) != 3 { return fmt.Errorf("Expected 3 fields, but instead got %d types", len(typ.Fields)) } fields := map[string]struct{}{ "id": {}, "name": {}, "category": {}, } for _, f := range typ.Fields { if _, ok := fields[f.Name]; !ok { return fmt.Errorf("Unexpected field %q", f.Name) } } return nil }, }, { name: "Extend unknown type", sdl: ` extend type User { name: String! } `, validateError: func(err error) error { msg := `trying to extend unknown type "User"` if err == nil || err.Error() != msg { return fmt.Errorf("expected error %q, but got %q", msg, err) } return nil }, }, { name: "Extend invalid syntax", sdl: ` extend invalid Node { id: ID! } `, validateError: func(err error) error { msg := `graphql: syntax error: unexpected "invalid", expecting "schema", "type", "enum", "interface", "union" or "input" (line 2, column 19)` if err == nil || err.Error() != msg { return fmt.Errorf("expected error %q, but got %q", msg, err) } return nil }, }, { name: "Parses directives", sdl: ` directive @objectdirective on OBJECT directive @fielddirective on FIELD_DEFINITION directive @enumdirective on ENUM directive @uniondirective on UNION directive @directive on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION directive @repeatabledirective repeatable on SCALAR interface NamedEntity @directive { name: String } scalar Time @directive type Photo @objectdirective { id: ID! @deprecated @fielddirective } type Person implements NamedEntity @objectdirective { name: String } enum Direction @enumdirective { NORTH @deprecated EAST SOUTH WEST } union Union @uniondirective = Photo | Person scalar Mass @repeatabledirective @repeatabledirective `, validateSchema: func(s *types.Schema) error { namedEntityDirectives := s.Types["NamedEntity"].(*types.InterfaceTypeDefinition).Directives if len(namedEntityDirectives) != 1 || namedEntityDirectives[0].Name.Name != "directive" { return fmt.Errorf("missing directive on NamedEntity interface, expected @directive but got %v", namedEntityDirectives) } timeDirectives := s.Types["Time"].(*types.ScalarTypeDefinition).Directives if len(timeDirectives) != 1 || timeDirectives[0].Name.Name != "directive" { return fmt.Errorf("missing directive on Time scalar, expected @directive but got %v", timeDirectives) } photo := s.Types["Photo"].(*types.ObjectTypeDefinition) photoDirectives := photo.Directives if len(photoDirectives) != 1 || photoDirectives[0].Name.Name != "objectdirective" { return fmt.Errorf("missing directive on Time scalar, expected @objectdirective but got %v", photoDirectives) } if len(photo.Fields.Get("id").Directives) != 2 { return fmt.Errorf("expected Photo.id to have 2 directives but got %v", photoDirectives) } directionDirectives := s.Types["Direction"].(*types.EnumTypeDefinition).Directives if len(directionDirectives) != 1 || directionDirectives[0].Name.Name != "enumdirective" { return fmt.Errorf("missing directive on Direction enum, expected @enumdirective but got %v", directionDirectives) } unionDirectives := s.Types["Union"].(*types.Union).Directives if len(unionDirectives) != 1 || unionDirectives[0].Name.Name != "uniondirective" { return fmt.Errorf("missing directive on Union union, expected @uniondirective but got %v", unionDirectives) } massDirectives := s.Types["Mass"].(*types.ScalarTypeDefinition).Directives if len(massDirectives) != 2 || massDirectives[0].Name.Name != "repeatabledirective" || massDirectives[1].Name.Name != "repeatabledirective" { return fmt.Errorf("missing directive on Repeatable scalar, expected @repeatabledirective @repeatabledirective but got %v", massDirectives) } return nil }, }, { name: "Sets Directive.Repeatable if `repeatable` keyword is given", sdl: ` directive @nonrepeatabledirective on SCALAR directive @repeatabledirective repeatable on SCALAR `, validateSchema: func(s *types.Schema) error { if dir := s.Directives["nonrepeatabledirective"]; dir.Repeatable { return fmt.Errorf("did not expect directive to be repeatable: %v", dir) } if dir := s.Directives["repeatabledirective"]; !dir.Repeatable { return fmt.Errorf("expected directive to be repeatable: %v", dir) } return nil }, }, { name: "Directive definition does not allow double-`repeatable`", sdl: ` directive @mydirective repeatable repeatable SCALAR scalar MyScalar @mydirective `, validateError: func(err error) error { msg := `graphql: syntax error: unexpected "repeatable", expecting "on" (line 2, column 38)` if err == nil || err.Error() != msg { return fmt.Errorf("expected error %q, but got %q", msg, err) } return nil }, }, { name: "Directive definition does not allow double-`on` instead of `repeatable on`", sdl: ` directive @mydirective on on SCALAR scalar MyScalar @mydirective `, validateError: func(err error) error { prefix := `graphql: syntax error: "on" is not a legal directive location` if err == nil || !strings.HasPrefix(err.Error(), prefix) { return fmt.Errorf("expected error starting with %q, but got %q", prefix, err) } return nil }, }, { name: "Disallow repeat of a directive if it is not `repeatable`", sdl: ` directive @nonrepeatabledirective on FIELD_DEFINITION type Foo { bar: String @nonrepeatabledirective @nonrepeatabledirective } `, validateError: func(err error) error { prefix := `graphql: non repeatable directive "nonrepeatabledirective" can not be repeated. Consider adding "repeatable"` if err == nil || !strings.HasPrefix(err.Error(), prefix) { return fmt.Errorf("expected error starting with %q, but got %q", prefix, err) } return nil }, }, { name: "Decorating scalar with an undeclared directive should return an error", sdl: ` scalar S @undeclareddirective `, validateError: func(err error) error { prefix := `graphql: directive "undeclareddirective" not found` if err == nil || !strings.HasPrefix(err.Error(), prefix) { return fmt.Errorf("expected error starting with %q, but got %q", prefix, err) } return nil }, }, { name: "Decorating argument with an undeclared directive should return an error", sdl: ` type Query { hello(name: String! @undeclareddirective): String! } `, validateError: func(err error) error { prefix := `graphql: directive "undeclareddirective" not found` if err == nil || !strings.HasPrefix(err.Error(), prefix) { return fmt.Errorf("expected error starting with %q, but got %q", prefix, err) } return nil }, }, { name: "Decorating input object with an undeclared directive should return an error", sdl: ` input InputObject @undeclareddirective{} `, validateError: func(err error) error { prefix := `graphql: directive "undeclareddirective" not found` if err == nil || !strings.HasPrefix(err.Error(), prefix) { return fmt.Errorf("expected error starting with %q, but got %q", prefix, err) } return nil }, }, { name: "Decorating interface with an undeclared directive should return an error", sdl: ` interface I @undeclareddirective {} `, validateError: func(err error) error { prefix := `graphql: directive "undeclareddirective" not found` if err == nil || !strings.HasPrefix(err.Error(), prefix) { return fmt.Errorf("expected error starting with %q, but got %q", prefix, err) } return nil }, }, } { t.Run(test.name, func(t *testing.T) { s, err := schema.ParseSchema(test.sdl, test.useStringDescriptions) if err != nil { if test.validateError == nil { t.Fatal(err) } if err2 := test.validateError(err); err2 != nil { t.Fatal(err2) } } if test.validateSchema != nil { if err := test.validateSchema(s); err != nil { t.Fatal(err) } } }) } } func TestInterfaceImplementsInterface(t *testing.T) { for _, tt := range []struct { name string sdl string useStringDescriptions bool validateError func(err error) error validateSchema func(s *types.Schema) error }{ { name: "Parses interface implementing other interface", sdl: ` interface Foo { field: String! } interface Bar implements Foo { field: String! } `, validateSchema: func(s *types.Schema) error { const implementedInterfaceName = "Bar" typ, ok := s.Types[implementedInterfaceName].(*types.InterfaceTypeDefinition) if !ok { return fmt.Errorf("interface %q not found", implementedInterfaceName) } if len(typ.Fields) != 1 { return fmt.Errorf("invalid number of fields: want %d, have %d", 1, len(typ.Fields)) } const fieldName = "field" if typ.Fields[0].Name != fieldName { return fmt.Errorf("field %q not found", fieldName) } if len(typ.Interfaces) != 1 { return fmt.Errorf("invalid number of implementing interfaces found on %q: want %d, have %d", implementedInterfaceName, 1, len(typ.Interfaces)) } const implementingInterfaceName = "Foo" if typ.Interfaces[0].Name != implementingInterfaceName { return fmt.Errorf("interface %q not found", implementingInterfaceName) } return nil }, }, { name: "Parses interface transitively implementing an interface that implements an interface", sdl: ` interface Foo { field: String! } interface Bar implements Foo { field: String! } interface Baz implements Bar & Foo { field: String! } `, validateSchema: func(s *types.Schema) error { const implementedInterfaceName = "Baz" typ, ok := s.Types[implementedInterfaceName].(*types.InterfaceTypeDefinition) if !ok { return fmt.Errorf("interface %q not found", implementedInterfaceName) } if len(typ.Fields) != 1 { return fmt.Errorf("invalid number of fields: want %d, have %d", 1, len(typ.Fields)) } const fieldName = "field" if typ.Fields[0].Name != fieldName { return fmt.Errorf("field %q not found", fieldName) } if len(typ.Interfaces) != 2 { return fmt.Errorf("invalid number of implementing interfaces found on %q: want %d, have %d", implementedInterfaceName, 2, len(typ.Interfaces)) } const firstImplementingInterfaceName = "Bar" if typ.Interfaces[0].Name != firstImplementingInterfaceName { return fmt.Errorf("first interface %q not found", firstImplementingInterfaceName) } const secondImplementingInterfaceName = "Foo" if typ.Interfaces[1].Name != secondImplementingInterfaceName { return fmt.Errorf("second interface %q not found", secondImplementingInterfaceName) } return nil }, }, { name: "Transitively implemented interfaces must also be defined on an implementing type or interface", sdl: ` interface A { message: String! } interface B implements A { message: String! name: String! } interface C implements B { message: String! name: String! hug: Boolean! } `, validateError: func(err error) error { msg := `graphql: interface "C" must explicitly implement transitive interface "A"` if err == nil || err.Error() != msg { return fmt.Errorf("expected error %q, but got %q", msg, err) } return nil }, }, } { t.Run(tt.name, func(t *testing.T) { s, err := schema.ParseSchema(tt.sdl, tt.useStringDescriptions) if err != nil { if tt.validateError == nil { t.Fatal(err) } if err2 := tt.validateError(err); err2 != nil { t.Fatal(err2) } } if tt.validateSchema != nil { if err2 := tt.validateSchema(s); err2 != nil { t.Fatal(err2) } } }) } } graphql-go-1.5.0/internal/validation/000077500000000000000000000000001435002731700175265ustar00rootroot00000000000000graphql-go-1.5.0/internal/validation/suggestion.go000066400000000000000000000025131435002731700222450ustar00rootroot00000000000000package validation import ( "fmt" "sort" "strconv" "strings" ) func makeSuggestion(prefix string, options []string, input string) string { var selected []string distances := make(map[string]int) for _, opt := range options { distance := levenshteinDistance(input, opt) threshold := max(len(input)/2, max(len(opt)/2, 1)) if distance < threshold { selected = append(selected, opt) distances[opt] = distance } } if len(selected) == 0 { return "" } sort.Slice(selected, func(i, j int) bool { return distances[selected[i]] < distances[selected[j]] }) parts := make([]string, len(selected)) for i, opt := range selected { parts[i] = strconv.Quote(opt) } if len(parts) > 1 { parts[len(parts)-1] = "or " + parts[len(parts)-1] } return fmt.Sprintf(" %s %s?", prefix, strings.Join(parts, ", ")) } func levenshteinDistance(s1, s2 string) int { column := make([]int, len(s1)+1) for y := range s1 { column[y+1] = y + 1 } for x, rx := range s2 { column[0] = x + 1 lastdiag := x for y, ry := range s1 { olddiag := column[y+1] if rx != ry { lastdiag++ } column[y+1] = min(column[y+1]+1, min(column[y]+1, lastdiag)) lastdiag = olddiag } } return column[len(s1)] } func min(a, b int) int { if a < b { return a } return b } func max(a, b int) int { if a > b { return a } return b } graphql-go-1.5.0/internal/validation/testdata/000077500000000000000000000000001435002731700213375ustar00rootroot00000000000000graphql-go-1.5.0/internal/validation/testdata/LICENSE000066400000000000000000000031441435002731700223460ustar00rootroot00000000000000The files in this testdata directory are derived from the graphql-js project: https://github.com/graphql/graphql-js BSD License For GraphQL software Copyright (c) 2015, Facebook, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name Facebook nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.graphql-go-1.5.0/internal/validation/testdata/export.js000066400000000000000000000100051435002731700232120ustar00rootroot00000000000000import fs from 'fs'; import Module from 'module'; import { testSchema } from './src/validation/__tests__/harness'; import { printSchema } from './src/utilities'; let schemas = []; function registerSchema(schema) { for (let i = 0; i < schemas.length; i++) { if (schemas[i] == schema) { return i; } } schemas.push(schema); return schemas.length - 1; } const harness = { expectPassesRule(rule, queryString) { harness.expectPassesRuleWithSchema(testSchema, rule, queryString); }, expectPassesRuleWithSchema(schema, rule, queryString, errors) { tests.push({ name: names.join('/'), rule: rule.name, schema: registerSchema(schema), query: queryString, errors: [], }); }, expectFailsRule(rule, queryString, errors) { harness.expectFailsRuleWithSchema(testSchema, rule, queryString, errors); }, expectFailsRuleWithSchema(schema, rule, queryString, errors) { tests.push({ name: names.join('/'), rule: rule.name, schema: registerSchema(schema), query: queryString, errors: errors, }); } }; let tests = []; let names = []; const fakeModules = { 'mocha': { describe(name, f) { switch (name) { case 'within schema language': return; } names.push(name); f(); names.pop(); }, it(name, f) { switch (name) { case 'ignores type definitions': case 'reports correctly when a non-exclusive follows an exclusive': case 'disallows differing subfields': return; } names.push(name); f(); names.pop(); }, }, './harness': harness, }; const originalLoader = Module._load; Module._load = function(request, parent, isMain) { return fakeModules[request] || originalLoader(request, parent, isMain); }; // TODO: Fix test failures. // require('./src/validation/__tests__/ExecutableDefinitions-test'); require('./src/validation/__tests__/FieldsOnCorrectType-test'); require('./src/validation/__tests__/FragmentsOnCompositeTypes-test'); // TODO: Fix test failures. // require('./src/validation/__tests__/KnownArgumentNames-test'); require('./src/validation/__tests__/KnownDirectives-test'); require('./src/validation/__tests__/KnownFragmentNames-test'); require('./src/validation/__tests__/KnownTypeNames-test'); require('./src/validation/__tests__/LoneAnonymousOperation-test'); require('./src/validation/__tests__/NoFragmentCycles-test'); require('./src/validation/__tests__/NoUndefinedVariables-test'); require('./src/validation/__tests__/NoUnusedFragments-test'); require('./src/validation/__tests__/NoUnusedVariables-test'); require('./src/validation/__tests__/OverlappingFieldsCanBeMerged-test'); // TODO: Fix test failures. // require('./src/validation/__tests__/PossibleFragmentSpreads-test'); require('./src/validation/__tests__/ProvidedNonNullArguments-test'); require('./src/validation/__tests__/ScalarLeafs-test'); // TODO: Add support for subscriptions. // require('./src/validation/__tests__/SingleFieldSubscriptions-test.js'); require('./src/validation/__tests__/UniqueArgumentNames-test'); require('./src/validation/__tests__/UniqueDirectivesPerLocation-test'); require('./src/validation/__tests__/UniqueFragmentNames-test'); require('./src/validation/__tests__/UniqueInputFieldNames-test'); require('./src/validation/__tests__/UniqueOperationNames-test'); require('./src/validation/__tests__/UniqueVariableNames-test'); // TODO: Fix test failures. // require('./src/validation/__tests__/ValuesofCorrectType-test'); require('./src/validation/__tests__/VariablesAreInputTypes-test'); // TODO: Fix test failures. // require('./src/validation/__tests__/VariablesDefaultValueAllowed-test'); require('./src/validation/__tests__/VariablesInAllowedPosition-test'); let output = JSON.stringify({ schemas: schemas.map(s => printSchema(s)), tests: tests, }, null, 2) output = output.replace(' Did you mean to use an inline fragment on \\"Dog\\" or \\"Cat\\"?', ''); output = output.replace(' Did you mean to use an inline fragment on \\"Being\\", \\"Pet\\", \\"Canine\\", \\"Dog\\", or \\"Cat\\"?', ''); output = output.replace(' Did you mean \\"Pet\\"?', ''); fs.writeFileSync("tests.json", output); graphql-go-1.5.0/internal/validation/testdata/gen.go000066400000000000000000000006401435002731700224370ustar00rootroot00000000000000// Package testdata copies validation test cases from the reference implementation at // github.com/graphql/graphql-js. // // Pre-requisites: // - nodejs // - npm >= 5.2.0 (for use of npx) // // Usage: // $ git clone https://github.com/graphql/graphql-js // $ go generate . package testdata //go:generate npm install //go:generate cp export.js graphql-js/export.js //go:generate npx babel-node graphql-js/export.js graphql-go-1.5.0/internal/validation/testdata/package-lock.json000066400000000000000000002462411435002731700245640ustar00rootroot00000000000000{ "name": "testdata", "version": "0.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, "anymatch": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", "dev": true, "optional": true, "requires": { "micromatch": "2.3.11", "normalize-path": "2.1.1" } }, "arr-diff": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "dev": true, "optional": true, "requires": { "arr-flatten": "1.1.0" } }, "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", "dev": true, "optional": true }, "array-unique": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", "dev": true, "optional": true }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, "async-each": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", "dev": true, "optional": true }, "babel-cli": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-cli/-/babel-cli-6.26.0.tgz", "integrity": "sha1-UCq1SHTX24itALiHoGODzgPQAvE=", "dev": true, "requires": { "babel-core": "6.26.0", "babel-polyfill": "6.26.0", "babel-register": "6.26.0", "babel-runtime": "6.26.0", "chokidar": "1.7.0", "commander": "2.15.0", "convert-source-map": "1.5.1", "fs-readdir-recursive": "1.1.0", "glob": "7.1.2", "lodash": "4.17.5", "output-file-sync": "1.1.2", "path-is-absolute": "1.0.1", "slash": "1.0.0", "source-map": "0.5.7", "v8flags": "2.1.1" } }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "dev": true, "requires": { "chalk": "1.1.3", "esutils": "2.0.2", "js-tokens": "3.0.2" } }, "babel-core": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz", "integrity": "sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=", "dev": true, "requires": { "babel-code-frame": "6.26.0", "babel-generator": "6.26.1", "babel-helpers": "6.24.1", "babel-messages": "6.23.0", "babel-register": "6.26.0", "babel-runtime": "6.26.0", "babel-template": "6.26.0", "babel-traverse": "6.26.0", "babel-types": "6.26.0", "babylon": "6.18.0", "convert-source-map": "1.5.1", "debug": "2.6.9", "json5": "0.5.1", "lodash": "4.17.5", "minimatch": "3.0.4", "path-is-absolute": "1.0.1", "private": "0.1.8", "slash": "1.0.0", "source-map": "0.5.7" } }, "babel-generator": { "version": "6.26.1", "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", "dev": true, "requires": { "babel-messages": "6.23.0", "babel-runtime": "6.26.0", "babel-types": "6.26.0", "detect-indent": "4.0.0", "jsesc": "1.3.0", "lodash": "4.17.5", "source-map": "0.5.7", "trim-right": "1.0.1" } }, "babel-helper-builder-binary-assignment-operator-visitor": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", "dev": true, "requires": { "babel-helper-explode-assignable-expression": "6.24.1", "babel-runtime": "6.26.0", "babel-types": "6.26.0" } }, "babel-helper-call-delegate": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", "dev": true, "requires": { "babel-helper-hoist-variables": "6.24.1", "babel-runtime": "6.26.0", "babel-traverse": "6.26.0", "babel-types": "6.26.0" } }, "babel-helper-define-map": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", "dev": true, "requires": { "babel-helper-function-name": "6.24.1", "babel-runtime": "6.26.0", "babel-types": "6.26.0", "lodash": "4.17.5" } }, "babel-helper-explode-assignable-expression": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", "dev": true, "requires": { "babel-runtime": "6.26.0", "babel-traverse": "6.26.0", "babel-types": "6.26.0" } }, "babel-helper-function-name": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", "dev": true, "requires": { "babel-helper-get-function-arity": "6.24.1", "babel-runtime": "6.26.0", "babel-template": "6.26.0", "babel-traverse": "6.26.0", "babel-types": "6.26.0" } }, "babel-helper-get-function-arity": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", "dev": true, "requires": { "babel-runtime": "6.26.0", "babel-types": "6.26.0" } }, "babel-helper-hoist-variables": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", "dev": true, "requires": { "babel-runtime": "6.26.0", "babel-types": "6.26.0" } }, "babel-helper-optimise-call-expression": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", "dev": true, "requires": { "babel-runtime": "6.26.0", "babel-types": "6.26.0" } }, "babel-helper-regex": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", "dev": true, "requires": { "babel-runtime": "6.26.0", "babel-types": "6.26.0", "lodash": "4.17.5" } }, "babel-helper-remap-async-to-generator": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", "dev": true, "requires": { "babel-helper-function-name": "6.24.1", "babel-runtime": "6.26.0", "babel-template": "6.26.0", "babel-traverse": "6.26.0", "babel-types": "6.26.0" } }, "babel-helper-replace-supers": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", "dev": true, "requires": { "babel-helper-optimise-call-expression": "6.24.1", "babel-messages": "6.23.0", "babel-runtime": "6.26.0", "babel-template": "6.26.0", "babel-traverse": "6.26.0", "babel-types": "6.26.0" } }, "babel-helpers": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", "dev": true, "requires": { "babel-runtime": "6.26.0", "babel-template": "6.26.0" } }, "babel-messages": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", "dev": true, "requires": { "babel-runtime": "6.26.0" } }, "babel-plugin-check-es2015-constants": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", "dev": true, "requires": { "babel-runtime": "6.26.0" } }, "babel-plugin-syntax-async-functions": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", "dev": true }, "babel-plugin-syntax-async-generators": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz", "integrity": "sha1-a8lj67FuzLrmuStZbrfzXDQqi5o=", "dev": true }, "babel-plugin-syntax-class-properties": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=", "dev": true }, "babel-plugin-syntax-exponentiation-operator": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", "dev": true }, "babel-plugin-syntax-flow": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", "integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=", "dev": true }, "babel-plugin-syntax-object-rest-spread": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", "dev": true }, "babel-plugin-syntax-trailing-function-commas": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", "dev": true }, "babel-plugin-transform-async-to-generator": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", "dev": true, "requires": { "babel-helper-remap-async-to-generator": "6.24.1", "babel-plugin-syntax-async-functions": "6.13.0", "babel-runtime": "6.26.0" } }, "babel-plugin-transform-class-properties": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz", "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", "dev": true, "requires": { "babel-helper-function-name": "6.24.1", "babel-plugin-syntax-class-properties": "6.13.0", "babel-runtime": "6.26.0", "babel-template": "6.26.0" } }, "babel-plugin-transform-es2015-arrow-functions": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", "dev": true, "requires": { "babel-runtime": "6.26.0" } }, "babel-plugin-transform-es2015-block-scoped-functions": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", "dev": true, "requires": { "babel-runtime": "6.26.0" } }, "babel-plugin-transform-es2015-block-scoping": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", "dev": true, "requires": { "babel-runtime": "6.26.0", "babel-template": "6.26.0", "babel-traverse": "6.26.0", "babel-types": "6.26.0", "lodash": "4.17.5" } }, "babel-plugin-transform-es2015-classes": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", "dev": true, "requires": { "babel-helper-define-map": "6.26.0", "babel-helper-function-name": "6.24.1", "babel-helper-optimise-call-expression": "6.24.1", "babel-helper-replace-supers": "6.24.1", "babel-messages": "6.23.0", "babel-runtime": "6.26.0", "babel-template": "6.26.0", "babel-traverse": "6.26.0", "babel-types": "6.26.0" } }, "babel-plugin-transform-es2015-computed-properties": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", "dev": true, "requires": { "babel-runtime": "6.26.0", "babel-template": "6.26.0" } }, "babel-plugin-transform-es2015-destructuring": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", "dev": true, "requires": { "babel-runtime": "6.26.0" } }, "babel-plugin-transform-es2015-duplicate-keys": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", "dev": true, "requires": { "babel-runtime": "6.26.0", "babel-types": "6.26.0" } }, "babel-plugin-transform-es2015-for-of": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", "dev": true, "requires": { "babel-runtime": "6.26.0" } }, "babel-plugin-transform-es2015-function-name": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", "dev": true, "requires": { "babel-helper-function-name": "6.24.1", "babel-runtime": "6.26.0", "babel-types": "6.26.0" } }, "babel-plugin-transform-es2015-literals": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", "dev": true, "requires": { "babel-runtime": "6.26.0" } }, "babel-plugin-transform-es2015-modules-amd": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", "dev": true, "requires": { "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", "babel-runtime": "6.26.0", "babel-template": "6.26.0" } }, "babel-plugin-transform-es2015-modules-commonjs": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz", "integrity": "sha1-DYOUApt9xqvhqX7xgeAHWN0uXYo=", "dev": true, "requires": { "babel-plugin-transform-strict-mode": "6.24.1", "babel-runtime": "6.26.0", "babel-template": "6.26.0", "babel-types": "6.26.0" } }, "babel-plugin-transform-es2015-modules-systemjs": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", "dev": true, "requires": { "babel-helper-hoist-variables": "6.24.1", "babel-runtime": "6.26.0", "babel-template": "6.26.0" } }, "babel-plugin-transform-es2015-modules-umd": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", "dev": true, "requires": { "babel-plugin-transform-es2015-modules-amd": "6.24.1", "babel-runtime": "6.26.0", "babel-template": "6.26.0" } }, "babel-plugin-transform-es2015-object-super": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", "dev": true, "requires": { "babel-helper-replace-supers": "6.24.1", "babel-runtime": "6.26.0" } }, "babel-plugin-transform-es2015-parameters": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", "dev": true, "requires": { "babel-helper-call-delegate": "6.24.1", "babel-helper-get-function-arity": "6.24.1", "babel-runtime": "6.26.0", "babel-template": "6.26.0", "babel-traverse": "6.26.0", "babel-types": "6.26.0" } }, "babel-plugin-transform-es2015-shorthand-properties": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", "dev": true, "requires": { "babel-runtime": "6.26.0", "babel-types": "6.26.0" } }, "babel-plugin-transform-es2015-spread": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", "dev": true, "requires": { "babel-runtime": "6.26.0" } }, "babel-plugin-transform-es2015-sticky-regex": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", "dev": true, "requires": { "babel-helper-regex": "6.26.0", "babel-runtime": "6.26.0", "babel-types": "6.26.0" } }, "babel-plugin-transform-es2015-template-literals": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", "dev": true, "requires": { "babel-runtime": "6.26.0" } }, "babel-plugin-transform-es2015-typeof-symbol": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", "dev": true, "requires": { "babel-runtime": "6.26.0" } }, "babel-plugin-transform-es2015-unicode-regex": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", "dev": true, "requires": { "babel-helper-regex": "6.26.0", "babel-runtime": "6.26.0", "regexpu-core": "2.0.0" } }, "babel-plugin-transform-exponentiation-operator": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", "dev": true, "requires": { "babel-helper-builder-binary-assignment-operator-visitor": "6.24.1", "babel-plugin-syntax-exponentiation-operator": "6.13.0", "babel-runtime": "6.26.0" } }, "babel-plugin-transform-flow-strip-types": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz", "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=", "dev": true, "requires": { "babel-plugin-syntax-flow": "6.18.0", "babel-runtime": "6.26.0" } }, "babel-plugin-transform-object-rest-spread": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz", "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=", "dev": true, "requires": { "babel-plugin-syntax-object-rest-spread": "6.13.0", "babel-runtime": "6.26.0" } }, "babel-plugin-transform-regenerator": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", "dev": true, "requires": { "regenerator-transform": "0.10.1" } }, "babel-plugin-transform-strict-mode": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", "dev": true, "requires": { "babel-runtime": "6.26.0", "babel-types": "6.26.0" } }, "babel-polyfill": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", "dev": true, "requires": { "babel-runtime": "6.26.0", "core-js": "2.5.3", "regenerator-runtime": "0.10.5" }, "dependencies": { "regenerator-runtime": { "version": "0.10.5", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", "dev": true } } }, "babel-preset-env": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.6.1.tgz", "integrity": "sha512-W6VIyA6Ch9ePMI7VptNn2wBM6dbG0eSz25HEiL40nQXCsXGTGZSTZu1Iap+cj3Q0S5a7T9+529l/5Bkvd+afNA==", "dev": true, "requires": { "babel-plugin-check-es2015-constants": "6.22.0", "babel-plugin-syntax-trailing-function-commas": "6.22.0", "babel-plugin-transform-async-to-generator": "6.24.1", "babel-plugin-transform-es2015-arrow-functions": "6.22.0", "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", "babel-plugin-transform-es2015-block-scoping": "6.26.0", "babel-plugin-transform-es2015-classes": "6.24.1", "babel-plugin-transform-es2015-computed-properties": "6.24.1", "babel-plugin-transform-es2015-destructuring": "6.23.0", "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", "babel-plugin-transform-es2015-for-of": "6.23.0", "babel-plugin-transform-es2015-function-name": "6.24.1", "babel-plugin-transform-es2015-literals": "6.22.0", "babel-plugin-transform-es2015-modules-amd": "6.24.1", "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", "babel-plugin-transform-es2015-modules-systemjs": "6.24.1", "babel-plugin-transform-es2015-modules-umd": "6.24.1", "babel-plugin-transform-es2015-object-super": "6.24.1", "babel-plugin-transform-es2015-parameters": "6.24.1", "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", "babel-plugin-transform-es2015-spread": "6.22.0", "babel-plugin-transform-es2015-sticky-regex": "6.24.1", "babel-plugin-transform-es2015-template-literals": "6.22.0", "babel-plugin-transform-es2015-typeof-symbol": "6.23.0", "babel-plugin-transform-es2015-unicode-regex": "6.24.1", "babel-plugin-transform-exponentiation-operator": "6.24.1", "babel-plugin-transform-regenerator": "6.26.0", "browserslist": "2.11.3", "invariant": "2.2.3", "semver": "5.5.0" } }, "babel-register": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", "dev": true, "requires": { "babel-core": "6.26.0", "babel-runtime": "6.26.0", "core-js": "2.5.3", "home-or-tmp": "2.0.0", "lodash": "4.17.5", "mkdirp": "0.5.1", "source-map-support": "0.4.18" } }, "babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { "core-js": "2.5.3", "regenerator-runtime": "0.11.1" } }, "babel-template": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", "dev": true, "requires": { "babel-runtime": "6.26.0", "babel-traverse": "6.26.0", "babel-types": "6.26.0", "babylon": "6.18.0", "lodash": "4.17.5" } }, "babel-traverse": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", "dev": true, "requires": { "babel-code-frame": "6.26.0", "babel-messages": "6.23.0", "babel-runtime": "6.26.0", "babel-types": "6.26.0", "babylon": "6.18.0", "debug": "2.6.9", "globals": "9.18.0", "invariant": "2.2.3", "lodash": "4.17.5" } }, "babel-types": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "dev": true, "requires": { "babel-runtime": "6.26.0", "esutils": "2.0.2", "lodash": "4.17.5", "to-fast-properties": "1.0.3" } }, "babylon": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", "dev": true }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, "binary-extensions": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", "dev": true, "optional": true }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" } }, "braces": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, "optional": true, "requires": { "expand-range": "1.8.2", "preserve": "0.2.0", "repeat-element": "1.1.2" } }, "browserslist": { "version": "2.11.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", "dev": true, "requires": { "caniuse-lite": "1.0.30000813", "electron-to-chromium": "1.3.37" } }, "caniuse-lite": { "version": "1.0.30000813", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000813.tgz", "integrity": "sha512-A8ITSmH5SFdMFdC704ggjg+x2z5PzQmVlG8tavwnfvbC33Q1UYrj0+G+Xm0SNAnd4He36fwUE/KEWytOEchw+A==", "dev": true }, "chai": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", "dev": true, "requires": { "assertion-error": "1.1.0", "check-error": "1.0.2", "deep-eql": "3.0.1", "get-func-name": "2.0.0", "pathval": "1.1.0", "type-detect": "4.0.8" } }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { "ansi-styles": "2.2.1", "escape-string-regexp": "1.0.5", "has-ansi": "2.0.0", "strip-ansi": "3.0.1", "supports-color": "2.0.0" } }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", "dev": true }, "chokidar": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", "dev": true, "optional": true, "requires": { "anymatch": "1.3.2", "async-each": "1.0.1", "fsevents": "1.1.3", "glob-parent": "2.0.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", "is-glob": "2.0.1", "path-is-absolute": "1.0.1", "readdirp": "2.1.0" } }, "commander": { "version": "2.15.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.0.tgz", "integrity": "sha512-7B1ilBwtYSbetCgTY1NJFg+gVpestg0fdA1MhC1Vs4ssyfSXnCAjFr+QcQM9/RedXC0EaUx1sG8Smgw2VfgKEg==", "dev": true }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, "convert-source-map": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", "dev": true }, "core-js": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=", "dev": true }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true, "optional": true }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { "ms": "2.0.0" } }, "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, "requires": { "type-detect": "4.0.8" } }, "detect-indent": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", "dev": true, "requires": { "repeating": "2.0.1" } }, "electron-to-chromium": { "version": "1.3.37", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.37.tgz", "integrity": "sha1-SpJzTgBEyM8LFVO+V+riGkxuX6s=", "dev": true }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, "esutils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, "expand-brackets": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, "optional": true, "requires": { "is-posix-bracket": "0.1.1" } }, "expand-range": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", "dev": true, "optional": true, "requires": { "fill-range": "2.2.3" } }, "extglob": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, "optional": true, "requires": { "is-extglob": "1.0.0" } }, "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", "dev": true, "optional": true }, "fill-range": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", "dev": true, "optional": true, "requires": { "is-number": "2.1.0", "isobject": "2.1.0", "randomatic": "1.1.7", "repeat-element": "1.1.2", "repeat-string": "1.6.1" } }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true, "optional": true }, "for-own": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", "dev": true, "optional": true, "requires": { "for-in": "1.0.2" } }, "fs-readdir-recursive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", "dev": true }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, "fsevents": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", "dev": true, "optional": true, "requires": { "nan": "2.9.2", "node-pre-gyp": "0.6.39" }, "dependencies": { "abbrev": { "version": "1.1.0", "bundled": true, "dev": true, "optional": true }, "ajv": { "version": "4.11.8", "bundled": true, "dev": true, "optional": true, "requires": { "co": "4.6.0", "json-stable-stringify": "1.0.1" } }, "ansi-regex": { "version": "2.1.1", "bundled": true, "dev": true }, "aproba": { "version": "1.1.1", "bundled": true, "dev": true, "optional": true }, "are-we-there-yet": { "version": "1.1.4", "bundled": true, "dev": true, "optional": true, "requires": { "delegates": "1.0.0", "readable-stream": "2.2.9" } }, "asn1": { "version": "0.2.3", "bundled": true, "dev": true, "optional": true }, "assert-plus": { "version": "0.2.0", "bundled": true, "dev": true, "optional": true }, "asynckit": { "version": "0.4.0", "bundled": true, "dev": true, "optional": true }, "aws-sign2": { "version": "0.6.0", "bundled": true, "dev": true, "optional": true }, "aws4": { "version": "1.6.0", "bundled": true, "dev": true, "optional": true }, "balanced-match": { "version": "0.4.2", "bundled": true, "dev": true }, "bcrypt-pbkdf": { "version": "1.0.1", "bundled": true, "dev": true, "optional": true, "requires": { "tweetnacl": "0.14.5" } }, "block-stream": { "version": "0.0.9", "bundled": true, "dev": true, "requires": { "inherits": "2.0.3" } }, "boom": { "version": "2.10.1", "bundled": true, "dev": true, "requires": { "hoek": "2.16.3" } }, "brace-expansion": { "version": "1.1.7", "bundled": true, "dev": true, "requires": { "balanced-match": "0.4.2", "concat-map": "0.0.1" } }, "buffer-shims": { "version": "1.0.0", "bundled": true, "dev": true }, "caseless": { "version": "0.12.0", "bundled": true, "dev": true, "optional": true }, "co": { "version": "4.6.0", "bundled": true, "dev": true, "optional": true }, "code-point-at": { "version": "1.1.0", "bundled": true, "dev": true }, "combined-stream": { "version": "1.0.5", "bundled": true, "dev": true, "requires": { "delayed-stream": "1.0.0" } }, "concat-map": { "version": "0.0.1", "bundled": true, "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, "dev": true }, "core-util-is": { "version": "1.0.2", "bundled": true, "dev": true }, "cryptiles": { "version": "2.0.5", "bundled": true, "dev": true, "requires": { "boom": "2.10.1" } }, "dashdash": { "version": "1.14.1", "bundled": true, "dev": true, "optional": true, "requires": { "assert-plus": "1.0.0" }, "dependencies": { "assert-plus": { "version": "1.0.0", "bundled": true, "dev": true, "optional": true } } }, "debug": { "version": "2.6.8", "bundled": true, "dev": true, "optional": true, "requires": { "ms": "2.0.0" } }, "deep-extend": { "version": "0.4.2", "bundled": true, "dev": true, "optional": true }, "delayed-stream": { "version": "1.0.0", "bundled": true, "dev": true }, "delegates": { "version": "1.0.0", "bundled": true, "dev": true, "optional": true }, "detect-libc": { "version": "1.0.2", "bundled": true, "dev": true, "optional": true }, "ecc-jsbn": { "version": "0.1.1", "bundled": true, "dev": true, "optional": true, "requires": { "jsbn": "0.1.1" } }, "extend": { "version": "3.0.1", "bundled": true, "dev": true, "optional": true }, "extsprintf": { "version": "1.0.2", "bundled": true, "dev": true }, "forever-agent": { "version": "0.6.1", "bundled": true, "dev": true, "optional": true }, "form-data": { "version": "2.1.4", "bundled": true, "dev": true, "optional": true, "requires": { "asynckit": "0.4.0", "combined-stream": "1.0.5", "mime-types": "2.1.15" } }, "fs.realpath": { "version": "1.0.0", "bundled": true, "dev": true }, "fstream": { "version": "1.0.11", "bundled": true, "dev": true, "requires": { "graceful-fs": "4.1.11", "inherits": "2.0.3", "mkdirp": "0.5.1", "rimraf": "2.6.1" } }, "fstream-ignore": { "version": "1.0.5", "bundled": true, "dev": true, "optional": true, "requires": { "fstream": "1.0.11", "inherits": "2.0.3", "minimatch": "3.0.4" } }, "gauge": { "version": "2.7.4", "bundled": true, "dev": true, "optional": true, "requires": { "aproba": "1.1.1", "console-control-strings": "1.1.0", "has-unicode": "2.0.1", "object-assign": "4.1.1", "signal-exit": "3.0.2", "string-width": "1.0.2", "strip-ansi": "3.0.1", "wide-align": "1.1.2" } }, "getpass": { "version": "0.1.7", "bundled": true, "dev": true, "optional": true, "requires": { "assert-plus": "1.0.0" }, "dependencies": { "assert-plus": { "version": "1.0.0", "bundled": true, "dev": true, "optional": true } } }, "glob": { "version": "7.1.2", "bundled": true, "dev": true, "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", "inherits": "2.0.3", "minimatch": "3.0.4", "once": "1.4.0", "path-is-absolute": "1.0.1" } }, "graceful-fs": { "version": "4.1.11", "bundled": true, "dev": true }, "har-schema": { "version": "1.0.5", "bundled": true, "dev": true, "optional": true }, "har-validator": { "version": "4.2.1", "bundled": true, "dev": true, "optional": true, "requires": { "ajv": "4.11.8", "har-schema": "1.0.5" } }, "has-unicode": { "version": "2.0.1", "bundled": true, "dev": true, "optional": true }, "hawk": { "version": "3.1.3", "bundled": true, "dev": true, "requires": { "boom": "2.10.1", "cryptiles": "2.0.5", "hoek": "2.16.3", "sntp": "1.0.9" } }, "hoek": { "version": "2.16.3", "bundled": true, "dev": true }, "http-signature": { "version": "1.1.1", "bundled": true, "dev": true, "optional": true, "requires": { "assert-plus": "0.2.0", "jsprim": "1.4.0", "sshpk": "1.13.0" } }, "inflight": { "version": "1.0.6", "bundled": true, "dev": true, "requires": { "once": "1.4.0", "wrappy": "1.0.2" } }, "inherits": { "version": "2.0.3", "bundled": true, "dev": true }, "ini": { "version": "1.3.4", "bundled": true, "dev": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, "dev": true, "requires": { "number-is-nan": "1.0.1" } }, "is-typedarray": { "version": "1.0.0", "bundled": true, "dev": true, "optional": true }, "isarray": { "version": "1.0.0", "bundled": true, "dev": true }, "isstream": { "version": "0.1.2", "bundled": true, "dev": true, "optional": true }, "jodid25519": { "version": "1.0.2", "bundled": true, "dev": true, "optional": true, "requires": { "jsbn": "0.1.1" } }, "jsbn": { "version": "0.1.1", "bundled": true, "dev": true, "optional": true }, "json-schema": { "version": "0.2.3", "bundled": true, "dev": true, "optional": true }, "json-stable-stringify": { "version": "1.0.1", "bundled": true, "dev": true, "optional": true, "requires": { "jsonify": "0.0.0" } }, "json-stringify-safe": { "version": "5.0.1", "bundled": true, "dev": true, "optional": true }, "jsonify": { "version": "0.0.0", "bundled": true, "dev": true, "optional": true }, "jsprim": { "version": "1.4.0", "bundled": true, "dev": true, "optional": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.0.2", "json-schema": "0.2.3", "verror": "1.3.6" }, "dependencies": { "assert-plus": { "version": "1.0.0", "bundled": true, "dev": true, "optional": true } } }, "mime-db": { "version": "1.27.0", "bundled": true, "dev": true }, "mime-types": { "version": "2.1.15", "bundled": true, "dev": true, "requires": { "mime-db": "1.27.0" } }, "minimatch": { "version": "3.0.4", "bundled": true, "dev": true, "requires": { "brace-expansion": "1.1.7" } }, "minimist": { "version": "0.0.8", "bundled": true, "dev": true }, "mkdirp": { "version": "0.5.1", "bundled": true, "dev": true, "requires": { "minimist": "0.0.8" } }, "ms": { "version": "2.0.0", "bundled": true, "dev": true, "optional": true }, "node-pre-gyp": { "version": "0.6.39", "bundled": true, "dev": true, "optional": true, "requires": { "detect-libc": "1.0.2", "hawk": "3.1.3", "mkdirp": "0.5.1", "nopt": "4.0.1", "npmlog": "4.1.0", "rc": "1.2.1", "request": "2.81.0", "rimraf": "2.6.1", "semver": "5.3.0", "tar": "2.2.1", "tar-pack": "3.4.0" } }, "nopt": { "version": "4.0.1", "bundled": true, "dev": true, "optional": true, "requires": { "abbrev": "1.1.0", "osenv": "0.1.4" } }, "npmlog": { "version": "4.1.0", "bundled": true, "dev": true, "optional": true, "requires": { "are-we-there-yet": "1.1.4", "console-control-strings": "1.1.0", "gauge": "2.7.4", "set-blocking": "2.0.0" } }, "number-is-nan": { "version": "1.0.1", "bundled": true, "dev": true }, "oauth-sign": { "version": "0.8.2", "bundled": true, "dev": true, "optional": true }, "object-assign": { "version": "4.1.1", "bundled": true, "dev": true, "optional": true }, "once": { "version": "1.4.0", "bundled": true, "dev": true, "requires": { "wrappy": "1.0.2" } }, "os-homedir": { "version": "1.0.2", "bundled": true, "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", "bundled": true, "dev": true, "optional": true }, "osenv": { "version": "0.1.4", "bundled": true, "dev": true, "optional": true, "requires": { "os-homedir": "1.0.2", "os-tmpdir": "1.0.2" } }, "path-is-absolute": { "version": "1.0.1", "bundled": true, "dev": true }, "performance-now": { "version": "0.2.0", "bundled": true, "dev": true, "optional": true }, "process-nextick-args": { "version": "1.0.7", "bundled": true, "dev": true }, "punycode": { "version": "1.4.1", "bundled": true, "dev": true, "optional": true }, "qs": { "version": "6.4.0", "bundled": true, "dev": true, "optional": true }, "rc": { "version": "1.2.1", "bundled": true, "dev": true, "optional": true, "requires": { "deep-extend": "0.4.2", "ini": "1.3.4", "minimist": "1.2.0", "strip-json-comments": "2.0.1" }, "dependencies": { "minimist": { "version": "1.2.0", "bundled": true, "dev": true, "optional": true } } }, "readable-stream": { "version": "2.2.9", "bundled": true, "dev": true, "requires": { "buffer-shims": "1.0.0", "core-util-is": "1.0.2", "inherits": "2.0.3", "isarray": "1.0.0", "process-nextick-args": "1.0.7", "string_decoder": "1.0.1", "util-deprecate": "1.0.2" } }, "request": { "version": "2.81.0", "bundled": true, "dev": true, "optional": true, "requires": { "aws-sign2": "0.6.0", "aws4": "1.6.0", "caseless": "0.12.0", "combined-stream": "1.0.5", "extend": "3.0.1", "forever-agent": "0.6.1", "form-data": "2.1.4", "har-validator": "4.2.1", "hawk": "3.1.3", "http-signature": "1.1.1", "is-typedarray": "1.0.0", "isstream": "0.1.2", "json-stringify-safe": "5.0.1", "mime-types": "2.1.15", "oauth-sign": "0.8.2", "performance-now": "0.2.0", "qs": "6.4.0", "safe-buffer": "5.0.1", "stringstream": "0.0.5", "tough-cookie": "2.3.2", "tunnel-agent": "0.6.0", "uuid": "3.0.1" } }, "rimraf": { "version": "2.6.1", "bundled": true, "dev": true, "requires": { "glob": "7.1.2" } }, "safe-buffer": { "version": "5.0.1", "bundled": true, "dev": true }, "semver": { "version": "5.3.0", "bundled": true, "dev": true, "optional": true }, "set-blocking": { "version": "2.0.0", "bundled": true, "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", "bundled": true, "dev": true, "optional": true }, "sntp": { "version": "1.0.9", "bundled": true, "dev": true, "requires": { "hoek": "2.16.3" } }, "sshpk": { "version": "1.13.0", "bundled": true, "dev": true, "optional": true, "requires": { "asn1": "0.2.3", "assert-plus": "1.0.0", "bcrypt-pbkdf": "1.0.1", "dashdash": "1.14.1", "ecc-jsbn": "0.1.1", "getpass": "0.1.7", "jodid25519": "1.0.2", "jsbn": "0.1.1", "tweetnacl": "0.14.5" }, "dependencies": { "assert-plus": { "version": "1.0.0", "bundled": true, "dev": true, "optional": true } } }, "string-width": { "version": "1.0.2", "bundled": true, "dev": true, "requires": { "code-point-at": "1.1.0", "is-fullwidth-code-point": "1.0.0", "strip-ansi": "3.0.1" } }, "string_decoder": { "version": "1.0.1", "bundled": true, "dev": true, "requires": { "safe-buffer": "5.0.1" } }, "stringstream": { "version": "0.0.5", "bundled": true, "dev": true, "optional": true }, "strip-ansi": { "version": "3.0.1", "bundled": true, "dev": true, "requires": { "ansi-regex": "2.1.1" } }, "strip-json-comments": { "version": "2.0.1", "bundled": true, "dev": true, "optional": true }, "tar": { "version": "2.2.1", "bundled": true, "dev": true, "requires": { "block-stream": "0.0.9", "fstream": "1.0.11", "inherits": "2.0.3" } }, "tar-pack": { "version": "3.4.0", "bundled": true, "dev": true, "optional": true, "requires": { "debug": "2.6.8", "fstream": "1.0.11", "fstream-ignore": "1.0.5", "once": "1.4.0", "readable-stream": "2.2.9", "rimraf": "2.6.1", "tar": "2.2.1", "uid-number": "0.0.6" } }, "tough-cookie": { "version": "2.3.2", "bundled": true, "dev": true, "optional": true, "requires": { "punycode": "1.4.1" } }, "tunnel-agent": { "version": "0.6.0", "bundled": true, "dev": true, "optional": true, "requires": { "safe-buffer": "5.0.1" } }, "tweetnacl": { "version": "0.14.5", "bundled": true, "dev": true, "optional": true }, "uid-number": { "version": "0.0.6", "bundled": true, "dev": true, "optional": true }, "util-deprecate": { "version": "1.0.2", "bundled": true, "dev": true }, "uuid": { "version": "3.0.1", "bundled": true, "dev": true, "optional": true }, "verror": { "version": "1.3.6", "bundled": true, "dev": true, "optional": true, "requires": { "extsprintf": "1.0.2" } }, "wide-align": { "version": "1.1.2", "bundled": true, "dev": true, "optional": true, "requires": { "string-width": "1.0.2" } }, "wrappy": { "version": "1.0.2", "bundled": true, "dev": true } } }, "get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", "inherits": "2.0.3", "minimatch": "3.0.4", "once": "1.4.0", "path-is-absolute": "1.0.1" } }, "glob-base": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", "dev": true, "optional": true, "requires": { "glob-parent": "2.0.0", "is-glob": "2.0.1" } }, "glob-parent": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "dev": true, "requires": { "is-glob": "2.0.1" } }, "globals": { "version": "9.18.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", "dev": true }, "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", "dev": true }, "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "dev": true, "requires": { "ansi-regex": "2.1.1" } }, "home-or-tmp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", "dev": true, "requires": { "os-homedir": "1.0.2", "os-tmpdir": "1.0.2" } }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { "once": "1.4.0", "wrappy": "1.0.2" } }, "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, "invariant": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.3.tgz", "integrity": "sha512-7Z5PPegwDTyjbaeCnV0efcyS6vdKAU51kpEmS7QFib3P4822l8ICYyMn7qvJnc+WzLoDsuI9gPMKbJ8pCu8XtA==", "dev": true, "requires": { "loose-envify": "1.3.1" } }, "is-binary-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "dev": true, "optional": true, "requires": { "binary-extensions": "1.11.0" } }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, "is-dotfile": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", "dev": true, "optional": true }, "is-equal-shallow": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", "dev": true, "optional": true, "requires": { "is-primitive": "2.0.0" } }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", "dev": true, "optional": true }, "is-extglob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", "dev": true }, "is-finite": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", "dev": true, "requires": { "number-is-nan": "1.0.1" } }, "is-glob": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, "requires": { "is-extglob": "1.0.0" } }, "is-number": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", "dev": true, "optional": true, "requires": { "kind-of": "3.2.2" } }, "is-posix-bracket": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", "dev": true, "optional": true }, "is-primitive": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", "dev": true, "optional": true }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, "isobject": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", "dev": true, "optional": true, "requires": { "isarray": "1.0.0" } }, "iterall": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.2.2.tgz", "integrity": "sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA==", "dev": true }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", "dev": true }, "jsesc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", "dev": true }, "json5": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", "dev": true }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { "is-buffer": "1.1.6" } }, "lodash": { "version": "4.17.5", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==", "dev": true }, "loose-envify": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", "dev": true, "requires": { "js-tokens": "3.0.2" } }, "micromatch": { "version": "2.3.11", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, "optional": true, "requires": { "arr-diff": "2.0.0", "array-unique": "0.2.1", "braces": "1.8.5", "expand-brackets": "0.1.5", "extglob": "0.3.2", "filename-regex": "2.0.1", "is-extglob": "1.0.0", "is-glob": "2.0.1", "kind-of": "3.2.2", "normalize-path": "2.1.1", "object.omit": "2.0.1", "parse-glob": "3.0.4", "regex-cache": "0.4.4" } }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { "brace-expansion": "1.1.11" } }, "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { "minimist": "0.0.8" } }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, "nan": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/nan/-/nan-2.9.2.tgz", "integrity": "sha512-ltW65co7f3PQWBDbqVvaU1WtFJUsNW7sWWm4HINhbMQIyVyzIeyZ8toX5TC5eeooE6piZoaEh4cZkueSKG3KYw==", "dev": true, "optional": true }, "normalize-path": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { "remove-trailing-separator": "1.1.0" } }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, "object.omit": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", "dev": true, "optional": true, "requires": { "for-own": "0.1.5", "is-extendable": "0.1.1" } }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { "wrappy": "1.0.2" } }, "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, "output-file-sync": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.2.tgz", "integrity": "sha1-0KM+7+YaIF+suQCS6CZZjVJFznY=", "dev": true, "requires": { "graceful-fs": "4.1.11", "mkdirp": "0.5.1", "object-assign": "4.1.1" } }, "parse-glob": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", "dev": true, "optional": true, "requires": { "glob-base": "0.3.0", "is-dotfile": "1.0.3", "is-extglob": "1.0.0", "is-glob": "2.0.1" } }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, "pathval": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", "dev": true }, "preserve": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", "dev": true, "optional": true }, "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", "dev": true }, "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true, "optional": true }, "randomatic": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", "dev": true, "optional": true, "requires": { "is-number": "3.0.0", "kind-of": "4.0.0" }, "dependencies": { "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "optional": true, "requires": { "kind-of": "3.2.2" }, "dependencies": { "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "optional": true, "requires": { "is-buffer": "1.1.6" } } } }, "kind-of": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "optional": true, "requires": { "is-buffer": "1.1.6" } } } }, "readable-stream": { "version": "2.3.5", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.5.tgz", "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", "dev": true, "optional": true, "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", "isarray": "1.0.0", "process-nextick-args": "2.0.0", "safe-buffer": "5.1.1", "string_decoder": "1.0.3", "util-deprecate": "1.0.2" } }, "readdirp": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", "dev": true, "optional": true, "requires": { "graceful-fs": "4.1.11", "minimatch": "3.0.4", "readable-stream": "2.3.5", "set-immediate-shim": "1.0.1" } }, "regenerate": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==", "dev": true }, "regenerator-runtime": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", "dev": true }, "regenerator-transform": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", "dev": true, "requires": { "babel-runtime": "6.26.0", "babel-types": "6.26.0", "private": "0.1.8" } }, "regex-cache": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", "dev": true, "optional": true, "requires": { "is-equal-shallow": "0.1.3" } }, "regexpu-core": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", "dev": true, "requires": { "regenerate": "1.3.3", "regjsgen": "0.2.0", "regjsparser": "0.1.5" } }, "regjsgen": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", "dev": true }, "regjsparser": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", "dev": true, "requires": { "jsesc": "0.5.0" }, "dependencies": { "jsesc": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", "dev": true } } }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", "dev": true }, "repeat-element": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", "dev": true }, "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true, "optional": true }, "repeating": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, "requires": { "is-finite": "1.0.2" } }, "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", "dev": true }, "semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", "dev": true }, "set-immediate-shim": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", "dev": true, "optional": true }, "slash": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", "dev": true }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, "source-map-support": { "version": "0.4.18", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", "dev": true, "requires": { "source-map": "0.5.7" } }, "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, "optional": true, "requires": { "safe-buffer": "5.1.1" } }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { "ansi-regex": "2.1.1" } }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true }, "to-fast-properties": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", "dev": true }, "trim-right": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", "dev": true }, "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, "user-home": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", "dev": true }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true, "optional": true }, "v8flags": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", "dev": true, "requires": { "user-home": "1.1.1" } }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true } } } graphql-go-1.5.0/internal/validation/testdata/package.json000066400000000000000000000021241435002731700236240ustar00rootroot00000000000000{ "name": "testdata", "version": "0.0.0", "description": "Extracts test data from github.com/graphql/graphql-js", "main": "", "devDependencies": { "babel-cli": "^6.26.0", "babel-plugin-syntax-async-functions": "^6.13.0", "babel-plugin-syntax-async-generators": "^6.13.0", "babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-es2015-classes": "^6.24.1", "babel-plugin-transform-es2015-destructuring": "^6.23.0", "babel-plugin-transform-es2015-spread": "^6.22.0", "babel-plugin-transform-flow-strip-types": "^6.22.0", "babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-preset-env": "^1.6.1", "chai": "^4.1.2", "iterall": "^1.2.2" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", "url": "git+https://github.com/graph-gophers/graphql-go.git" }, "author": "", "license": "BSD-3-Clause", "bugs": { "url": "https://github.com/graph-gophers/graphql-go/issues" }, "homepage": "https://github.com/graph-gophers/graphql-go#readme" } graphql-go-1.5.0/internal/validation/testdata/tests.json000066400000000000000000004331321435002731700234020ustar00rootroot00000000000000{ "schemas": [ "schema {\n query: QueryRoot\n}\n\ndirective @onQuery on QUERY\n\ndirective @onMutation on MUTATION\n\ndirective @onSubscription on SUBSCRIPTION\n\ndirective @onField on FIELD\n\ndirective @onFragmentDefinition on FRAGMENT_DEFINITION\n\ndirective @onFragmentSpread on FRAGMENT_SPREAD\n\ndirective @onInlineFragment on INLINE_FRAGMENT\n\ndirective @onSchema on SCHEMA\n\ndirective @onScalar on SCALAR\n\ndirective @onObject on OBJECT\n\ndirective @onFieldDefinition on FIELD_DEFINITION\n\ndirective @onArgumentDefinition on ARGUMENT_DEFINITION\n\ndirective @onInterface on INTERFACE\n\ndirective @onUnion on UNION\n\ndirective @onEnum on ENUM\n\ndirective @onEnumValue on ENUM_VALUE\n\ndirective @onInputObject on INPUT_OBJECT\n\ndirective @onInputFieldDefinition on INPUT_FIELD_DEFINITION\n\ntype Alien implements Being & Intelligent {\n iq: Int\n name(surname: Boolean): String\n numEyes: Int\n}\n\nscalar Any\n\ninterface Being {\n name(surname: Boolean): String\n}\n\ninterface Canine {\n name(surname: Boolean): String\n}\n\ntype Cat implements Being & Pet {\n name(surname: Boolean): String\n nickname: String\n meows: Boolean\n meowVolume: Int\n furColor: FurColor\n}\n\nunion CatOrDog = Dog | Cat\n\ninput ComplexInput {\n requiredField: Boolean!\n intField: Int\n stringField: String\n booleanField: Boolean\n stringListField: [String]\n enumField: FurColor\n nestedInput: SimpleInput}\n\ntype ComplicatedArgs {\n intArgField(intArg: Int): String\n nonNullIntArgField(nonNullIntArg: Int!): String\n stringArgField(stringArg: String): String\n booleanArgField(booleanArg: Boolean): String\n enumArgField(enumArg: FurColor): String\n enumArrayArgField(enumArrayArg: [FurColor!]!): String\n floatArgField(floatArg: Float): String\n idArgField(idArg: ID): String\n stringListArgField(stringListArg: [String]): String\n stringListNonNullArgField(stringListNonNullArg: [String!]): String\n complexArgField(complexArg: ComplexInput): String\n multipleReqs(req1: Int!, req2: Int!): String\n multipleOpts(opt1: Int = 0, opt2: Int = 0): String\n multipleOptAndReq(req1: Int!, req2: Int!, opt1: Int = 0, opt2: Int = 0): String\n}\n\ntype Dog implements Being & Pet & Canine {\n name(surname: Boolean): String\n nickname: String\n barkVolume: Int\n barks: Boolean\n doesKnowCommand(dogCommand: DogCommand): Boolean\n isHousetrained(atOtherHomes: Boolean = true): Boolean\n isAtLocation(x: Int, y: Int): Boolean\n}\n\nenum DogCommand {\n SIT\n HEEL\n DOWN\n}\n\nunion DogOrHuman = Dog | Human\n\nenum FurColor {\n BROWN\n BLACK\n TAN\n SPOTTED\n NO_FUR\n UNKNOWN\n}\n\ntype Human implements Being & Intelligent {\n name(surname: Boolean): String\n pets: [Pet]\n relatives: [Human]\n iq: Int\n}\n\nunion HumanOrAlien = Human | Alien\n\ninterface Intelligent {\n iq: Int\n}\n\nscalar Invalid\n\ninput SimpleInput {\n stringField: String\n stringListField: [String!]\n}\n\ninterface Pet {\n name(surname: Boolean): String\n}\n\ntype QueryRoot {\n human(id: ID): Human\n alien: Alien\n dog: Dog\n cat: Cat\n pet: Pet\n catOrDog: CatOrDog\n dogOrHuman: DogOrHuman\n humanOrAlien: HumanOrAlien\n complicatedArgs: ComplicatedArgs\n invalidArg(arg: Invalid): String\n anyArg(arg: Any): String\n}\n", "schema {\n query: QueryRoot\n}\n\ntype Connection {\n edges: [Edge]\n}\n\ntype Edge {\n node: Node\n}\n\ntype IntBox implements SomeBox {\n scalar: Int\n deepBox: IntBox\n unrelatedField: String\n listStringBox: [StringBox]\n stringBox: StringBox\n intBox: IntBox\n}\n\ntype Node {\n id: ID\n name: String\n}\n\ninterface NonNullStringBox1 {\n scalar: String!\n}\n\ntype NonNullStringBox1Impl implements SomeBox & NonNullStringBox1 {\n scalar: String!\n unrelatedField: String\n deepBox: SomeBox\n}\n\ninterface NonNullStringBox2 {\n scalar: String!\n}\n\ntype NonNullStringBox2Impl implements SomeBox & NonNullStringBox2 {\n scalar: String!\n unrelatedField: String\n deepBox: SomeBox\n}\n\ntype QueryRoot {\n someBox: SomeBox\n connection: Connection\n}\n\ninterface SomeBox {\n deepBox: SomeBox\n unrelatedField: String\n}\n\ntype StringBox implements SomeBox {\n scalar: String\n deepBox: StringBox\n unrelatedField: String\n listStringBox: [StringBox]\n stringBox: StringBox\n intBox: IntBox\n}\n", "type Foo {\n constructor: String\n}\n\ntype Query {\n foo: Foo\n}\n" ], "tests": [ { "name": "Validate: Fields on correct type/Object field selection", "rule": "FieldsOnCorrectType", "schema": 0, "query": "\n fragment objectFieldSelection on Dog {\n __typename\n name\n }\n ", "errors": [] }, { "name": "Validate: Fields on correct type/Aliased object field selection", "rule": "FieldsOnCorrectType", "schema": 0, "query": "\n fragment aliasedObjectFieldSelection on Dog {\n tn : __typename\n otherName : name\n }\n ", "errors": [] }, { "name": "Validate: Fields on correct type/Interface field selection", "rule": "FieldsOnCorrectType", "schema": 0, "query": "\n fragment interfaceFieldSelection on Pet {\n __typename\n name\n }\n ", "errors": [] }, { "name": "Validate: Fields on correct type/Aliased interface field selection", "rule": "FieldsOnCorrectType", "schema": 0, "query": "\n fragment interfaceFieldSelection on Pet {\n otherName : name\n }\n ", "errors": [] }, { "name": "Validate: Fields on correct type/Lying alias selection", "rule": "FieldsOnCorrectType", "schema": 0, "query": "\n fragment lyingAliasSelection on Dog {\n name : nickname\n }\n ", "errors": [] }, { "name": "Validate: Fields on correct type/Ignores fields on unknown type", "rule": "FieldsOnCorrectType", "schema": 0, "query": "\n fragment unknownSelection on UnknownType {\n unknownField\n }\n ", "errors": [] }, { "name": "Validate: Fields on correct type/reports errors when type is known again", "rule": "FieldsOnCorrectType", "schema": 0, "query": "\n fragment typeKnownAgain on Pet {\n unknown_pet_field {\n ... on Cat {\n unknown_cat_field\n }\n }\n }", "errors": [ { "message": "Cannot query field \"unknown_pet_field\" on type \"Pet\".", "locations": [ { "line": 3, "column": 9 } ] }, { "message": "Cannot query field \"unknown_cat_field\" on type \"Cat\".", "locations": [ { "line": 5, "column": 13 } ] } ] }, { "name": "Validate: Fields on correct type/Field not defined on fragment", "rule": "FieldsOnCorrectType", "schema": 0, "query": "\n fragment fieldNotDefined on Dog {\n meowVolume\n }", "errors": [ { "message": "Cannot query field \"meowVolume\" on type \"Dog\". Did you mean \"barkVolume\"?", "locations": [ { "line": 3, "column": 9 } ] } ] }, { "name": "Validate: Fields on correct type/Ignores deeply unknown field", "rule": "FieldsOnCorrectType", "schema": 0, "query": "\n fragment deepFieldNotDefined on Dog {\n unknown_field {\n deeper_unknown_field\n }\n }", "errors": [ { "message": "Cannot query field \"unknown_field\" on type \"Dog\".", "locations": [ { "line": 3, "column": 9 } ] } ] }, { "name": "Validate: Fields on correct type/Sub-field not defined", "rule": "FieldsOnCorrectType", "schema": 0, "query": "\n fragment subFieldNotDefined on Human {\n pets {\n unknown_field\n }\n }", "errors": [ { "message": "Cannot query field \"unknown_field\" on type \"Pet\".", "locations": [ { "line": 4, "column": 11 } ] } ] }, { "name": "Validate: Fields on correct type/Field not defined on inline fragment", "rule": "FieldsOnCorrectType", "schema": 0, "query": "\n fragment fieldNotDefined on Pet {\n ... on Dog {\n meowVolume\n }\n }", "errors": [ { "message": "Cannot query field \"meowVolume\" on type \"Dog\". Did you mean \"barkVolume\"?", "locations": [ { "line": 4, "column": 11 } ] } ] }, { "name": "Validate: Fields on correct type/Aliased field target not defined", "rule": "FieldsOnCorrectType", "schema": 0, "query": "\n fragment aliasedFieldTargetNotDefined on Dog {\n volume : mooVolume\n }", "errors": [ { "message": "Cannot query field \"mooVolume\" on type \"Dog\". Did you mean \"barkVolume\"?", "locations": [ { "line": 3, "column": 9 } ] } ] }, { "name": "Validate: Fields on correct type/Aliased lying field target not defined", "rule": "FieldsOnCorrectType", "schema": 0, "query": "\n fragment aliasedLyingFieldTargetNotDefined on Dog {\n barkVolume : kawVolume\n }", "errors": [ { "message": "Cannot query field \"kawVolume\" on type \"Dog\". Did you mean \"barkVolume\"?", "locations": [ { "line": 3, "column": 9 } ] } ] }, { "name": "Validate: Fields on correct type/Not defined on interface", "rule": "FieldsOnCorrectType", "schema": 0, "query": "\n fragment notDefinedOnInterface on Pet {\n tailLength\n }", "errors": [ { "message": "Cannot query field \"tailLength\" on type \"Pet\".", "locations": [ { "line": 3, "column": 9 } ] } ] }, { "name": "Validate: Fields on correct type/Defined on implementors but not on interface", "rule": "FieldsOnCorrectType", "schema": 0, "query": "\n fragment definedOnImplementorsButNotInterface on Pet {\n nickname\n }", "errors": [ { "message": "Cannot query field \"nickname\" on type \"Pet\".", "locations": [ { "line": 3, "column": 9 } ] } ] }, { "name": "Validate: Fields on correct type/Meta field selection on union", "rule": "FieldsOnCorrectType", "schema": 0, "query": "\n fragment directFieldSelectionOnUnion on CatOrDog {\n __typename\n }", "errors": [] }, { "name": "Validate: Fields on correct type/Direct field selection on union", "rule": "FieldsOnCorrectType", "schema": 0, "query": "\n fragment directFieldSelectionOnUnion on CatOrDog {\n directField\n }", "errors": [ { "message": "Cannot query field \"directField\" on type \"CatOrDog\".", "locations": [ { "line": 3, "column": 9 } ] } ] }, { "name": "Validate: Fields on correct type/Defined on implementors queried on union", "rule": "FieldsOnCorrectType", "schema": 0, "query": "\n fragment definedOnImplementorsQueriedOnUnion on CatOrDog {\n name\n }", "errors": [ { "message": "Cannot query field \"name\" on type \"CatOrDog\".", "locations": [ { "line": 3, "column": 9 } ] } ] }, { "name": "Validate: Fields on correct type/valid field in inline fragment", "rule": "FieldsOnCorrectType", "schema": 0, "query": "\n fragment objectFieldSelection on Pet {\n ... on Dog {\n name\n }\n ... {\n name\n }\n }\n ", "errors": [] }, { "name": "Validate: Fragments on composite types/object is valid fragment type", "rule": "FragmentsOnCompositeTypes", "schema": 0, "query": "\n fragment validFragment on Dog {\n barks\n }\n ", "errors": [] }, { "name": "Validate: Fragments on composite types/interface is valid fragment type", "rule": "FragmentsOnCompositeTypes", "schema": 0, "query": "\n fragment validFragment on Pet {\n name\n }\n ", "errors": [] }, { "name": "Validate: Fragments on composite types/object is valid inline fragment type", "rule": "FragmentsOnCompositeTypes", "schema": 0, "query": "\n fragment validFragment on Pet {\n ... on Dog {\n barks\n }\n }\n ", "errors": [] }, { "name": "Validate: Fragments on composite types/inline fragment without type is valid", "rule": "FragmentsOnCompositeTypes", "schema": 0, "query": "\n fragment validFragment on Pet {\n ... {\n name\n }\n }\n ", "errors": [] }, { "name": "Validate: Fragments on composite types/union is valid fragment type", "rule": "FragmentsOnCompositeTypes", "schema": 0, "query": "\n fragment validFragment on CatOrDog {\n __typename\n }\n ", "errors": [] }, { "name": "Validate: Fragments on composite types/scalar is invalid fragment type", "rule": "FragmentsOnCompositeTypes", "schema": 0, "query": "\n fragment scalarFragment on Boolean {\n bad\n }\n ", "errors": [ { "message": "Fragment \"scalarFragment\" cannot condition on non composite type \"Boolean\".", "locations": [ { "line": 2, "column": 34 } ] } ] }, { "name": "Validate: Fragments on composite types/enum is invalid fragment type", "rule": "FragmentsOnCompositeTypes", "schema": 0, "query": "\n fragment scalarFragment on FurColor {\n bad\n }\n ", "errors": [ { "message": "Fragment \"scalarFragment\" cannot condition on non composite type \"FurColor\".", "locations": [ { "line": 2, "column": 34 } ] } ] }, { "name": "Validate: Fragments on composite types/input object is invalid fragment type", "rule": "FragmentsOnCompositeTypes", "schema": 0, "query": "\n fragment inputFragment on ComplexInput {\n stringField\n }\n ", "errors": [ { "message": "Fragment \"inputFragment\" cannot condition on non composite type \"ComplexInput\".", "locations": [ { "line": 2, "column": 33 } ] } ] }, { "name": "Validate: Fragments on composite types/scalar is invalid inline fragment type", "rule": "FragmentsOnCompositeTypes", "schema": 0, "query": "\n fragment invalidFragment on Pet {\n ... on String {\n barks\n }\n }\n ", "errors": [ { "message": "Fragment cannot condition on non composite type \"String\".", "locations": [ { "line": 3, "column": 16 } ] } ] }, { "name": "Validate: Known directives/with no directives", "rule": "KnownDirectives", "schema": 0, "query": "\n query Foo {\n name\n ...Frag\n }\n\n fragment Frag on Dog {\n name\n }\n ", "errors": [] }, { "name": "Validate: Known directives/with known directives", "rule": "KnownDirectives", "schema": 0, "query": "\n {\n dog @include(if: true) {\n name\n }\n human @skip(if: false) {\n name\n }\n }\n ", "errors": [] }, { "name": "Validate: Known directives/with unknown directive", "rule": "KnownDirectives", "schema": 0, "query": "\n {\n dog @unknown(directive: \"value\") {\n name\n }\n }\n ", "errors": [ { "message": "Unknown directive \"unknown\".", "locations": [ { "line": 3, "column": 13 } ] } ] }, { "name": "Validate: Known directives/with many unknown directives", "rule": "KnownDirectives", "schema": 0, "query": "\n {\n dog @unknown(directive: \"value\") {\n name\n }\n human @unknown(directive: \"value\") {\n name\n pets @unknown(directive: \"value\") {\n name\n }\n }\n }\n ", "errors": [ { "message": "Unknown directive \"unknown\".", "locations": [ { "line": 3, "column": 13 } ] }, { "message": "Unknown directive \"unknown\".", "locations": [ { "line": 6, "column": 15 } ] }, { "message": "Unknown directive \"unknown\".", "locations": [ { "line": 8, "column": 16 } ] } ] }, { "name": "Validate: Known directives/with well placed directives", "rule": "KnownDirectives", "schema": 0, "query": "\n query Foo @onQuery {\n name @include(if: true)\n ...Frag @include(if: true)\n skippedField @skip(if: true)\n ...SkippedFrag @skip(if: true)\n }\n\n mutation Bar @onMutation {\n someField\n }\n ", "errors": [] }, { "name": "Validate: Known directives/with misplaced directives", "rule": "KnownDirectives", "schema": 0, "query": "\n query Foo @include(if: true) {\n name @onQuery\n ...Frag @onQuery\n }\n\n mutation Bar @onQuery {\n someField\n }\n ", "errors": [ { "message": "Directive \"include\" may not be used on QUERY.", "locations": [ { "line": 2, "column": 17 } ] }, { "message": "Directive \"onQuery\" may not be used on FIELD.", "locations": [ { "line": 3, "column": 14 } ] }, { "message": "Directive \"onQuery\" may not be used on FRAGMENT_SPREAD.", "locations": [ { "line": 4, "column": 17 } ] }, { "message": "Directive \"onQuery\" may not be used on MUTATION.", "locations": [ { "line": 7, "column": 20 } ] } ] }, { "name": "Validate: Known fragment names/known fragment names are valid", "rule": "KnownFragmentNames", "schema": 0, "query": "\n {\n human(id: 4) {\n ...HumanFields1\n ... on Human {\n ...HumanFields2\n }\n ... {\n name\n }\n }\n }\n fragment HumanFields1 on Human {\n name\n ...HumanFields3\n }\n fragment HumanFields2 on Human {\n name\n }\n fragment HumanFields3 on Human {\n name\n }\n ", "errors": [] }, { "name": "Validate: Known fragment names/unknown fragment names are invalid", "rule": "KnownFragmentNames", "schema": 0, "query": "\n {\n human(id: 4) {\n ...UnknownFragment1\n ... on Human {\n ...UnknownFragment2\n }\n }\n }\n fragment HumanFields on Human {\n name\n ...UnknownFragment3\n }\n ", "errors": [ { "message": "Unknown fragment \"UnknownFragment1\".", "locations": [ { "line": 4, "column": 14 } ] }, { "message": "Unknown fragment \"UnknownFragment2\".", "locations": [ { "line": 6, "column": 16 } ] }, { "message": "Unknown fragment \"UnknownFragment3\".", "locations": [ { "line": 12, "column": 12 } ] } ] }, { "name": "Validate: Known type names/known type names are valid", "rule": "KnownTypeNames", "schema": 0, "query": "\n query Foo($var: String, $required: [String!]!) {\n user(id: 4) {\n pets { ... on Pet { name }, ...PetFields, ... { name } }\n }\n }\n fragment PetFields on Pet {\n name\n }\n ", "errors": [] }, { "name": "Validate: Known type names/unknown type names are invalid", "rule": "KnownTypeNames", "schema": 0, "query": "\n query Foo($var: JumbledUpLetters) {\n user(id: 4) {\n name\n pets { ... on Badger { name }, ...PetFields }\n }\n }\n fragment PetFields on Peettt {\n name\n }\n ", "errors": [ { "message": "Unknown type \"JumbledUpLetters\".", "locations": [ { "line": 2, "column": 23 } ] }, { "message": "Unknown type \"Badger\".", "locations": [ { "line": 5, "column": 25 } ] }, { "message": "Unknown type \"Peettt\".", "locations": [ { "line": 8, "column": 29 } ] } ] }, { "name": "Validate: Anonymous operation must be alone/no operations", "rule": "LoneAnonymousOperation", "schema": 0, "query": "\n fragment fragA on Type {\n field\n }\n ", "errors": [] }, { "name": "Validate: Anonymous operation must be alone/one anon operation", "rule": "LoneAnonymousOperation", "schema": 0, "query": "\n {\n field\n }\n ", "errors": [] }, { "name": "Validate: Anonymous operation must be alone/multiple named operations", "rule": "LoneAnonymousOperation", "schema": 0, "query": "\n query Foo {\n field\n }\n\n query Bar {\n field\n }\n ", "errors": [] }, { "name": "Validate: Anonymous operation must be alone/anon operation with fragment", "rule": "LoneAnonymousOperation", "schema": 0, "query": "\n {\n ...Foo\n }\n fragment Foo on Type {\n field\n }\n ", "errors": [] }, { "name": "Validate: Anonymous operation must be alone/multiple anon operations", "rule": "LoneAnonymousOperation", "schema": 0, "query": "\n {\n fieldA\n }\n {\n fieldB\n }\n ", "errors": [ { "message": "This anonymous operation must be the only defined operation.", "locations": [ { "line": 2, "column": 7 } ] }, { "message": "This anonymous operation must be the only defined operation.", "locations": [ { "line": 5, "column": 7 } ] } ] }, { "name": "Validate: Anonymous operation must be alone/anon operation with a mutation", "rule": "LoneAnonymousOperation", "schema": 0, "query": "\n {\n fieldA\n }\n mutation Foo {\n fieldB\n }\n ", "errors": [ { "message": "This anonymous operation must be the only defined operation.", "locations": [ { "line": 2, "column": 7 } ] } ] }, { "name": "Validate: Anonymous operation must be alone/anon operation with a subscription", "rule": "LoneAnonymousOperation", "schema": 0, "query": "\n {\n fieldA\n }\n subscription Foo {\n fieldB\n }\n ", "errors": [ { "message": "This anonymous operation must be the only defined operation.", "locations": [ { "line": 2, "column": 7 } ] } ] }, { "name": "Validate: No circular fragment spreads/single reference is valid", "rule": "NoFragmentCycles", "schema": 0, "query": "\n fragment fragA on Dog { ...fragB }\n fragment fragB on Dog { name }\n ", "errors": [] }, { "name": "Validate: No circular fragment spreads/spreading twice is not circular", "rule": "NoFragmentCycles", "schema": 0, "query": "\n fragment fragA on Dog { ...fragB, ...fragB }\n fragment fragB on Dog { name }\n ", "errors": [] }, { "name": "Validate: No circular fragment spreads/spreading twice indirectly is not circular", "rule": "NoFragmentCycles", "schema": 0, "query": "\n fragment fragA on Dog { ...fragB, ...fragC }\n fragment fragB on Dog { ...fragC }\n fragment fragC on Dog { name }\n ", "errors": [] }, { "name": "Validate: No circular fragment spreads/double spread within abstract types", "rule": "NoFragmentCycles", "schema": 0, "query": "\n fragment nameFragment on Pet {\n ... on Dog { name }\n ... on Cat { name }\n }\n\n fragment spreadsInAnon on Pet {\n ... on Dog { ...nameFragment }\n ... on Cat { ...nameFragment }\n }\n ", "errors": [] }, { "name": "Validate: No circular fragment spreads/does not false positive on unknown fragment", "rule": "NoFragmentCycles", "schema": 0, "query": "\n fragment nameFragment on Pet {\n ...UnknownFragment\n }\n ", "errors": [] }, { "name": "Validate: No circular fragment spreads/spreading recursively within field fails", "rule": "NoFragmentCycles", "schema": 0, "query": "\n fragment fragA on Human { relatives { ...fragA } },\n ", "errors": [ { "message": "Cannot spread fragment \"fragA\" within itself.", "locations": [ { "line": 2, "column": 45 } ] } ] }, { "name": "Validate: No circular fragment spreads/no spreading itself directly", "rule": "NoFragmentCycles", "schema": 0, "query": "\n fragment fragA on Dog { ...fragA }\n ", "errors": [ { "message": "Cannot spread fragment \"fragA\" within itself.", "locations": [ { "line": 2, "column": 31 } ] } ] }, { "name": "Validate: No circular fragment spreads/no spreading itself directly within inline fragment", "rule": "NoFragmentCycles", "schema": 0, "query": "\n fragment fragA on Pet {\n ... on Dog {\n ...fragA\n }\n }\n ", "errors": [ { "message": "Cannot spread fragment \"fragA\" within itself.", "locations": [ { "line": 4, "column": 11 } ] } ] }, { "name": "Validate: No circular fragment spreads/no spreading itself indirectly", "rule": "NoFragmentCycles", "schema": 0, "query": "\n fragment fragA on Dog { ...fragB }\n fragment fragB on Dog { ...fragA }\n ", "errors": [ { "message": "Cannot spread fragment \"fragA\" within itself via fragB.", "locations": [ { "line": 2, "column": 31 }, { "line": 3, "column": 31 } ] } ] }, { "name": "Validate: No circular fragment spreads/no spreading itself indirectly reports opposite order", "rule": "NoFragmentCycles", "schema": 0, "query": "\n fragment fragB on Dog { ...fragA }\n fragment fragA on Dog { ...fragB }\n ", "errors": [ { "message": "Cannot spread fragment \"fragB\" within itself via fragA.", "locations": [ { "line": 2, "column": 31 }, { "line": 3, "column": 31 } ] } ] }, { "name": "Validate: No circular fragment spreads/no spreading itself indirectly within inline fragment", "rule": "NoFragmentCycles", "schema": 0, "query": "\n fragment fragA on Pet {\n ... on Dog {\n ...fragB\n }\n }\n fragment fragB on Pet {\n ... on Dog {\n ...fragA\n }\n }\n ", "errors": [ { "message": "Cannot spread fragment \"fragA\" within itself via fragB.", "locations": [ { "line": 4, "column": 11 }, { "line": 9, "column": 11 } ] } ] }, { "name": "Validate: No circular fragment spreads/no spreading itself deeply", "rule": "NoFragmentCycles", "schema": 0, "query": "\n fragment fragA on Dog { ...fragB }\n fragment fragB on Dog { ...fragC }\n fragment fragC on Dog { ...fragO }\n fragment fragX on Dog { ...fragY }\n fragment fragY on Dog { ...fragZ }\n fragment fragZ on Dog { ...fragO }\n fragment fragO on Dog { ...fragP }\n fragment fragP on Dog { ...fragA, ...fragX }\n ", "errors": [ { "message": "Cannot spread fragment \"fragA\" within itself via fragB, fragC, fragO, fragP.", "locations": [ { "line": 2, "column": 31 }, { "line": 3, "column": 31 }, { "line": 4, "column": 31 }, { "line": 8, "column": 31 }, { "line": 9, "column": 31 } ] }, { "message": "Cannot spread fragment \"fragO\" within itself via fragP, fragX, fragY, fragZ.", "locations": [ { "line": 8, "column": 31 }, { "line": 9, "column": 41 }, { "line": 5, "column": 31 }, { "line": 6, "column": 31 }, { "line": 7, "column": 31 } ] } ] }, { "name": "Validate: No circular fragment spreads/no spreading itself deeply two paths", "rule": "NoFragmentCycles", "schema": 0, "query": "\n fragment fragA on Dog { ...fragB, ...fragC }\n fragment fragB on Dog { ...fragA }\n fragment fragC on Dog { ...fragA }\n ", "errors": [ { "message": "Cannot spread fragment \"fragA\" within itself via fragB.", "locations": [ { "line": 2, "column": 31 }, { "line": 3, "column": 31 } ] }, { "message": "Cannot spread fragment \"fragA\" within itself via fragC.", "locations": [ { "line": 2, "column": 41 }, { "line": 4, "column": 31 } ] } ] }, { "name": "Validate: No circular fragment spreads/no spreading itself deeply two paths -- alt traverse order", "rule": "NoFragmentCycles", "schema": 0, "query": "\n fragment fragA on Dog { ...fragC }\n fragment fragB on Dog { ...fragC }\n fragment fragC on Dog { ...fragA, ...fragB }\n ", "errors": [ { "message": "Cannot spread fragment \"fragA\" within itself via fragC.", "locations": [ { "line": 2, "column": 31 }, { "line": 4, "column": 31 } ] }, { "message": "Cannot spread fragment \"fragC\" within itself via fragB.", "locations": [ { "line": 4, "column": 41 }, { "line": 3, "column": 31 } ] } ] }, { "name": "Validate: No circular fragment spreads/no spreading itself deeply and immediately", "rule": "NoFragmentCycles", "schema": 0, "query": "\n fragment fragA on Dog { ...fragB }\n fragment fragB on Dog { ...fragB, ...fragC }\n fragment fragC on Dog { ...fragA, ...fragB }\n ", "errors": [ { "message": "Cannot spread fragment \"fragB\" within itself.", "locations": [ { "line": 3, "column": 31 } ] }, { "message": "Cannot spread fragment \"fragA\" within itself via fragB, fragC.", "locations": [ { "line": 2, "column": 31 }, { "line": 3, "column": 41 }, { "line": 4, "column": 31 } ] }, { "message": "Cannot spread fragment \"fragB\" within itself via fragC.", "locations": [ { "line": 3, "column": 41 }, { "line": 4, "column": 41 } ] } ] }, { "name": "Validate: No undefined variables/all variables defined", "rule": "NoUndefinedVariables", "schema": 0, "query": "\n query Foo($a: String, $b: String, $c: String) {\n field(a: $a, b: $b, c: $c)\n }\n ", "errors": [] }, { "name": "Validate: No undefined variables/all variables deeply defined", "rule": "NoUndefinedVariables", "schema": 0, "query": "\n query Foo($a: String, $b: String, $c: String) {\n field(a: $a) {\n field(b: $b) {\n field(c: $c)\n }\n }\n }\n ", "errors": [] }, { "name": "Validate: No undefined variables/all variables deeply in inline fragments defined", "rule": "NoUndefinedVariables", "schema": 0, "query": "\n query Foo($a: String, $b: String, $c: String) {\n ... on Type {\n field(a: $a) {\n field(b: $b) {\n ... on Type {\n field(c: $c)\n }\n }\n }\n }\n }\n ", "errors": [] }, { "name": "Validate: No undefined variables/all variables in fragments deeply defined", "rule": "NoUndefinedVariables", "schema": 0, "query": "\n query Foo($a: String, $b: String, $c: String) {\n ...FragA\n }\n fragment FragA on Type {\n field(a: $a) {\n ...FragB\n }\n }\n fragment FragB on Type {\n field(b: $b) {\n ...FragC\n }\n }\n fragment FragC on Type {\n field(c: $c)\n }\n ", "errors": [] }, { "name": "Validate: No undefined variables/variable within single fragment defined in multiple operations", "rule": "NoUndefinedVariables", "schema": 0, "query": "\n query Foo($a: String) {\n ...FragA\n }\n query Bar($a: String) {\n ...FragA\n }\n fragment FragA on Type {\n field(a: $a)\n }\n ", "errors": [] }, { "name": "Validate: No undefined variables/variable within fragments defined in operations", "rule": "NoUndefinedVariables", "schema": 0, "query": "\n query Foo($a: String) {\n ...FragA\n }\n query Bar($b: String) {\n ...FragB\n }\n fragment FragA on Type {\n field(a: $a)\n }\n fragment FragB on Type {\n field(b: $b)\n }\n ", "errors": [] }, { "name": "Validate: No undefined variables/variable within recursive fragment defined", "rule": "NoUndefinedVariables", "schema": 0, "query": "\n query Foo($a: String) {\n ...FragA\n }\n fragment FragA on Type {\n field(a: $a) {\n ...FragA\n }\n }\n ", "errors": [] }, { "name": "Validate: No undefined variables/variable not defined", "rule": "NoUndefinedVariables", "schema": 0, "query": "\n query Foo($a: String, $b: String, $c: String) {\n field(a: $a, b: $b, c: $c, d: $d)\n }\n ", "errors": [ { "message": "Variable \"$d\" is not defined by operation \"Foo\".", "locations": [ { "line": 3, "column": 39 }, { "line": 2, "column": 7 } ] } ] }, { "name": "Validate: No undefined variables/variable not defined by un-named query", "rule": "NoUndefinedVariables", "schema": 0, "query": "\n {\n field(a: $a)\n }\n ", "errors": [ { "message": "Variable \"$a\" is not defined.", "locations": [ { "line": 3, "column": 18 }, { "line": 2, "column": 7 } ] } ] }, { "name": "Validate: No undefined variables/multiple variables not defined", "rule": "NoUndefinedVariables", "schema": 0, "query": "\n query Foo($b: String) {\n field(a: $a, b: $b, c: $c)\n }\n ", "errors": [ { "message": "Variable \"$a\" is not defined by operation \"Foo\".", "locations": [ { "line": 3, "column": 18 }, { "line": 2, "column": 7 } ] }, { "message": "Variable \"$c\" is not defined by operation \"Foo\".", "locations": [ { "line": 3, "column": 32 }, { "line": 2, "column": 7 } ] } ] }, { "name": "Validate: No undefined variables/variable in fragment not defined by un-named query", "rule": "NoUndefinedVariables", "schema": 0, "query": "\n {\n ...FragA\n }\n fragment FragA on Type {\n field(a: $a)\n }\n ", "errors": [ { "message": "Variable \"$a\" is not defined.", "locations": [ { "line": 6, "column": 18 }, { "line": 2, "column": 7 } ] } ] }, { "name": "Validate: No undefined variables/variable in fragment not defined by operation", "rule": "NoUndefinedVariables", "schema": 0, "query": "\n query Foo($a: String, $b: String) {\n ...FragA\n }\n fragment FragA on Type {\n field(a: $a) {\n ...FragB\n }\n }\n fragment FragB on Type {\n field(b: $b) {\n ...FragC\n }\n }\n fragment FragC on Type {\n field(c: $c)\n }\n ", "errors": [ { "message": "Variable \"$c\" is not defined by operation \"Foo\".", "locations": [ { "line": 16, "column": 18 }, { "line": 2, "column": 7 } ] } ] }, { "name": "Validate: No undefined variables/multiple variables in fragments not defined", "rule": "NoUndefinedVariables", "schema": 0, "query": "\n query Foo($b: String) {\n ...FragA\n }\n fragment FragA on Type {\n field(a: $a) {\n ...FragB\n }\n }\n fragment FragB on Type {\n field(b: $b) {\n ...FragC\n }\n }\n fragment FragC on Type {\n field(c: $c)\n }\n ", "errors": [ { "message": "Variable \"$a\" is not defined by operation \"Foo\".", "locations": [ { "line": 6, "column": 18 }, { "line": 2, "column": 7 } ] }, { "message": "Variable \"$c\" is not defined by operation \"Foo\".", "locations": [ { "line": 16, "column": 18 }, { "line": 2, "column": 7 } ] } ] }, { "name": "Validate: No undefined variables/single variable in fragment not defined by multiple operations", "rule": "NoUndefinedVariables", "schema": 0, "query": "\n query Foo($a: String) {\n ...FragAB\n }\n query Bar($a: String) {\n ...FragAB\n }\n fragment FragAB on Type {\n field(a: $a, b: $b)\n }\n ", "errors": [ { "message": "Variable \"$b\" is not defined by operation \"Foo\".", "locations": [ { "line": 9, "column": 25 }, { "line": 2, "column": 7 } ] }, { "message": "Variable \"$b\" is not defined by operation \"Bar\".", "locations": [ { "line": 9, "column": 25 }, { "line": 5, "column": 7 } ] } ] }, { "name": "Validate: No undefined variables/variables in fragment not defined by multiple operations", "rule": "NoUndefinedVariables", "schema": 0, "query": "\n query Foo($b: String) {\n ...FragAB\n }\n query Bar($a: String) {\n ...FragAB\n }\n fragment FragAB on Type {\n field(a: $a, b: $b)\n }\n ", "errors": [ { "message": "Variable \"$a\" is not defined by operation \"Foo\".", "locations": [ { "line": 9, "column": 18 }, { "line": 2, "column": 7 } ] }, { "message": "Variable \"$b\" is not defined by operation \"Bar\".", "locations": [ { "line": 9, "column": 25 }, { "line": 5, "column": 7 } ] } ] }, { "name": "Validate: No undefined variables/variable in fragment used by other operation", "rule": "NoUndefinedVariables", "schema": 0, "query": "\n query Foo($b: String) {\n ...FragA\n }\n query Bar($a: String) {\n ...FragB\n }\n fragment FragA on Type {\n field(a: $a)\n }\n fragment FragB on Type {\n field(b: $b)\n }\n ", "errors": [ { "message": "Variable \"$a\" is not defined by operation \"Foo\".", "locations": [ { "line": 9, "column": 18 }, { "line": 2, "column": 7 } ] }, { "message": "Variable \"$b\" is not defined by operation \"Bar\".", "locations": [ { "line": 12, "column": 18 }, { "line": 5, "column": 7 } ] } ] }, { "name": "Validate: No undefined variables/multiple undefined variables produce multiple errors", "rule": "NoUndefinedVariables", "schema": 0, "query": "\n query Foo($b: String) {\n ...FragAB\n }\n query Bar($a: String) {\n ...FragAB\n }\n fragment FragAB on Type {\n field1(a: $a, b: $b)\n ...FragC\n field3(a: $a, b: $b)\n }\n fragment FragC on Type {\n field2(c: $c)\n }\n ", "errors": [ { "message": "Variable \"$a\" is not defined by operation \"Foo\".", "locations": [ { "line": 9, "column": 19 }, { "line": 2, "column": 7 } ] }, { "message": "Variable \"$a\" is not defined by operation \"Foo\".", "locations": [ { "line": 11, "column": 19 }, { "line": 2, "column": 7 } ] }, { "message": "Variable \"$c\" is not defined by operation \"Foo\".", "locations": [ { "line": 14, "column": 19 }, { "line": 2, "column": 7 } ] }, { "message": "Variable \"$b\" is not defined by operation \"Bar\".", "locations": [ { "line": 9, "column": 26 }, { "line": 5, "column": 7 } ] }, { "message": "Variable \"$b\" is not defined by operation \"Bar\".", "locations": [ { "line": 11, "column": 26 }, { "line": 5, "column": 7 } ] }, { "message": "Variable \"$c\" is not defined by operation \"Bar\".", "locations": [ { "line": 14, "column": 19 }, { "line": 5, "column": 7 } ] } ] }, { "name": "Validate: No unused fragments/all fragment names are used", "rule": "NoUnusedFragments", "schema": 0, "query": "\n {\n human(id: 4) {\n ...HumanFields1\n ... on Human {\n ...HumanFields2\n }\n }\n }\n fragment HumanFields1 on Human {\n name\n ...HumanFields3\n }\n fragment HumanFields2 on Human {\n name\n }\n fragment HumanFields3 on Human {\n name\n }\n ", "errors": [] }, { "name": "Validate: No unused fragments/all fragment names are used by multiple operations", "rule": "NoUnusedFragments", "schema": 0, "query": "\n query Foo {\n human(id: 4) {\n ...HumanFields1\n }\n }\n query Bar {\n human(id: 4) {\n ...HumanFields2\n }\n }\n fragment HumanFields1 on Human {\n name\n ...HumanFields3\n }\n fragment HumanFields2 on Human {\n name\n }\n fragment HumanFields3 on Human {\n name\n }\n ", "errors": [] }, { "name": "Validate: No unused fragments/contains unknown fragments", "rule": "NoUnusedFragments", "schema": 0, "query": "\n query Foo {\n human(id: 4) {\n ...HumanFields1\n }\n }\n query Bar {\n human(id: 4) {\n ...HumanFields2\n }\n }\n fragment HumanFields1 on Human {\n name\n ...HumanFields3\n }\n fragment HumanFields2 on Human {\n name\n }\n fragment HumanFields3 on Human {\n name\n }\n fragment Unused1 on Human {\n name\n }\n fragment Unused2 on Human {\n name\n }\n ", "errors": [ { "message": "Fragment \"Unused1\" is never used.", "locations": [ { "line": 22, "column": 7 } ] }, { "message": "Fragment \"Unused2\" is never used.", "locations": [ { "line": 25, "column": 7 } ] } ] }, { "name": "Validate: No unused fragments/contains unknown fragments with ref cycle", "rule": "NoUnusedFragments", "schema": 0, "query": "\n query Foo {\n human(id: 4) {\n ...HumanFields1\n }\n }\n query Bar {\n human(id: 4) {\n ...HumanFields2\n }\n }\n fragment HumanFields1 on Human {\n name\n ...HumanFields3\n }\n fragment HumanFields2 on Human {\n name\n }\n fragment HumanFields3 on Human {\n name\n }\n fragment Unused1 on Human {\n name\n ...Unused2\n }\n fragment Unused2 on Human {\n name\n ...Unused1\n }\n ", "errors": [ { "message": "Fragment \"Unused1\" is never used.", "locations": [ { "line": 22, "column": 7 } ] }, { "message": "Fragment \"Unused2\" is never used.", "locations": [ { "line": 26, "column": 7 } ] } ] }, { "name": "Validate: No unused fragments/contains unknown and undef fragments", "rule": "NoUnusedFragments", "schema": 0, "query": "\n query Foo {\n human(id: 4) {\n ...bar\n }\n }\n fragment foo on Human {\n name\n }\n ", "errors": [ { "message": "Fragment \"foo\" is never used.", "locations": [ { "line": 7, "column": 7 } ] } ] }, { "name": "Validate: No unused variables/uses all variables", "rule": "NoUnusedVariables", "schema": 0, "query": "\n query ($a: String, $b: String, $c: String) {\n field(a: $a, b: $b, c: $c)\n }\n ", "errors": [] }, { "name": "Validate: No unused variables/uses all variables deeply", "rule": "NoUnusedVariables", "schema": 0, "query": "\n query Foo($a: String, $b: String, $c: String) {\n field(a: $a) {\n field(b: $b) {\n field(c: $c)\n }\n }\n }\n ", "errors": [] }, { "name": "Validate: No invalid default String variable values", "rule": "DefaultValuesOfCorrectType", "schema": 0, "query": "\n query Foo($a: String = -\"\") {\n field(a: $a)\n }\n ", "errors": [ { "message": "Variable \"$a\" of type \"String\" has invalid default value -\"\".\nExpected type \"String\", found -\"\".", "locations": [ { "line": 2, "column": 30 } ] } ] }, { "name": "Validate: No invalid default Int variable values/bad input", "rule": "DefaultValuesOfCorrectType", "schema": 0, "query": "\n query Foo($a: Int = -\"\") {\n field(a: $a)\n }\n ", "errors": [ { "message": "Variable \"$a\" of type \"Int\" has invalid default value -\"\".\nExpected type \"Int\", found -\"\".", "locations": [ { "line": 2, "column": 27 } ] } ] }, { "name": "Validate: No invalid default Int variable values/value out of range", "rule": "DefaultValuesOfCorrectType", "schema": 0, "query": "\n query Foo($a: Int = -2147483649) {\n field(a: $a)\n }\n ", "errors": [ { "message": "Variable \"$a\" of type \"Int\" has invalid default value -2147483649.\nExpected type \"Int\", found -2147483649.", "locations": [ { "line": 2, "column": 27 } ] } ] }, { "name": "Validate: No invalid default Float variable values", "rule": "DefaultValuesOfCorrectType", "schema": 0, "query": "\n query Foo($a: Float = -\"\") {\n field(a: $a)\n }\n ", "errors": [ { "message": "Variable \"$a\" of type \"Float\" has invalid default value -\"\".\nExpected type \"Float\", found -\"\".", "locations": [ { "line": 2, "column": 29 } ] } ] }, { "name": "Validate: No invalid default Float variable values/value out of range", "rule": "DefaultValuesOfCorrectType", "schema": 0, "query": "\n query Foo($a: Float = 1.8e+308) {\n field(a: $a)\n }\n ", "errors": [ { "message": "Variable \"$a\" of type \"Float\" has invalid default value 1.8e+308.\nExpected type \"Float\", found 1.8e+308.", "locations": [ { "line": 2, "column": 29 } ] } ] }, { "name": "Validate: No invalid default Boolean variable values", "rule": "DefaultValuesOfCorrectType", "schema": 0, "query": "\n query Foo($a: Boolean = \"false\") {\n field(a: $a)\n }\n ", "errors": [ { "message": "Variable \"$a\" of type \"Boolean\" has invalid default value \"false\".\nExpected type \"Boolean\", found \"false\".", "locations": [ { "line": 2, "column": 31 } ] } ] }, { "name": "Validate: No invalid default ID variable values", "rule": "DefaultValuesOfCorrectType", "schema": 0, "query": "\n query Foo($a: ID = false) {\n field(a: $a)\n }\n ", "errors": [ { "message": "Variable \"$a\" of type \"ID\" has invalid default value false.\nExpected type \"ID\", found false.", "locations": [ { "line": 2, "column": 26 } ] } ] }, { "name": "Validate: No unused variables/uses all variables deeply in inline fragments", "rule": "NoUnusedVariables", "schema": 0, "query": "\n query Foo($a: String, $b: String, $c: String) {\n ... on Type {\n field(a: $a) {\n field(b: $b) {\n ... on Type {\n field(c: $c)\n }\n }\n }\n }\n }\n ", "errors": [] }, { "name": "Validate: fragments are used even when they are nested", "rule": "NoUnusedFragments", "schema": 1, "query": "\n query Foo() {\n ...StringFragment\n stringBox {\n ...StringFragment\n ...StringFragmentPrime\n}\n}\n\n\n fragment StringFragment on StringBox {\n scalar\n}\n\n fragment StringFragmentPrime on StringBox {\n unrelatedField\n}\n", "errors": [] }, { "name": "Validate: No unused variables/uses all variables in fragments", "rule": "NoUnusedVariables", "schema": 0, "query": "\n query Foo($a: String, $b: String, $c: String) {\n ...FragA\n }\n fragment FragA on Type {\n field(a: $a) {\n ...FragB\n }\n }\n fragment FragB on Type {\n field(b: $b) {\n ...FragC\n }\n }\n fragment FragC on Type {\n field(c: $c)\n }\n ", "errors": [] }, { "name": "Validate: No unused variables/variable used by fragment in multiple operations", "rule": "NoUnusedVariables", "schema": 0, "query": "\n query Foo($a: String) {\n ...FragA\n }\n query Bar($b: String) {\n ...FragB\n }\n fragment FragA on Type {\n field(a: $a)\n }\n fragment FragB on Type {\n field(b: $b)\n }\n ", "errors": [] }, { "name": "Validate: No unused variables/variable used by recursive fragment", "rule": "NoUnusedVariables", "schema": 0, "query": "\n query Foo($a: String) {\n ...FragA\n }\n fragment FragA on Type {\n field(a: $a) {\n ...FragA\n }\n }\n ", "errors": [] }, { "name": "Validate: No unused variables/variable not used", "rule": "NoUnusedVariables", "schema": 0, "query": "\n query ($a: String, $b: String, $c: String) {\n field(a: $a, b: $b)\n }\n ", "errors": [ { "message": "Variable \"$c\" is never used.", "locations": [ { "line": 2, "column": 38 } ] } ] }, { "name": "Validate: No unused variables/multiple variables not used", "rule": "NoUnusedVariables", "schema": 0, "query": "\n query Foo($a: String, $b: String, $c: String) {\n field(b: $b)\n }\n ", "errors": [ { "message": "Variable \"$a\" is never used in operation \"Foo\".", "locations": [ { "line": 2, "column": 17 } ] }, { "message": "Variable \"$c\" is never used in operation \"Foo\".", "locations": [ { "line": 2, "column": 41 } ] } ] }, { "name": "Validate: No unused variables/variable not used in fragments", "rule": "NoUnusedVariables", "schema": 0, "query": "\n query Foo($a: String, $b: String, $c: String) {\n ...FragA\n }\n fragment FragA on Type {\n field(a: $a) {\n ...FragB\n }\n }\n fragment FragB on Type {\n field(b: $b) {\n ...FragC\n }\n }\n fragment FragC on Type {\n field\n }\n ", "errors": [ { "message": "Variable \"$c\" is never used in operation \"Foo\".", "locations": [ { "line": 2, "column": 41 } ] } ] }, { "name": "Validate: No unused variables/multiple variables not used in fragments", "rule": "NoUnusedVariables", "schema": 0, "query": "\n query Foo($a: String, $b: String, $c: String) {\n ...FragA\n }\n fragment FragA on Type {\n field {\n ...FragB\n }\n }\n fragment FragB on Type {\n field(b: $b) {\n ...FragC\n }\n }\n fragment FragC on Type {\n field\n }\n ", "errors": [ { "message": "Variable \"$a\" is never used in operation \"Foo\".", "locations": [ { "line": 2, "column": 17 } ] }, { "message": "Variable \"$c\" is never used in operation \"Foo\".", "locations": [ { "line": 2, "column": 41 } ] } ] }, { "name": "Validate: No unused variables/variable not used by unreferenced fragment", "rule": "NoUnusedVariables", "schema": 0, "query": "\n query Foo($b: String) {\n ...FragA\n }\n fragment FragA on Type {\n field(a: $a)\n }\n fragment FragB on Type {\n field(b: $b)\n }\n ", "errors": [ { "message": "Variable \"$b\" is never used in operation \"Foo\".", "locations": [ { "line": 2, "column": 17 } ] } ] }, { "name": "Validate: No unused variables/variable not used by fragment used by other operation", "rule": "NoUnusedVariables", "schema": 0, "query": "\n query Foo($b: String) {\n ...FragA\n }\n query Bar($a: String) {\n ...FragB\n }\n fragment FragA on Type {\n field(a: $a)\n }\n fragment FragB on Type {\n field(b: $b)\n }\n ", "errors": [ { "message": "Variable \"$b\" is never used in operation \"Foo\".", "locations": [ { "line": 2, "column": 17 } ] }, { "message": "Variable \"$a\" is never used in operation \"Bar\".", "locations": [ { "line": 5, "column": 17 } ] } ] }, { "name": "Validate: Arguments have valid type/valid enum constant in query", "rule": "ArgumentsOfCorrectType", "schema": 0, "query": "\n query Query {\n complicatedArgs {\n enumArgField(enumArg: BROWN)\n }\n }\n ", "errors": [] }, { "name": "Validate: Arguments have valid type/invalid enum constant in query text", "rule": "ArgumentsOfCorrectType", "schema": 0, "query": "\n query Query {\n complicatedArgs {\n enumArgField(enumArg: RAINBOW)\n }\n }\n ", "errors": [ { "message": "Argument \"enumArg\" has invalid value RAINBOW.\nExpected type \"FurColor\", found RAINBOW.", "locations": [ { "line": 4, "column": 33 } ] } ] }, { "name": "Validate: Arguments have valid type/optional enum constant in query", "rule": "ArgumentsOfCorrectType", "schema": 0, "query": "\n query Query {\n complicatedArgs {\n enumArgField()\n }\n }\n ", "errors": [] }, { "name": "Validate: Variables have valid type/valid enum constant in variable", "rule": "VariablesOfCorrectType", "schema": 0, "query": "\n query Query($color: FurColor) {\n complicatedArgs {\n enumArgField(enumArg: $color)\n }\n }\n ", "vars": { "color": "BROWN" }, "errors": [] }, { "name": "Validate: Variables have valid type/invalid enum constant in variable", "rule": "VariablesOfCorrectType", "schema": 0, "query": "\n query Query($color: FurColor) {\n complicatedArgs {\n enumArgField(enumArg: $color)\n }\n }\n ", "vars": { "color": "RAINBOW" }, "errors": [ { "message": "Variable \"color\" has invalid value RAINBOW.\nExpected type \"FurColor\", found RAINBOW.", "locations": [ { "line": 2, "column": 19 } ] } ] }, { "name": "Validate: Variables have valid type/optional enum constant variable null", "rule": "VariablesOfCorrectType", "schema": 0, "query": "\n query Query($color: FurColor) {\n complicatedArgs {\n enumArgField(enumArg: $color)\n }\n }\n ", "vars": { "color": null }, "errors": [] }, { "name": "Validate: Variables have valid type/list with single value in variable", "rule": "VariablesOfCorrectType", "schema": 0, "query": "\n query Query($stringListArg: [String!])\n {\n stringListArgField(stringListArg: $stringListArg)\n }\n ", "vars": { "stringListArg": "single value" }, "errors": [] }, { "name": "Validate: Variables have valid type/list with list value in variable", "rule": "VariablesOfCorrectType", "schema": 0, "query": "\n query Query($stringListArg: [String!])\n {\n stringListArgField(stringListArg: $stringListArg)\n }\n ", "vars": { "stringListArg": ["first value", "second value"] }, "errors": [] }, { "name": "Validate: Variables have valid type/input type with invalid input in variable", "rule": "VariablesOfCorrectType", "schema": 0, "query": "\n query Query($complexVar: ComplexInput)\n {\n complicatedArgs {\n complexArgField(complexArg: $complexVar)\n }\n }\n ", "vars": { "complexVar": "not input" }, "errors": [ { "message": "Variable \"complexVar\" has invalid type string.\nExpected type \"ComplexInput\", found not input.", "locations": [ { "line": 2, "column": 19 } ] } ] }, { "name": "Validate: Variables have valid type/input type with invalid enum constant in variable", "rule": "VariablesOfCorrectType", "schema": 0, "query": "\n query Query($complexVar: ComplexInput)\n {\n complicatedArgs {\n complexArgField(complexArg: $complexVar)\n }\n }\n ", "vars": { "complexVar": { "requiredField": true, "enumField": "RAINBOW" } }, "errors": [ { "message": "Variable \"enumField\" has invalid value RAINBOW.\nExpected type \"FurColor\", found RAINBOW.", "locations": [ { "line": 73, "column": 3 } ] } ] }, { "name": "Validate: Variables have valid type/input type with optional enum constant variable null", "rule": "VariablesOfCorrectType", "schema": 0, "query": "\n query Query($complexVar: ComplexInput)\n {\n complicatedArgs {\n complexArgField(complexArg: $complexVar)\n }\n }\n ", "vars": { "complexVar": { "requiredField": true, "enumField": null } }, "errors": [] }, { "name": "Validate: Variables have valid type/input type with nested input string from variable", "rule": "VariablesOfCorrectType", "schema": 0, "query": "\n query Query($complexVar: ComplexInput)\n {\n complicatedArgs {\n complexArgField(complexArg: $complexVar)\n }\n }\n ", "vars": { "complexVar": { "requiredField": true, "nestedInput": { "stringField": "something" } } }, "errors": [] }, { "name": "Validate: Variables have valid type/input type with nested input string list with single value from variable", "rule": "VariablesOfCorrectType", "schema": 0, "query": "\n query Query($complexVar: ComplexInput)\n {\n complicatedArgs {\n complexArgField(complexArg: $complexVar)\n }\n }\n ", "vars": { "complexVar": { "requiredField": true, "nestedInput": { "stringListField": "something" } } }, "errors": [] }, { "name": "Validate: Variables have valid type/input type with nested input string list from variable", "rule": "VariablesOfCorrectType", "schema": 0, "query": "\n query Query($complexVar: ComplexInput)\n {\n complicatedArgs {\n complexArgField(complexArg: $complexVar)\n }\n }\n ", "vars": { "complexVar": { "requiredField": true, "nestedInput": { "stringListField": ["first", "second"] } } }, "errors": [] }, { "name": "Validate: Variables have valid type/number as enum", "rule": "VariablesOfCorrectType", "schema": 0, "query": "\n query Query($color: FurColor) {\n complicatedArgs {\n enumArgField(enumArg: $color)\n }\n }\n ", "vars": { "color": 42 }, "errors": [ { "message": "Variable \"color\" has invalid type float64.\nExpected type \"FurColor\", found 42.", "locations": [ { "line": 2, "column": 19 } ] } ] }, { "name": "Validate: Variables have valid type/valid enum array constant in variable", "rule": "VariablesOfCorrectType", "schema": 0, "query": "\n query Query($colors: [FurColor!]!) {\n complicatedArgs {\n enumArrayArgField(enumArrayArg: $colors)\n }\n }\n ", "vars": { "colors": ["BROWN", "BLACK", "SPOTTED"] }, "errors": [] }, { "name": "Validate: Variables have valid type/invalid enum array constant in variable", "rule": "VariablesOfCorrectType", "schema": 0, "query": "\n query Query($colors: [FurColor!]!) {\n complicatedArgs {\n enumArrayArgField(enumArrayArg: $colors)\n }\n }\n ", "vars": { "colors": ["TEAL", "AUBERGINE"] }, "errors": [ { "message": "Variable \"colors\" has invalid value TEAL.\nExpected type \"FurColor\", found TEAL.", "locations": [ { "line": 2, "column": 19 } ] }, { "message": "Variable \"colors\" has invalid value AUBERGINE.\nExpected type \"FurColor\", found AUBERGINE.", "locations": [ { "line": 2, "column": 19 } ] } ] }, { "name": "Validate: Variables have valid type/string as enum array variable", "rule": "VariablesOfCorrectType", "schema": 0, "query": "\n query Query($colors: [FurColor!]!) {\n complicatedArgs {\n enumArrayArgField(enumArrayArg: $colors)\n }\n }\n ", "vars": { "colors": "BROWN" }, "errors": [] }, { "name": "Validate: Overlapping fields can be merged/unique fields", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n fragment uniqueFields on Dog {\n name\n nickname\n }\n ", "errors": [] }, { "name": "Validate: Overlapping fields can be merged/identical fields", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n fragment mergeIdenticalFields on Dog {\n name\n name\n }\n ", "errors": [] }, { "name": "Validate: Overlapping fields can be merged/identical fields with identical args", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n fragment mergeIdenticalFieldsWithIdenticalArgs on Dog {\n doesKnowCommand(dogCommand: SIT)\n doesKnowCommand(dogCommand: SIT)\n }\n ", "errors": [] }, { "name": "Validate: Overlapping fields can be merged/identical fields with identical directives", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n fragment mergeSameFieldsWithSameDirectives on Dog {\n name @include(if: true)\n name @include(if: true)\n }\n ", "errors": [] }, { "name": "Validate: Overlapping fields can be merged/different args with different aliases", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n fragment differentArgsWithDifferentAliases on Dog {\n knowsSit: doesKnowCommand(dogCommand: SIT)\n knowsDown: doesKnowCommand(dogCommand: DOWN)\n }\n ", "errors": [] }, { "name": "Validate: Overlapping fields can be merged/different directives with different aliases", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n fragment differentDirectivesWithDifferentAliases on Dog {\n nameIfTrue: name @include(if: true)\n nameIfFalse: name @include(if: false)\n }\n ", "errors": [] }, { "name": "Validate: Overlapping fields can be merged/different skip/include directives accepted", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n fragment differentDirectivesWithDifferentAliases on Dog {\n name @include(if: true)\n name @include(if: false)\n }\n ", "errors": [] }, { "name": "Validate: Overlapping fields can be merged/Same aliases with different field targets", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n fragment sameAliasesWithDifferentFieldTargets on Dog {\n fido: name\n fido: nickname\n }\n ", "errors": [ { "message": "Fields \"fido\" conflict because name and nickname are different fields. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 3, "column": 9 }, { "line": 4, "column": 9 } ] } ] }, { "name": "Validate: Overlapping fields can be merged/Same aliases allowed on non-overlapping fields", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n fragment sameAliasesWithDifferentFieldTargets on Pet {\n ... on Dog {\n name\n }\n ... on Cat {\n name: nickname\n }\n }\n ", "errors": [] }, { "name": "Validate: Overlapping fields can be merged/Alias masking direct field access", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n fragment aliasMaskingDirectFieldAccess on Dog {\n name: nickname\n name\n }\n ", "errors": [ { "message": "Fields \"name\" conflict because nickname and name are different fields. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 3, "column": 9 }, { "line": 4, "column": 9 } ] } ] }, { "name": "Validate: Overlapping fields can be merged/different args, second adds an argument", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n fragment conflictingArgs on Dog {\n doesKnowCommand\n doesKnowCommand(dogCommand: HEEL)\n }\n ", "errors": [ { "message": "Fields \"doesKnowCommand\" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 3, "column": 9 }, { "line": 4, "column": 9 } ] } ] }, { "name": "Validate: Overlapping fields can be merged/different args, second missing an argument", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n fragment conflictingArgs on Dog {\n doesKnowCommand(dogCommand: SIT)\n doesKnowCommand\n }\n ", "errors": [ { "message": "Fields \"doesKnowCommand\" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 3, "column": 9 }, { "line": 4, "column": 9 } ] } ] }, { "name": "Validate: Overlapping fields can be merged/conflicting args", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n fragment conflictingArgs on Dog {\n doesKnowCommand(dogCommand: SIT)\n doesKnowCommand(dogCommand: HEEL)\n }\n ", "errors": [ { "message": "Fields \"doesKnowCommand\" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 3, "column": 9 }, { "line": 4, "column": 9 } ] } ] }, { "name": "Validate: Overlapping fields can be merged/allows different args where no conflict is possible", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n fragment conflictingArgs on Pet {\n ... on Dog {\n name(surname: true)\n }\n ... on Cat {\n name\n }\n }\n ", "errors": [] }, { "name": "Validate: Overlapping fields can be merged/encounters conflict in fragments", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n {\n ...A\n ...B\n }\n fragment A on Type {\n x: a\n }\n fragment B on Type {\n x: b\n }\n ", "errors": [ { "message": "Fields \"x\" conflict because a and b are different fields. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 7, "column": 9 }, { "line": 10, "column": 9 } ] } ] }, { "name": "Validate: Overlapping fields can be merged/reports each conflict once", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n {\n f1 {\n ...A\n ...B\n }\n f2 {\n ...B\n ...A\n }\n f3 {\n ...A\n ...B\n x: c\n }\n }\n fragment A on Type {\n x: a\n }\n fragment B on Type {\n x: b\n }\n ", "errors": [ { "message": "Fields \"x\" conflict because a and b are different fields. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 18, "column": 9 }, { "line": 21, "column": 9 } ] }, { "message": "Fields \"x\" conflict because c and a are different fields. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 14, "column": 11 }, { "line": 18, "column": 9 } ] }, { "message": "Fields \"x\" conflict because c and b are different fields. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 14, "column": 11 }, { "line": 21, "column": 9 } ] } ] }, { "name": "Validate: Overlapping fields can be merged/deep conflict", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n {\n field {\n x: a\n },\n field {\n x: b\n }\n }\n ", "errors": [ { "message": "Fields \"field\" conflict because subfields \"x\" conflict because a and b are different fields. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 3, "column": 9 }, { "line": 4, "column": 11 }, { "line": 6, "column": 9 }, { "line": 7, "column": 11 } ] } ] }, { "name": "Validate: Overlapping fields can be merged/deep conflict with multiple issues", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n {\n field {\n x: a\n y: c\n },\n field {\n x: b\n y: d\n }\n }\n ", "errors": [ { "message": "Fields \"field\" conflict because subfields \"x\" conflict because a and b are different fields and subfields \"y\" conflict because c and d are different fields. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 3, "column": 9 }, { "line": 4, "column": 11 }, { "line": 5, "column": 11 }, { "line": 7, "column": 9 }, { "line": 8, "column": 11 }, { "line": 9, "column": 11 } ] } ] }, { "name": "Validate: Overlapping fields can be merged/very deep conflict", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n {\n field {\n deepField {\n x: a\n }\n },\n field {\n deepField {\n x: b\n }\n }\n }\n ", "errors": [ { "message": "Fields \"field\" conflict because subfields \"deepField\" conflict because subfields \"x\" conflict because a and b are different fields. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 3, "column": 9 }, { "line": 4, "column": 11 }, { "line": 5, "column": 13 }, { "line": 8, "column": 9 }, { "line": 9, "column": 11 }, { "line": 10, "column": 13 } ] } ] }, { "name": "Validate: Overlapping fields can be merged/reports deep conflict to nearest common ancestor", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n {\n field {\n deepField {\n x: a\n }\n deepField {\n x: b\n }\n },\n field {\n deepField {\n y\n }\n }\n }\n ", "errors": [ { "message": "Fields \"deepField\" conflict because subfields \"x\" conflict because a and b are different fields. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 4, "column": 11 }, { "line": 5, "column": 13 }, { "line": 7, "column": 11 }, { "line": 8, "column": 13 } ] } ] }, { "name": "Validate: Overlapping fields can be merged/reports deep conflict to nearest common ancestor in fragments", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n {\n field {\n ...F\n }\n field {\n ...F\n }\n }\n fragment F on T {\n deepField {\n deeperField {\n x: a\n }\n deeperField {\n x: b\n }\n },\n deepField {\n deeperField {\n y\n }\n }\n }\n ", "errors": [ { "message": "Fields \"deeperField\" conflict because subfields \"x\" conflict because a and b are different fields. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 12, "column": 11 }, { "line": 13, "column": 13 }, { "line": 15, "column": 11 }, { "line": 16, "column": 13 } ] } ] }, { "name": "Validate: Overlapping fields can be merged/reports deep conflict in nested fragments", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n {\n field {\n ...F\n }\n field {\n ...I\n }\n }\n fragment F on T {\n x: a\n ...G\n }\n fragment G on T {\n y: c\n }\n fragment I on T {\n y: d\n ...J\n }\n fragment J on T {\n x: b\n }\n ", "errors": [ { "message": "Fields \"field\" conflict because subfields \"x\" conflict because a and b are different fields and subfields \"y\" conflict because c and d are different fields. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 3, "column": 9 }, { "line": 11, "column": 9 }, { "line": 15, "column": 9 }, { "line": 6, "column": 9 }, { "line": 22, "column": 9 }, { "line": 18, "column": 9 } ] } ] }, { "name": "Validate: Overlapping fields can be merged/ignores unknown fragments", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n {\n field\n ...Unknown\n ...Known\n }\n\n fragment Known on T {\n field\n ...OtherUnknown\n }\n ", "errors": [] }, { "name": "Validate: Overlapping fields can be merged/return types must be unambiguous/conflicting return types which potentially overlap", "rule": "OverlappingFieldsCanBeMerged", "schema": 1, "query": "\n {\n someBox {\n ...on IntBox {\n scalar\n }\n ...on NonNullStringBox1 {\n scalar\n }\n }\n }\n ", "errors": [ { "message": "Fields \"scalar\" conflict because they return conflicting types Int and String!. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 5, "column": 15 }, { "line": 8, "column": 15 } ] } ] }, { "name": "Validate: Overlapping fields can be merged/return types must be unambiguous/compatible return shapes on different return types", "rule": "OverlappingFieldsCanBeMerged", "schema": 1, "query": "\n {\n someBox {\n ... on SomeBox {\n deepBox {\n unrelatedField\n }\n }\n ... on StringBox {\n deepBox {\n unrelatedField\n }\n }\n }\n }\n ", "errors": [] }, { "name": "Validate: Overlapping fields can be merged/return types must be unambiguous/disallows differing return types despite no overlap", "rule": "OverlappingFieldsCanBeMerged", "schema": 1, "query": "\n {\n someBox {\n ... on IntBox {\n scalar\n }\n ... on StringBox {\n scalar\n }\n }\n }\n ", "errors": [ { "message": "Fields \"scalar\" conflict because they return conflicting types Int and String. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 5, "column": 15 }, { "line": 8, "column": 15 } ] } ] }, { "name": "Validate: Overlapping fields can be merged/return types must be unambiguous/disallows differing return type nullability despite no overlap", "rule": "OverlappingFieldsCanBeMerged", "schema": 1, "query": "\n {\n someBox {\n ... on NonNullStringBox1 {\n scalar\n }\n ... on StringBox {\n scalar\n }\n }\n }\n ", "errors": [ { "message": "Fields \"scalar\" conflict because they return conflicting types String! and String. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 5, "column": 15 }, { "line": 8, "column": 15 } ] } ] }, { "name": "Validate: Overlapping fields can be merged/return types must be unambiguous/disallows differing return type list despite no overlap", "rule": "OverlappingFieldsCanBeMerged", "schema": 1, "query": "\n {\n someBox {\n ... on IntBox {\n box: listStringBox {\n scalar\n }\n }\n ... on StringBox {\n box: stringBox {\n scalar\n }\n }\n }\n }\n ", "errors": [ { "message": "Fields \"box\" conflict because they return conflicting types [StringBox] and StringBox. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 5, "column": 15 }, { "line": 10, "column": 15 } ] } ] }, { "name": "Validate: Overlapping fields can be merged/return types must be unambiguous/disallows differing return type list despite no overlap", "rule": "OverlappingFieldsCanBeMerged", "schema": 1, "query": "\n {\n someBox {\n ... on IntBox {\n box: stringBox {\n scalar\n }\n }\n ... on StringBox {\n box: listStringBox {\n scalar\n }\n }\n }\n }\n ", "errors": [ { "message": "Fields \"box\" conflict because they return conflicting types StringBox and [StringBox]. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 5, "column": 15 }, { "line": 10, "column": 15 } ] } ] }, { "name": "Validate: Overlapping fields can be merged/return types must be unambiguous/disallows differing deep return types despite no overlap", "rule": "OverlappingFieldsCanBeMerged", "schema": 1, "query": "\n {\n someBox {\n ... on IntBox {\n box: stringBox {\n scalar\n }\n }\n ... on StringBox {\n box: intBox {\n scalar\n }\n }\n }\n }\n ", "errors": [ { "message": "Fields \"box\" conflict because subfields \"scalar\" conflict because they return conflicting types String and Int. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 5, "column": 15 }, { "line": 6, "column": 17 }, { "line": 10, "column": 15 }, { "line": 11, "column": 17 } ] } ] }, { "name": "Validate: Overlapping fields can be merged/return types must be unambiguous/allows non-conflicting overlaping types", "rule": "OverlappingFieldsCanBeMerged", "schema": 1, "query": "\n {\n someBox {\n ... on IntBox {\n scalar: unrelatedField\n }\n ... on StringBox {\n scalar\n }\n }\n }\n ", "errors": [] }, { "name": "Validate: Overlapping fields can be merged/return types must be unambiguous/same wrapped scalar return types", "rule": "OverlappingFieldsCanBeMerged", "schema": 1, "query": "\n {\n someBox {\n ...on NonNullStringBox1 {\n scalar\n }\n ...on NonNullStringBox2 {\n scalar\n }\n }\n }\n ", "errors": [] }, { "name": "Validate: Overlapping fields can be merged/return types must be unambiguous/allows inline typeless fragments", "rule": "OverlappingFieldsCanBeMerged", "schema": 1, "query": "\n {\n a\n ... {\n a\n }\n }\n ", "errors": [] }, { "name": "Validate: Overlapping fields can be merged/return types must be unambiguous/compares deep types including list", "rule": "OverlappingFieldsCanBeMerged", "schema": 1, "query": "\n {\n connection {\n ...edgeID\n edges {\n node {\n id: name\n }\n }\n }\n }\n\n fragment edgeID on Connection {\n edges {\n node {\n id\n }\n }\n }\n ", "errors": [ { "message": "Fields \"edges\" conflict because subfields \"node\" conflict because subfields \"id\" conflict because name and id are different fields. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 5, "column": 13 }, { "line": 6, "column": 15 }, { "line": 7, "column": 17 }, { "line": 14, "column": 11 }, { "line": 15, "column": 13 }, { "line": 16, "column": 15 } ] } ] }, { "name": "Validate: Overlapping fields can be merged/return types must be unambiguous/ignores unknown types", "rule": "OverlappingFieldsCanBeMerged", "schema": 1, "query": "\n {\n someBox {\n ...on UnknownType {\n scalar\n }\n ...on NonNullStringBox2 {\n scalar\n }\n }\n }\n ", "errors": [] }, { "name": "Validate: Overlapping fields can be merged/return types must be unambiguous/works for field names that are JS keywords", "rule": "OverlappingFieldsCanBeMerged", "schema": 2, "query": "{\n foo {\n constructor\n }\n }", "errors": [] }, { "name": "Validate: Overlapping fields can be merged/does not infinite loop on recursive fragment", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n fragment fragA on Human { name, relatives { name, ...fragA } }\n ", "errors": [] }, { "name": "Validate: Overlapping fields can be merged/does not infinite loop on immediately recursive fragment", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n fragment fragA on Human { name, ...fragA }\n ", "errors": [] }, { "name": "Validate: Overlapping fields can be merged/does not infinite loop on transitively recursive fragment", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n fragment fragA on Human { name, ...fragB }\n fragment fragB on Human { name, ...fragC }\n fragment fragC on Human { name, ...fragA }\n ", "errors": [] }, { "name": "Validate: Overlapping fields can be merged/finds invalid case even with immediately recursive fragment", "rule": "OverlappingFieldsCanBeMerged", "schema": 0, "query": "\n fragment sameAliasesWithDifferentFieldTargets on Dog {\n ...sameAliasesWithDifferentFieldTargets\n fido: name\n fido: nickname\n }\n ", "errors": [ { "message": "Fields \"fido\" conflict because name and nickname are different fields. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 4, "column": 9 }, { "line": 5, "column": 9 } ] } ] }, { "name": "Validate: Provided required arguments/ignores unknown arguments", "rule": "ProvidedNonNullArguments", "schema": 0, "query": "\n {\n dog {\n isHousetrained(unknownArgument: true)\n }\n }\n ", "errors": [] }, { "name": "Validate: Provided required arguments/Valid non-nullable value/Arg on optional arg", "rule": "ProvidedNonNullArguments", "schema": 0, "query": "\n {\n dog {\n isHousetrained(atOtherHomes: true)\n }\n }\n ", "errors": [] }, { "name": "Validate: Provided required arguments/Valid non-nullable value/No Arg on optional arg", "rule": "ProvidedNonNullArguments", "schema": 0, "query": "\n {\n dog {\n isHousetrained\n }\n }\n ", "errors": [] }, { "name": "Validate: Provided required arguments/Valid non-nullable value/Multiple args", "rule": "ProvidedNonNullArguments", "schema": 0, "query": "\n {\n complicatedArgs {\n multipleReqs(req1: 1, req2: 2)\n }\n }\n ", "errors": [] }, { "name": "Validate: Provided required arguments/Valid non-nullable value/Multiple args reverse order", "rule": "ProvidedNonNullArguments", "schema": 0, "query": "\n {\n complicatedArgs {\n multipleReqs(req2: 2, req1: 1)\n }\n }\n ", "errors": [] }, { "name": "Validate: Provided required arguments/Valid non-nullable value/No args on multiple optional", "rule": "ProvidedNonNullArguments", "schema": 0, "query": "\n {\n complicatedArgs {\n multipleOpts\n }\n }\n ", "errors": [] }, { "name": "Validate: Provided required arguments/Valid non-nullable value/One arg on multiple optional", "rule": "ProvidedNonNullArguments", "schema": 0, "query": "\n {\n complicatedArgs {\n multipleOpts(opt1: 1)\n }\n }\n ", "errors": [] }, { "name": "Validate: Provided required arguments/Valid non-nullable value/Second arg on multiple optional", "rule": "ProvidedNonNullArguments", "schema": 0, "query": "\n {\n complicatedArgs {\n multipleOpts(opt2: 1)\n }\n }\n ", "errors": [] }, { "name": "Validate: Provided required arguments/Valid non-nullable value/Multiple reqs on mixedList", "rule": "ProvidedNonNullArguments", "schema": 0, "query": "\n {\n complicatedArgs {\n multipleOptAndReq(req1: 3, req2: 4)\n }\n }\n ", "errors": [] }, { "name": "Validate: Provided required arguments/Valid non-nullable value/Multiple reqs and one opt on mixedList", "rule": "ProvidedNonNullArguments", "schema": 0, "query": "\n {\n complicatedArgs {\n multipleOptAndReq(req1: 3, req2: 4, opt1: 5)\n }\n }\n ", "errors": [] }, { "name": "Validate: Provided required arguments/Valid non-nullable value/All reqs and opts on mixedList", "rule": "ProvidedNonNullArguments", "schema": 0, "query": "\n {\n complicatedArgs {\n multipleOptAndReq(req1: 3, req2: 4, opt1: 5, opt2: 6)\n }\n }\n ", "errors": [] }, { "name": "Validate: Provided required arguments/Invalid non-nullable value/Missing one non-nullable argument", "rule": "ProvidedNonNullArguments", "schema": 0, "query": "\n {\n complicatedArgs {\n multipleReqs(req2: 2)\n }\n }\n ", "errors": [ { "message": "Field \"multipleReqs\" argument \"req1\" of type \"Int!\" is required but not provided.", "locations": [ { "line": 4, "column": 13 } ] } ] }, { "name": "Validate: Provided required arguments/Invalid non-nullable value/Missing multiple non-nullable arguments", "rule": "ProvidedNonNullArguments", "schema": 0, "query": "\n {\n complicatedArgs {\n multipleReqs\n }\n }\n ", "errors": [ { "message": "Field \"multipleReqs\" argument \"req1\" of type \"Int!\" is required but not provided.", "locations": [ { "line": 4, "column": 13 } ] }, { "message": "Field \"multipleReqs\" argument \"req2\" of type \"Int!\" is required but not provided.", "locations": [ { "line": 4, "column": 13 } ] } ] }, { "name": "Validate: Provided required arguments/Invalid non-nullable value/Incorrect value and missing argument", "rule": "ProvidedNonNullArguments", "schema": 0, "query": "\n {\n complicatedArgs {\n multipleReqs(req1: \"one\")\n }\n }\n ", "errors": [ { "message": "Field \"multipleReqs\" argument \"req2\" of type \"Int!\" is required but not provided.", "locations": [ { "line": 4, "column": 13 } ] } ] }, { "name": "Validate: Provided required arguments/Directive arguments/ignores unknown directives", "rule": "ProvidedNonNullArguments", "schema": 0, "query": "\n {\n dog @unknown\n }\n ", "errors": [] }, { "name": "Validate: Provided required arguments/Directive arguments/with directives of valid types", "rule": "ProvidedNonNullArguments", "schema": 0, "query": "\n {\n dog @include(if: true) {\n name\n }\n human @skip(if: false) {\n name\n }\n }\n ", "errors": [] }, { "name": "Validate: Provided required arguments/Directive arguments/with directive with missing types", "rule": "ProvidedNonNullArguments", "schema": 0, "query": "\n {\n dog @include {\n name @skip\n }\n }\n ", "errors": [ { "message": "Directive \"@include\" argument \"if\" of type \"Boolean!\" is required but not provided.", "locations": [ { "line": 3, "column": 15 } ] }, { "message": "Directive \"@skip\" argument \"if\" of type \"Boolean!\" is required but not provided.", "locations": [ { "line": 4, "column": 18 } ] } ] }, { "name": "Validate: Scalar leafs/valid scalar selection", "rule": "ScalarLeafs", "schema": 0, "query": "\n fragment scalarSelection on Dog {\n barks\n }\n ", "errors": [] }, { "name": "Validate: Scalar leafs/object type missing selection", "rule": "ScalarLeafs", "schema": 0, "query": "\n query directQueryOnObjectWithoutSubFields {\n human\n }\n ", "errors": [ { "message": "Field \"human\" of type \"Human\" must have a selection of subfields. Did you mean \"human { ... }\"?", "locations": [ { "line": 3, "column": 9 } ] } ] }, { "name": "Validate: Scalar leafs/interface type missing selection", "rule": "ScalarLeafs", "schema": 0, "query": "\n {\n human { pets }\n }\n ", "errors": [ { "message": "Field \"pets\" of type \"[Pet]\" must have a selection of subfields. Did you mean \"pets { ... }\"?", "locations": [ { "line": 3, "column": 17 } ] } ] }, { "name": "Validate: Scalar leafs/valid scalar selection with args", "rule": "ScalarLeafs", "schema": 0, "query": "\n fragment scalarSelectionWithArgs on Dog {\n doesKnowCommand(dogCommand: SIT)\n }\n ", "errors": [] }, { "name": "Validate: Scalar leafs/scalar selection not allowed on Boolean", "rule": "ScalarLeafs", "schema": 0, "query": "\n fragment scalarSelectionsNotAllowedOnBoolean on Dog {\n barks { sinceWhen }\n }\n ", "errors": [ { "message": "Field \"barks\" must not have a selection since type \"Boolean\" has no subfields.", "locations": [ { "line": 3, "column": 15 } ] } ] }, { "name": "Validate: Scalar leafs/scalar selection not allowed on Enum", "rule": "ScalarLeafs", "schema": 0, "query": "\n fragment scalarSelectionsNotAllowedOnEnum on Cat {\n furColor { inHexdec }\n }\n ", "errors": [ { "message": "Field \"furColor\" must not have a selection since type \"FurColor\" has no subfields.", "locations": [ { "line": 3, "column": 18 } ] } ] }, { "name": "Validate: Scalar leafs/scalar selection not allowed with args", "rule": "ScalarLeafs", "schema": 0, "query": "\n fragment scalarSelectionsNotAllowedWithArgs on Dog {\n doesKnowCommand(dogCommand: SIT) { sinceWhen }\n }\n ", "errors": [ { "message": "Field \"doesKnowCommand\" must not have a selection since type \"Boolean\" has no subfields.", "locations": [ { "line": 3, "column": 42 } ] } ] }, { "name": "Validate: Scalar leafs/Scalar selection not allowed with directives", "rule": "ScalarLeafs", "schema": 0, "query": "\n fragment scalarSelectionsNotAllowedWithDirectives on Dog {\n name @include(if: true) { isAlsoHumanName }\n }\n ", "errors": [ { "message": "Field \"name\" must not have a selection since type \"String\" has no subfields.", "locations": [ { "line": 3, "column": 33 } ] } ] }, { "name": "Validate: Scalar leafs/Scalar selection not allowed with directives and args", "rule": "ScalarLeafs", "schema": 0, "query": "\n fragment scalarSelectionsNotAllowedWithDirectivesAndArgs on Dog {\n doesKnowCommand(dogCommand: SIT) @include(if: true) { sinceWhen }\n }\n ", "errors": [ { "message": "Field \"doesKnowCommand\" must not have a selection since type \"Boolean\" has no subfields.", "locations": [ { "line": 3, "column": 61 } ] } ] }, { "name": "Validate: Unique argument names/no arguments on field", "rule": "UniqueArgumentNames", "schema": 0, "query": "\n {\n field\n }\n ", "errors": [] }, { "name": "Validate: Unique argument names/no arguments on directive", "rule": "UniqueArgumentNames", "schema": 0, "query": "\n {\n field @directive\n }\n ", "errors": [] }, { "name": "Validate: Unique argument names/argument on field", "rule": "UniqueArgumentNames", "schema": 0, "query": "\n {\n field(arg: \"value\")\n }\n ", "errors": [] }, { "name": "Validate: Unique argument names/argument on directive", "rule": "UniqueArgumentNames", "schema": 0, "query": "\n {\n field @directive(arg: \"value\")\n }\n ", "errors": [] }, { "name": "Validate: Unique argument names/same argument on two fields", "rule": "UniqueArgumentNames", "schema": 0, "query": "\n {\n one: field(arg: \"value\")\n two: field(arg: \"value\")\n }\n ", "errors": [] }, { "name": "Validate: Unique argument names/same argument on field and directive", "rule": "UniqueArgumentNames", "schema": 0, "query": "\n {\n field(arg: \"value\") @directive(arg: \"value\")\n }\n ", "errors": [] }, { "name": "Validate: Unique argument names/same argument on two directives", "rule": "UniqueArgumentNames", "schema": 0, "query": "\n {\n field @directive1(arg: \"value\") @directive2(arg: \"value\")\n }\n ", "errors": [] }, { "name": "Validate: Unique argument names/multiple field arguments", "rule": "UniqueArgumentNames", "schema": 0, "query": "\n {\n field(arg1: \"value\", arg2: \"value\", arg3: \"value\")\n }\n ", "errors": [] }, { "name": "Validate: Unique argument names/multiple directive arguments", "rule": "UniqueArgumentNames", "schema": 0, "query": "\n {\n field @directive(arg1: \"value\", arg2: \"value\", arg3: \"value\")\n }\n ", "errors": [] }, { "name": "Validate: Unique argument names/duplicate field arguments", "rule": "UniqueArgumentNames", "schema": 0, "query": "\n {\n field(arg1: \"value\", arg1: \"value\")\n }\n ", "errors": [ { "message": "There can be only one argument named \"arg1\".", "locations": [ { "line": 3, "column": 15 }, { "line": 3, "column": 30 } ] } ] }, { "name": "Validate: Unique argument names/many duplicate field arguments", "rule": "UniqueArgumentNames", "schema": 0, "query": "\n {\n field(arg1: \"value\", arg1: \"value\", arg1: \"value\")\n }\n ", "errors": [ { "message": "There can be only one argument named \"arg1\".", "locations": [ { "line": 3, "column": 15 }, { "line": 3, "column": 30 } ] }, { "message": "There can be only one argument named \"arg1\".", "locations": [ { "line": 3, "column": 15 }, { "line": 3, "column": 45 } ] } ] }, { "name": "Validate: Unique argument names/duplicate directive arguments", "rule": "UniqueArgumentNames", "schema": 0, "query": "\n {\n field @directive(arg1: \"value\", arg1: \"value\")\n }\n ", "errors": [ { "message": "There can be only one argument named \"arg1\".", "locations": [ { "line": 3, "column": 26 }, { "line": 3, "column": 41 } ] } ] }, { "name": "Validate: Unique argument names/many duplicate directive arguments", "rule": "UniqueArgumentNames", "schema": 0, "query": "\n {\n field @directive(arg1: \"value\", arg1: \"value\", arg1: \"value\")\n }\n ", "errors": [ { "message": "There can be only one argument named \"arg1\".", "locations": [ { "line": 3, "column": 26 }, { "line": 3, "column": 41 } ] }, { "message": "There can be only one argument named \"arg1\".", "locations": [ { "line": 3, "column": 26 }, { "line": 3, "column": 56 } ] } ] }, { "name": "Validate: Directives Are Unique Per Location/no directives", "rule": "UniqueDirectivesPerLocation", "schema": 0, "query": "\n fragment Test on Type {\n field\n }\n ", "errors": [] }, { "name": "Validate: Directives Are Unique Per Location/unique directives in different locations", "rule": "UniqueDirectivesPerLocation", "schema": 0, "query": "\n fragment Test on Type @directiveA {\n field @directiveB\n }\n ", "errors": [] }, { "name": "Validate: Directives Are Unique Per Location/unique directives in same locations", "rule": "UniqueDirectivesPerLocation", "schema": 0, "query": "\n fragment Test on Type @directiveA @directiveB {\n field @directiveA @directiveB\n }\n ", "errors": [] }, { "name": "Validate: Directives Are Unique Per Location/same directives in different locations", "rule": "UniqueDirectivesPerLocation", "schema": 0, "query": "\n fragment Test on Type @directiveA {\n field @directiveA\n }\n ", "errors": [] }, { "name": "Validate: Directives Are Unique Per Location/same directives in similar locations", "rule": "UniqueDirectivesPerLocation", "schema": 0, "query": "\n fragment Test on Type {\n field @directive\n field @directive\n }\n ", "errors": [] }, { "name": "Validate: Directives Are Unique Per Location/duplicate directives in one location", "rule": "UniqueDirectivesPerLocation", "schema": 0, "query": "\n fragment Test on Type {\n field @directive @directive\n }\n ", "errors": [ { "message": "The directive \"directive\" can only be used once at this location.", "locations": [ { "line": 3, "column": 15 }, { "line": 3, "column": 26 } ] } ] }, { "name": "Validate: Directives Are Unique Per Location/many duplicate directives in one location", "rule": "UniqueDirectivesPerLocation", "schema": 0, "query": "\n fragment Test on Type {\n field @directive @directive @directive\n }\n ", "errors": [ { "message": "The directive \"directive\" can only be used once at this location.", "locations": [ { "line": 3, "column": 15 }, { "line": 3, "column": 26 } ] }, { "message": "The directive \"directive\" can only be used once at this location.", "locations": [ { "line": 3, "column": 15 }, { "line": 3, "column": 37 } ] } ] }, { "name": "Validate: Directives Are Unique Per Location/different duplicate directives in one location", "rule": "UniqueDirectivesPerLocation", "schema": 0, "query": "\n fragment Test on Type {\n field @directiveA @directiveB @directiveA @directiveB\n }\n ", "errors": [ { "message": "The directive \"directiveA\" can only be used once at this location.", "locations": [ { "line": 3, "column": 15 }, { "line": 3, "column": 39 } ] }, { "message": "The directive \"directiveB\" can only be used once at this location.", "locations": [ { "line": 3, "column": 27 }, { "line": 3, "column": 51 } ] } ] }, { "name": "Validate: Directives Are Unique Per Location/duplicate directives in many locations", "rule": "UniqueDirectivesPerLocation", "schema": 0, "query": "\n fragment Test on Type @directive @directive {\n field @directive @directive\n }\n ", "errors": [ { "message": "The directive \"directive\" can only be used once at this location.", "locations": [ { "line": 2, "column": 29 }, { "line": 2, "column": 40 } ] }, { "message": "The directive \"directive\" can only be used once at this location.", "locations": [ { "line": 3, "column": 15 }, { "line": 3, "column": 26 } ] } ] }, { "name": "Validate: Unique fragment names/no fragments", "rule": "UniqueFragmentNames", "schema": 0, "query": "\n {\n field\n }\n ", "errors": [] }, { "name": "Validate: Unique fragment names/one fragment", "rule": "UniqueFragmentNames", "schema": 0, "query": "\n {\n ...fragA\n }\n\n fragment fragA on Type {\n field\n }\n ", "errors": [] }, { "name": "Validate: Unique fragment names/many fragments", "rule": "UniqueFragmentNames", "schema": 0, "query": "\n {\n ...fragA\n ...fragB\n ...fragC\n }\n fragment fragA on Type {\n fieldA\n }\n fragment fragB on Type {\n fieldB\n }\n fragment fragC on Type {\n fieldC\n }\n ", "errors": [] }, { "name": "Validate: Unique fragment names/inline fragments are always unique", "rule": "UniqueFragmentNames", "schema": 0, "query": "\n {\n ...on Type {\n fieldA\n }\n ...on Type {\n fieldB\n }\n }\n ", "errors": [] }, { "name": "Validate: Unique fragment names/fragment and operation named the same", "rule": "UniqueFragmentNames", "schema": 0, "query": "\n query Foo {\n ...Foo\n }\n fragment Foo on Type {\n field\n }\n ", "errors": [] }, { "name": "Validate: Unique fragment names/fragments named the same", "rule": "UniqueFragmentNames", "schema": 0, "query": "\n {\n ...fragA\n }\n fragment fragA on Type {\n fieldA\n }\n fragment fragA on Type {\n fieldB\n }\n ", "errors": [ { "message": "There can be only one fragment named \"fragA\".", "locations": [ { "line": 5, "column": 16 }, { "line": 8, "column": 16 } ] } ] }, { "name": "Validate: Unique fragment names/fragments named the same without being referenced", "rule": "UniqueFragmentNames", "schema": 0, "query": "\n fragment fragA on Type {\n fieldA\n }\n fragment fragA on Type {\n fieldB\n }\n ", "errors": [ { "message": "There can be only one fragment named \"fragA\".", "locations": [ { "line": 2, "column": 16 }, { "line": 5, "column": 16 } ] } ] }, { "name": "Validate: Unique input field names/input object with fields", "rule": "UniqueInputFieldNames", "schema": 0, "query": "\n {\n field(arg: { f: true })\n }\n ", "errors": [] }, { "name": "Validate: Unique input field names/same input object within two args", "rule": "UniqueInputFieldNames", "schema": 0, "query": "\n {\n field(arg1: { f: true }, arg2: { f: true })\n }\n ", "errors": [] }, { "name": "Validate: Unique input field names/multiple input object fields", "rule": "UniqueInputFieldNames", "schema": 0, "query": "\n {\n field(arg: { f1: \"value\", f2: \"value\", f3: \"value\" })\n }\n ", "errors": [] }, { "name": "Validate: Unique input field names/allows for nested input objects with similar fields", "rule": "UniqueInputFieldNames", "schema": 0, "query": "\n {\n field(arg: {\n deep: {\n deep: {\n id: 1\n }\n id: 1\n }\n id: 1\n })\n }\n ", "errors": [] }, { "name": "Validate: Unique input field names/duplicate input object fields", "rule": "UniqueInputFieldNames", "schema": 0, "query": "\n {\n field(arg: { f1: \"value\", f1: \"value\" })\n }\n ", "errors": [ { "message": "There can be only one input field named \"f1\".", "locations": [ { "line": 3, "column": 22 }, { "line": 3, "column": 35 } ] } ] }, { "name": "Validate: Unique input field names/many duplicate input object fields", "rule": "UniqueInputFieldNames", "schema": 0, "query": "\n {\n field(arg: { f1: \"value\", f1: \"value\", f1: \"value\" })\n }\n ", "errors": [ { "message": "There can be only one input field named \"f1\".", "locations": [ { "line": 3, "column": 22 }, { "line": 3, "column": 35 } ] }, { "message": "There can be only one input field named \"f1\".", "locations": [ { "line": 3, "column": 22 }, { "line": 3, "column": 48 } ] } ] }, { "name": "Validate: Unique operation names/no operations", "rule": "UniqueOperationNames", "schema": 0, "query": "\n fragment fragA on Type {\n field\n }\n ", "errors": [] }, { "name": "Validate: Unique operation names/one anon operation", "rule": "UniqueOperationNames", "schema": 0, "query": "\n {\n field\n }\n ", "errors": [] }, { "name": "Validate: Unique operation names/one named operation", "rule": "UniqueOperationNames", "schema": 0, "query": "\n query Foo {\n field\n }\n ", "errors": [] }, { "name": "Validate: Unique operation names/multiple operations", "rule": "UniqueOperationNames", "schema": 0, "query": "\n query Foo {\n field\n }\n\n query Bar {\n field\n }\n ", "errors": [] }, { "name": "Validate: Unique operation names/multiple operations of different types", "rule": "UniqueOperationNames", "schema": 0, "query": "\n query Foo {\n field\n }\n\n mutation Bar {\n field\n }\n\n subscription Baz {\n field\n }\n ", "errors": [] }, { "name": "Validate: Unique operation names/fragment and operation named the same", "rule": "UniqueOperationNames", "schema": 0, "query": "\n query Foo {\n ...Foo\n }\n fragment Foo on Type {\n field\n }\n ", "errors": [] }, { "name": "Validate: Unique operation names/multiple operations of same name", "rule": "UniqueOperationNames", "schema": 0, "query": "\n query Foo {\n fieldA\n }\n query Foo {\n fieldB\n }\n ", "errors": [ { "message": "There can be only one operation named \"Foo\".", "locations": [ { "line": 2, "column": 13 }, { "line": 5, "column": 13 } ] } ] }, { "name": "Validate: Unique operation names/multiple ops of same name of different types (mutation)", "rule": "UniqueOperationNames", "schema": 0, "query": "\n query Foo {\n fieldA\n }\n mutation Foo {\n fieldB\n }\n ", "errors": [ { "message": "There can be only one operation named \"Foo\".", "locations": [ { "line": 2, "column": 13 }, { "line": 5, "column": 16 } ] } ] }, { "name": "Validate: Unique operation names/multiple ops of same name of different types (subscription)", "rule": "UniqueOperationNames", "schema": 0, "query": "\n query Foo {\n fieldA\n }\n subscription Foo {\n fieldB\n }\n ", "errors": [ { "message": "There can be only one operation named \"Foo\".", "locations": [ { "line": 2, "column": 13 }, { "line": 5, "column": 20 } ] } ] }, { "name": "Validate: Unique variable names/unique variable names", "rule": "UniqueVariableNames", "schema": 0, "query": "\n query A($x: Int, $y: String) { __typename }\n query B($x: String, $y: Int) { __typename }\n ", "errors": [] }, { "name": "Validate: Unique variable names/duplicate variable names", "rule": "UniqueVariableNames", "schema": 0, "query": "\n query A($x: Int, $x: Int, $x: String) { __typename }\n query B($x: String, $x: Int) { __typename }\n query C($x: Int, $x: Int) { __typename }\n ", "errors": [ { "message": "There can be only one variable named \"x\".", "locations": [ { "line": 2, "column": 16 }, { "line": 2, "column": 25 } ] }, { "message": "There can be only one variable named \"x\".", "locations": [ { "line": 2, "column": 16 }, { "line": 2, "column": 34 } ] }, { "message": "There can be only one variable named \"x\".", "locations": [ { "line": 3, "column": 16 }, { "line": 3, "column": 28 } ] }, { "message": "There can be only one variable named \"x\".", "locations": [ { "line": 4, "column": 16 }, { "line": 4, "column": 25 } ] } ] }, { "name": "Validate: Variables are input types/input types are valid", "rule": "VariablesAreInputTypes", "schema": 0, "query": "\n query Foo($a: String, $b: [Boolean!]!, $c: ComplexInput) {\n field(a: $a, b: $b, c: $c)\n }\n ", "errors": [] }, { "name": "Validate: Variables are input types/output types are invalid", "rule": "VariablesAreInputTypes", "schema": 0, "query": "\n query Foo($a: Dog, $b: [[CatOrDog!]]!, $c: Pet) {\n field(a: $a, b: $b, c: $c)\n }\n ", "errors": [ { "locations": [ { "line": 2, "column": 21 } ], "message": "Variable \"$a\" cannot be non-input type \"Dog\"." }, { "locations": [ { "line": 2, "column": 30 } ], "message": "Variable \"$b\" cannot be non-input type \"[[CatOrDog!]]!\"." }, { "locations": [ { "line": 2, "column": 50 } ], "message": "Variable \"$c\" cannot be non-input type \"Pet\"." } ] }, { "name": "Validate: Variables are in allowed positions/Boolean => Boolean", "rule": "VariablesInAllowedPosition", "schema": 0, "query": "\n query Query($booleanArg: Boolean)\n {\n complicatedArgs {\n booleanArgField(booleanArg: $booleanArg)\n }\n }\n ", "errors": [] }, { "name": "Validate: Variables are in allowed positions/Boolean => Boolean within fragment", "rule": "VariablesInAllowedPosition", "schema": 0, "query": "\n fragment booleanArgFrag on ComplicatedArgs {\n booleanArgField(booleanArg: $booleanArg)\n }\n query Query($booleanArg: Boolean)\n {\n complicatedArgs {\n ...booleanArgFrag\n }\n }\n ", "errors": [] }, { "name": "Validate: Variables are in allowed positions/Boolean => Boolean within fragment", "rule": "VariablesInAllowedPosition", "schema": 0, "query": "\n query Query($booleanArg: Boolean)\n {\n complicatedArgs {\n ...booleanArgFrag\n }\n }\n fragment booleanArgFrag on ComplicatedArgs {\n booleanArgField(booleanArg: $booleanArg)\n }\n ", "errors": [] }, { "name": "Validate: Variables are in allowed positions/Boolean! => Boolean", "rule": "VariablesInAllowedPosition", "schema": 0, "query": "\n query Query($nonNullBooleanArg: Boolean!)\n {\n complicatedArgs {\n booleanArgField(booleanArg: $nonNullBooleanArg)\n }\n }\n ", "errors": [] }, { "name": "Validate: Variables are in allowed positions/Boolean! => Boolean within fragment", "rule": "VariablesInAllowedPosition", "schema": 0, "query": "\n fragment booleanArgFrag on ComplicatedArgs {\n booleanArgField(booleanArg: $nonNullBooleanArg)\n }\n\n query Query($nonNullBooleanArg: Boolean!)\n {\n complicatedArgs {\n ...booleanArgFrag\n }\n }\n ", "errors": [] }, { "name": "Validate: Variables are in allowed positions/Int => Int! with default", "rule": "VariablesInAllowedPosition", "schema": 0, "query": "\n query Query($intArg: Int = 1)\n {\n complicatedArgs {\n nonNullIntArgField(nonNullIntArg: $intArg)\n }\n }\n ", "errors": [] }, { "name": "Validate: Variables are in allowed positions/[String] => [String]", "rule": "VariablesInAllowedPosition", "schema": 0, "query": "\n query Query($stringListVar: [String])\n {\n complicatedArgs {\n stringListArgField(stringListArg: $stringListVar)\n }\n }\n ", "errors": [] }, { "name": "Validate: Variables are in allowed positions/[String!] => [String]", "rule": "VariablesInAllowedPosition", "schema": 0, "query": "\n query Query($stringListVar: [String!])\n {\n complicatedArgs {\n stringListArgField(stringListArg: $stringListVar)\n }\n }\n ", "errors": [] }, { "name": "Validate: Variables are in allowed positions/String => [String] in item position", "rule": "VariablesInAllowedPosition", "schema": 0, "query": "\n query Query($stringVar: String)\n {\n complicatedArgs {\n stringListArgField(stringListArg: [$stringVar])\n }\n }\n ", "errors": [] }, { "name": "Validate: Variables are in allowed positions/String! => [String] in item position", "rule": "VariablesInAllowedPosition", "schema": 0, "query": "\n query Query($stringVar: String!)\n {\n complicatedArgs {\n stringListArgField(stringListArg: [$stringVar])\n }\n }\n ", "errors": [] }, { "name": "Validate: Variables are in allowed positions/ComplexInput => ComplexInput", "rule": "VariablesInAllowedPosition", "schema": 0, "query": "\n query Query($complexVar: ComplexInput)\n {\n complicatedArgs {\n complexArgField(complexArg: $complexVar)\n }\n }\n ", "errors": [] }, { "name": "Validate: Variables are in allowed positions/ComplexInput => ComplexInput in field position", "rule": "VariablesInAllowedPosition", "schema": 0, "query": "\n query Query($boolVar: Boolean = false)\n {\n complicatedArgs {\n complexArgField(complexArg: {requiredArg: $boolVar})\n }\n }\n ", "errors": [] }, { "name": "Validate: Variables are in allowed positions/Boolean! => Boolean! in directive", "rule": "VariablesInAllowedPosition", "schema": 0, "query": "\n query Query($boolVar: Boolean!)\n {\n dog @include(if: $boolVar)\n }\n ", "errors": [] }, { "name": "Validate: Variables are in allowed positions/Boolean => Boolean! in directive with default", "rule": "VariablesInAllowedPosition", "schema": 0, "query": "\n query Query($boolVar: Boolean = false)\n {\n dog @include(if: $boolVar)\n }\n ", "errors": [] }, { "name": "Validate: Variables are in allowed positions/Int => Int!", "rule": "VariablesInAllowedPosition", "schema": 0, "query": "\n query Query($intArg: Int) {\n complicatedArgs {\n nonNullIntArgField(nonNullIntArg: $intArg)\n }\n }\n ", "errors": [ { "message": "Variable \"$intArg\" of type \"Int\" used in position expecting type \"Int!\".", "locations": [ { "line": 2, "column": 19 }, { "line": 4, "column": 45 } ] } ] }, { "name": "Validate: Variables are in allowed positions/Int => Int! within fragment", "rule": "VariablesInAllowedPosition", "schema": 0, "query": "\n fragment nonNullIntArgFieldFrag on ComplicatedArgs {\n nonNullIntArgField(nonNullIntArg: $intArg)\n }\n\n query Query($intArg: Int) {\n complicatedArgs {\n ...nonNullIntArgFieldFrag\n }\n }\n ", "errors": [ { "message": "Variable \"$intArg\" of type \"Int\" used in position expecting type \"Int!\".", "locations": [ { "line": 6, "column": 19 }, { "line": 3, "column": 43 } ] } ] }, { "name": "Validate: Variables are in allowed positions/Int => Int! within nested fragment", "rule": "VariablesInAllowedPosition", "schema": 0, "query": "\n fragment outerFrag on ComplicatedArgs {\n ...nonNullIntArgFieldFrag\n }\n\n fragment nonNullIntArgFieldFrag on ComplicatedArgs {\n nonNullIntArgField(nonNullIntArg: $intArg)\n }\n\n query Query($intArg: Int) {\n complicatedArgs {\n ...outerFrag\n }\n }\n ", "errors": [ { "message": "Variable \"$intArg\" of type \"Int\" used in position expecting type \"Int!\".", "locations": [ { "line": 10, "column": 19 }, { "line": 7, "column": 43 } ] } ] }, { "name": "Validate: Variables are in allowed positions/String over Boolean", "rule": "VariablesInAllowedPosition", "schema": 0, "query": "\n query Query($stringVar: String) {\n complicatedArgs {\n booleanArgField(booleanArg: $stringVar)\n }\n }\n ", "errors": [ { "message": "Variable \"$stringVar\" of type \"String\" used in position expecting type \"Boolean\".", "locations": [ { "line": 2, "column": 19 }, { "line": 4, "column": 39 } ] } ] }, { "name": "Validate: Variables are in allowed positions/String => [String]", "rule": "VariablesInAllowedPosition", "schema": 0, "query": "\n query Query($stringVar: String) {\n complicatedArgs {\n stringListArgField(stringListArg: $stringVar)\n }\n }\n ", "errors": [ { "message": "Variable \"$stringVar\" of type \"String\" used in position expecting type \"[String]\".", "locations": [ { "line": 2, "column": 19 }, { "line": 4, "column": 45 } ] } ] }, { "name": "Validate: Variables are in allowed positions/Boolean => Boolean! in directive", "rule": "VariablesInAllowedPosition", "schema": 0, "query": "\n query Query($boolVar: Boolean) {\n dog @include(if: $boolVar)\n }\n ", "errors": [ { "message": "Variable \"$boolVar\" of type \"Boolean\" used in position expecting type \"Boolean!\".", "locations": [ { "line": 2, "column": 19 }, { "line": 3, "column": 26 } ] } ] }, { "name": "Validate: Variables are in allowed positions/String => Boolean! in directive", "rule": "VariablesInAllowedPosition", "schema": 0, "query": "\n query Query($stringVar: String) {\n dog @include(if: $stringVar)\n }\n ", "errors": [ { "message": "Variable \"$stringVar\" of type \"String\" used in position expecting type \"Boolean!\".", "locations": [ { "line": 2, "column": 19 }, { "line": 3, "column": 26 } ] } ] }, { "name": "Validate: Variables are in allowed positions/[String] => [String!]", "rule": "VariablesInAllowedPosition", "schema": 0, "query": "\n query Query($stringListVar: [String])\n {\n complicatedArgs {\n stringListNonNullArgField(stringListNonNullArg: $stringListVar)\n }\n }\n ", "errors": [ { "message": "Variable \"$stringListVar\" of type \"[String]\" used in position expecting type \"[String!]\".", "locations": [ { "line": 2, "column": 19 }, { "line": 5, "column": 59 } ] } ] } ] } graphql-go-1.5.0/internal/validation/validate_max_depth_test.go000066400000000000000000000225211435002731700247400ustar00rootroot00000000000000package validation import ( "testing" "github.com/graph-gophers/graphql-go/internal/query" "github.com/graph-gophers/graphql-go/internal/schema" "github.com/graph-gophers/graphql-go/types" ) const ( simpleSchema = `schema { query: Query } type Query { characters: [Character]! } type Character { id: ID! name: String! friends: [Character]! }` interfaceSimple = `schema { query: Query } type Query { characters: [Character] } interface Character { id: ID! name: String! friends: [Character] enemies: [Character] appearsIn: [Episode]! } enum Episode { NEWHOPE EMPIRE JEDI } type Starship { id: ID! } type Human implements Character { id: ID! name: String! friends: [Character] enemies: [Character] appearsIn: [Episode]! starships: [Starship] totalCredits: Int } type Droid implements Character { id: ID! name: String! friends: [Character] enemies: [Character] appearsIn: [Episode]! primaryFunction: String }` ) type maxDepthTestCase struct { name string query string depth int failure bool expectedErrors []string } func (tc maxDepthTestCase) Run(t *testing.T, s *types.Schema) { t.Run(tc.name, func(t *testing.T) { doc, qErr := query.Parse(tc.query) if qErr != nil { t.Fatal(qErr) } errs := Validate(s, doc, nil, tc.depth) if len(tc.expectedErrors) > 0 { if len(errs) > 0 { for _, expected := range tc.expectedErrors { found := false for _, err := range errs { if err.Rule == expected { found = true break } } if !found { t.Errorf("expected error %v is missing", expected) } } } else { t.Errorf("expected errors [%v] are missing", tc.expectedErrors) } } if (len(errs) > 0) != tc.failure { t.Errorf("expected failure: %t, actual errors (%d): %v", tc.failure, len(errs), errs) } }) } func TestMaxDepth(t *testing.T) { s, err := schema.ParseSchema(simpleSchema, false) if err != nil { t.Fatal(err) } for _, tc := range []maxDepthTestCase{ { name: "off", query: `query Okay { # depth 0 characters { # depth 1 id # depth 2 name # depth 2 friends { # depth 2 friends { # depth 3 friends { # depth 4 id # depth 5 name # depth 5 } } } } }`, depth: 0, }, { name: "maxDepth-1", query: `query Fine { # depth 0 characters { # depth 1 id # depth 2 name # depth 2 friends { # depth 2 id # depth 3 name # depth 3 } } }`, depth: 4, }, { name: "maxDepth", query: `query Deep { # depth 0 characters { # depth 1 id # depth 2 name # depth 2 friends { # depth 2 id # depth 3 name # depth 3 } } }`, depth: 3, }, { name: "maxDepth+1", query: `query TooDeep { # depth 0 characters { # depth 1 id # depth 2 name # depth 2 friends { # depth 2 friends { # depth 3 friends { # depth 4 id # depth 5 name # depth 5 } } } } }`, depth: 4, failure: true, }, } { tc.Run(t, s) } } func TestMaxDepthInlineFragments(t *testing.T) { s, err := schema.ParseSchema(interfaceSimple, false) if err != nil { t.Fatal(err) } for _, tc := range []maxDepthTestCase{ { name: "maxDepth-1", query: `query { # depth 0 characters { # depth 1 name # depth 2 ... on Human { # depth 2 totalCredits # depth 2 } } }`, depth: 3, }, { name: "maxDepth", query: `query { # depth 0 characters { # depth 1 ... on Droid { # depth 2 primaryFunction # depth 2 } } }`, depth: 2, }, { name: "maxDepth+1", query: `query { # depth 0 characters { # depth 1 ... on Droid { # depth 2 primaryFunction # depth 2 } } }`, depth: 1, failure: true, }, } { tc.Run(t, s) } } func TestMaxDepthFragmentSpreads(t *testing.T) { s, err := schema.ParseSchema(interfaceSimple, false) if err != nil { t.Fatal(err) } for _, tc := range []maxDepthTestCase{ { name: "maxDepth-1", query: `fragment friend on Character { id # depth 5 name friends { name # depth 6 } } query { # depth 0 characters { # depth 1 id # depth 2 name # depth 2 friends { # depth 2 friends { # depth 3 friends { # depth 4 ...friend # depth 5 } } } } }`, depth: 7, }, { name: "maxDepth", query: `fragment friend on Character { id # depth 5 name } query { # depth 0 characters { # depth 1 id # depth 2 name # depth 2 friends { # depth 2 friends { # depth 3 friends { # depth 4 ...friend # depth 5 } } } } }`, depth: 5, }, { name: "maxDepth+1", query: `fragment friend on Character { id # depth 6 name friends { name # depth 7 } } query { # depth 0 characters { # depth 1 id # depth 2 name # depth 2 friends { # depth 2 friends { # depth 3 friends { # depth 4 friends { # depth 5 ...friend # depth 6 } } } } } }`, depth: 6, failure: true, }, { name: "spreadAtDifferentDepths", query: ` fragment character on Character { name # depth + 0 friends { # depth + 0 name # depth + 1 } } query laterDepthValidated { ...character # depth 1 (+1) enemies { # depth 1 friends { # depth 2 ...character # depth 2 (+1), should error! } } } `, depth: 2, failure: true, }, { name: "spreadAtSameDepth", query: ` fragment character on Character { name # depth + 0 friends { # depth + 0 name # depth + 1 } } query { characters { # depth 1 friends { # depth 2 ...character # depth 3 (+1) } enemies { # depth 2 ...character # depth 3 (+1) } } } `, depth: 4, }, { name: "fragmentCycle", query: ` fragment X on Query { ...Y } fragment Y on Query { ...Z } fragment Z on Query { ...X } query { ...X } `, depth: 10, failure: true, }, } { tc.Run(t, s) } } func TestMaxDepthUnknownFragmentSpreads(t *testing.T) { s, err := schema.ParseSchema(interfaceSimple, false) if err != nil { t.Fatal(err) } for _, tc := range []maxDepthTestCase{ { name: "maxDepthUnknownFragment", query: `query { # depth 0 characters { # depth 1 id # depth 2 name # depth 2 friends { # depth 2 friends { # depth 3 friends { # depth 4 friends { # depth 5 ...unknownFragment # depth 6 } } } } } }`, depth: 6, failure: true, expectedErrors: []string{"MaxDepthEvaluationError"}, }, } { tc.Run(t, s) } } func TestMaxDepthValidation(t *testing.T) { s, err := schema.ParseSchema(interfaceSimple, false) if err != nil { t.Fatal(err) } for _, tc := range []struct { name string query string maxDepth int expected bool }{ { name: "off", query: `query Fine { # depth 0 characters { # depth 1 id # depth 2 name # depth 2 friends { # depth 2 id # depth 3 name # depth 3 } } }`, maxDepth: 0, }, { name: "fields", query: `query Fine { # depth 0 characters { # depth 1 id # depth 2 name # depth 2 friends { # depth 2 id # depth 3 name # depth 3 } } }`, maxDepth: 2, expected: true, }, { name: "fragment", query: `fragment friend on Character { id # depth 6 name friends { name # depth 7 } } query { # depth 0 characters { # depth 1 id # depth 2 name # depth 2 friends { # depth 2 friends { # depth 3 friends { # depth 4 friends { # depth 5 ...friend # depth 6 } } } } } }`, maxDepth: 5, expected: true, }, { name: "inlinefragment", query: `query { # depth 0 characters { # depth 1 ... on Droid { # depth 2 primaryFunction # depth 2 } } }`, maxDepth: 1, expected: true, }, } { t.Run(tc.name, func(t *testing.T) { doc, err := query.Parse(tc.query) if err != nil { t.Fatal(err) } context := newContext(s, doc, tc.maxDepth) op := doc.Operations[0] opc := &opContext{context: context, ops: doc.Operations} actual := validateMaxDepth(opc, op.Selections, nil, 1) if actual != tc.expected { t.Errorf("expected %t, actual %t", tc.expected, actual) } }) } } graphql-go-1.5.0/internal/validation/validation.go000066400000000000000000000675111435002731700222210ustar00rootroot00000000000000package validation import ( "fmt" "math" "reflect" "strconv" "strings" "text/scanner" "github.com/graph-gophers/graphql-go/errors" "github.com/graph-gophers/graphql-go/internal/common" "github.com/graph-gophers/graphql-go/internal/query" "github.com/graph-gophers/graphql-go/types" ) type varSet map[*types.InputValueDefinition]struct{} type selectionPair struct{ a, b types.Selection } type nameSet map[string]errors.Location type fieldInfo struct { sf *types.FieldDefinition parent types.NamedType } type context struct { schema *types.Schema doc *types.ExecutableDefinition errs []*errors.QueryError opErrs map[*types.OperationDefinition][]*errors.QueryError usedVars map[*types.OperationDefinition]varSet fieldMap map[*types.Field]fieldInfo overlapValidated map[selectionPair]struct{} maxDepth int } func (c *context) addErr(loc errors.Location, rule string, format string, a ...interface{}) { c.addErrMultiLoc([]errors.Location{loc}, rule, format, a...) } func (c *context) addErrMultiLoc(locs []errors.Location, rule string, format string, a ...interface{}) { c.errs = append(c.errs, &errors.QueryError{ Message: fmt.Sprintf(format, a...), Locations: locs, Rule: rule, }) } type opContext struct { *context ops []*types.OperationDefinition } func newContext(s *types.Schema, doc *types.ExecutableDefinition, maxDepth int) *context { return &context{ schema: s, doc: doc, opErrs: make(map[*types.OperationDefinition][]*errors.QueryError), usedVars: make(map[*types.OperationDefinition]varSet), fieldMap: make(map[*types.Field]fieldInfo), overlapValidated: make(map[selectionPair]struct{}), maxDepth: maxDepth, } } func Validate(s *types.Schema, doc *types.ExecutableDefinition, variables map[string]interface{}, maxDepth int) []*errors.QueryError { c := newContext(s, doc, maxDepth) opNames := make(nameSet) fragUsedBy := make(map[*types.FragmentDefinition][]*types.OperationDefinition) for _, op := range doc.Operations { c.usedVars[op] = make(varSet) opc := &opContext{c, []*types.OperationDefinition{op}} // Check if max depth is exceeded, if it's set. If max depth is exceeded, // don't continue to validate the document and exit early. if validateMaxDepth(opc, op.Selections, nil, 1) { return c.errs } if op.Name.Name == "" && len(doc.Operations) != 1 { c.addErr(op.Loc, "LoneAnonymousOperation", "This anonymous operation must be the only defined operation.") } if op.Name.Name != "" { validateName(c, opNames, op.Name, "UniqueOperationNames", "operation") } validateDirectives(opc, string(op.Type), op.Directives) varNames := make(nameSet) for _, v := range op.Vars { validateName(c, varNames, v.Name, "UniqueVariableNames", "variable") t := resolveType(c, v.Type) if !canBeInput(t) { c.addErr(v.TypeLoc, "VariablesAreInputTypes", "Variable %q cannot be non-input type %q.", "$"+v.Name.Name, t) } validateValue(opc, v, variables[v.Name.Name], t) if v.Default != nil { validateLiteral(opc, v.Default) if t != nil { if nn, ok := t.(*types.NonNull); ok { c.addErr(v.Default.Location(), "DefaultValuesOfCorrectType", "Variable %q of type %q is required and will not use the default value. Perhaps you meant to use type %q.", "$"+v.Name.Name, t, nn.OfType) } if ok, reason := validateValueType(opc, v.Default, t); !ok { c.addErr(v.Default.Location(), "DefaultValuesOfCorrectType", "Variable %q of type %q has invalid default value %s.\n%s", "$"+v.Name.Name, t, v.Default, reason) } } } } var entryPoint types.NamedType switch op.Type { case query.Query: entryPoint = s.EntryPoints["query"] case query.Mutation: entryPoint = s.EntryPoints["mutation"] case query.Subscription: entryPoint = s.EntryPoints["subscription"] default: panic("unreachable") } validateSelectionSet(opc, op.Selections, entryPoint) fragUsed := make(map[*types.FragmentDefinition]struct{}) markUsedFragments(c, op.Selections, fragUsed) for frag := range fragUsed { fragUsedBy[frag] = append(fragUsedBy[frag], op) } } fragNames := make(nameSet) fragVisited := make(map[*types.FragmentDefinition]struct{}) for _, frag := range doc.Fragments { opc := &opContext{c, fragUsedBy[frag]} validateName(c, fragNames, frag.Name, "UniqueFragmentNames", "fragment") validateDirectives(opc, "FRAGMENT_DEFINITION", frag.Directives) t := unwrapType(resolveType(c, &frag.On)) // continue even if t is nil if t != nil && !canBeFragment(t) { c.addErr(frag.On.Loc, "FragmentsOnCompositeTypes", "Fragment %q cannot condition on non composite type %q.", frag.Name.Name, t) continue } validateSelectionSet(opc, frag.Selections, t) if _, ok := fragVisited[frag]; !ok { detectFragmentCycle(c, frag.Selections, fragVisited, nil, map[string]int{frag.Name.Name: 0}) } } for _, frag := range doc.Fragments { if len(fragUsedBy[frag]) == 0 { c.addErr(frag.Loc, "NoUnusedFragments", "Fragment %q is never used.", frag.Name.Name) } } for _, op := range doc.Operations { c.errs = append(c.errs, c.opErrs[op]...) opUsedVars := c.usedVars[op] for _, v := range op.Vars { if _, ok := opUsedVars[v]; !ok { opSuffix := "" if op.Name.Name != "" { opSuffix = fmt.Sprintf(" in operation %q", op.Name.Name) } c.addErr(v.Loc, "NoUnusedVariables", "Variable %q is never used%s.", "$"+v.Name.Name, opSuffix) } } } return c.errs } func validateValue(c *opContext, v *types.InputValueDefinition, val interface{}, t types.Type) { switch t := t.(type) { case *types.NonNull: if val == nil { c.addErr(v.Loc, "VariablesOfCorrectType", "Variable \"%s\" has invalid value null.\nExpected type \"%s\", found null.", v.Name.Name, t) return } validateValue(c, v, val, t.OfType) case *types.List: if val == nil { return } vv, ok := val.([]interface{}) if !ok { // Input coercion rules allow single items without wrapping array validateValue(c, v, val, t.OfType) return } for _, elem := range vv { validateValue(c, v, elem, t.OfType) } case *types.EnumTypeDefinition: if val == nil { return } e, ok := val.(string) if !ok { c.addErr(v.Loc, "VariablesOfCorrectType", "Variable \"%s\" has invalid type %T.\nExpected type \"%s\", found %v.", v.Name.Name, val, t, val) return } for _, option := range t.EnumValuesDefinition { if option.EnumValue == e { return } } c.addErr(v.Loc, "VariablesOfCorrectType", "Variable \"%s\" has invalid value %s.\nExpected type \"%s\", found %s.", v.Name.Name, e, t, e) case *types.InputObject: if val == nil { return } in, ok := val.(map[string]interface{}) if !ok { c.addErr(v.Loc, "VariablesOfCorrectType", "Variable \"%s\" has invalid type %T.\nExpected type \"%s\", found %s.", v.Name.Name, val, t, val) return } for _, f := range t.Values { fieldVal := in[f.Name.Name] validateValue(c, f, fieldVal, f.Type) } } } // validates the query doesn't go deeper than maxDepth (if set). Returns whether // or not query validated max depth to avoid excessive recursion. // // The visited map is necessary to ensure that max depth validation does not get stuck in cyclical // fragment spreads. func validateMaxDepth(c *opContext, sels []types.Selection, visited map[*types.FragmentDefinition]struct{}, depth int) bool { // maxDepth checking is turned off when maxDepth is 0 if c.maxDepth == 0 { return false } exceededMaxDepth := false if visited == nil { visited = map[*types.FragmentDefinition]struct{}{} } for _, sel := range sels { switch sel := sel.(type) { case *types.Field: if depth > c.maxDepth { exceededMaxDepth = true c.addErr(sel.Alias.Loc, "MaxDepthExceeded", "Field %q has depth %d that exceeds max depth %d", sel.Name.Name, depth, c.maxDepth) continue } exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, sel.SelectionSet, visited, depth+1) case *types.InlineFragment: // Depth is not checked because inline fragments resolve to other fields which are checked. // Depth is not incremented because inline fragments have the same depth as neighboring fields exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, sel.Selections, visited, depth) case *types.FragmentSpread: // Depth is not checked because fragments resolve to other fields which are checked. frag := c.doc.Fragments.Get(sel.Name.Name) if frag == nil { // In case of unknown fragment (invalid request), ignore max depth evaluation c.addErr(sel.Loc, "MaxDepthEvaluationError", "Unknown fragment %q. Unable to evaluate depth.", sel.Name.Name) continue } if _, ok := visited[frag]; ok { // we've already seen this fragment, don't check depth again. continue } visited[frag] = struct{}{} // Depth is not incremented because fragments have the same depth as surrounding fields exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, frag.Selections, visited, depth) } } return exceededMaxDepth } func validateSelectionSet(c *opContext, sels []types.Selection, t types.NamedType) { for _, sel := range sels { validateSelection(c, sel, t) } for i, a := range sels { for _, b := range sels[i+1:] { c.validateOverlap(a, b, nil, nil) } } } func validateSelection(c *opContext, sel types.Selection, t types.NamedType) { switch sel := sel.(type) { case *types.Field: validateDirectives(c, "FIELD", sel.Directives) fieldName := sel.Name.Name var f *types.FieldDefinition switch fieldName { case "__typename": f = &types.FieldDefinition{ Name: "__typename", Type: c.schema.Types["String"], } case "__schema": f = &types.FieldDefinition{ Name: "__schema", Type: c.schema.Types["__Schema"], } case "__type": f = &types.FieldDefinition{ Name: "__type", Arguments: types.ArgumentsDefinition{ &types.InputValueDefinition{ Name: types.Ident{Name: "name"}, Type: &types.NonNull{OfType: c.schema.Types["String"]}, }, }, Type: c.schema.Types["__Type"], } case "_service": f = &types.FieldDefinition{ Name: "_service", Type: c.schema.Types["_Service"], } default: f = fields(t).Get(fieldName) if f == nil && t != nil { suggestion := makeSuggestion("Did you mean", fields(t).Names(), fieldName) c.addErr(sel.Alias.Loc, "FieldsOnCorrectType", "Cannot query field %q on type %q.%s", fieldName, t, suggestion) } } c.fieldMap[sel] = fieldInfo{sf: f, parent: t} validateArgumentLiterals(c, sel.Arguments) if f != nil { validateArgumentTypes(c, sel.Arguments, f.Arguments, sel.Alias.Loc, func() string { return fmt.Sprintf("field %q of type %q", fieldName, t) }, func() string { return fmt.Sprintf("Field %q", fieldName) }, ) } var ft types.Type if f != nil { ft = f.Type sf := hasSubfields(ft) if sf && sel.SelectionSet == nil { c.addErr(sel.Alias.Loc, "ScalarLeafs", "Field %q of type %q must have a selection of subfields. Did you mean \"%s { ... }\"?", fieldName, ft, fieldName) } if !sf && sel.SelectionSet != nil { c.addErr(sel.SelectionSetLoc, "ScalarLeafs", "Field %q must not have a selection since type %q has no subfields.", fieldName, ft) } } if sel.SelectionSet != nil { validateSelectionSet(c, sel.SelectionSet, unwrapType(ft)) } case *types.InlineFragment: validateDirectives(c, "INLINE_FRAGMENT", sel.Directives) if sel.On.Name != "" { fragTyp := unwrapType(resolveType(c.context, &sel.On)) if fragTyp != nil && !compatible(t, fragTyp) { c.addErr(sel.Loc, "PossibleFragmentSpreads", "Fragment cannot be spread here as objects of type %q can never be of type %q.", t, fragTyp) } t = fragTyp // continue even if t is nil } if t != nil && !canBeFragment(t) { c.addErr(sel.On.Loc, "FragmentsOnCompositeTypes", "Fragment cannot condition on non composite type %q.", t) return } validateSelectionSet(c, sel.Selections, unwrapType(t)) case *types.FragmentSpread: validateDirectives(c, "FRAGMENT_SPREAD", sel.Directives) frag := c.doc.Fragments.Get(sel.Name.Name) if frag == nil { c.addErr(sel.Name.Loc, "KnownFragmentNames", "Unknown fragment %q.", sel.Name.Name) return } fragTyp := c.schema.Types[frag.On.Name] if !compatible(t, fragTyp) { c.addErr(sel.Loc, "PossibleFragmentSpreads", "Fragment %q cannot be spread here as objects of type %q can never be of type %q.", frag.Name.Name, t, fragTyp) } default: panic("unreachable") } } func compatible(a, b types.Type) bool { for _, pta := range possibleTypes(a) { for _, ptb := range possibleTypes(b) { if pta == ptb { return true } } } return false } func possibleTypes(t types.Type) []*types.ObjectTypeDefinition { switch t := t.(type) { case *types.ObjectTypeDefinition: return []*types.ObjectTypeDefinition{t} case *types.InterfaceTypeDefinition: return t.PossibleTypes case *types.Union: return t.UnionMemberTypes default: return nil } } func markUsedFragments(c *context, sels []types.Selection, fragUsed map[*types.FragmentDefinition]struct{}) { for _, sel := range sels { switch sel := sel.(type) { case *types.Field: if sel.SelectionSet != nil { markUsedFragments(c, sel.SelectionSet, fragUsed) } case *types.InlineFragment: markUsedFragments(c, sel.Selections, fragUsed) case *types.FragmentSpread: frag := c.doc.Fragments.Get(sel.Name.Name) if frag == nil { return } if _, ok := fragUsed[frag]; ok { continue } fragUsed[frag] = struct{}{} markUsedFragments(c, frag.Selections, fragUsed) default: panic("unreachable") } } } func detectFragmentCycle(c *context, sels []types.Selection, fragVisited map[*types.FragmentDefinition]struct{}, spreadPath []*types.FragmentSpread, spreadPathIndex map[string]int) { for _, sel := range sels { detectFragmentCycleSel(c, sel, fragVisited, spreadPath, spreadPathIndex) } } func detectFragmentCycleSel(c *context, sel types.Selection, fragVisited map[*types.FragmentDefinition]struct{}, spreadPath []*types.FragmentSpread, spreadPathIndex map[string]int) { switch sel := sel.(type) { case *types.Field: if sel.SelectionSet != nil { detectFragmentCycle(c, sel.SelectionSet, fragVisited, spreadPath, spreadPathIndex) } case *types.InlineFragment: detectFragmentCycle(c, sel.Selections, fragVisited, spreadPath, spreadPathIndex) case *types.FragmentSpread: frag := c.doc.Fragments.Get(sel.Name.Name) if frag == nil { return } spreadPath = append(spreadPath, sel) if i, ok := spreadPathIndex[frag.Name.Name]; ok { cyclePath := spreadPath[i:] via := "" if len(cyclePath) > 1 { names := make([]string, len(cyclePath)-1) for i, frag := range cyclePath[:len(cyclePath)-1] { names[i] = frag.Name.Name } via = " via " + strings.Join(names, ", ") } locs := make([]errors.Location, len(cyclePath)) for i, frag := range cyclePath { locs[i] = frag.Loc } c.addErrMultiLoc(locs, "NoFragmentCycles", "Cannot spread fragment %q within itself%s.", frag.Name.Name, via) return } if _, ok := fragVisited[frag]; ok { return } fragVisited[frag] = struct{}{} spreadPathIndex[frag.Name.Name] = len(spreadPath) detectFragmentCycle(c, frag.Selections, fragVisited, spreadPath, spreadPathIndex) delete(spreadPathIndex, frag.Name.Name) default: panic("unreachable") } } func (c *context) validateOverlap(a, b types.Selection, reasons *[]string, locs *[]errors.Location) { if a == b { return } if _, ok := c.overlapValidated[selectionPair{a, b}]; ok { return } c.overlapValidated[selectionPair{a, b}] = struct{}{} c.overlapValidated[selectionPair{b, a}] = struct{}{} switch a := a.(type) { case *types.Field: switch b := b.(type) { case *types.Field: if b.Alias.Loc.Before(a.Alias.Loc) { a, b = b, a } if reasons2, locs2 := c.validateFieldOverlap(a, b); len(reasons2) != 0 { locs2 = append(locs2, a.Alias.Loc, b.Alias.Loc) if reasons == nil { c.addErrMultiLoc(locs2, "OverlappingFieldsCanBeMerged", "Fields %q conflict because %s. Use different aliases on the fields to fetch both if this was intentional.", a.Alias.Name, strings.Join(reasons2, " and ")) return } for _, r := range reasons2 { *reasons = append(*reasons, fmt.Sprintf("subfields %q conflict because %s", a.Alias.Name, r)) } *locs = append(*locs, locs2...) } case *types.InlineFragment: for _, sel := range b.Selections { c.validateOverlap(a, sel, reasons, locs) } case *types.FragmentSpread: if frag := c.doc.Fragments.Get(b.Name.Name); frag != nil { for _, sel := range frag.Selections { c.validateOverlap(a, sel, reasons, locs) } } default: panic("unreachable") } case *types.InlineFragment: for _, sel := range a.Selections { c.validateOverlap(sel, b, reasons, locs) } case *types.FragmentSpread: if frag := c.doc.Fragments.Get(a.Name.Name); frag != nil { for _, sel := range frag.Selections { c.validateOverlap(sel, b, reasons, locs) } } default: panic("unreachable") } } func (c *context) validateFieldOverlap(a, b *types.Field) ([]string, []errors.Location) { if a.Alias.Name != b.Alias.Name { return nil, nil } if asf := c.fieldMap[a].sf; asf != nil { if bsf := c.fieldMap[b].sf; bsf != nil { if !typesCompatible(asf.Type, bsf.Type) { return []string{fmt.Sprintf("they return conflicting types %s and %s", asf.Type, bsf.Type)}, nil } } } at := c.fieldMap[a].parent bt := c.fieldMap[b].parent if at == nil || bt == nil || at == bt { if a.Name.Name != b.Name.Name { return []string{fmt.Sprintf("%s and %s are different fields", a.Name.Name, b.Name.Name)}, nil } if argumentsConflict(a.Arguments, b.Arguments) { return []string{"they have differing arguments"}, nil } } var reasons []string var locs []errors.Location for _, a2 := range a.SelectionSet { for _, b2 := range b.SelectionSet { c.validateOverlap(a2, b2, &reasons, &locs) } } return reasons, locs } func argumentsConflict(a, b types.ArgumentList) bool { if len(a) != len(b) { return true } for _, argA := range a { valB, ok := b.Get(argA.Name.Name) if !ok || !reflect.DeepEqual(argA.Value.Deserialize(nil), valB.Deserialize(nil)) { return true } } return false } func fields(t types.Type) types.FieldsDefinition { switch t := t.(type) { case *types.ObjectTypeDefinition: return t.Fields case *types.InterfaceTypeDefinition: return t.Fields default: return nil } } func unwrapType(t types.Type) types.NamedType { if t == nil { return nil } for { switch t2 := t.(type) { case types.NamedType: return t2 case *types.List: t = t2.OfType case *types.NonNull: t = t2.OfType default: panic("unreachable") } } } func resolveType(c *context, t types.Type) types.Type { t2, err := common.ResolveType(t, c.schema.Resolve) if err != nil { c.errs = append(c.errs, err) } return t2 } func validateDirectives(c *opContext, loc string, directives types.DirectiveList) { directiveNames := make(nameSet) for _, d := range directives { dirName := d.Name.Name validateNameCustomMsg(c.context, directiveNames, d.Name, "UniqueDirectivesPerLocation", func() string { return fmt.Sprintf("The directive %q can only be used once at this location.", dirName) }) validateArgumentLiterals(c, d.Arguments) dd, ok := c.schema.Directives[dirName] if !ok { c.addErr(d.Name.Loc, "KnownDirectives", "Unknown directive %q.", dirName) continue } locOK := false for _, allowedLoc := range dd.Locations { if loc == allowedLoc { locOK = true break } } if !locOK { c.addErr(d.Name.Loc, "KnownDirectives", "Directive %q may not be used on %s.", dirName, loc) } validateArgumentTypes(c, d.Arguments, dd.Arguments, d.Name.Loc, func() string { return fmt.Sprintf("directive %q", "@"+dirName) }, func() string { return fmt.Sprintf("Directive %q", "@"+dirName) }, ) } } func validateName(c *context, set nameSet, name types.Ident, rule string, kind string) { validateNameCustomMsg(c, set, name, rule, func() string { return fmt.Sprintf("There can be only one %s named %q.", kind, name.Name) }) } func validateNameCustomMsg(c *context, set nameSet, name types.Ident, rule string, msg func() string) { if loc, ok := set[name.Name]; ok { c.addErrMultiLoc([]errors.Location{loc, name.Loc}, rule, msg()) return } set[name.Name] = name.Loc } func validateArgumentTypes(c *opContext, args types.ArgumentList, argDecls types.ArgumentsDefinition, loc errors.Location, owner1, owner2 func() string) { for _, selArg := range args { arg := argDecls.Get(selArg.Name.Name) if arg == nil { c.addErr(selArg.Name.Loc, "KnownArgumentNames", "Unknown argument %q on %s.", selArg.Name.Name, owner1()) continue } value := selArg.Value if ok, reason := validateValueType(c, value, arg.Type); !ok { c.addErr(value.Location(), "ArgumentsOfCorrectType", "Argument %q has invalid value %s.\n%s", arg.Name.Name, value, reason) } } for _, decl := range argDecls { if _, ok := decl.Type.(*types.NonNull); ok { if _, ok := args.Get(decl.Name.Name); !ok { c.addErr(loc, "ProvidedNonNullArguments", "%s argument %q of type %q is required but not provided.", owner2(), decl.Name.Name, decl.Type) } } } } func validateArgumentLiterals(c *opContext, args types.ArgumentList) { argNames := make(nameSet) for _, arg := range args { validateName(c.context, argNames, arg.Name, "UniqueArgumentNames", "argument") validateLiteral(c, arg.Value) } } func validateLiteral(c *opContext, l types.Value) { switch l := l.(type) { case *types.ObjectValue: fieldNames := make(nameSet) for _, f := range l.Fields { validateName(c.context, fieldNames, f.Name, "UniqueInputFieldNames", "input field") validateLiteral(c, f.Value) } case *types.ListValue: for _, entry := range l.Values { validateLiteral(c, entry) } case *types.Variable: for _, op := range c.ops { v := op.Vars.Get(l.Name) if v == nil { byOp := "" if op.Name.Name != "" { byOp = fmt.Sprintf(" by operation %q", op.Name.Name) } c.opErrs[op] = append(c.opErrs[op], &errors.QueryError{ Message: fmt.Sprintf("Variable %q is not defined%s.", "$"+l.Name, byOp), Locations: []errors.Location{l.Loc, op.Loc}, Rule: "NoUndefinedVariables", }) continue } validateValueType(c, l, resolveType(c.context, v.Type)) c.usedVars[op][v] = struct{}{} } } } func validateValueType(c *opContext, v types.Value, t types.Type) (bool, string) { if v, ok := v.(*types.Variable); ok { for _, op := range c.ops { if v2 := op.Vars.Get(v.Name); v2 != nil { t2, err := common.ResolveType(v2.Type, c.schema.Resolve) if _, ok := t2.(*types.NonNull); !ok && v2.Default != nil { t2 = &types.NonNull{OfType: t2} } if err == nil && !typeCanBeUsedAs(t2, t) { c.addErrMultiLoc([]errors.Location{v2.Loc, v.Loc}, "VariablesInAllowedPosition", "Variable %q of type %q used in position expecting type %q.", "$"+v.Name, t2, t) } } } return true, "" } if nn, ok := t.(*types.NonNull); ok { if isNull(v) { return false, fmt.Sprintf("Expected %q, found null.", t) } t = nn.OfType } if isNull(v) { return true, "" } switch t := t.(type) { case *types.ScalarTypeDefinition, *types.EnumTypeDefinition: if lit, ok := v.(*types.PrimitiveValue); ok { if validateBasicLit(lit, t) { return true, "" } return false, fmt.Sprintf("Expected type %q, found %s.", t, v) } return true, "" case *types.List: list, ok := v.(*types.ListValue) if !ok { return validateValueType(c, v, t.OfType) // single value instead of list } for i, entry := range list.Values { if ok, reason := validateValueType(c, entry, t.OfType); !ok { return false, fmt.Sprintf("In element #%d: %s", i, reason) } } return true, "" case *types.InputObject: v, ok := v.(*types.ObjectValue) if !ok { return false, fmt.Sprintf("Expected %q, found not an object.", t) } for _, f := range v.Fields { name := f.Name.Name iv := t.Values.Get(name) if iv == nil { return false, fmt.Sprintf("In field %q: Unknown field.", name) } if ok, reason := validateValueType(c, f.Value, iv.Type); !ok { return false, fmt.Sprintf("In field %q: %s", name, reason) } } for _, iv := range t.Values { found := false for _, f := range v.Fields { if f.Name.Name == iv.Name.Name { found = true break } } if !found { if _, ok := iv.Type.(*types.NonNull); ok && iv.Default == nil { return false, fmt.Sprintf("In field %q: Expected %q, found null.", iv.Name.Name, iv.Type) } } } return true, "" } return false, fmt.Sprintf("Expected type %q, found %s.", t, v) } func validateBasicLit(v *types.PrimitiveValue, t types.Type) bool { switch t := t.(type) { case *types.ScalarTypeDefinition: switch t.Name { case "Int": if v.Type != scanner.Int { return false } return validateBuiltInScalar(v.Text, "Int") case "Float": return (v.Type == scanner.Int || v.Type == scanner.Float) && validateBuiltInScalar(v.Text, "Float") case "String": return v.Type == scanner.String && validateBuiltInScalar(v.Text, "String") case "Boolean": return v.Type == scanner.Ident && validateBuiltInScalar(v.Text, "Boolean") case "ID": return (v.Type == scanner.Int && validateBuiltInScalar(v.Text, "Int")) || (v.Type == scanner.String && validateBuiltInScalar(v.Text, "String")) default: //TODO: Type-check against expected type by Unmarshalling return true } case *types.EnumTypeDefinition: if v.Type != scanner.Ident { return false } for _, option := range t.EnumValuesDefinition { if option.EnumValue == v.Text { return true } } return false } return false } func validateBuiltInScalar(v string, n string) bool { switch n { case "Int": f, err := strconv.ParseFloat(v, 64) if err != nil { return false } return f >= math.MinInt32 && f <= math.MaxInt32 case "Float": f, fe := strconv.ParseFloat(v, 64) return fe == nil && f >= math.SmallestNonzeroFloat64 && f <= math.MaxFloat64 case "String": vl := len(v) return vl >= 2 && v[0] == '"' && v[vl-1] == '"' case "Boolean": return v == "true" || v == "false" default: return false } } func canBeFragment(t types.Type) bool { switch t.(type) { case *types.ObjectTypeDefinition, *types.InterfaceTypeDefinition, *types.Union: return true default: return false } } func canBeInput(t types.Type) bool { switch t := t.(type) { case *types.InputObject, *types.ScalarTypeDefinition, *types.EnumTypeDefinition: return true case *types.List: return canBeInput(t.OfType) case *types.NonNull: return canBeInput(t.OfType) default: return false } } func hasSubfields(t types.Type) bool { switch t := t.(type) { case *types.ObjectTypeDefinition, *types.InterfaceTypeDefinition, *types.Union: return true case *types.List: return hasSubfields(t.OfType) case *types.NonNull: return hasSubfields(t.OfType) default: return false } } func isLeaf(t types.Type) bool { switch t.(type) { case *types.ScalarTypeDefinition, *types.EnumTypeDefinition: return true default: return false } } func isNull(lit interface{}) bool { _, ok := lit.(*types.NullValue) return ok } func typesCompatible(a, b types.Type) bool { al, aIsList := a.(*types.List) bl, bIsList := b.(*types.List) if aIsList || bIsList { return aIsList && bIsList && typesCompatible(al.OfType, bl.OfType) } ann, aIsNN := a.(*types.NonNull) bnn, bIsNN := b.(*types.NonNull) if aIsNN || bIsNN { return aIsNN && bIsNN && typesCompatible(ann.OfType, bnn.OfType) } if isLeaf(a) || isLeaf(b) { return a == b } return true } func typeCanBeUsedAs(t, as types.Type) bool { nnT, okT := t.(*types.NonNull) if okT { t = nnT.OfType } nnAs, okAs := as.(*types.NonNull) if okAs { as = nnAs.OfType if !okT { return false // nullable can not be used as non-null } } if t == as { return true } if lT, ok := t.(*types.List); ok { if lAs, ok := as.(*types.List); ok { return typeCanBeUsedAs(lT.OfType, lAs.OfType) } } return false } graphql-go-1.5.0/internal/validation/validation_test.go000066400000000000000000000032661435002731700232550ustar00rootroot00000000000000package validation_test import ( "os" "reflect" "sort" "testing" "encoding/json" "github.com/graph-gophers/graphql-go/errors" "github.com/graph-gophers/graphql-go/internal/query" "github.com/graph-gophers/graphql-go/internal/schema" "github.com/graph-gophers/graphql-go/internal/validation" "github.com/graph-gophers/graphql-go/types" ) type Test struct { Name string Rule string Schema int Query string Vars map[string]interface{} Errors []*errors.QueryError } func TestValidate(t *testing.T) { f, err := os.Open("testdata/tests.json") if err != nil { t.Fatal(err) } var testData struct { Schemas []string Tests []*Test } if err := json.NewDecoder(f).Decode(&testData); err != nil { t.Fatal(err) } schemas := make([]*types.Schema, len(testData.Schemas)) for i, schemaStr := range testData.Schemas { schemas[i] = schema.New() err := schema.Parse(schemas[i], schemaStr, false) if err != nil { t.Fatal(err) } } for _, test := range testData.Tests { t.Run(test.Name, func(t *testing.T) { d, err := query.Parse(test.Query) if err != nil { t.Fatal(err) } errs := validation.Validate(schemas[test.Schema], d, test.Vars, 0) got := []*errors.QueryError{} for _, err := range errs { if err.Rule == test.Rule { err.Rule = "" got = append(got, err) } } sortLocations(test.Errors) sortLocations(got) if !reflect.DeepEqual(test.Errors, got) { t.Errorf("wrong errors\nexpected: %v\ngot: %v", test.Errors, got) } }) } } func sortLocations(errs []*errors.QueryError) { for _, err := range errs { locs := err.Locations sort.Slice(locs, func(i, j int) bool { return locs[i].Before(locs[j]) }) } } graphql-go-1.5.0/introspection.go000066400000000000000000000041721435002731700170130ustar00rootroot00000000000000package graphql import ( "context" "encoding/json" "github.com/graph-gophers/graphql-go/internal/exec/resolvable" "github.com/graph-gophers/graphql-go/introspection" ) // Inspect allows inspection of the given schema. func (s *Schema) Inspect() *introspection.Schema { return introspection.WrapSchema(s.schema) } // ToJSON encodes the schema in a JSON format used by tools like Relay. func (s *Schema) ToJSON() ([]byte, error) { result := s.exec(context.Background(), introspectionQuery, "", nil, &resolvable.Schema{ Meta: s.res.Meta, Query: &resolvable.Object{}, Schema: *s.schema, }) if len(result.Errors) != 0 { panic(result.Errors[0]) } return json.MarshalIndent(result.Data, "", "\t") } var introspectionQuery = ` query { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description locations args { ...InputValue } } } } fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef } } fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } } } ` graphql-go-1.5.0/introspection/000077500000000000000000000000001435002731700164605ustar00rootroot00000000000000graphql-go-1.5.0/introspection/introspection.go000066400000000000000000000142171435002731700217140ustar00rootroot00000000000000package introspection import ( "sort" "github.com/graph-gophers/graphql-go/types" ) type Schema struct { schema *types.Schema } // WrapSchema is only used internally. func WrapSchema(schema *types.Schema) *Schema { return &Schema{schema} } func (r *Schema) Types() []*Type { var names []string for name := range r.schema.Types { names = append(names, name) } sort.Strings(names) l := make([]*Type, len(names)) for i, name := range names { l[i] = &Type{r.schema.Types[name]} } return l } func (r *Schema) Directives() []*Directive { var names []string for name := range r.schema.Directives { names = append(names, name) } sort.Strings(names) l := make([]*Directive, len(names)) for i, name := range names { l[i] = &Directive{r.schema.Directives[name]} } return l } func (r *Schema) QueryType() *Type { t, ok := r.schema.EntryPoints["query"] if !ok { return nil } return &Type{t} } func (r *Schema) MutationType() *Type { t, ok := r.schema.EntryPoints["mutation"] if !ok { return nil } return &Type{t} } func (r *Schema) SubscriptionType() *Type { t, ok := r.schema.EntryPoints["subscription"] if !ok { return nil } return &Type{t} } type Type struct { typ types.Type } // WrapType is only used internally. func WrapType(typ types.Type) *Type { return &Type{typ} } func (r *Type) Kind() string { return r.typ.Kind() } func (r *Type) Name() *string { if named, ok := r.typ.(types.NamedType); ok { name := named.TypeName() return &name } return nil } func (r *Type) Description() *string { if named, ok := r.typ.(types.NamedType); ok { desc := named.Description() if desc == "" { return nil } return &desc } return nil } func (r *Type) Fields(args *struct{ IncludeDeprecated bool }) *[]*Field { var fields types.FieldsDefinition switch t := r.typ.(type) { case *types.ObjectTypeDefinition: fields = t.Fields case *types.InterfaceTypeDefinition: fields = t.Fields default: return nil } var l []*Field for _, f := range fields { if d := f.Directives.Get("deprecated"); d == nil || args.IncludeDeprecated { l = append(l, &Field{field: f}) } } return &l } func (r *Type) Interfaces() *[]*Type { t, ok := r.typ.(*types.ObjectTypeDefinition) if !ok { return nil } l := make([]*Type, len(t.Interfaces)) for i, intf := range t.Interfaces { l[i] = &Type{intf} } return &l } func (r *Type) PossibleTypes() *[]*Type { var possibleTypes []*types.ObjectTypeDefinition switch t := r.typ.(type) { case *types.InterfaceTypeDefinition: possibleTypes = t.PossibleTypes case *types.Union: possibleTypes = t.UnionMemberTypes default: return nil } l := make([]*Type, len(possibleTypes)) for i, intf := range possibleTypes { l[i] = &Type{intf} } return &l } func (r *Type) EnumValues(args *struct{ IncludeDeprecated bool }) *[]*EnumValue { t, ok := r.typ.(*types.EnumTypeDefinition) if !ok { return nil } var l []*EnumValue for _, v := range t.EnumValuesDefinition { if d := v.Directives.Get("deprecated"); d == nil || args.IncludeDeprecated { l = append(l, &EnumValue{v}) } } return &l } func (r *Type) InputFields() *[]*InputValue { t, ok := r.typ.(*types.InputObject) if !ok { return nil } l := make([]*InputValue, len(t.Values)) for i, v := range t.Values { l[i] = &InputValue{v} } return &l } func (r *Type) OfType() *Type { switch t := r.typ.(type) { case *types.List: return &Type{t.OfType} case *types.NonNull: return &Type{t.OfType} default: return nil } } func (r *Type) SpecifiedByURL() *string { switch t := r.typ.(type) { case *types.ScalarTypeDefinition: if d := t.Directives.Get("specifiedBy"); d != nil { arg := d.Arguments.MustGet("url") url := arg.Deserialize(nil).(string) return &url } default: return nil } return nil } type Field struct { field *types.FieldDefinition } func (r *Field) Name() string { return r.field.Name } func (r *Field) Description() *string { if r.field.Desc == "" { return nil } return &r.field.Desc } func (r *Field) Args() []*InputValue { l := make([]*InputValue, len(r.field.Arguments)) for i, v := range r.field.Arguments { l[i] = &InputValue{v} } return l } func (r *Field) Type() *Type { return &Type{r.field.Type} } func (r *Field) IsDeprecated() bool { return r.field.Directives.Get("deprecated") != nil } func (r *Field) DeprecationReason() *string { d := r.field.Directives.Get("deprecated") if d == nil { return nil } reason := d.Arguments.MustGet("reason").Deserialize(nil).(string) return &reason } type InputValue struct { value *types.InputValueDefinition } func (r *InputValue) Name() string { return r.value.Name.Name } func (r *InputValue) Description() *string { if r.value.Desc == "" { return nil } return &r.value.Desc } func (r *InputValue) Type() *Type { return &Type{r.value.Type} } func (r *InputValue) DefaultValue() *string { if r.value.Default == nil { return nil } s := r.value.Default.String() return &s } type EnumValue struct { value *types.EnumValueDefinition } func (r *EnumValue) Name() string { return r.value.EnumValue } func (r *EnumValue) Description() *string { if r.value.Desc == "" { return nil } return &r.value.Desc } func (r *EnumValue) IsDeprecated() bool { return r.value.Directives.Get("deprecated") != nil } func (r *EnumValue) DeprecationReason() *string { d := r.value.Directives.Get("deprecated") if d == nil { return nil } reason := d.Arguments.MustGet("reason").Deserialize(nil).(string) return &reason } type Directive struct { directive *types.DirectiveDefinition } func (r *Directive) Name() string { return r.directive.Name } func (r *Directive) Description() *string { if r.directive.Desc == "" { return nil } return &r.directive.Desc } func (r *Directive) Locations() []string { return r.directive.Locations } func (r *Directive) Args() []*InputValue { l := make([]*InputValue, len(r.directive.Arguments)) for i, v := range r.directive.Arguments { l[i] = &InputValue{v} } return l } type Service struct { schema *types.Schema } // WrapService is only used internally. func WrapService(schema *types.Schema) *Service { return &Service{schema} } func (r *Service) SDL() string { return r.schema.SchemaString } graphql-go-1.5.0/introspection_test.go000066400000000000000000000037151435002731700200540ustar00rootroot00000000000000package graphql_test import ( "bytes" "encoding/json" "io/ioutil" "testing" "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/example/social" "github.com/graph-gophers/graphql-go/example/starwars" ) var socialSchema = graphql.MustParseSchema(social.Schema, &social.Resolver{}, graphql.UseFieldResolvers()) func TestSchema_ToJSON(t *testing.T) { t.Parallel() type args struct { Schema *graphql.Schema } type want struct { JSON []byte } testTable := []struct { Name string Args args Want want }{ { Name: "Social Schema", Args: args{Schema: socialSchema}, Want: want{JSON: mustReadFile("example/social/introspect.json")}, }, { Name: "Star Wars Schema", Args: args{Schema: graphql.MustParseSchema(starwars.Schema, &starwars.Resolver{})}, Want: want{JSON: mustReadFile("example/starwars/introspect.json")}, }, { Name: "Star Wars Schema without Resolver", Args: args{Schema: graphql.MustParseSchema(starwars.Schema, nil)}, Want: want{JSON: mustReadFile("example/starwars/introspect.json")}, }, } for _, tt := range testTable { t.Run(tt.Name, func(t *testing.T) { j, err := tt.Args.Schema.ToJSON() if err != nil { t.Fatalf("invalid schema %s", err.Error()) } // Verify JSON to avoid red herring errors. got, err := formatJSON(j) if err != nil { t.Fatalf("got: invalid JSON: %s", err) } want, err := formatJSON(tt.Want.JSON) if err != nil { t.Fatalf("want: invalid JSON: %s", err) } if !bytes.Equal(got, want) { t.Logf("got: %s", got) t.Logf("want: %s", want) t.Fail() } }) } } func formatJSON(data []byte) ([]byte, error) { var v interface{} if err := json.Unmarshal(data, &v); err != nil { return nil, err } formatted, err := json.Marshal(v) if err != nil { return nil, err } return formatted, nil } func mustReadFile(filename string) []byte { b, err := ioutil.ReadFile(filename) if err != nil { panic(err) } return b } graphql-go-1.5.0/log/000077500000000000000000000000001435002731700143415ustar00rootroot00000000000000graphql-go-1.5.0/log/log.go000066400000000000000000000012731435002731700154540ustar00rootroot00000000000000package log import ( "context" "log" "runtime" ) // Logger is the interface used to log panics that occur during query execution. It is settable via graphql.ParseSchema type Logger interface { LogPanic(ctx context.Context, value interface{}) } // DefaultLogger is the default logger used to log panics that occur during query execution type DefaultLogger struct{} // LogPanic is used to log recovered panic values that occur during query execution func (l *DefaultLogger) LogPanic(ctx context.Context, value interface{}) { const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] log.Printf("graphql: panic occurred: %v\n%s\ncontext: %v", value, buf, ctx) } graphql-go-1.5.0/nullable_types.go000066400000000000000000000066001435002731700171330ustar00rootroot00000000000000package graphql import ( "fmt" "math" ) // NullString is a string that can be null. Use it in input structs to // differentiate a value explicitly set to null from an omitted value. // When the value is defined (either null or a value) Set is true. type NullString struct { Value *string Set bool } func (NullString) ImplementsGraphQLType(name string) bool { return name == "String" } func (s *NullString) UnmarshalGraphQL(input interface{}) error { s.Set = true if input == nil { return nil } switch v := input.(type) { case string: s.Value = &v return nil default: return fmt.Errorf("wrong type for String: %T", v) } } func (s *NullString) Nullable() {} // NullBool is a string that can be null. Use it in input structs to // differentiate a value explicitly set to null from an omitted value. // When the value is defined (either null or a value) Set is true. type NullBool struct { Value *bool Set bool } func (NullBool) ImplementsGraphQLType(name string) bool { return name == "Boolean" } func (s *NullBool) UnmarshalGraphQL(input interface{}) error { s.Set = true if input == nil { return nil } switch v := input.(type) { case bool: s.Value = &v return nil default: return fmt.Errorf("wrong type for Boolean: %T", v) } } func (s *NullBool) Nullable() {} // NullInt is a string that can be null. Use it in input structs to // differentiate a value explicitly set to null from an omitted value. // When the value is defined (either null or a value) Set is true. type NullInt struct { Value *int32 Set bool } func (NullInt) ImplementsGraphQLType(name string) bool { return name == "Int" } func (s *NullInt) UnmarshalGraphQL(input interface{}) error { s.Set = true if input == nil { return nil } switch v := input.(type) { case int32: s.Value = &v return nil case float64: coerced := int32(v) if v < math.MinInt32 || v > math.MaxInt32 || float64(coerced) != v { return fmt.Errorf("not a 32-bit integer") } s.Value = &coerced return nil default: return fmt.Errorf("wrong type for Int: %T", v) } } func (s *NullInt) Nullable() {} // NullFloat is a string that can be null. Use it in input structs to // differentiate a value explicitly set to null from an omitted value. // When the value is defined (either null or a value) Set is true. type NullFloat struct { Value *float64 Set bool } func (NullFloat) ImplementsGraphQLType(name string) bool { return name == "Float" } func (s *NullFloat) UnmarshalGraphQL(input interface{}) error { s.Set = true if input == nil { return nil } switch v := input.(type) { case float64: s.Value = &v return nil case int32: coerced := float64(v) s.Value = &coerced return nil case int: coerced := float64(v) s.Value = &coerced return nil default: return fmt.Errorf("wrong type for Float: %T", v) } } func (s *NullFloat) Nullable() {} // NullTime is a string that can be null. Use it in input structs to // differentiate a value explicitly set to null from an omitted value. // When the value is defined (either null or a value) Set is true. type NullTime struct { Value *Time Set bool } func (NullTime) ImplementsGraphQLType(name string) bool { return name == "Time" } func (s *NullTime) UnmarshalGraphQL(input interface{}) error { s.Set = true if input == nil { return nil } s.Value = new(Time) return s.Value.UnmarshalGraphQL(input) } func (s *NullTime) Nullable() {} graphql-go-1.5.0/nullable_types_test.go000066400000000000000000000074721435002731700202020ustar00rootroot00000000000000package graphql_test import ( "math" "testing" . "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/decode" ) func TestNullInt_ImplementsUnmarshaler(t *testing.T) { defer func() { if err := recover(); err != nil { t.Error(err) } }() // assert *NullInt implements decode.Unmarshaler interface var _ decode.Unmarshaler = (*NullInt)(nil) } func TestNullInt_UnmarshalGraphQL(t *testing.T) { type args struct { input interface{} } a := float64(math.MaxInt32 + 1) b := float64(math.MinInt32 - 1) c := 1234.6 good := int32(1234) ref := NullInt{ Value: &good, Set: true, } t.Run("invalid", func(t *testing.T) { tests := []struct { name string args args wantErr string }{ { name: "boolean", args: args{input: true}, wantErr: "wrong type for Int: bool", }, { name: "int32 out of range (+)", args: args{ input: a, }, wantErr: "not a 32-bit integer", }, { name: "int32 out of range (-)", args: args{ input: b, }, wantErr: "not a 32-bit integer", }, { name: "non-integer", args: args{ input: c, }, wantErr: "not a 32-bit integer", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gt := new(NullInt) if err := gt.UnmarshalGraphQL(tt.args.input); err != nil { if err.Error() != tt.wantErr { t.Errorf("UnmarshalGraphQL() error = %v, want = %s", err, tt.wantErr) } return } t.Error("UnmarshalGraphQL() expected error not raised") }) } }) tests := []struct { name string args args wantEq NullInt }{ { name: "int32", args: args{ input: good, }, wantEq: ref, }, { name: "float64", args: args{ input: float64(good), }, wantEq: ref, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gt := new(NullInt) if err := gt.UnmarshalGraphQL(tt.args.input); err != nil { t.Errorf("UnmarshalGraphQL() error = %v", err) return } if *gt.Value != *tt.wantEq.Value { t.Errorf("UnmarshalGraphQL() got = %v, want = %v", *gt.Value, *tt.wantEq.Value) } }) } } func TestNullFloat_ImplementsUnmarshaler(t *testing.T) { defer func() { if err := recover(); err != nil { t.Error(err) } }() // assert *NullFloat implements decode.Unmarshaler interface var _ decode.Unmarshaler = (*NullFloat)(nil) } func TestNullFloat_UnmarshalGraphQL(t *testing.T) { type args struct { input interface{} } good := float64(1234) ref := NullFloat{ Value: &good, Set: true, } t.Run("invalid", func(t *testing.T) { tests := []struct { name string args args wantErr string }{ { name: "boolean", args: args{input: true}, wantErr: "wrong type for Float: bool", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gt := new(NullFloat) if err := gt.UnmarshalGraphQL(tt.args.input); err != nil { if err.Error() != tt.wantErr { t.Errorf("UnmarshalGraphQL() error = %v, want = %s", err, tt.wantErr) } return } t.Error("UnmarshalGraphQL() expected error not raised") }) } }) tests := []struct { name string args args wantEq NullFloat }{ { name: "int", args: args{ input: int(good), }, wantEq: ref, }, { name: "int32", args: args{ input: int32(good), }, wantEq: ref, }, { name: "float64", args: args{ input: good, }, wantEq: ref, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gt := new(NullFloat) if err := gt.UnmarshalGraphQL(tt.args.input); err != nil { t.Errorf("UnmarshalGraphQL() error = %v", err) return } if *gt.Value != *tt.wantEq.Value { t.Errorf("UnmarshalGraphQL() got = %v, want = %v", *gt.Value, *tt.wantEq.Value) } }) } } graphql-go-1.5.0/relay/000077500000000000000000000000001435002731700146745ustar00rootroot00000000000000graphql-go-1.5.0/relay/relay.go000066400000000000000000000031711435002731700163410ustar00rootroot00000000000000package relay import ( "encoding/base64" "encoding/json" "errors" "fmt" "net/http" "strings" graphql "github.com/graph-gophers/graphql-go" ) func MarshalID(kind string, spec interface{}) graphql.ID { d, err := json.Marshal(spec) if err != nil { panic(fmt.Errorf("relay.MarshalID: %s", err)) } return graphql.ID(base64.URLEncoding.EncodeToString(append([]byte(kind+":"), d...))) } func UnmarshalKind(id graphql.ID) string { s, err := base64.URLEncoding.DecodeString(string(id)) if err != nil { return "" } i := strings.IndexByte(string(s), ':') if i == -1 { return "" } return string(s[:i]) } func UnmarshalSpec(id graphql.ID, v interface{}) error { s, err := base64.URLEncoding.DecodeString(string(id)) if err != nil { return err } i := strings.IndexByte(string(s), ':') if i == -1 { return errors.New("invalid graphql.ID") } return json.Unmarshal(s[i+1:], v) } type Handler struct { Schema *graphql.Schema } func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var params struct { Query string `json:"query"` OperationName string `json:"operationName"` Variables map[string]interface{} `json:"variables"` } if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } response := h.Schema.Exec(r.Context(), params.Query, params.OperationName, params.Variables) responseJSON, err := json.Marshal(response) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Write(responseJSON) } graphql-go-1.5.0/relay/relay_test.go000066400000000000000000000020751435002731700174020ustar00rootroot00000000000000package relay_test import ( "net/http/httptest" "strings" "testing" "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/example/starwars" "github.com/graph-gophers/graphql-go/relay" ) var starwarsSchema = graphql.MustParseSchema(starwars.Schema, &starwars.Resolver{}) func TestServeHTTP(t *testing.T) { w := httptest.NewRecorder() r := httptest.NewRequest("POST", "/some/path/here", strings.NewReader(`{"query":"{ hero { name } }", "operationName":"", "variables": null}`)) h := relay.Handler{Schema: starwarsSchema} h.ServeHTTP(w, r) if w.Code != 200 { t.Fatalf("Expected status code 200, got %d.", w.Code) } contentType := w.Header().Get("Content-Type") if contentType != "application/json" { t.Fatalf("Invalid content-type. Expected [application/json], but instead got [%s]", contentType) } expectedResponse := `{"data":{"hero":{"name":"R2-D2"}}}` actualResponse := w.Body.String() if expectedResponse != actualResponse { t.Fatalf("Invalid response. Expected [%s], but instead got [%s]", expectedResponse, actualResponse) } } graphql-go-1.5.0/scripts/000077500000000000000000000000001435002731700152475ustar00rootroot00000000000000graphql-go-1.5.0/scripts/golangci_install.sh000077500000000000000000000247621435002731700211320ustar00rootroot00000000000000#!/bin/sh set -e # Code generated by godownloader. DO NOT EDIT. # usage() { this=$1 cat </dev/null } echoerr() { echo "$@" 1>&2 } log_prefix() { echo "$0" } _logp=6 log_set_priority() { _logp="$1" } log_priority() { if test -z "$1"; then echo "$_logp" return fi [ "$1" -le "$_logp" ] } log_tag() { case $1 in 0) echo "emerg" ;; 1) echo "alert" ;; 2) echo "crit" ;; 3) echo "err" ;; 4) echo "warning" ;; 5) echo "notice" ;; 6) echo "info" ;; 7) echo "debug" ;; *) echo "$1" ;; esac } log_debug() { log_priority 7 || return 0 echoerr "$(log_prefix)" "$(log_tag 7)" "$@" } log_info() { log_priority 6 || return 0 echoerr "$(log_prefix)" "$(log_tag 6)" "$@" } log_err() { log_priority 3 || return 0 echoerr "$(log_prefix)" "$(log_tag 3)" "$@" } log_crit() { log_priority 2 || return 0 echoerr "$(log_prefix)" "$(log_tag 2)" "$@" } uname_os() { os=$(uname -s | tr '[:upper:]' '[:lower:]') case "$os" in cygwin_nt*) os="windows" ;; mingw*) os="windows" ;; msys_nt*) os="windows" ;; esac echo "$os" } uname_arch() { arch=$(uname -m) case $arch in x86_64) arch="amd64" ;; x86) arch="386" ;; i686) arch="386" ;; i386) arch="386" ;; aarch64) arch="arm64" ;; armv5*) arch="armv5" ;; armv6*) arch="armv6" ;; armv7*) arch="armv7" ;; esac echo ${arch} } uname_os_check() { os=$(uname_os) case "$os" in darwin) return 0 ;; dragonfly) return 0 ;; freebsd) return 0 ;; linux) return 0 ;; android) return 0 ;; nacl) return 0 ;; netbsd) return 0 ;; openbsd) return 0 ;; plan9) return 0 ;; solaris) return 0 ;; windows) return 0 ;; esac log_crit "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib" return 1 } uname_arch_check() { arch=$(uname_arch) case "$arch" in 386) return 0 ;; amd64) return 0 ;; arm64) return 0 ;; armv5) return 0 ;; armv6) return 0 ;; armv7) return 0 ;; ppc64) return 0 ;; ppc64le) return 0 ;; mips) return 0 ;; mipsle) return 0 ;; mips64) return 0 ;; mips64le) return 0 ;; s390x) return 0 ;; amd64p32) return 0 ;; esac log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib" return 1 } untar() { tarball=$1 case "${tarball}" in *.tar.gz | *.tgz) tar --no-same-owner -xzf "${tarball}" ;; *.tar) tar --no-same-owner -xf "${tarball}" ;; *.zip) unzip "${tarball}" ;; *) log_err "untar unknown archive format for ${tarball}" return 1 ;; esac } http_download_curl() { local_file=$1 source_url=$2 header=$3 if [ -z "$header" ]; then code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url") else code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url") fi if [ "$code" != "200" ]; then log_debug "http_download_curl received HTTP status $code" return 1 fi return 0 } http_download_wget() { local_file=$1 source_url=$2 header=$3 if [ -z "$header" ]; then wget -q -O "$local_file" "$source_url" else wget -q --header "$header" -O "$local_file" "$source_url" fi } http_download() { log_debug "http_download $2" if is_command curl; then http_download_curl "$@" return elif is_command wget; then http_download_wget "$@" return fi log_crit "http_download unable to find wget or curl" return 1 } http_copy() { tmp=$(mktemp) http_download "${tmp}" "$1" "$2" || return 1 body=$(cat "$tmp") rm -f "${tmp}" echo "$body" } github_release() { owner_repo=$1 version=$2 test -z "$version" && version="latest" giturl="https://github.com/${owner_repo}/releases/${version}" json=$(http_copy "$giturl" "Accept:application/json") test -z "$json" && return 1 version=$(echo "$json" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//') test -z "$version" && return 1 echo "$version" } hash_sha256() { TARGET=${1:-/dev/stdin} if is_command gsha256sum; then hash=$(gsha256sum "$TARGET") || return 1 echo "$hash" | cut -d ' ' -f 1 elif is_command sha256sum; then hash=$(sha256sum "$TARGET") || return 1 echo "$hash" | cut -d ' ' -f 1 elif is_command shasum; then hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1 echo "$hash" | cut -d ' ' -f 1 elif is_command openssl; then hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1 echo "$hash" | cut -d ' ' -f a else log_crit "hash_sha256 unable to find command to compute sha-256 hash" return 1 fi } hash_sha256_verify() { TARGET=$1 checksums=$2 if [ -z "$checksums" ]; then log_err "hash_sha256_verify checksum file not specified in arg2" return 1 fi BASENAME=${TARGET##*/} want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1) if [ -z "$want" ]; then log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'" return 1 fi got=$(hash_sha256 "$TARGET") if [ "$want" != "$got" ]; then log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got" return 1 fi } cat /dev/null <= 1e10 { sec := input / 1e9 nsec := input - (sec * 1e9) t.Time = time.Unix(sec, nsec) } else { t.Time = time.Unix(input, 0) } return nil case float64: t.Time = time.Unix(int64(input), 0) return nil default: return fmt.Errorf("wrong type for Time: %T", input) } } // MarshalJSON is a custom marshaler for Time // // This function will be called whenever you // query for fields that use the Time type func (t Time) MarshalJSON() ([]byte, error) { return json.Marshal(t.Time) } graphql-go-1.5.0/time_test.go000066400000000000000000000063041435002731700161070ustar00rootroot00000000000000package graphql_test import ( "bytes" "encoding/json" "testing" "time" . "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/decode" ) func TestTime_ImplementsUnmarshaler(t *testing.T) { defer func() { if err := recover(); err != nil { t.Error(err) } }() // assert *Time implements decode.Unmarshaler interface var _ decode.Unmarshaler = (*Time)(nil) } func TestTime_ImplementsGraphQLType(t *testing.T) { gt := &Time{} if gt.ImplementsGraphQLType("foobar") { t.Error("Type *Time must not claim to implement GraphQL type 'foobar'") } if !gt.ImplementsGraphQLType("Time") { t.Error("Failed asserting *Time implements GraphQL type Time") } } func TestTime_MarshalJSON(t *testing.T) { var err error var b1, b2 []byte ref := time.Date(2021, time.April, 20, 12, 3, 23, 551476231, time.UTC) if b1, err = json.Marshal(ref); err != nil { t.Error(err) return } if b2, err = json.Marshal(Time{Time: ref}); err != nil { t.Errorf("MarshalJSON() error = %v", err) return } if !bytes.Equal(b1, b2) { t.Errorf("MarshalJSON() got = %s, want = %s", b2, b1) } } func TestTime_UnmarshalGraphQL(t *testing.T) { type args struct { input interface{} } ref := time.Date(2021, time.April, 20, 12, 3, 23, 551476231, time.UTC) refZeroNano := time.Unix(ref.Unix(), 0) t.Run("invalid", func(t *testing.T) { tests := []struct { name string args args wantErr string }{ { name: "boolean", args: args{input: true}, wantErr: "wrong type for Time: bool", }, { name: "invalid format", args: args{input: ref.Format(time.ANSIC)}, wantErr: `parsing time "Tue Apr 20 12:03:23 2021" as "2006-01-02T15:04:05Z07:00": cannot parse "Tue Apr 20 12:03:23 2021" as "2006"`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gt := new(Time) if err := gt.UnmarshalGraphQL(tt.args.input); err != nil { if err.Error() != tt.wantErr { t.Errorf("UnmarshalGraphQL() error = %v, want = %s", err, tt.wantErr) } return } t.Error("UnmarshalGraphQL() expected error not raised") }) } }) tests := []struct { name string args args wantEq time.Time }{ { name: "time.Time", args: args{ input: ref, }, wantEq: ref, }, { name: "string", args: args{ input: ref.Format(time.RFC3339), }, wantEq: refZeroNano, }, { name: "bytes", args: args{ input: []byte(ref.Format(time.RFC3339)), }, wantEq: refZeroNano, }, { name: "int32", args: args{ input: int32(ref.Unix()), }, wantEq: refZeroNano, }, { name: "int64", args: args{ input: ref.Unix(), }, wantEq: refZeroNano, }, { name: "int64-nano", args: args{ input: ref.UnixNano(), }, wantEq: ref, }, { name: "float64", args: args{ input: float64(ref.Unix()), }, wantEq: refZeroNano, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gt := &Time{} if err := gt.UnmarshalGraphQL(tt.args.input); err != nil { t.Errorf("UnmarshalGraphQL() error = %v", err) return } if !gt.Equal(tt.wantEq) { t.Errorf("UnmarshalGraphQL() got = %v, want = %v", gt, tt.wantEq) } }) } } graphql-go-1.5.0/trace/000077500000000000000000000000001435002731700146565ustar00rootroot00000000000000graphql-go-1.5.0/trace/noop/000077500000000000000000000000001435002731700156315ustar00rootroot00000000000000graphql-go-1.5.0/trace/noop/trace.go000066400000000000000000000015361435002731700172630ustar00rootroot00000000000000// Package noop defines a no-op tracer implementation. package noop import ( "context" "github.com/graph-gophers/graphql-go/errors" "github.com/graph-gophers/graphql-go/introspection" ) // Tracer is a no-op tracer that does nothing. type Tracer struct{} func (Tracer) TraceQuery(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, varTypes map[string]*introspection.Type) (context.Context, func([]*errors.QueryError)) { return ctx, func(errs []*errors.QueryError) {} } func (Tracer) TraceField(ctx context.Context, label, typeName, fieldName string, trivial bool, args map[string]interface{}) (context.Context, func(*errors.QueryError)) { return ctx, func(err *errors.QueryError) {} } func (Tracer) TraceValidation(context.Context) func([]*errors.QueryError) { return func(errs []*errors.QueryError) {} } graphql-go-1.5.0/trace/noop/trace_test.go000066400000000000000000000010311435002731700203100ustar00rootroot00000000000000package noop_test import ( "testing" "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/example/starwars" "github.com/graph-gophers/graphql-go/trace/noop" "github.com/graph-gophers/graphql-go/trace/tracer" ) func TestInterfaceImplementation(t *testing.T) { var _ tracer.ValidationTracer = &noop.Tracer{} var _ tracer.Tracer = &noop.Tracer{} } func TestTracerOption(t *testing.T) { _, err := graphql.ParseSchema(starwars.Schema, nil, graphql.Tracer(noop.Tracer{})) if err != nil { t.Fatal(err) } } graphql-go-1.5.0/trace/opentracing/000077500000000000000000000000001435002731700171675ustar00rootroot00000000000000graphql-go-1.5.0/trace/opentracing/trace.go000066400000000000000000000042661435002731700206240ustar00rootroot00000000000000package opentracing import ( "context" "fmt" "github.com/graph-gophers/graphql-go/errors" "github.com/graph-gophers/graphql-go/introspection" opentracing "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" "github.com/opentracing/opentracing-go/log" ) // Tracer implements the graphql-go Tracer interface and creates OpenTracing spans. type Tracer struct{} func (Tracer) TraceQuery(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, varTypes map[string]*introspection.Type) (context.Context, func([]*errors.QueryError)) { span, spanCtx := opentracing.StartSpanFromContext(ctx, "GraphQL request") span.SetTag("graphql.query", queryString) if operationName != "" { span.SetTag("graphql.operationName", operationName) } if len(variables) != 0 { span.LogFields(log.Object("graphql.variables", variables)) } return spanCtx, func(errs []*errors.QueryError) { if len(errs) > 0 { msg := errs[0].Error() if len(errs) > 1 { msg += fmt.Sprintf(" (and %d more errors)", len(errs)-1) } ext.Error.Set(span, true) span.SetTag("graphql.error", msg) } span.Finish() } } func (Tracer) TraceField(ctx context.Context, label, typeName, fieldName string, trivial bool, args map[string]interface{}) (context.Context, func(*errors.QueryError)) { if trivial { return ctx, noop } span, spanCtx := opentracing.StartSpanFromContext(ctx, label) span.SetTag("graphql.type", typeName) span.SetTag("graphql.field", fieldName) for name, value := range args { span.SetTag("graphql.args."+name, value) } return spanCtx, func(err *errors.QueryError) { if err != nil { ext.Error.Set(span, true) span.SetTag("graphql.error", err.Error()) } span.Finish() } } func (Tracer) TraceValidation(ctx context.Context) func([]*errors.QueryError) { span, _ := opentracing.StartSpanFromContext(ctx, "Validate Query") return func(errs []*errors.QueryError) { if len(errs) > 0 { msg := errs[0].Error() if len(errs) > 1 { msg += fmt.Sprintf(" (and %d more errors)", len(errs)-1) } ext.Error.Set(span, true) span.SetTag("graphql.error", msg) } span.Finish() } } func noop(*errors.QueryError) {} graphql-go-1.5.0/trace/opentracing/trace_test.go000066400000000000000000000010741435002731700216550ustar00rootroot00000000000000package opentracing_test import ( "testing" "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/example/starwars" "github.com/graph-gophers/graphql-go/trace/opentracing" "github.com/graph-gophers/graphql-go/trace/tracer" ) func TestInterfaceImplementation(t *testing.T) { var _ tracer.ValidationTracer = &opentracing.Tracer{} var _ tracer.Tracer = &opentracing.Tracer{} } func TestTracerOption(t *testing.T) { _, err := graphql.ParseSchema(starwars.Schema, nil, graphql.Tracer(opentracing.Tracer{})) if err != nil { t.Fatal(err) } } graphql-go-1.5.0/trace/otel/000077500000000000000000000000001435002731700156215ustar00rootroot00000000000000graphql-go-1.5.0/trace/otel/trace.go000066400000000000000000000052251435002731700172520ustar00rootroot00000000000000package otel import ( "context" "fmt" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" oteltrace "go.opentelemetry.io/otel/trace" "github.com/graph-gophers/graphql-go/errors" "github.com/graph-gophers/graphql-go/introspection" ) // DefaultTracer creates a tracer using a default name. func DefaultTracer() *Tracer { return &Tracer{ Tracer: otel.Tracer("graphql-go"), } } // Tracer is an OpenTelemetry implementation for graphql-go. Set the Tracer // property to your tracer instance as required. type Tracer struct { Tracer oteltrace.Tracer } func (t *Tracer) TraceQuery(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, varTypes map[string]*introspection.Type) (context.Context, func([]*errors.QueryError)) { spanCtx, span := t.Tracer.Start(ctx, "GraphQL Request") var attributes []attribute.KeyValue attributes = append(attributes, attribute.String("graphql.query", queryString)) if operationName != "" { attributes = append(attributes, attribute.String("graphql.operationName", operationName)) } if len(variables) != 0 { attributes = append(attributes, attribute.String("graphql.variables", fmt.Sprintf("%v", variables))) } span.SetAttributes(attributes...) return spanCtx, func(errs []*errors.QueryError) { if len(errs) > 0 { msg := errs[0].Error() if len(errs) > 1 { msg += fmt.Sprintf(" (and %d more errors)", len(errs)-1) } span.SetStatus(codes.Error, msg) } span.End() } } func (t *Tracer) TraceField(ctx context.Context, label, typeName, fieldName string, trivial bool, args map[string]interface{}) (context.Context, func(*errors.QueryError)) { if trivial { return ctx, func(*errors.QueryError) {} } var attributes []attribute.KeyValue spanCtx, span := t.Tracer.Start(ctx, fmt.Sprintf("Field: %v", label)) attributes = append(attributes, attribute.String("graphql.type", typeName)) attributes = append(attributes, attribute.String("graphql.field", fieldName)) for name, value := range args { attributes = append(attributes, attribute.String("graphql.args."+name, fmt.Sprintf("%v", value))) } span.SetAttributes(attributes...) return spanCtx, func(err *errors.QueryError) { if err != nil { span.SetStatus(codes.Error, err.Error()) } span.End() } } func (t *Tracer) TraceValidation(ctx context.Context) func([]*errors.QueryError) { _, span := t.Tracer.Start(ctx, "GraphQL Validate") return func(errs []*errors.QueryError) { if len(errs) > 0 { msg := errs[0].Error() if len(errs) > 1 { msg += fmt.Sprintf(" (and %d more errors)", len(errs)-1) } span.SetStatus(codes.Error, msg) } span.End() } } graphql-go-1.5.0/trace/otel/trace_test.go000066400000000000000000000013731435002731700203110ustar00rootroot00000000000000package otel_test import ( "testing" "go.opentelemetry.io/otel" "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/example/starwars" otelgraphql "github.com/graph-gophers/graphql-go/trace/otel" "github.com/graph-gophers/graphql-go/trace/tracer" ) func TestInterfaceImplementation(t *testing.T) { var _ tracer.ValidationTracer = &otelgraphql.Tracer{} var _ tracer.Tracer = &otelgraphql.Tracer{} } func TestTracerOption(t *testing.T) { _, err := graphql.ParseSchema(starwars.Schema, nil, graphql.Tracer(otelgraphql.DefaultTracer())) if err != nil { t.Fatal(err) } _, err = graphql.ParseSchema(starwars.Schema, nil, graphql.Tracer(&otelgraphql.Tracer{Tracer: otel.Tracer("example")})) if err != nil { t.Fatal(err) } } graphql-go-1.5.0/trace/trace.go000066400000000000000000000017151435002731700163070ustar00rootroot00000000000000// The trace package provides tracing functionality. // Deprecated: this package has been deprecated. Use package trace/tracer instead. package trace import ( "github.com/graph-gophers/graphql-go/errors" "github.com/graph-gophers/graphql-go/trace/noop" "github.com/graph-gophers/graphql-go/trace/opentracing" "github.com/graph-gophers/graphql-go/trace/tracer" ) // Deprecated: this type has been deprecated. Use tracer.QueryFinishFunc instead. type TraceQueryFinishFunc = func([]*errors.QueryError) // Deprecated: this type has been deprecated. Use tarcer.FieldFinishFunc instead. type TraceFieldFinishFunc = func(*errors.QueryError) // Deprecated: this interface has been deprecated. Use tracer.Tracer instead. type Tracer = tracer.Tracer // Deprecated: this type has been deprecated. Use opentracing.Tracer instead. type OpenTracingTracer = opentracing.Tracer // Deprecated: this type has been deprecated. Use noop.Tracer instead. type NoopTracer = noop.Tracer graphql-go-1.5.0/trace/trace_test.go000066400000000000000000000023651435002731700173500ustar00rootroot00000000000000package trace_test import ( "testing" "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/errors" "github.com/graph-gophers/graphql-go/example/starwars" "github.com/graph-gophers/graphql-go/trace" "github.com/graph-gophers/graphql-go/trace/tracer" ) func TestInterfaceImplementation(t *testing.T) { var _ tracer.ValidationTracer = &trace.OpenTracingTracer{} var _ tracer.Tracer = &trace.OpenTracingTracer{} var _ tracer.ValidationTracer = &trace.NoopTracer{} var _ tracer.Tracer = &trace.NoopTracer{} } func TestTracerOption(t *testing.T) { _, err := graphql.ParseSchema(starwars.Schema, nil, graphql.Tracer(trace.OpenTracingTracer{})) if err != nil { t.Fatal(err) } } // MockVlidationTracer is a struct that implements the tracer.LegacyValidationTracer inteface. type MockValidationTracer struct{} func (MockValidationTracer) TraceValidation() func([]*errors.QueryError) { return func([]*errors.QueryError) {} } func TestValidationTracer(t *testing.T) { // test the legacy validation tracer interface (validating without using context) to ensure backwards compatibility vt := MockValidationTracer{} _, err := graphql.ParseSchema(starwars.Schema, nil, graphql.ValidationTracer(vt)) if err != nil { t.Fatal(err) } } graphql-go-1.5.0/trace/tracer/000077500000000000000000000000001435002731700161365ustar00rootroot00000000000000graphql-go-1.5.0/trace/tracer/tracer.go000066400000000000000000000022541435002731700177500ustar00rootroot00000000000000package tracer import ( "context" "github.com/graph-gophers/graphql-go/errors" "github.com/graph-gophers/graphql-go/introspection" ) type QueryFinishFunc = func([]*errors.QueryError) type FieldFinishFunc = func(*errors.QueryError) type ValidationFinishFunc = func([]*errors.QueryError) type Tracer interface { TraceQuery(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, varTypes map[string]*introspection.Type) (context.Context, QueryFinishFunc) TraceField(ctx context.Context, label, typeName, fieldName string, trivial bool, args map[string]interface{}) (context.Context, FieldFinishFunc) } type ValidationTracer interface { TraceValidation(ctx context.Context) ValidationFinishFunc } // Deprecated: use ValidationTracerContext instead. type LegacyValidationTracer interface { TraceValidation() func([]*errors.QueryError) } // Deprecated: use a Tracer which implements ValidationTracerContext. type LegacyNoopValidationTracer struct{} // Deprecated: use a Tracer which implements ValidationTracerContext. func (LegacyNoopValidationTracer) TraceValidation() func([]*errors.QueryError) { return func(errs []*errors.QueryError) {} } graphql-go-1.5.0/trace/validation_trace.go000066400000000000000000000012541435002731700205170ustar00rootroot00000000000000package trace import ( "github.com/graph-gophers/graphql-go/errors" "github.com/graph-gophers/graphql-go/trace/tracer" ) // Deprecated: this type has been deprecated. Use tracer.ValidationFinishFunc instead. type TraceValidationFinishFunc = func([]*errors.QueryError) // Deprecated: use ValidationTracerContext. type ValidationTracer = tracer.LegacyValidationTracer //nolint:staticcheck // Deprecated: this type has been deprecated. Use tracer.ValidationTracer instead. type ValidationTracerContext = tracer.ValidationTracer // Deprecated: use a tracer that implements ValidationTracerContext. type NoopValidationTracer = tracer.LegacyNoopValidationTracer //nolint:staticcheck graphql-go-1.5.0/types/000077500000000000000000000000001435002731700147245ustar00rootroot00000000000000graphql-go-1.5.0/types/argument.go000066400000000000000000000021311435002731700170720ustar00rootroot00000000000000package types // Argument is a representation of the GraphQL Argument. // // https://spec.graphql.org/draft/#sec-Language.Arguments type Argument struct { Name Ident Value Value Directives DirectiveList } // ArgumentList is a collection of GraphQL Arguments. type ArgumentList []*Argument // Returns a Value in the ArgumentList by name. func (l ArgumentList) Get(name string) (Value, bool) { for _, arg := range l { if arg.Name.Name == name { return arg.Value, true } } return nil, false } // MustGet returns a Value in the ArgumentList by name. // MustGet will panic if the argument name is not found in the ArgumentList. func (l ArgumentList) MustGet(name string) Value { value, ok := l.Get(name) if !ok { panic("argument not found") } return value } type ArgumentsDefinition []*InputValueDefinition // Get returns an InputValueDefinition in the ArgumentsDefinition by name or nil if not found. func (a ArgumentsDefinition) Get(name string) *InputValueDefinition { for _, inputValue := range a { if inputValue.Name.Name == name { return inputValue } } return nil } graphql-go-1.5.0/types/directive.go000066400000000000000000000014601435002731700172320ustar00rootroot00000000000000package types import "github.com/graph-gophers/graphql-go/errors" // Directive is a representation of the GraphQL Directive. // // http://spec.graphql.org/draft/#sec-Language.Directives type Directive struct { Name Ident Arguments ArgumentList } // DirectiveDefinition is a representation of the GraphQL DirectiveDefinition. // // http://spec.graphql.org/draft/#sec-Type-System.Directives type DirectiveDefinition struct { Name string Desc string Repeatable bool Locations []string Arguments ArgumentsDefinition Loc errors.Location } type DirectiveList []*Directive // Returns the Directive in the DirectiveList by name or nil if not found. func (l DirectiveList) Get(name string) *Directive { for _, d := range l { if d.Name.Name == name { return d } } return nil } graphql-go-1.5.0/types/doc.go000066400000000000000000000003121435002731700160140ustar00rootroot00000000000000/* Package types represents all types from the GraphQL specification in code. The names of the Go types, whenever possible, match 1:1 with the names from the specification. */ package types graphql-go-1.5.0/types/enum.go000066400000000000000000000020501435002731700162140ustar00rootroot00000000000000package types import "github.com/graph-gophers/graphql-go/errors" // EnumTypeDefinition defines a set of possible enum values. // // Like scalar types, an EnumTypeDefinition also represents a leaf value in a GraphQL type system. // // http://spec.graphql.org/draft/#sec-Enums type EnumTypeDefinition struct { Name string EnumValuesDefinition []*EnumValueDefinition Desc string Directives DirectiveList Loc errors.Location } // EnumValueDefinition are unique values that may be serialized as a string: the name of the // represented value. // // http://spec.graphql.org/draft/#EnumValueDefinition type EnumValueDefinition struct { EnumValue string Directives DirectiveList Desc string Loc errors.Location } func (*EnumTypeDefinition) Kind() string { return "ENUM" } func (t *EnumTypeDefinition) String() string { return t.Name } func (t *EnumTypeDefinition) TypeName() string { return t.Name } func (t *EnumTypeDefinition) Description() string { return t.Desc } graphql-go-1.5.0/types/extension.go000066400000000000000000000005301435002731700172650ustar00rootroot00000000000000package types import "github.com/graph-gophers/graphql-go/errors" // Extension type defines a GraphQL type extension. // Schemas, Objects, Inputs and Scalars can be extended. // // https://spec.graphql.org/draft/#sec-Type-System-Extensions type Extension struct { Type NamedType Directives DirectiveList Loc errors.Location } graphql-go-1.5.0/types/field.go000066400000000000000000000016601435002731700163410ustar00rootroot00000000000000package types import "github.com/graph-gophers/graphql-go/errors" // FieldDefinition is a representation of a GraphQL FieldDefinition. // // http://spec.graphql.org/draft/#FieldDefinition type FieldDefinition struct { Name string Arguments ArgumentsDefinition Type Type Directives DirectiveList Desc string Loc errors.Location } // FieldsDefinition is a list of an ObjectTypeDefinition's Fields. // // https://spec.graphql.org/draft/#FieldsDefinition type FieldsDefinition []*FieldDefinition // Get returns a FieldDefinition in a FieldsDefinition by name or nil if not found. func (l FieldsDefinition) Get(name string) *FieldDefinition { for _, f := range l { if f.Name == name { return f } } return nil } // Names returns a slice of FieldDefinition names. func (l FieldsDefinition) Names() []string { names := make([]string, len(l)) for i, f := range l { names[i] = f.Name } return names } graphql-go-1.5.0/types/fragment.go000066400000000000000000000022061435002731700170560ustar00rootroot00000000000000package types import "github.com/graph-gophers/graphql-go/errors" type Fragment struct { On TypeName Selections SelectionSet } // InlineFragment is a representation of the GraphQL InlineFragment. // // http://spec.graphql.org/draft/#InlineFragment type InlineFragment struct { Fragment Directives DirectiveList Loc errors.Location } // FragmentDefinition is a representation of the GraphQL FragmentDefinition. // // http://spec.graphql.org/draft/#FragmentDefinition type FragmentDefinition struct { Fragment Name Ident Directives DirectiveList Loc errors.Location } // FragmentSpread is a representation of the GraphQL FragmentSpread. // // http://spec.graphql.org/draft/#FragmentSpread type FragmentSpread struct { Name Ident Directives DirectiveList Loc errors.Location } type FragmentList []*FragmentDefinition // Returns a FragmentDefinition by name or nil if not found. func (l FragmentList) Get(name string) *FragmentDefinition { for _, f := range l { if f.Name.Name == name { return f } } return nil } func (InlineFragment) isSelection() {} func (FragmentSpread) isSelection() {} graphql-go-1.5.0/types/input.go000066400000000000000000000024561435002731700164210ustar00rootroot00000000000000package types import "github.com/graph-gophers/graphql-go/errors" // InputValueDefinition is a representation of the GraphQL InputValueDefinition. // // http://spec.graphql.org/draft/#InputValueDefinition type InputValueDefinition struct { Name Ident Type Type Default Value Desc string Directives DirectiveList Loc errors.Location TypeLoc errors.Location } type InputValueDefinitionList []*InputValueDefinition // Returns an InputValueDefinition by name or nil if not found. func (l InputValueDefinitionList) Get(name string) *InputValueDefinition { for _, v := range l { if v.Name.Name == name { return v } } return nil } // InputObject types define a set of input fields; the input fields are either scalars, enums, or // other input objects. // // This allows arguments to accept arbitrarily complex structs. // // http://spec.graphql.org/draft/#sec-Input-Objects type InputObject struct { Name string Desc string Values ArgumentsDefinition Directives DirectiveList Loc errors.Location } func (*InputObject) Kind() string { return "INPUT_OBJECT" } func (t *InputObject) String() string { return t.Name } func (t *InputObject) TypeName() string { return t.Name } func (t *InputObject) Description() string { return t.Desc } graphql-go-1.5.0/types/interface.go000066400000000000000000000017001435002731700172110ustar00rootroot00000000000000package types import "github.com/graph-gophers/graphql-go/errors" // InterfaceTypeDefinition recusrively defines list of named fields with their arguments via the // implementation chain of interfaces. // // GraphQL objects can then implement these interfaces which requires that the object type will // define all fields defined by those interfaces. // // http://spec.graphql.org/draft/#sec-Interfaces type InterfaceTypeDefinition struct { Name string PossibleTypes []*ObjectTypeDefinition Fields FieldsDefinition Desc string Directives DirectiveList Loc errors.Location Interfaces []*InterfaceTypeDefinition } func (*InterfaceTypeDefinition) Kind() string { return "INTERFACE" } func (t *InterfaceTypeDefinition) String() string { return t.Name } func (t *InterfaceTypeDefinition) TypeName() string { return t.Name } func (t *InterfaceTypeDefinition) Description() string { return t.Desc } graphql-go-1.5.0/types/object.go000066400000000000000000000013671435002731700165300ustar00rootroot00000000000000package types import "github.com/graph-gophers/graphql-go/errors" // ObjectTypeDefinition represents a GraphQL ObjectTypeDefinition. // // type FooObject { // foo: String // } // // https://spec.graphql.org/draft/#sec-Objects type ObjectTypeDefinition struct { Name string Interfaces []*InterfaceTypeDefinition Fields FieldsDefinition Desc string Directives DirectiveList InterfaceNames []string Loc errors.Location } func (*ObjectTypeDefinition) Kind() string { return "OBJECT" } func (t *ObjectTypeDefinition) String() string { return t.Name } func (t *ObjectTypeDefinition) TypeName() string { return t.Name } func (t *ObjectTypeDefinition) Description() string { return t.Desc } graphql-go-1.5.0/types/query.go000066400000000000000000000027111435002731700164210ustar00rootroot00000000000000package types import "github.com/graph-gophers/graphql-go/errors" // ExecutableDefinition represents a set of operations or fragments that can be executed // against a schema. // // http://spec.graphql.org/draft/#ExecutableDefinition type ExecutableDefinition struct { Operations OperationList Fragments FragmentList } // OperationDefinition represents a GraphQL Operation. // // https://spec.graphql.org/draft/#sec-Language.Operations type OperationDefinition struct { Type OperationType Name Ident Vars ArgumentsDefinition Selections SelectionSet Directives DirectiveList Loc errors.Location } type OperationType string // A Selection is a field requested in a GraphQL operation. // // http://spec.graphql.org/draft/#Selection type Selection interface { isSelection() } // A SelectionSet represents a collection of Selections // // http://spec.graphql.org/draft/#sec-Selection-Sets type SelectionSet []Selection // Field represents a field used in a query. type Field struct { Alias Ident Name Ident Arguments ArgumentList Directives DirectiveList SelectionSet SelectionSet SelectionSetLoc errors.Location } func (Field) isSelection() {} type OperationList []*OperationDefinition // Get returns an OperationDefinition by name or nil if not found. func (l OperationList) Get(name string) *OperationDefinition { for _, f := range l { if f.Name.Name == name { return f } } return nil } graphql-go-1.5.0/types/scalar.go000066400000000000000000000013721435002731700165230ustar00rootroot00000000000000package types import "github.com/graph-gophers/graphql-go/errors" // ScalarTypeDefinition types represent primitive leaf values (e.g. a string or an integer) in a GraphQL type // system. // // GraphQL responses take the form of a hierarchical tree; the leaves on these trees are GraphQL // scalars. // // http://spec.graphql.org/draft/#sec-Scalars type ScalarTypeDefinition struct { Name string Desc string Directives DirectiveList Loc errors.Location } func (*ScalarTypeDefinition) Kind() string { return "SCALAR" } func (t *ScalarTypeDefinition) String() string { return t.Name } func (t *ScalarTypeDefinition) TypeName() string { return t.Name } func (t *ScalarTypeDefinition) Description() string { return t.Desc } graphql-go-1.5.0/types/schema.go000066400000000000000000000026571435002731700165250ustar00rootroot00000000000000package types // Schema represents a GraphQL service's collective type system capabilities. // A schema is defined in terms of the types and directives it supports as well as the root // operation types for each kind of operation: `query`, `mutation`, and `subscription`. // // For a more formal definition, read the relevant section in the specification: // // http://spec.graphql.org/draft/#sec-Schema type Schema struct { // EntryPoints determines the place in the type system where `query`, `mutation`, and // `subscription` operations begin. // // http://spec.graphql.org/draft/#sec-Root-Operation-Types // EntryPoints map[string]NamedType // Types are the fundamental unit of any GraphQL schema. // There are six kinds of named types, and two wrapping types. // // http://spec.graphql.org/draft/#sec-Types Types map[string]NamedType // Directives are used to annotate various parts of a GraphQL document as an indicator that they // should be evaluated differently by a validator, executor, or client tool such as a code // generator. // // http://spec.graphql.org/#sec-Type-System.Directives Directives map[string]*DirectiveDefinition UseFieldResolvers bool EntryPointNames map[string]string Objects []*ObjectTypeDefinition Unions []*Union Enums []*EnumTypeDefinition Extensions []*Extension SchemaString string } func (s *Schema) Resolve(name string) Type { return s.Types[name] } graphql-go-1.5.0/types/types.go000066400000000000000000000032071435002731700164210ustar00rootroot00000000000000package types import ( "github.com/graph-gophers/graphql-go/errors" ) // TypeName is a base building block for GraphQL type references. type TypeName struct { Ident } // NamedType represents a type with a name. // // http://spec.graphql.org/draft/#NamedType type NamedType interface { Type TypeName() string Description() string } type Ident struct { Name string Loc errors.Location } type Type interface { // Kind returns one possible GraphQL type kind. A type kind must be // valid as defined by the GraphQL spec. // // https://spec.graphql.org/draft/#sec-Type-Kinds Kind() string // String serializes a Type into a GraphQL specification format type. // // http://spec.graphql.org/draft/#sec-Serialization-Format String() string } // List represents a GraphQL ListType. // // http://spec.graphql.org/draft/#ListType type List struct { // OfType represents the inner-type of a List type. // For example, the List type `[Foo]` has an OfType of Foo. OfType Type } // NonNull represents a GraphQL NonNullType. // // https://spec.graphql.org/draft/#NonNullType type NonNull struct { // OfType represents the inner-type of a NonNull type. // For example, the NonNull type `Foo!` has an OfType of Foo. OfType Type } func (*List) Kind() string { return "LIST" } func (*NonNull) Kind() string { return "NON_NULL" } func (*TypeName) Kind() string { panic("TypeName needs to be resolved to actual type") } func (t *List) String() string { return "[" + t.OfType.String() + "]" } func (t *NonNull) String() string { return t.OfType.String() + "!" } func (*TypeName) String() string { panic("TypeName needs to be resolved to actual type") } graphql-go-1.5.0/types/union.go000066400000000000000000000015071435002731700164060ustar00rootroot00000000000000package types import "github.com/graph-gophers/graphql-go/errors" // Union types represent objects that could be one of a list of GraphQL object types, but provides no // guaranteed fields between those types. // // They also differ from interfaces in that object types declare what interfaces they implement, but // are not aware of what unions contain them. // // http://spec.graphql.org/draft/#sec-Unions type Union struct { Name string UnionMemberTypes []*ObjectTypeDefinition Desc string Directives DirectiveList TypeNames []string Loc errors.Location } func (*Union) Kind() string { return "UNION" } func (t *Union) String() string { return t.Name } func (t *Union) TypeName() string { return t.Name } func (t *Union) Description() string { return t.Desc } graphql-go-1.5.0/types/value.go000066400000000000000000000067351435002731700164020ustar00rootroot00000000000000package types import ( "strconv" "strings" "text/scanner" "github.com/graph-gophers/graphql-go/errors" ) // Value represents a literal input or literal default value in the GraphQL Specification. // // http://spec.graphql.org/draft/#sec-Input-Values type Value interface { // Deserialize transforms a GraphQL specification format literal into a Go type. Deserialize(vars map[string]interface{}) interface{} // String serializes a Value into a GraphQL specification format literal. String() string Location() errors.Location } // PrimitiveValue represents one of the following GraphQL scalars: Int, Float, // String, or Boolean type PrimitiveValue struct { Type rune Text string Loc errors.Location } func (val *PrimitiveValue) Deserialize(vars map[string]interface{}) interface{} { switch val.Type { case scanner.Int: value, err := strconv.ParseInt(val.Text, 10, 32) if err != nil { panic(err) } return int32(value) case scanner.Float: value, err := strconv.ParseFloat(val.Text, 64) if err != nil { panic(err) } return value case scanner.String: value, err := strconv.Unquote(val.Text) if err != nil { panic(err) } return value case scanner.Ident: switch val.Text { case "true": return true case "false": return false default: return val.Text } default: panic("invalid literal value") } } func (val *PrimitiveValue) String() string { return val.Text } func (val *PrimitiveValue) Location() errors.Location { return val.Loc } // ListValue represents a literal list Value in the GraphQL specification. // // http://spec.graphql.org/draft/#sec-List-Value type ListValue struct { Values []Value Loc errors.Location } func (val *ListValue) Deserialize(vars map[string]interface{}) interface{} { entries := make([]interface{}, len(val.Values)) for i, entry := range val.Values { entries[i] = entry.Deserialize(vars) } return entries } func (val *ListValue) String() string { entries := make([]string, len(val.Values)) for i, entry := range val.Values { entries[i] = entry.String() } return "[" + strings.Join(entries, ", ") + "]" } func (val *ListValue) Location() errors.Location { return val.Loc } // ObjectValue represents a literal object Value in the GraphQL specification. // // http://spec.graphql.org/draft/#sec-Object-Value type ObjectValue struct { Fields []*ObjectField Loc errors.Location } // ObjectField represents field/value pairs in a literal ObjectValue. type ObjectField struct { Name Ident Value Value } func (val *ObjectValue) Deserialize(vars map[string]interface{}) interface{} { fields := make(map[string]interface{}, len(val.Fields)) for _, f := range val.Fields { fields[f.Name.Name] = f.Value.Deserialize(vars) } return fields } func (val *ObjectValue) String() string { entries := make([]string, 0, len(val.Fields)) for _, f := range val.Fields { entries = append(entries, f.Name.Name+": "+f.Value.String()) } return "{" + strings.Join(entries, ", ") + "}" } func (val *ObjectValue) Location() errors.Location { return val.Loc } // NullValue represents a literal `null` Value in the GraphQL specification. // // http://spec.graphql.org/draft/#sec-Null-Value type NullValue struct { Loc errors.Location } func (val *NullValue) Deserialize(vars map[string]interface{}) interface{} { return nil } func (val *NullValue) String() string { return "null" } func (val *NullValue) Location() errors.Location { return val.Loc } graphql-go-1.5.0/types/variable.go000066400000000000000000000010171435002731700170370ustar00rootroot00000000000000package types import "github.com/graph-gophers/graphql-go/errors" // Variable is used in GraphQL operations to parameterize an input value. // // http://spec.graphql.org/draft/#Variable type Variable struct { Name string Loc errors.Location } func (v Variable) Deserialize(vars map[string]interface{}) interface{} { return vars[v.Name] } func (v Variable) String() string { return "$" + v.Name } func (v *Variable) Location() errors.Location { return v.Loc }