├── .github
└── workflows
│ ├── full_codegen.yml
│ └── test.yml
├── .gitignore
├── .idea
├── .gitignore
├── go-gql-client.iml
├── modules.xml
└── vcs.xml
├── LICENSE
├── Makefile
├── OPS.md
├── README.md
├── client
├── client.go
├── extensions
│ └── apq.go
├── interface.go
├── transport
│ ├── response.go
│ ├── response_chan.go
│ ├── response_proxy.go
│ ├── response_single.go
│ ├── transport.go
│ ├── transport_http.go
│ ├── transport_mock.go
│ ├── transport_split.go
│ ├── transport_ws.go
│ ├── upload.go
│ └── wshandler.go
└── transport_mock_test.go
├── clientgen
├── LICENSE
├── client.go
├── modelgen.go
├── query.go
├── query_source.go
├── source.go
├── source_generator.go
├── template.go
└── template.gotpl
├── config
└── config.go
├── example
├── .gqlgen.yml
├── .gqlgenc.yml
├── client
│ ├── gen_client.go
│ └── scalars.go
├── client_apq_test.go
├── client_http_test.go
├── client_split_test.go
├── client_unstable_ws_test.go
├── client_ws_test.go
├── cmd
│ └── memleak.go
├── codegen_test.go
├── common_test.go
├── go.mod
├── go.sum
├── query.go
├── query.graphql
├── schema.graphql
├── server
│ ├── generated
│ │ └── generated.go
│ ├── model
│ │ └── models_gen.go
│ ├── resolver.go
│ ├── schema.resolvers.go
│ └── tools.go
└── somelib
│ └── lib.go
├── generator
└── generator.go
├── go.mod
├── go.sum
├── introspection
├── LICENSE
├── parse.go
├── parser_test.go
├── query.go
├── testdata
│ └── introspection_result_no_mutation.json
└── type.go
└── main.go
/.github/workflows/full_codegen.yml:
--------------------------------------------------------------------------------
1 | name: Full Codegen
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | name: Build & Test
13 | steps:
14 | - uses: actions/checkout@v2
15 |
16 | - name: Set up Go
17 | uses: actions/setup-go@v2
18 | with:
19 | go-version: 1.16
20 |
21 | - name: Full Codegen
22 | run: make example-fullgen
23 |
24 | - name: Test Example
25 | run: make example-test
26 |
27 | - name: Codegen
28 | run: make example-genall
29 |
30 | - name: Test Example
31 | run: make example-test
32 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | strategy:
13 | fail-fast: false
14 | matrix:
15 | go: [ '1.15', '1.16', '1.17', '1.18' ]
16 | name: Build & Test Go ${{ matrix.go }}
17 | steps:
18 | - uses: actions/checkout@v2
19 |
20 | - name: Set up Go
21 | uses: actions/setup-go@v2
22 | with:
23 | go-version: ${{ matrix.go }}
24 |
25 | - name: Build
26 | run: go build -v ./...
27 |
28 | - name: Test
29 | run: make test
30 |
31 | - name: Test Example
32 | run: make example-test
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
3 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
4 |
5 | # User-specific stuff
6 | .idea/**/workspace.xml
7 | .idea/**/tasks.xml
8 | .idea/**/usage.statistics.xml
9 | .idea/**/dictionaries
10 | .idea/**/shelf
11 |
12 | # Generated files
13 | .idea/**/contentModel.xml
14 |
15 | # Sensitive or high-churn files
16 | .idea/**/dataSources/
17 | .idea/**/dataSources.ids
18 | .idea/**/dataSources.local.xml
19 | .idea/**/sqlDataSources.xml
20 | .idea/**/dynamic.xml
21 | .idea/**/uiDesigner.xml
22 | .idea/**/dbnavigator.xml
23 |
24 | # Gradle
25 | .idea/**/gradle.xml
26 | .idea/**/libraries
27 |
28 | # Gradle and Maven with auto-import
29 | # When using Gradle or Maven with auto-import, you should exclude module files,
30 | # since they will be recreated, and may cause churn. Uncomment if using
31 | # auto-import.
32 | # .idea/artifacts
33 | # .idea/compiler.xml
34 | # .idea/jarRepositories.xml
35 | # .idea/modules.xml
36 | # .idea/*.iml
37 | # .idea/modules
38 | # *.iml
39 | # *.ipr
40 |
41 | # CMake
42 | cmake-build-*/
43 |
44 | # Mongo Explorer plugin
45 | .idea/**/mongoSettings.xml
46 |
47 | # File-based project format
48 | *.iws
49 |
50 | # IntelliJ
51 | out/
52 |
53 | # mpeltonen/sbt-idea plugin
54 | .idea_modules/
55 |
56 | # JIRA plugin
57 | atlassian-ide-plugin.xml
58 |
59 | # Cursive Clojure plugin
60 | .idea/replstate.xml
61 |
62 | # Crashlytics plugin (for Android Studio and IntelliJ)
63 | com_crashlytics_export_strings.xml
64 | crashlytics.properties
65 | crashlytics-build.properties
66 | fabric.properties
67 |
68 | # Editor-based Rest Client
69 | .idea/httpRequests
70 |
71 | # Android studio 3.1+ serialized cache file
72 | .idea/caches/build_file_checksums.ser
73 |
74 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Datasource local storage ignored files
5 | /dataSources/
6 | /dataSources.local.xml
7 | # Editor-based HTTP Client requests
8 | /httpRequests/
9 |
--------------------------------------------------------------------------------
/.idea/go-gql-client.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Infiot Inc
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | go test -v -count=1 ./...
3 |
4 | example-fullgen:
5 | rm example/client/gen_client.go || true
6 | rm example/server/generated/generated.go || true
7 | rm example/server/model/models_gen.go || true
8 | make example-genall
9 |
10 | example-genall:
11 | make example-gqlgen
12 | make example-gqlgenc
13 |
14 | example-gqlgen:
15 | cd example && go run github.com/99designs/gqlgen
16 |
17 | example-gqlgenc:
18 | cd example && go run github.com/infiotinc/gqlgenc
19 |
20 | example-test:
21 | cd example && go test -v -count=1 ./...
22 |
23 | example-run-memleak:
24 | cd example && go run ./cmd/memleak.go
25 |
26 | tag:
27 | git tag -a ${TAG} -m ${TAG}
28 | git push origin ${TAG}
29 |
--------------------------------------------------------------------------------
/OPS.md:
--------------------------------------------------------------------------------
1 | # OPS
2 |
3 | > For maintenance purposes
4 |
5 | ## Release
6 |
7 | TAG=v0.0.x make tag
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # gqlgenc
2 |
3 | > gqlgenc is a fully featured go gql client, powered by codegen
4 |
5 | ## Why yet another Go GQL client ?
6 |
7 | | Package | Codegen | Subscription | Extensions |
8 | |---------------------------------------------|---------|--------------|------------|
9 | | https://github.com/shurcooL/graphql | ❌ | ❌ |❌ |
10 | | https://github.com/Yamashou/gqlgenc | ✅ | ❌ |❌ |
11 | | https://github.com/hasura/go-graphql-client | ❌ | ✅ |❌ |
12 | | ✨[https://github.com/infiotinc/gqlgenc](https://github.com/infiotinc/gqlgenc)✨| ✅ | ✅ | ✅ |
13 |
14 | ## GQL Client
15 |
16 | ### Transports
17 |
18 | gqlgenc is transport agnostic, and ships with 3 transport implementations:
19 |
20 | - http: Transports GQL queries over http
21 | - ws: Transports GQL queries over websocket
22 | - split: Can be used to have a single client use multiple transports depending on the type of query (`query`, `mutation` over http and `subscription` over ws)
23 |
24 | ### Quickstart
25 |
26 | Quickstart with a client with http & ws transports:
27 |
28 | ```go
29 | package main
30 |
31 | import (
32 | "context"
33 | "github.com/infiotinc/gqlgenc/client"
34 | "github.com/infiotinc/gqlgenc/client/transport"
35 | )
36 |
37 | func main() {
38 | wstr := &transport.Ws{
39 | URL: "wss://example.org/graphql",
40 | }
41 | wstr.Start(context.Background())
42 | defer wstr.Close()
43 |
44 | httptr := &transport.Http{
45 | URL: "https://example.org/graphql",
46 | }
47 |
48 | tr := transport.SplitSubscription(wstr, httptr)
49 |
50 | cli := &client.Client {
51 | Transport: tr,
52 | }
53 | }
54 | ```
55 |
56 | ### Query/Mutation
57 |
58 | ```go
59 | var res struct {
60 | Room string `json:"room"`
61 | }
62 | _, err := cli.Query(ctx, "", "query { room }", nil, &res) // or Mutation
63 | if err != nil {
64 | panic(err)
65 | }
66 | ```
67 |
68 | ### Subscription
69 |
70 | ```go
71 | sub, stop := cli.Subscription(ctx, "", "subscription { newRoom }", nil)
72 | defer stop()
73 |
74 | for sub.Next() {
75 | msg := sub.Get()
76 |
77 | if len(msg.Errors) > 0 {
78 | // Do something with them
79 | }
80 |
81 | var res struct {
82 | Room string `json:"newRoom"`
83 | }
84 | err := msg.UnmarshalData(&res)
85 | if err != nil {
86 | // Do something with that
87 | }
88 | }
89 |
90 | if err := sub.Err(); err != nil {
91 | panic(err)
92 | }
93 | ```
94 |
95 | ## GQL Client Codegen
96 |
97 | Create a `.gqlgenc.yml` at the root of your module:
98 |
99 | ```yaml
100 | client:
101 | package: graph
102 | filename: ./graph/gen_client.go
103 | models:
104 | Int:
105 | model: github.com/99designs/gqlgen/graphql.Int64
106 | DateTime:
107 | model: github.com/99designs/gqlgen/graphql.Time
108 | # The schema can be fetched from files or through introspection
109 | schema:
110 | - schema.graphqls
111 | endpoint:
112 | url: https://api.annict.com/graphql # Where do you want to send your request?
113 | headers: # If you need header for getting introspection query, set it
114 | Authorization: "Bearer ${ANNICT_KEY}" # support environment variables
115 | query:
116 | - query.graphql
117 |
118 | ```
119 |
120 | Fill your `query.graphql` with queries:
121 | ```graphql
122 | query GetRoom {
123 | room(name: "secret room") {
124 | name
125 | }
126 | }
127 | ```
128 |
129 | Run `go run github.com/infiotinc/gqlgenc`
130 |
131 | Enjoy:
132 | ```go
133 | // Create codegen client
134 | gql := &graph.Client{
135 | Client: cli,
136 | }
137 |
138 | gql.GetRoom(...)
139 | ```
140 |
141 | ## Input as `map`
142 |
143 | In Go, working with JSON and nullity can be tricky. The recommended way to deal with such case is through maps. You can ask gqlgenc to generate such maps with helpers through config:
144 |
145 | Globally:
146 | ```yaml
147 | client:
148 | input_as_map: true
149 | ```
150 |
151 | Per model:
152 | ```yaml
153 | models:
154 | SomeInput:
155 | as_map: true
156 | ```
157 |
158 | ## Extensions
159 |
160 | ### APQ
161 |
162 | [Automatic Persisted Queries](https://www.apollographql.com/docs/apollo-server/performance/apq/) can be enabled by adding:
163 |
164 | ```go
165 | cli.Use(&extensions.APQ{})
166 | ```
167 |
168 | ## File Upload
169 |
170 | - In the `Http` transport, set `UseFormMultipart` to `true`
171 |
172 | - In `.gqlgenc.yaml`:
173 |
174 | ```yaml
175 | models:
176 | Upload:
177 | model: github.com/infiotinc/gqlgenc/client/transport.Upload
178 | ```
179 |
180 | - Profit
181 |
182 | ```go
183 | up := transport.NewUpload(someFile)
184 |
185 | _, _, err := gql.MyUploadFile(ctx, up)
186 | ```
187 |
188 | ## Acknowledgements
189 |
190 | This repo is based on the great work of [Yamashou/gqlgenc](https://github.com/Yamashou/gqlgenc) and [hasura/go-graphql-client](https://github.com/hasura/go-graphql-client)
191 |
--------------------------------------------------------------------------------
/client/client.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/infiotinc/gqlgenc/client/transport"
7 | )
8 |
9 | type extensions struct {
10 | aroundRequest []AroundRequest
11 | }
12 |
13 | func (es *extensions) Use(e Extension) {
14 | if a, ok := e.(AroundRequest); ok {
15 | es.aroundRequest = append(es.aroundRequest, a)
16 | }
17 | }
18 |
19 | func (es *extensions) RunAroundRequest(req transport.Request, h RequestHandler) transport.Response {
20 | run := h
21 |
22 | for _, _e := range es.aroundRequest {
23 | e := _e // Local ref
24 | next := run // Local ref
25 | run = func(req transport.Request) transport.Response {
26 | return e.AroundRequest(req, next)
27 | }
28 | }
29 |
30 | return run(req)
31 | }
32 |
33 | type Client struct {
34 | Transport transport.Transport
35 |
36 | extensions
37 | }
38 |
39 | func (c *Client) doSingle(
40 | ctx context.Context,
41 | operation transport.Operation,
42 | operationName string,
43 | query string,
44 | variables map[string]interface{},
45 | t interface{},
46 | ) (transport.OperationResponse, error) {
47 | res := c.do(transport.Request{
48 | Context: ctx,
49 | Operation: operation,
50 | Query: query,
51 | OperationName: operationName,
52 | Variables: variables,
53 | })
54 | defer res.Close()
55 |
56 | if !res.Next() {
57 | if err := res.Err(); err != nil {
58 | return transport.OperationResponse{}, err
59 | }
60 |
61 | return transport.OperationResponse{}, fmt.Errorf("no response")
62 | }
63 |
64 | opres := res.Get()
65 |
66 | err := opres.UnmarshalData(t)
67 |
68 | if len(opres.Errors) > 0 {
69 | return opres, opres.Errors
70 | }
71 |
72 | return opres, err
73 | }
74 |
75 | func (c *Client) do(req transport.Request) transport.Response {
76 | if req.Extensions == nil {
77 | req.Extensions = map[string]interface{}{}
78 | }
79 |
80 | res := c.RunAroundRequest(req, c.Transport.Request)
81 |
82 | go func() {
83 | select {
84 | case <-req.Context.Done():
85 | res.Close()
86 | case <-res.Done():
87 | }
88 | }()
89 |
90 | return res
91 | }
92 |
93 | // Query runs a query
94 | // operationName is optional
95 | func (c *Client) Query(ctx context.Context, operationName string, query string, variables map[string]interface{}, t interface{}) (transport.OperationResponse, error) {
96 | return c.doSingle(ctx, transport.Query, operationName, query, variables, t)
97 | }
98 |
99 | // Mutation runs a mutation
100 | // operationName is optional
101 | func (c *Client) Mutation(ctx context.Context, operationName string, query string, variables map[string]interface{}, t interface{}) (transport.OperationResponse, error) {
102 | return c.doSingle(ctx, transport.Mutation, operationName, query, variables, t)
103 | }
104 |
105 | // Subscription starts a GQL subscription
106 | // operationName is optional
107 | func (c *Client) Subscription(ctx context.Context, operationName string, query string, variables map[string]interface{}) transport.Response {
108 | return c.do(transport.Request{
109 | Context: ctx,
110 | Operation: transport.Subscription,
111 | Query: query,
112 | OperationName: operationName,
113 | Variables: variables,
114 | })
115 | }
116 |
--------------------------------------------------------------------------------
/client/extensions/apq.go:
--------------------------------------------------------------------------------
1 | package extensions
2 |
3 | import (
4 | "crypto/sha256"
5 | "fmt"
6 | "github.com/infiotinc/gqlgenc/client"
7 | "github.com/infiotinc/gqlgenc/client/transport"
8 | )
9 |
10 | const APQKey = "persistedQuery"
11 |
12 | type APQExtension struct {
13 | Version int64 `json:"version"`
14 | Sha256Hash string `json:"sha256Hash"`
15 | }
16 |
17 | type APQ struct{}
18 |
19 | var _ client.AroundRequest = (*APQ)(nil)
20 |
21 | func (a *APQ) ExtensionName() string {
22 | return "apq"
23 | }
24 |
25 | func (a *APQ) AroundRequest(req transport.Request, next client.RequestHandler) transport.Response {
26 | if _, ok := req.Extensions[APQKey]; !ok {
27 | sum := sha256.Sum256([]byte(req.Query))
28 | req.Extensions[APQKey] = APQExtension{
29 | Version: 1,
30 | Sha256Hash: fmt.Sprintf("%x", sum),
31 | }
32 | }
33 |
34 | res := next(transport.Request{
35 | Context: req.Context,
36 | Operation: req.Operation,
37 | OperationName: req.OperationName,
38 | Variables: req.Variables,
39 | Extensions: req.Extensions,
40 | })
41 |
42 | nres := transport.NewProxyResponse()
43 |
44 | nres.Bind(res, func(opres transport.OperationResponse, send func()) {
45 | for _, err := range opres.Errors {
46 | if code, ok := err.Extensions["code"]; ok {
47 | if code == "PERSISTED_QUERY_NOT_FOUND" {
48 | nres.Unbind(res)
49 | go res.Close()
50 |
51 | nres.Bind(next(req), nil)
52 | return
53 | }
54 | }
55 | }
56 |
57 | send()
58 | })
59 |
60 | return nres
61 | }
62 |
--------------------------------------------------------------------------------
/client/interface.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "github.com/infiotinc/gqlgenc/client/transport"
5 | )
6 |
7 | type (
8 | Extension interface{
9 | ExtensionName() string
10 | }
11 |
12 | RequestHandler func(req transport.Request) transport.Response
13 |
14 | AroundRequest interface {
15 | AroundRequest(req transport.Request, next RequestHandler) transport.Response
16 | }
17 | )
18 |
19 |
--------------------------------------------------------------------------------
/client/transport/response.go:
--------------------------------------------------------------------------------
1 | package transport
2 |
3 | import "sync"
4 |
5 | type Response interface {
6 | Next() bool
7 | Get() OperationResponse
8 | Close()
9 | CloseWithError(err error)
10 | Err() error
11 | Done() <-chan struct{}
12 | }
13 |
14 | type SendResponse interface {
15 | Response
16 | Send(OperationResponse)
17 | }
18 |
19 | type responseError struct {
20 | err error
21 | m sync.Mutex
22 | }
23 |
24 | func (r *responseError) CloseWithError(err error) {
25 | if err == nil {
26 | panic("CloseWithError: err must be non-nil")
27 | }
28 |
29 | r.m.Lock()
30 | defer r.m.Unlock()
31 |
32 | if r.err != nil {
33 | return
34 | }
35 |
36 | r.err = err
37 | }
38 |
39 | func (r *responseError) Err() error {
40 | r.m.Lock()
41 | defer r.m.Unlock()
42 |
43 | return r.err
44 | }
45 |
--------------------------------------------------------------------------------
/client/transport/response_chan.go:
--------------------------------------------------------------------------------
1 | package transport
2 |
3 | import "sync"
4 |
5 | type ChanResponse struct {
6 | responseError
7 |
8 | ch chan OperationResponse
9 | close func() error
10 | closed bool
11 |
12 | cor OperationResponse
13 | m sync.Mutex
14 | dc chan struct{}
15 | }
16 |
17 | func NewChanResponse(onClose func() error) *ChanResponse {
18 | return &ChanResponse{
19 | ch: make(chan OperationResponse),
20 | dc: make(chan struct{}),
21 | close: onClose,
22 | }
23 | }
24 |
25 | func (r *ChanResponse) Next() bool {
26 | if r.err != nil {
27 | return false
28 | }
29 |
30 | or, ok := <-r.ch
31 | r.cor = or
32 | return ok
33 | }
34 |
35 | func (r *ChanResponse) Get() OperationResponse {
36 | return r.cor
37 | }
38 |
39 | func (r *ChanResponse) Close() {
40 | if r.close != nil {
41 | r.err = r.close()
42 | }
43 | r.CloseCh()
44 | }
45 |
46 | func (r *ChanResponse) CloseWithError(err error) {
47 | r.responseError.CloseWithError(err)
48 | r.CloseCh()
49 |
50 | if r.close != nil {
51 | _ = r.close()
52 | }
53 | }
54 |
55 | func (r *ChanResponse) CloseCh() {
56 | r.m.Lock()
57 | defer r.m.Unlock()
58 |
59 | if r.closed {
60 | return
61 | }
62 |
63 | close(r.ch)
64 | close(r.dc)
65 | r.closed = true
66 | }
67 |
68 | func (r *ChanResponse) Done() <-chan struct{} {
69 | return r.dc
70 | }
71 |
72 | func (r *ChanResponse) Send(op OperationResponse) {
73 | r.m.Lock()
74 | defer r.m.Unlock()
75 |
76 | select {
77 | case r.ch <- op:
78 | case <-r.Done():
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/client/transport/response_proxy.go:
--------------------------------------------------------------------------------
1 | package transport
2 |
3 | import (
4 | "sync"
5 | "sync/atomic"
6 | )
7 |
8 | type proxyTarget SendResponse
9 |
10 | type ProxyResponse struct {
11 | proxyTarget
12 | binds []Response
13 | m sync.RWMutex
14 | inFlight int32
15 | }
16 |
17 | func (p *ProxyResponse) Bound(res Response) bool {
18 | p.m.RLock()
19 | defer p.m.RUnlock()
20 |
21 | for _, b := range p.binds {
22 | if b == res {
23 | return true
24 | }
25 | }
26 |
27 | return false
28 | }
29 |
30 | func (p *ProxyResponse) Bind(res Response, onOpres func(response OperationResponse, send func())) {
31 | atomic.AddInt32(&p.inFlight, 1)
32 |
33 | if onOpres == nil {
34 | onOpres = func(_ OperationResponse, send func()) {
35 | send()
36 | }
37 | }
38 |
39 | p.m.Lock()
40 | p.binds = append(p.binds, res)
41 | p.m.Unlock()
42 |
43 | go func() {
44 | go func() {
45 | select {
46 | case <-res.Done():
47 | case <-p.Done():
48 | res.Close()
49 | }
50 | }()
51 |
52 | for res.Next() {
53 | opres := res.Get()
54 |
55 | onOpres(opres, func() {
56 | if p.Bound(res) {
57 | p.Send(opres)
58 | }
59 | })
60 |
61 | if !p.Bound(res) {
62 | break
63 | }
64 | }
65 |
66 | if p.Bound(res) {
67 | if err := res.Err(); err != nil {
68 | p.CloseWithError(err)
69 | }
70 | }
71 |
72 | atomic.AddInt32(&p.inFlight, -1)
73 | p.Unbind(res)
74 | }()
75 | }
76 |
77 | func (p *ProxyResponse) Unbind(res Response) {
78 | p.m.Lock()
79 | binds := make([]Response, 0)
80 | for _, b := range p.binds {
81 | if b == res {
82 | continue
83 | }
84 |
85 | binds = append(binds, b)
86 | }
87 | p.binds = binds
88 | p.m.Unlock()
89 |
90 | if atomic.LoadInt32(&p.inFlight) == 0 {
91 | p.Close()
92 | }
93 | }
94 |
95 | func NewProxyResponse() *ProxyResponse {
96 | return &ProxyResponse{
97 | proxyTarget: NewChanResponse(nil),
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/client/transport/response_single.go:
--------------------------------------------------------------------------------
1 | package transport
2 |
3 | import "sync"
4 |
5 | type SingleResponse struct {
6 | or OperationResponse
7 |
8 | calledNext bool
9 | dm sync.Mutex
10 | dc chan struct{}
11 |
12 | responseError
13 | }
14 |
15 | func NewSingleResponse(or OperationResponse) *SingleResponse {
16 | return &SingleResponse{or: or}
17 | }
18 |
19 | func NewErrorResponse(err error) Response {
20 | res := NewSingleResponse(OperationResponse{})
21 | res.CloseWithError(err)
22 |
23 | return res
24 | }
25 |
26 | func (r *SingleResponse) Next() bool {
27 | if r.calledNext || r.err != nil {
28 | return false
29 | }
30 |
31 | r.calledNext = true
32 |
33 | return true
34 | }
35 |
36 | func (r *SingleResponse) Get() OperationResponse {
37 | return r.or
38 | }
39 |
40 | func (r *SingleResponse) Close() {}
41 |
42 | func (r *SingleResponse) Done() <-chan struct{} {
43 | r.dm.Lock()
44 | if r.dc == nil {
45 | r.dc = make(chan struct{})
46 | close(r.dc)
47 | }
48 | r.dm.Unlock()
49 |
50 | return r.dc
51 | }
52 |
--------------------------------------------------------------------------------
/client/transport/transport.go:
--------------------------------------------------------------------------------
1 | package transport
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "github.com/vektah/gqlparser/v2/gqlerror"
7 | )
8 |
9 | type Operation string
10 |
11 | const (
12 | Query Operation = "query"
13 | Mutation Operation = "mutation"
14 | Subscription Operation = "subscription"
15 | )
16 |
17 | type OperationRequest struct {
18 | Query string `json:"query,omitempty"`
19 | OperationName string `json:"operationName,omitempty"`
20 | Variables map[string]interface{} `json:"variables,omitempty"`
21 | Extensions map[string]interface{} `json:"extensions,omitempty"`
22 | }
23 |
24 | func NewOperationRequestFromRequest(req Request) OperationRequest {
25 | return OperationRequest{
26 | Query: req.Query,
27 | OperationName: req.OperationName,
28 | Variables: req.Variables,
29 | Extensions: req.Extensions,
30 | }
31 | }
32 |
33 | type OperationResponse struct {
34 | Data json.RawMessage `json:"data,omitempty"`
35 | Errors gqlerror.List `json:"errors,omitempty"`
36 | Extensions RawExtensions `json:"extensions,omitempty"`
37 | }
38 |
39 | func (r OperationResponse) UnmarshalData(t interface{}) error {
40 | if r.Data == nil {
41 | return nil
42 | }
43 |
44 | return json.Unmarshal(r.Data, t)
45 | }
46 |
47 | type RawExtensions map[string]json.RawMessage
48 |
49 | func (es RawExtensions) Unmarshal(name string, t interface{}) error {
50 | if es == nil {
51 | return nil
52 | }
53 |
54 | ex, ok := es[name]
55 | if !ok {
56 | return nil
57 | }
58 |
59 | return json.Unmarshal(ex, t)
60 | }
61 |
62 | type Request struct {
63 | Context context.Context
64 | Operation Operation
65 |
66 | OperationName string
67 | Query string
68 | Variables map[string]interface{}
69 | Extensions map[string]interface{}
70 | }
71 |
72 | type Transport interface {
73 | Request(req Request) Response
74 | }
75 |
--------------------------------------------------------------------------------
/client/transport/transport_http.go:
--------------------------------------------------------------------------------
1 | package transport
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "io"
8 | "io/ioutil"
9 | "mime/multipart"
10 | "net/http"
11 | "reflect"
12 | "strings"
13 | )
14 |
15 | type HttpRequestOption func(req *http.Request)
16 |
17 | type Http struct {
18 | URL string
19 | // Client defaults to http.DefaultClient
20 | Client *http.Client
21 | RequestOptions []HttpRequestOption
22 | UseFormMultipart bool
23 | }
24 |
25 | func (h *Http) Request(req Request) Response {
26 | opres, err := h.request(req)
27 | if err != nil {
28 | return NewErrorResponse(err)
29 | }
30 |
31 | return NewSingleResponse(*opres)
32 | }
33 |
34 | func (h *Http) request(gqlreq Request) (*OperationResponse, error) {
35 | if h.Client == nil {
36 | h.Client = http.DefaultClient
37 | }
38 |
39 | bodyb, err := json.Marshal(NewOperationRequestFromRequest(gqlreq))
40 | if err != nil {
41 | return nil, err
42 | }
43 |
44 | var req *http.Request
45 | if h.UseFormMultipart {
46 | req, err = h.formReq(gqlreq, bodyb)
47 | if err != nil {
48 | return nil, err
49 | }
50 | } else {
51 | req, err = http.NewRequestWithContext(gqlreq.Context, "POST", h.URL, bytes.NewReader(bodyb))
52 | if err != nil {
53 | return nil, err
54 | }
55 | req.Header.Set("Content-Type", "application/json")
56 | }
57 |
58 | for _, ro := range h.RequestOptions {
59 | ro(req)
60 | }
61 |
62 | res, err := h.Client.Do(req)
63 | if err != nil {
64 | return nil, err
65 | }
66 | defer res.Body.Close()
67 |
68 | data, err := ioutil.ReadAll(res.Body)
69 | if err != nil {
70 | return nil, err
71 | }
72 |
73 | var opres OperationResponse
74 | err = json.Unmarshal(data, &opres)
75 | if err != nil {
76 | return nil, err
77 | }
78 |
79 | if len(opres.Data) == 0 && len(opres.Errors) == 0 {
80 | return nil, fmt.Errorf("no data nor errors, got %v: %.1000s", res.StatusCode, data)
81 | }
82 |
83 | return &opres, nil
84 | }
85 |
86 | func (h *Http) jsonFormField(w *multipart.Writer, name string, v interface{}) error {
87 | fw, err := w.CreateFormField(name)
88 | if err != nil {
89 | return err
90 | }
91 |
92 | return json.NewEncoder(fw).Encode(v)
93 | }
94 |
95 | func (h *Http) formReq(gqlreq Request, bodyb []byte) (*http.Request, error) {
96 | var b bytes.Buffer
97 | w := multipart.NewWriter(&b)
98 |
99 | filesMap := make(map[string][]string)
100 |
101 | i := 0
102 | for p, f := range h.collectUploads("variables", gqlreq.Variables) {
103 | k := fmt.Sprintf("%v", i)
104 | fw, err := w.CreateFormFile(k, f.Name)
105 | if err != nil {
106 | return nil, err
107 | }
108 |
109 | // Write file to field
110 | if _, err := io.Copy(fw, f.File); err != nil {
111 | return nil, err
112 | }
113 |
114 | filesMap[k] = []string{p}
115 | i++
116 | }
117 |
118 | err := w.WriteField("operations", string(bodyb))
119 | if err != nil {
120 | return nil, err
121 | }
122 |
123 | err = h.jsonFormField(w, "map", filesMap)
124 | if err != nil {
125 | return nil, err
126 | }
127 |
128 | err = w.Close()
129 | if err != nil {
130 | return nil, err
131 | }
132 |
133 | req, err := http.NewRequest("POST", h.URL, &b)
134 | if err != nil {
135 | return nil, err
136 | }
137 | req.Header.Set("Content-Type", w.FormDataContentType())
138 |
139 | return req, nil
140 | }
141 |
142 | func (h *Http) collectUploads(path string, in interface{}) map[string]Upload {
143 | if up, ok := in.(Upload); ok {
144 | return map[string]Upload{
145 | path: up,
146 | }
147 | }
148 |
149 | v := reflect.ValueOf(in)
150 | switch v.Kind() {
151 | case reflect.Slice, reflect.Array:
152 | rs := make(map[string]Upload)
153 | for i := 0; i < v.Len(); i++ {
154 | p := fmt.Sprintf("%v.%v", path, i)
155 | for fk, f := range h.collectUploads(p, v.Index(i).Interface()) {
156 | rs[fk] = f
157 | }
158 | }
159 | return rs
160 | case reflect.Struct:
161 | rs := make(map[string]Upload)
162 | for i := 0; i < v.NumField(); i++ {
163 | f := v.Field(i)
164 |
165 | if !f.CanInterface() {
166 | continue // private field
167 | }
168 |
169 | ft := v.Type()
170 | k := ft.Field(i).Tag.Get("json")
171 |
172 | if strings.Contains(k, ",") {
173 | i := strings.Index(k, ",")
174 | k = k[:i]
175 | }
176 |
177 | if k == "-" {
178 | continue
179 | }
180 |
181 | p := fmt.Sprintf("%v.%v", path, k)
182 | for fk, f := range h.collectUploads(p, f.Interface()) {
183 | rs[fk] = f
184 | }
185 | }
186 | return rs
187 | case reflect.Map:
188 | rs := make(map[string]Upload)
189 | iter := v.MapRange()
190 | for iter.Next() {
191 | p := fmt.Sprintf("%v.%v", path, iter.Key().Interface())
192 | for fk, f := range h.collectUploads(p, iter.Value().Interface()) {
193 | rs[fk] = f
194 | }
195 | }
196 | return rs
197 |
198 | case reflect.Ptr:
199 | if v.IsNil() {
200 | return nil
201 | }
202 |
203 | return h.collectUploads(path, v.Elem().Interface())
204 | }
205 |
206 | return nil
207 | }
208 |
--------------------------------------------------------------------------------
/client/transport/transport_mock.go:
--------------------------------------------------------------------------------
1 | package transport
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/vektah/gqlparser/v2/gqlerror"
7 | )
8 |
9 | type Mock map[string]Func
10 |
11 | func (m Mock) Request(req Request) Response {
12 | h, ok := m[req.Query]
13 | if !ok {
14 | panic(fmt.Sprintf("query not handled: \n%v", req.Query))
15 | }
16 |
17 | return h(req)
18 | }
19 |
20 | func NewMockOperationResponse(v interface{}, errs gqlerror.List) OperationResponse {
21 | data, _ := json.Marshal(v)
22 |
23 | return OperationResponse{
24 | Data: data,
25 | Errors: errs,
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/client/transport/transport_split.go:
--------------------------------------------------------------------------------
1 | package transport
2 |
3 | type Func func(Request) Response
4 |
5 | func (f Func) Request(req Request) Response {
6 | return f(req)
7 | }
8 |
9 | func Split(f func(Request) (Transport, error)) Transport {
10 | return Func(func(req Request) Response {
11 | tr, err := f(req)
12 | if err != nil {
13 | return NewErrorResponse(err)
14 | }
15 |
16 | return tr.Request(req)
17 | })
18 | }
19 |
20 | // SplitSubscription routes subscription to subtr, and other type of queries to othertr
21 | func SplitSubscription(subtr, othertr Transport) Transport {
22 | return Split(func(req Request) (Transport, error) {
23 | if req.Operation == Subscription {
24 | return subtr, nil
25 | }
26 |
27 | return othertr, nil
28 | })
29 | }
30 |
--------------------------------------------------------------------------------
/client/transport/transport_ws.go:
--------------------------------------------------------------------------------
1 | package transport
2 |
3 | // Original work from https://github.com/hasura/go-graphql-client/blob/0806e5ec7/subscription.go
4 | import (
5 | "context"
6 | "encoding/json"
7 | "errors"
8 | "fmt"
9 | "github.com/vektah/gqlparser/v2/gqlerror"
10 | "io"
11 | "nhooyr.io/websocket"
12 | "os"
13 | "strconv"
14 | "strings"
15 | "sync"
16 | "sync/atomic"
17 | "time"
18 | )
19 |
20 | type OperationMessageType string
21 |
22 | const (
23 | // GQL_CONNECTION_INIT the Client sends this message after plain websocket connection to start the communication with the server
24 | GQL_CONNECTION_INIT OperationMessageType = "connection_init"
25 | // GQL_CONNECTION_ERROR The server may responses with this message to the GQL_CONNECTION_INIT from client, indicates the server rejected the connection.
26 | GQL_CONNECTION_ERROR OperationMessageType = "conn_err"
27 | // GQL_START Client sends this message to execute GraphQL operation
28 | GQL_START OperationMessageType = "start"
29 | // GQL_STOP Client sends this message in order to stop a running GraphQL operation execution (for example: unsubscribe)
30 | GQL_STOP OperationMessageType = "stop"
31 | // GQL_ERROR Server sends this message upon a failing operation, before the GraphQL execution, usually due to GraphQL validation errors (resolver errors are part of GQL_DATA message, and will be added as errors array)
32 | GQL_ERROR OperationMessageType = "error"
33 | // GQL_DATA The server sends this message to transfter the GraphQL execution result from the server to the client, this message is a response for GQL_START message.
34 | GQL_DATA OperationMessageType = "data"
35 | // GQL_COMPLETE Server sends this message to indicate that a GraphQL operation is done, and no more data will arrive for the specific operation.
36 | GQL_COMPLETE OperationMessageType = "complete"
37 | // GQL_CONNECTION_KEEP_ALIVE Server message that should be sent right after each GQL_CONNECTION_ACK processed and then periodically to keep the client connection alive.
38 | // The client starts to consider the keep alive message only upon the first received keep alive message from the server.
39 | GQL_CONNECTION_KEEP_ALIVE OperationMessageType = "ka"
40 | // GQL_CONNECTION_ACK The server may responses with this message to the GQL_CONNECTION_INIT from client, indicates the server accepted the connection. May optionally include a payload.
41 | GQL_CONNECTION_ACK OperationMessageType = "connection_ack"
42 | // GQL_CONNECTION_TERMINATE the Client sends this message to terminate the connection.
43 | GQL_CONNECTION_TERMINATE OperationMessageType = "connection_terminate"
44 |
45 | // GQL_UNKNOWN is an Unknown operation type, for logging only
46 | GQL_UNKNOWN OperationMessageType = "unknown"
47 | // GQL_INTERNAL is the Internal status, for logging only
48 | GQL_INTERNAL OperationMessageType = "internal"
49 | )
50 |
51 | type WebsocketConn interface {
52 | ReadJSON(v interface{}) error
53 | WriteJSON(v interface{}) error
54 | Close() error
55 | // SetReadLimit sets the maximum size in bytes for a message read from the peer. If a
56 | // message exceeds the limit, the connection sends a close message to the peer
57 | // and returns ErrReadLimit to the application.
58 | SetReadLimit(limit int64)
59 | }
60 |
61 | type OperationMessage struct {
62 | ID string `json:"id,omitempty"`
63 | Type OperationMessageType `json:"type"`
64 | Payload json.RawMessage `json:"payload,omitempty"`
65 | }
66 |
67 | func (msg OperationMessage) String() string {
68 | return fmt.Sprintf("%v %v %s", msg.ID, msg.Type, msg.Payload)
69 | }
70 |
71 | type ConnOptions struct {
72 | Context context.Context
73 | URL string
74 | Timeout time.Duration
75 | }
76 |
77 | type wsResponse struct {
78 | *ChanResponse
79 | Context context.Context
80 | OperationRequest OperationRequest
81 | started bool
82 | }
83 |
84 | type WebsocketConnProvider func(ctx context.Context, URL string) (WebsocketConn, error)
85 |
86 | type Status int
87 |
88 | const (
89 | StatusDisconnected Status = iota
90 | StatusConnected
91 | StatusReady
92 | )
93 |
94 | func (s Status) String() string {
95 | switch s {
96 | case StatusDisconnected:
97 | return "disconnected"
98 | case StatusConnected:
99 | return "connected"
100 | case StatusReady:
101 | return "ready"
102 | }
103 |
104 | panic("unknown status")
105 | }
106 |
107 | // Ws transports GQL queries over websocket
108 | // Start() must be called to initiate the websocket connection
109 | // Close() must be called to dispose of the transport
110 | type Ws struct {
111 | URL string
112 | // WebsocketConnProvider defaults to DefaultWebsocketConnProvider(30 * time.Second)
113 | WebsocketConnProvider WebsocketConnProvider
114 | // ConnectionParams will be sent during the connection init
115 | ConnectionParams interface{}
116 |
117 | cancel context.CancelFunc
118 | conn WebsocketConn
119 | running bool
120 | status Status
121 | sc *sync.Cond
122 |
123 | ops map[string]*wsResponse
124 | opsm sync.Mutex
125 |
126 | o sync.Once
127 | errCh chan error
128 | i uint64
129 | rm sync.Mutex
130 | log bool
131 | }
132 |
133 | func (t *Ws) sendErr(err error) {
134 | select {
135 | case t.errCh <- err: // Attempt to write err
136 | default:
137 | }
138 | }
139 |
140 | func (t *Ws) init() {
141 | t.o.Do(func() {
142 | t.ops = make(map[string]*wsResponse)
143 |
144 | t.sc = sync.NewCond(&sync.Mutex{})
145 |
146 | t.conn = &closedWs{}
147 |
148 | if t.WebsocketConnProvider == nil {
149 | t.WebsocketConnProvider = DefaultWebsocketConnProvider(30 * time.Second)
150 | }
151 |
152 | t.log, _ = strconv.ParseBool(os.Getenv("GQLGENC_WS_LOG"))
153 | })
154 | }
155 |
156 | func (t *Ws) WaitFor(st Status, s time.Duration) {
157 | t.init()
158 |
159 | t.printLog(GQL_INTERNAL, "WAIT FOR", st)
160 |
161 | for {
162 | t.waitFor(st)
163 |
164 | time.Sleep(s)
165 |
166 | // After timeout, is it still in the expected status ?
167 | if t.status == st {
168 | break
169 | }
170 | }
171 |
172 | t.printLog(GQL_INTERNAL, "DONE WAIT FOR", st)
173 | }
174 |
175 | func (t *Ws) waitFor(s Status) {
176 | t.init()
177 |
178 | t.sc.L.Lock()
179 | defer t.sc.L.Unlock()
180 | for t.status != s {
181 | t.sc.Wait()
182 | }
183 | }
184 |
185 | func (t *Ws) setRunning(v bool) {
186 | t.printLog(GQL_INTERNAL, "SET ISRUNNING", v)
187 | t.running = v
188 | if v == false {
189 | t.setStatus(StatusDisconnected)
190 | } else {
191 | t.sc.Broadcast()
192 | }
193 | }
194 |
195 | func (t *Ws) setStatus(s Status) {
196 | if t.status == s {
197 | return
198 | }
199 |
200 | t.printLog(GQL_INTERNAL, "SET STATUS", s)
201 | t.status = s
202 | t.sc.Broadcast()
203 | }
204 |
205 | func (t *Ws) Start(ctx context.Context) <-chan error {
206 | t.init()
207 |
208 | if t.running {
209 | panic("transport is already running")
210 | }
211 |
212 | t.errCh = make(chan error)
213 |
214 | go t.run(ctx)
215 |
216 | return t.errCh
217 | }
218 |
219 | func (t *Ws) readJson(v interface{}) error {
220 | return t.conn.ReadJSON(v)
221 | }
222 |
223 | func (t *Ws) writeJson(v interface{}) error {
224 | return t.conn.WriteJSON(v)
225 | }
226 |
227 | func (t *Ws) run(inctx context.Context) {
228 | defer func() {
229 | t.setRunning(false)
230 | close(t.errCh)
231 | }()
232 |
233 | t.setRunning(true)
234 |
235 | ctx := inctx
236 | for {
237 | //t.printLog(GQL_INTERNAL, "STATUS", t.status)
238 |
239 | select {
240 | case <-inctx.Done():
241 | err := inctx.Err()
242 | t.printLog(GQL_INTERNAL, "CTX DONE", err)
243 | t.sendErr(err)
244 | return
245 | default:
246 | // continue...
247 | }
248 |
249 | if t.status == StatusDisconnected {
250 | t.printLog(GQL_INTERNAL, "CANCEL PREV CTX")
251 |
252 | if t.cancel != nil {
253 | t.cancel()
254 | }
255 | ctx, t.cancel = context.WithCancel(inctx)
256 |
257 | t.printLog(GQL_INTERNAL, "CONNECTING")
258 | conn, err := t.WebsocketConnProvider(ctx, t.URL)
259 | if err != nil {
260 | t.printLog(GQL_INTERNAL, "WebsocketConnProvider ERR", err)
261 | t.ResetWithErr(err)
262 | time.Sleep(time.Second)
263 | continue
264 | }
265 | t.printLog(GQL_INTERNAL, "HAS CONN")
266 | t.conn = conn
267 | t.setStatus(StatusConnected)
268 |
269 | err = t.sendConnectionInit()
270 | if err != nil {
271 | t.printLog(GQL_INTERNAL, "sendConnectionInit ERR", err)
272 | t.ResetWithErr(err)
273 | time.Sleep(time.Second)
274 | continue
275 | }
276 |
277 | t.printLog(GQL_INTERNAL, "CONNECTED")
278 | }
279 |
280 | var message OperationMessage
281 | if err := t.readJson(&message); err != nil {
282 | // Is expected as part of conn.ReadJSON timeout, we have not received a message or
283 | // a KA, the connection is probably dead... RIP
284 | if errors.Is(err, context.DeadlineExceeded) {
285 | t.printLog(GQL_INTERNAL, "READ DEADLINE EXCEEDED")
286 | t.ResetWithErr(err)
287 | continue
288 | }
289 |
290 | if errors.Is(err, io.EOF) || strings.Contains(err.Error(), "EOF") {
291 | t.printLog(GQL_INTERNAL, "EOF")
292 | t.ResetWithErr(err)
293 | continue
294 | }
295 |
296 | closeStatus := websocket.CloseStatus(err)
297 | if closeStatus == websocket.StatusNormalClosure {
298 | t.printLog(GQL_INTERNAL, "NORMAL CLOSURE")
299 | // close event from websocket client, exiting...
300 | return
301 | }
302 |
303 | t.printLog(GQL_INTERNAL, "READ JSON ERR", err)
304 | t.ResetWithErr(err)
305 | continue
306 | }
307 |
308 | switch message.Type {
309 | case GQL_CONNECTION_ACK:
310 | t.printLog(GQL_CONNECTION_ACK, message)
311 | t.setStatus(StatusReady)
312 |
313 | t.opsm.Lock()
314 | for id, op := range t.ops {
315 | if err := t.startOp(id, op); err != nil {
316 | t.printLog(GQL_INTERNAL, "ACK: START OP FAILED")
317 | _ = t.cancelOp(id)
318 | t.sendErr(err)
319 | }
320 | }
321 | t.opsm.Unlock()
322 | case GQL_CONNECTION_KEEP_ALIVE:
323 | t.printLog(GQL_CONNECTION_KEEP_ALIVE, message)
324 | case GQL_CONNECTION_ERROR:
325 | t.printLog(GQL_CONNECTION_ERROR, message)
326 | t.setStatus(StatusDisconnected)
327 | t.ResetWithErr(fmt.Errorf("gql conn error: %v", message))
328 | case GQL_COMPLETE:
329 | t.printLog(GQL_COMPLETE, message)
330 | _ = t.cancelOp(message.ID)
331 | case GQL_ERROR:
332 | t.printLog(GQL_ERROR, message)
333 | fallthrough
334 | case GQL_DATA:
335 | t.printLog(GQL_DATA, message)
336 |
337 | id := message.ID
338 | t.opsm.Lock()
339 | op, ok := t.ops[id]
340 | if !ok {
341 | continue
342 | }
343 | t.opsm.Unlock()
344 |
345 | var out OperationResponse
346 | err := json.Unmarshal(message.Payload, &out)
347 | if err != nil {
348 | out.Errors = append(out.Errors, gqlerror.WrapPath(nil, err))
349 | }
350 | op.Send(out)
351 | default:
352 | t.printLog(GQL_UNKNOWN, message)
353 | }
354 | }
355 | }
356 |
357 | func (t *Ws) sendConnectionInit() error {
358 | var bParams []byte = nil
359 | if t.ConnectionParams != nil {
360 | var err error
361 | bParams, err = json.Marshal(t.ConnectionParams)
362 | if err != nil {
363 | return err
364 | }
365 | }
366 |
367 | msg := OperationMessage{
368 | Type: GQL_CONNECTION_INIT,
369 | Payload: bParams,
370 | }
371 |
372 | t.printLog(GQL_CONNECTION_INIT, msg)
373 | return t.writeJson(msg)
374 | }
375 |
376 | func (t *Ws) Reset() {
377 | t.init()
378 |
379 | t.ResetWithErr(nil)
380 | }
381 |
382 | func (t *Ws) ResetWithErr(err error) {
383 | t.init()
384 |
385 | t.rm.Lock()
386 | defer t.rm.Unlock()
387 |
388 | if t.status == StatusDisconnected {
389 | return
390 | }
391 |
392 | t.setStatus(StatusDisconnected)
393 |
394 | t.printLog(GQL_INTERNAL, "RESET", err)
395 |
396 | if err != nil {
397 | t.sendErr(err)
398 | }
399 |
400 | for id, op := range t.ops {
401 | if op.started {
402 | _ = t.stopOp(id)
403 | op.started = false
404 | }
405 | }
406 |
407 | _ = t.closeConn()
408 |
409 | atomic.StoreUint64(&t.i, 0)
410 | }
411 |
412 | func (t *Ws) terminate() error {
413 | msg := OperationMessage{
414 | Type: GQL_CONNECTION_TERMINATE,
415 | }
416 |
417 | t.printLog(GQL_CONNECTION_TERMINATE, msg)
418 | return t.writeJson(msg)
419 | }
420 |
421 | func (t *Ws) closeConn() error {
422 | _ = t.terminate()
423 | err := t.conn.Close()
424 | t.conn = &closedWs{}
425 | t.cancel()
426 |
427 | t.printLog(GQL_INTERNAL, "DONE CLOSE CONN", err)
428 |
429 | return err
430 | }
431 |
432 | func (t *Ws) Close() error {
433 | t.init()
434 |
435 | t.printLog(GQL_INTERNAL, "CLOSE")
436 |
437 | for id := range t.ops {
438 | _ = t.cancelOp(id)
439 | }
440 |
441 | return t.closeConn()
442 | }
443 |
444 | func (t *Ws) Request(req Request) Response {
445 | t.init()
446 |
447 | t.printLog(GQL_INTERNAL, "REQ")
448 |
449 | id := fmt.Sprintf("%v", atomic.AddUint64(&t.i, 1))
450 |
451 | res := &wsResponse{
452 | Context: req.Context,
453 | OperationRequest: NewOperationRequestFromRequest(req),
454 | ChanResponse: NewChanResponse(
455 | func() error {
456 | t.printLog(GQL_INTERNAL, "CLOSE RES")
457 | return t.cancelOp(id)
458 | },
459 | ),
460 | }
461 |
462 | t.printLog(GQL_INTERNAL, "ADD TO OPS")
463 | t.opsm.Lock()
464 | t.ops[id] = res
465 | t.opsm.Unlock()
466 |
467 | if t.status == StatusReady {
468 | err := t.startOp(id, res)
469 | if err != nil {
470 | return NewErrorResponse(err)
471 | }
472 | }
473 |
474 | return res
475 | }
476 |
477 | func (t *Ws) printLog(typ OperationMessageType, rest ...interface{}) {
478 | if t.log {
479 | fmt.Printf("# %-20v: ", typ)
480 | fmt.Println(rest...)
481 | }
482 | }
483 |
484 | func (t *Ws) registerOp(id string, op *wsResponse) {
485 | t.opsm.Lock()
486 | defer t.opsm.Unlock()
487 |
488 | t.ops[id] = op
489 | }
490 |
491 | func (t *Ws) startOp(id string, op *wsResponse) error {
492 | if op.started {
493 | return nil
494 | }
495 |
496 | t.printLog(GQL_INTERNAL, "START OP")
497 |
498 | payload, err := json.Marshal(op.OperationRequest)
499 | if err != nil {
500 | return err
501 | }
502 |
503 | msg := OperationMessage{
504 | ID: id,
505 | Type: GQL_START,
506 | Payload: payload,
507 | }
508 |
509 | t.printLog(GQL_START, msg)
510 | if err := t.writeJson(msg); err != nil {
511 | t.printLog(GQL_INTERNAL, "GQL_START ERR", err)
512 | return err
513 | }
514 |
515 | op.started = true
516 |
517 | return nil
518 | }
519 |
520 | func (t *Ws) stopOp(id string) error {
521 | t.printLog(GQL_INTERNAL, "STOP OP", id)
522 |
523 | msg := OperationMessage{
524 | ID: id,
525 | Type: GQL_STOP,
526 | }
527 |
528 | t.printLog(GQL_STOP, msg)
529 | return t.writeJson(msg)
530 | }
531 |
532 | func (t *Ws) cancelOp(id string) error {
533 | t.printLog(GQL_INTERNAL, "CANCEL OP", id)
534 |
535 | t.opsm.Lock()
536 | op, ok := t.ops[id]
537 | if !ok {
538 | t.opsm.Unlock()
539 | return nil
540 | }
541 | delete(t.ops, id)
542 | t.opsm.Unlock()
543 |
544 | op.CloseCh()
545 |
546 | return t.stopOp(id)
547 | }
548 |
549 | func (t *Ws) GetConn() WebsocketConn {
550 | return t.conn
551 | }
552 |
--------------------------------------------------------------------------------
/client/transport/upload.go:
--------------------------------------------------------------------------------
1 | package transport
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 | )
8 |
9 | func NewUpload(f *os.File) Upload {
10 | return Upload{
11 | File: f,
12 | Name: f.Name(),
13 | }
14 | }
15 |
16 | type Upload struct {
17 | Name string
18 | File io.Reader
19 | }
20 |
21 | func (u Upload) MarshalJSON() ([]byte, error) {
22 | return []byte(`null`), nil // Should be marshaled to null, will be taken care of in multipart form
23 | }
24 |
25 | func (u *Upload) UnmarshalJSON(data []byte) error {
26 | return fmt.Errorf("type Upload should not be unmarshaled")
27 | }
28 |
--------------------------------------------------------------------------------
/client/transport/wshandler.go:
--------------------------------------------------------------------------------
1 | package transport
2 |
3 | // Original work from https://github.com/hasura/go-graphql-client/blob/0806e5ec7/subscription.go
4 |
5 | import (
6 | "context"
7 | "fmt"
8 | "nhooyr.io/websocket"
9 | "nhooyr.io/websocket/wsjson"
10 | "time"
11 | )
12 |
13 | type closedWs struct{}
14 |
15 | var ErrClosedConnection = fmt.Errorf("closed connection")
16 |
17 | func (c closedWs) ReadJSON(v interface{}) error {
18 | return ErrClosedConnection
19 | }
20 |
21 | func (c closedWs) WriteJSON(v interface{}) error {
22 | return ErrClosedConnection
23 | }
24 |
25 | func (c closedWs) Close() error {
26 | return nil
27 | }
28 |
29 | func (c closedWs) SetReadLimit(limit int64) {
30 |
31 | }
32 |
33 | var _ WebsocketConn = (*closedWs)(nil)
34 |
35 | // WebsocketHandler is default websocket handler implementation using https://github.com/nhooyr/websocket
36 | type WebsocketHandler struct {
37 | ctx context.Context
38 | timeout time.Duration
39 | *websocket.Conn
40 | }
41 |
42 | func (wh *WebsocketHandler) WriteJSON(v interface{}) error {
43 | ctx, cancel := context.WithTimeout(wh.ctx, wh.timeout)
44 | defer cancel()
45 |
46 | return wsjson.Write(ctx, wh.Conn, v)
47 | }
48 |
49 | func (wh *WebsocketHandler) ReadJSON(v interface{}) error {
50 | if wh.timeout > 0 {
51 | ctx, cancel := context.WithTimeout(wh.ctx, wh.timeout)
52 | defer cancel()
53 |
54 | return wsjson.Read(ctx, wh.Conn, v)
55 | }
56 |
57 | return wsjson.Read(wh.ctx, wh.Conn, v)
58 | }
59 |
60 | func (wh *WebsocketHandler) Close() error {
61 | return wh.Conn.Close(websocket.StatusNormalClosure, "close websocket")
62 | }
63 |
64 | type WsDialOption func(o *websocket.DialOptions)
65 |
66 | // DefaultWebsocketConnProvider is the connections factory
67 | // A timeout of 0 means no timeout, reading will block until a message is received or the context is canceled
68 | // If your server supports the keepalive, set the timeout to something greater than the server keepalive
69 | // (for example 15s for a 10s keepalive)
70 | func DefaultWebsocketConnProvider(timeout time.Duration, optionfs ...WsDialOption) WebsocketConnProvider {
71 | return func(ctx context.Context, URL string) (WebsocketConn, error) {
72 | options := &websocket.DialOptions{
73 | Subprotocols: []string{"graphql-ws"},
74 | }
75 | for _, f := range optionfs {
76 | f(options)
77 | }
78 |
79 | c, _, err := websocket.Dial(ctx, URL, options)
80 | if err != nil {
81 | return nil, err
82 | }
83 |
84 | return &WebsocketHandler{
85 | ctx: ctx,
86 | Conn: c,
87 | timeout: timeout,
88 | }, nil
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/client/transport_mock_test.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/infiotinc/gqlgenc/client/transport"
7 | "github.com/stretchr/testify/assert"
8 | "os"
9 | "testing"
10 | )
11 |
12 | func TestSingleResponse(t *testing.T) {
13 | _ = os.Setenv("GQLGENC_WS_LOG", "1")
14 |
15 | cli := &Client{
16 | Transport: transport.Mock{
17 | "query": func(req transport.Request) transport.Response {
18 | return transport.NewSingleResponse(transport.NewMockOperationResponse("hey", nil))
19 | },
20 | },
21 | }
22 |
23 | var res string
24 | _, err := cli.Query(context.Background(), "", "query", nil, &res)
25 | if err != nil {
26 | t.Fatal(err)
27 | }
28 |
29 | assert.Equal(t, "hey", res)
30 | }
31 |
32 | func TestChanResponse(t *testing.T) {
33 | _ = os.Setenv("GQLGENC_WS_LOG", "1")
34 |
35 | cli := &Client{
36 | Transport: transport.Mock{
37 | "query": func(req transport.Request) transport.Response {
38 | res := transport.NewChanResponse(nil)
39 |
40 | go func() {
41 | for i := 0; i < 3; i++ {
42 | res.Send(transport.NewMockOperationResponse(fmt.Sprintf("hey%v", i), nil))
43 | }
44 | res.CloseCh()
45 | }()
46 |
47 | return res
48 | },
49 | },
50 | }
51 |
52 | sub := cli.Subscription(context.Background(), "", "query", nil)
53 | defer sub.Close()
54 |
55 | msgs := make([]string, 0)
56 |
57 | for sub.Next() {
58 | res := sub.Get()
59 |
60 | var data string
61 | err := res.UnmarshalData(&data)
62 | if err != nil {
63 | t.Fatal(err)
64 | }
65 |
66 | msgs = append(msgs, data)
67 | }
68 |
69 | if err := sub.Err(); err != nil {
70 | t.Fatal(err)
71 | }
72 |
73 | assert.Len(t, msgs, 3)
74 | }
75 |
--------------------------------------------------------------------------------
/clientgen/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Yamashou
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/clientgen/client.go:
--------------------------------------------------------------------------------
1 | package clientgen
2 |
3 | import (
4 | "fmt"
5 | gqlgenCfg "github.com/99designs/gqlgen/codegen/config"
6 | "github.com/99designs/gqlgen/plugin"
7 | "github.com/infiotinc/gqlgenc/config"
8 | )
9 |
10 | var _ plugin.ConfigMutator = &Plugin{}
11 |
12 | type Plugin struct {
13 | queryFilePaths []string
14 | Client gqlgenCfg.PackageConfig
15 | GenerateConfig *config.GenerateConfig
16 | Cfg *config.Config
17 | }
18 |
19 | func New(cfg *config.Config) *Plugin {
20 | return &Plugin{
21 | queryFilePaths: cfg.Query,
22 | Client: cfg.Client.PackageConfig,
23 | GenerateConfig: cfg.Generate,
24 | Cfg: cfg,
25 | }
26 | }
27 |
28 | func (p *Plugin) Name() string {
29 | return "clientgen"
30 | }
31 |
32 | func (p *Plugin) MutateConfig(cfg *gqlgenCfg.Config) error {
33 | querySources, err := LoadQuerySources(p.queryFilePaths)
34 | if err != nil {
35 | return fmt.Errorf("load query sources failed: %w", err)
36 | }
37 |
38 | // 1. 全体のqueryDocumentを1度にparse
39 | // 1. Parse document from source of query
40 | queryDocument, err := ParseQueryDocuments(cfg.Schema, querySources)
41 | if err != nil {
42 | return fmt.Errorf(": %w", err)
43 | }
44 |
45 | // 2. OperationごとのqueryDocumentを作成
46 | // 2. Separate documents for each operation
47 | queryDocuments, err := QueryDocumentsByOperations(cfg.Schema, queryDocument.Operations)
48 | if err != nil {
49 | return fmt.Errorf("parse query document failed: %w", err)
50 | }
51 |
52 | // 3. テンプレートと情報ソースを元にコード生成
53 | // 3. Generate code from template and document source
54 | sourceGenerator := NewSourceGenerator(cfg, p.Cfg, p.Client)
55 | source := NewSource(cfg.Schema, queryDocument, sourceGenerator, p.GenerateConfig)
56 |
57 | err = source.ExtraTypes()
58 | if err != nil {
59 | return fmt.Errorf("generating extra types failed: %w", err)
60 | }
61 |
62 | err = source.Fragments()
63 | if err != nil {
64 | return fmt.Errorf("generating fragment failed: %w", err)
65 | }
66 |
67 | operationResponses, err := source.OperationResponses()
68 | if err != nil {
69 | return fmt.Errorf("generating operation response failed: %w", err)
70 | }
71 |
72 | operations := source.Operations(queryDocuments, operationResponses)
73 |
74 | genTypes := sourceGenerator.GenTypes()
75 | ptrTypes := sourceGenerator.PtrTypes()
76 |
77 | generateClient := p.GenerateConfig.ShouldGenerateClient()
78 | if err := RenderTemplate(cfg, genTypes, ptrTypes, operations, generateClient, p.Client); err != nil {
79 | return fmt.Errorf("template failed: %w", err)
80 | }
81 |
82 | return nil
83 | }
84 |
--------------------------------------------------------------------------------
/clientgen/modelgen.go:
--------------------------------------------------------------------------------
1 | package clientgen
2 |
3 | import (
4 | "fmt"
5 | "github.com/99designs/gqlgen/codegen/templates"
6 | "github.com/vektah/gqlparser/v2/ast"
7 | "go/constant"
8 | "go/types"
9 | )
10 |
11 | func (r *SourceGenerator) genFromDefinition(def *ast.Definition) types.Type {
12 | switch def.Kind {
13 | case ast.InputObject:
14 | if r.ccfg.Client.InputAsMap || r.ccfg.Models[def.Name].AsMap {
15 | genType := r.GetGenType(NewFieldPath(def.Kind, def.Name).Name())
16 |
17 | for _, field := range def.Fields {
18 | fieldDef := r.cfg.Schema.Types[field.Type.Name()]
19 |
20 | typ := r.namedType(NewFieldPath(fieldDef.Kind, fieldDef.Name), func() types.Type {
21 | return r.genFromDefinition(fieldDef)
22 | })
23 |
24 | typ = r.binder.CopyModifiersFromAst(field.Type, typ)
25 |
26 | f := MapField{
27 | Name: field.Name,
28 | Type: typ,
29 | }
30 |
31 | if field.Type.NonNull {
32 | genType.MapReq = append(genType.MapReq, f)
33 | } else {
34 | r.collectPtrTypes(typ, false)
35 |
36 | genType.MapOpt = append(genType.MapOpt, f)
37 | }
38 | }
39 |
40 | return types.NewMap(
41 | types.Typ[types.String],
42 | types.NewInterfaceType(nil, nil),
43 | )
44 | }
45 |
46 | fallthrough // Not input as map, treat as object
47 | case ast.Object:
48 | vars := make([]*types.Var, 0, len(def.Fields))
49 | tags := make([]string, 0, len(def.Fields))
50 |
51 | for _, field := range def.Fields {
52 | fieldDef := r.cfg.Schema.Types[field.Type.Name()]
53 |
54 | typ := r.namedType(NewFieldPath(fieldDef.Kind, fieldDef.Name), func() types.Type {
55 | return r.genFromDefinition(fieldDef)
56 | })
57 |
58 | typ = r.binder.CopyModifiersFromAst(field.Type, typ)
59 |
60 | if isStruct(typ) && (fieldDef.Kind == ast.Object || fieldDef.Kind == ast.InputObject) {
61 | typ = types.NewPointer(typ)
62 | }
63 |
64 | name := field.Name
65 | if nameOveride := r.cfg.Models[def.Name].Fields[field.Name].FieldName; nameOveride != "" {
66 | name = nameOveride
67 | }
68 |
69 | vars = append(vars, types.NewVar(0, nil, templates.ToGo(name), typ))
70 | tags = append(tags, `json:"`+name+`"`)
71 | }
72 |
73 | return types.NewStruct(vars, tags)
74 |
75 | case ast.Enum:
76 | genType := r.GetGenType(NewFieldPath(def.Kind, def.Name).Name())
77 |
78 | consts := make([]*types.Const, 0)
79 | for _, v := range def.EnumValues {
80 | consts = append(consts, types.NewConst(
81 | 0,
82 | r.client.Pkg(),
83 | fmt.Sprintf("%v%v", templates.ToGo(def.Name), templates.ToGo(v.Name)),
84 | genType.RefType,
85 | constant.MakeString(v.Name),
86 | ))
87 | }
88 |
89 | genType.Consts = consts
90 |
91 | return types.Typ[types.String]
92 | case ast.Scalar:
93 | panic("scalars must be predeclared: " + def.Name)
94 | }
95 |
96 | panic("cannot generate type for def: " + def.Name)
97 | }
98 |
99 | func isStruct(t types.Type) bool {
100 | _, is := t.Underlying().(*types.Struct)
101 | return is
102 | }
103 |
--------------------------------------------------------------------------------
/clientgen/query.go:
--------------------------------------------------------------------------------
1 | package clientgen
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/vektah/gqlparser/v2/ast"
7 | "github.com/vektah/gqlparser/v2/parser"
8 | "github.com/vektah/gqlparser/v2/validator"
9 | )
10 |
11 | func ParseQueryDocuments(schema *ast.Schema, querySources []*ast.Source) (*ast.QueryDocument, error) {
12 | var queryDocument ast.QueryDocument
13 | for _, querySource := range querySources {
14 | query, gqlerr := parser.ParseQuery(querySource)
15 | if gqlerr != nil {
16 | return nil, fmt.Errorf(": %w", gqlerr)
17 | }
18 |
19 | mergeQueryDocument(&queryDocument, query)
20 | }
21 |
22 | if errs := validator.Validate(schema, &queryDocument); errs != nil {
23 | return nil, fmt.Errorf(": %w", errs)
24 | }
25 |
26 | return &queryDocument, nil
27 | }
28 |
29 | func mergeQueryDocument(q, other *ast.QueryDocument) {
30 | q.Operations = append(q.Operations, other.Operations...)
31 | q.Fragments = append(q.Fragments, other.Fragments...)
32 | }
33 |
34 | func QueryDocumentsByOperations(schema *ast.Schema, operations ast.OperationList) ([]*ast.QueryDocument, error) {
35 | queryDocuments := make([]*ast.QueryDocument, 0, len(operations))
36 | for _, operation := range operations {
37 | fragments := fragmentsInOperationDefinition(operation)
38 |
39 | queryDocument := &ast.QueryDocument{
40 | Operations: ast.OperationList{operation},
41 | Fragments: fragments,
42 | Position: nil,
43 | }
44 |
45 | if errs := validator.Validate(schema, queryDocument); errs != nil {
46 | return nil, fmt.Errorf(": %w", errs)
47 | }
48 |
49 | queryDocuments = append(queryDocuments, queryDocument)
50 | }
51 |
52 | return queryDocuments, nil
53 | }
54 |
55 | func fragmentsInOperationDefinition(operation *ast.OperationDefinition) ast.FragmentDefinitionList {
56 | fragments := fragmentsInOperationWalker(operation.SelectionSet)
57 | uniqueFragments := fragmentsUnique(fragments)
58 |
59 | return uniqueFragments
60 | }
61 |
62 | func fragmentsUnique(fragments ast.FragmentDefinitionList) ast.FragmentDefinitionList {
63 | uniqueMap := make(map[string]*ast.FragmentDefinition)
64 | for _, fragment := range fragments {
65 | uniqueMap[fragment.Name] = fragment
66 | }
67 |
68 | uniqueFragments := make(ast.FragmentDefinitionList, 0, len(uniqueMap))
69 | for _, fragment := range uniqueMap {
70 | uniqueFragments = append(uniqueFragments, fragment)
71 | }
72 |
73 | return uniqueFragments
74 | }
75 |
76 | func fragmentsInOperationWalker(selectionSet ast.SelectionSet) ast.FragmentDefinitionList {
77 | var fragments ast.FragmentDefinitionList
78 | for _, selection := range selectionSet {
79 | var selectionSet ast.SelectionSet
80 | switch selection := selection.(type) {
81 | case *ast.Field:
82 | selectionSet = selection.SelectionSet
83 | case *ast.InlineFragment:
84 | selectionSet = selection.SelectionSet
85 | case *ast.FragmentSpread:
86 | fragments = append(fragments, selection.Definition)
87 | selectionSet = selection.Definition.SelectionSet
88 | }
89 |
90 | fragments = append(fragments, fragmentsInOperationWalker(selectionSet)...)
91 | }
92 |
93 | return fragments
94 | }
95 |
--------------------------------------------------------------------------------
/clientgen/query_source.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2020 gqlgen authors
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | SOFTWARE.
21 | */
22 |
23 | package clientgen
24 |
25 | import (
26 | "fmt"
27 | "io/ioutil"
28 | "os"
29 | "path/filepath"
30 | "regexp"
31 | "strings"
32 |
33 | "github.com/99designs/gqlgen/codegen/config"
34 | "github.com/vektah/gqlparser/v2/ast"
35 | )
36 |
37 | var path2regex = strings.NewReplacer(
38 | `.`, `\.`,
39 | `*`, `.+`,
40 | `\`, `[\\/]`,
41 | `/`, `[\\/]`,
42 | )
43 |
44 | // LoadQuerySourceなどは、gqlgenがLoadConfigでSchemaを読み込む時の実装をコピーして一部修正している
45 | // **/test/*.graphqlなどに対応している
46 | func LoadQuerySources(queryFileNames []string) ([]*ast.Source, error) {
47 | var noGlobQueryFileNames config.StringList
48 |
49 | var err error
50 | preGlobbing := queryFileNames
51 | for _, f := range preGlobbing {
52 | var matches []string
53 |
54 | // for ** we want to override default globbing patterns and walk all
55 | // subdirectories to match schema files.
56 | if strings.Contains(f, "**") {
57 | pathParts := strings.SplitN(f, "**", 2)
58 | rest := strings.TrimPrefix(strings.TrimPrefix(pathParts[1], `\`), `/`)
59 | // turn the rest of the glob into a regex, anchored only at the end because ** allows
60 | // for any number of dirs in between and walk will let us match against the full path name
61 | globRe := regexp.MustCompile(path2regex.Replace(rest) + `$`)
62 |
63 | if err := filepath.Walk(pathParts[0], func(path string, info os.FileInfo, err error) error {
64 | if err != nil {
65 | return err
66 | }
67 |
68 | if globRe.MatchString(strings.TrimPrefix(path, pathParts[0])) {
69 | matches = append(matches, path)
70 | }
71 |
72 | return nil
73 | }); err != nil {
74 | return nil, fmt.Errorf("failed to walk schema at root %s: %w", pathParts[0], err)
75 | }
76 | } else {
77 | matches, err = filepath.Glob(f)
78 | if err != nil {
79 | return nil, fmt.Errorf("failed to glob schema filename %v: %w", f, err)
80 | }
81 | }
82 |
83 | for _, m := range matches {
84 | if noGlobQueryFileNames.Has(m) {
85 | continue
86 | }
87 |
88 | noGlobQueryFileNames = append(noGlobQueryFileNames, m)
89 | }
90 | }
91 |
92 | querySources := make([]*ast.Source, 0, len(noGlobQueryFileNames))
93 | for _, filename := range noGlobQueryFileNames {
94 | filename = filepath.ToSlash(filename)
95 | var err error
96 | var schemaRaw []byte
97 | schemaRaw, err = ioutil.ReadFile(filename)
98 | if err != nil {
99 | return nil, fmt.Errorf("unable to open schema: %w", err)
100 | }
101 |
102 | querySources = append(querySources, &ast.Source{Name: filename, Input: string(schemaRaw)})
103 | }
104 |
105 | return querySources, nil
106 | }
107 |
--------------------------------------------------------------------------------
/clientgen/source.go:
--------------------------------------------------------------------------------
1 | package clientgen
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/infiotinc/gqlgenc/config"
7 | "github.com/vektah/gqlparser/v2/ast"
8 | "github.com/vektah/gqlparser/v2/formatter"
9 | "go/types"
10 | )
11 |
12 | type Source struct {
13 | schema *ast.Schema
14 | queryDocument *ast.QueryDocument
15 | sourceGenerator *SourceGenerator
16 | generateConfig *config.GenerateConfig
17 | }
18 |
19 | func NewSource(schema *ast.Schema, queryDocument *ast.QueryDocument, sourceGenerator *SourceGenerator, generateConfig *config.GenerateConfig) *Source {
20 | return &Source{
21 | schema: schema,
22 | queryDocument: queryDocument,
23 | sourceGenerator: sourceGenerator,
24 | generateConfig: generateConfig,
25 | }
26 | }
27 |
28 | type TypeTarget struct {
29 | Type types.Type
30 | Name string
31 | }
32 |
33 | type MapField struct {
34 | Name string
35 | Type types.Type
36 | }
37 |
38 | type Type struct {
39 | Name string
40 | Path FieldPath
41 | Type types.Type
42 | UnmarshalTypes map[string]TypeTarget
43 | RefType *types.Named
44 | Consts []*types.Const
45 |
46 | MapReq []MapField
47 | MapOpt []MapField
48 | }
49 |
50 | func (t Type) IsInputMap() bool {
51 | return len(t.MapReq) > 0 || len(t.MapOpt) > 0
52 | }
53 |
54 | func (s *Source) Fragments() error {
55 | for _, fragment := range s.queryDocument.Fragments {
56 | path := NewFieldPath(fragment.Definition.Kind, fragment.Name)
57 | _ = s.sourceGenerator.namedType(path, func() types.Type {
58 | responseFields := s.sourceGenerator.NewResponseFields(path, &fragment.SelectionSet)
59 |
60 | typ := s.sourceGenerator.genFromResponseFields(path, responseFields)
61 | return typ
62 | })
63 | }
64 |
65 | return nil
66 | }
67 |
68 | func (s *Source) ExtraTypes() error {
69 | for _, t := range s.sourceGenerator.ccfg.Client.ExtraTypes {
70 | def := s.sourceGenerator.cfg.Schema.Types[t]
71 |
72 | if def == nil {
73 | panic("type " + t + " does not exist in schema")
74 | }
75 |
76 | _ = s.sourceGenerator.namedType(NewFieldPath(def.Kind, def.Name), func() types.Type {
77 | return s.sourceGenerator.genFromDefinition(def)
78 | })
79 | }
80 |
81 | return nil
82 | }
83 |
84 | type Operation struct {
85 | Name string
86 | ResponseType types.Type
87 | Operation string
88 | OperationType string
89 | Args []*Argument
90 | VariableDefinitions ast.VariableDefinitionList
91 | }
92 |
93 | func NewOperation(operation *OperationResponse, queryDocument *ast.QueryDocument, args []*Argument) *Operation {
94 | return &Operation{
95 | Name: operation.Name,
96 | OperationType: string(operation.Operation.Operation),
97 | ResponseType: operation.Type,
98 | Operation: queryString(queryDocument),
99 | Args: args,
100 | VariableDefinitions: operation.Operation.VariableDefinitions,
101 | }
102 | }
103 |
104 | func (s *Source) Operations(queryDocuments []*ast.QueryDocument, operationResponses []*OperationResponse) []*Operation {
105 | operations := make([]*Operation, 0, len(s.queryDocument.Operations))
106 |
107 | queryDocumentsMap := queryDocumentMapByOperationName(queryDocuments)
108 | operationArgsMap := s.operationArgsMapByOperationName()
109 | for _, operation := range operationResponses {
110 | queryDocument := queryDocumentsMap[operation.Name]
111 | args := operationArgsMap[operation.Name]
112 | operations = append(operations, NewOperation(operation, queryDocument, args))
113 | }
114 |
115 | return operations
116 | }
117 |
118 | func (s *Source) operationArgsMapByOperationName() map[string][]*Argument {
119 | operationArgsMap := make(map[string][]*Argument)
120 | for _, operation := range s.queryDocument.Operations {
121 | operationArgsMap[operation.Name] = s.sourceGenerator.OperationArguments(operation.VariableDefinitions)
122 | }
123 |
124 | return operationArgsMap
125 | }
126 |
127 | func queryDocumentMapByOperationName(queryDocuments []*ast.QueryDocument) map[string]*ast.QueryDocument {
128 | queryDocumentMap := make(map[string]*ast.QueryDocument)
129 | for _, queryDocument := range queryDocuments {
130 | operation := queryDocument.Operations[0]
131 | queryDocumentMap[operation.Name] = queryDocument
132 | }
133 |
134 | return queryDocumentMap
135 | }
136 |
137 | func queryString(queryDocument *ast.QueryDocument) string {
138 | var buf bytes.Buffer
139 | astFormatter := formatter.NewFormatter(&buf)
140 | astFormatter.FormatQueryDocument(queryDocument)
141 |
142 | return buf.String()
143 | }
144 |
145 | type OperationResponse struct {
146 | Operation *ast.OperationDefinition
147 | Name string
148 | Type types.Type
149 | }
150 |
151 | const OperationKind ast.DefinitionKind = "OPERATION"
152 |
153 | func (s *Source) OperationResponses() ([]*OperationResponse, error) {
154 | operationResponses := make([]*OperationResponse, 0, len(s.queryDocument.Operations))
155 | for _, operationResponse := range s.queryDocument.Operations {
156 | name := getResponseStructName(operationResponse, s.generateConfig)
157 |
158 | path := NewFieldPath(OperationKind, name)
159 | namedType := s.sourceGenerator.namedType(path, func() types.Type {
160 | responseFields := s.sourceGenerator.NewResponseFields(path, &operationResponse.SelectionSet)
161 |
162 | typ := s.sourceGenerator.genFromResponseFields(path, responseFields)
163 | return typ
164 | })
165 |
166 | operationResponses = append(operationResponses, &OperationResponse{
167 | Operation: operationResponse,
168 | Name: name,
169 | Type: namedType,
170 | })
171 | }
172 |
173 | return operationResponses, nil
174 | }
175 |
176 | func getResponseStructName(operation *ast.OperationDefinition, generateConfig *config.GenerateConfig) string {
177 | name := operation.Name
178 | if generateConfig != nil {
179 | if generateConfig.Prefix != nil {
180 | if operation.Operation == ast.Subscription {
181 | name = fmt.Sprintf("%s%s", generateConfig.Prefix.Subscription, name)
182 | }
183 |
184 | if operation.Operation == ast.Mutation {
185 | name = fmt.Sprintf("%s%s", generateConfig.Prefix.Mutation, name)
186 | }
187 |
188 | if operation.Operation == ast.Query {
189 | name = fmt.Sprintf("%s%s", generateConfig.Prefix.Query, name)
190 | }
191 | }
192 |
193 | if generateConfig.Suffix != nil {
194 | if operation.Operation == ast.Subscription {
195 | name = fmt.Sprintf("%s%s", name, generateConfig.Suffix.Subscription)
196 | }
197 |
198 | if operation.Operation == ast.Mutation {
199 | name = fmt.Sprintf("%s%s", name, generateConfig.Suffix.Mutation)
200 | }
201 |
202 | if operation.Operation == ast.Query {
203 | name = fmt.Sprintf("%s%s", name, generateConfig.Suffix.Query)
204 | }
205 | }
206 | }
207 |
208 | return name
209 | }
210 |
--------------------------------------------------------------------------------
/clientgen/source_generator.go:
--------------------------------------------------------------------------------
1 | package clientgen
2 |
3 | import (
4 | "fmt"
5 | "github.com/99designs/gqlgen/codegen/config"
6 | "github.com/99designs/gqlgen/codegen/templates"
7 | config2 "github.com/infiotinc/gqlgenc/config"
8 | "github.com/vektah/gqlparser/v2/ast"
9 | "go/types"
10 | "math"
11 | "sort"
12 | "strings"
13 | )
14 |
15 | type FieldPath struct {
16 | Kind ast.DefinitionKind
17 | path []string
18 | }
19 |
20 | func NewFieldPath(kind ast.DefinitionKind, name string) FieldPath {
21 | return FieldPath{
22 | Kind: kind,
23 | path: []string{name},
24 | }
25 | }
26 |
27 | func (p FieldPath) With(n string) FieldPath {
28 | p.path = append(p.path, n)
29 | return p
30 | }
31 |
32 | func (p FieldPath) Name() string {
33 | pn := make([]string, 0, len(p.path))
34 | for _, n := range p.path {
35 | pn = append(pn, templates.ToGo(n))
36 | }
37 |
38 | return strings.Join(pn, "_")
39 | }
40 |
41 | func (p FieldPath) String() string {
42 | return strings.Join(p.path, ".")
43 | }
44 |
45 | type Argument struct {
46 | Variable string
47 | Type types.Type
48 | }
49 |
50 | type ResponseField struct {
51 | Name string
52 | IsFragmentSpread bool
53 | IsInlineFragment bool
54 | Type types.Type
55 | Tags []string
56 | ResponseFields ResponseFieldList
57 | }
58 |
59 | type ResponseFieldList []*ResponseField
60 |
61 | func (rs ResponseFieldList) IsFragment() bool {
62 | if len(rs) != 1 {
63 | return false
64 | }
65 |
66 | return rs[0].IsInlineFragment || rs[0].IsFragmentSpread
67 | }
68 |
69 | func (rs ResponseFieldList) IsBasicType() bool {
70 | return len(rs) == 0
71 | }
72 |
73 | func (rs ResponseFieldList) IsStructType() bool {
74 | return len(rs) > 0 && !rs.IsFragment()
75 | }
76 |
77 | type genType struct {
78 | name string
79 | typ *Type
80 | }
81 |
82 | type PtrType struct {
83 | Name string
84 | Type types.Type
85 | }
86 |
87 | type SourceGenerator struct {
88 | cfg *config.Config
89 | ccfg *config2.Config
90 | binder *config.Binder
91 | client config.PackageConfig
92 |
93 | genTypes []genType
94 | ptrTypes map[types.Type]PtrType
95 | }
96 |
97 | func (r *SourceGenerator) RegisterGenType(name string, typ *Type) {
98 | if gt := r.GetGenType(name); gt != nil {
99 | panic(name + ": gen type already defined")
100 | }
101 |
102 | if typ.RefType == nil {
103 | typ.RefType = types.NewNamed(
104 | types.NewTypeName(0, r.client.Pkg(), name, nil),
105 | nil,
106 | nil,
107 | )
108 | }
109 |
110 | r.genTypes = append(r.genTypes, genType{
111 | name: name,
112 | typ: typ,
113 | })
114 | }
115 |
116 | func (r *SourceGenerator) GetGenType(name string) *Type {
117 | for _, gt := range r.genTypes {
118 | if gt.name == name {
119 | return gt.typ
120 | }
121 | }
122 |
123 | return nil
124 | }
125 |
126 | func (r *SourceGenerator) GenTypes() []*Type {
127 | typs := make([]*Type, 0)
128 | for _, gt := range r.genTypes {
129 | typs = append(typs, gt.typ)
130 | }
131 |
132 | sort.SliceStable(typs, func(i, j int) bool {
133 | pi := typs[i].Path.path
134 | pj := typs[j].Path.path
135 |
136 | for n := 0; n < int(math.Min(float64(len(pi)), float64(len(pj)))); n++ {
137 | if pi[n] != pj[n] {
138 | return pi[n] < pj[n]
139 | }
140 | }
141 |
142 | return len(pi) < len(pj)
143 | })
144 |
145 | return typs
146 | }
147 |
148 | func (r *SourceGenerator) PtrTypes() []PtrType {
149 | typs := make([]PtrType, 0)
150 | for _, t := range r.ptrTypes {
151 | typs = append(typs, t)
152 | }
153 |
154 | sort.SliceStable(typs, func(i, j int) bool {
155 | pi := typs[i].Name
156 | pj := typs[j].Name
157 |
158 | return pi < pj
159 | })
160 |
161 | return typs
162 | }
163 |
164 | func NewSourceGenerator(cfg *config.Config, ccfg *config2.Config, client config.PackageConfig) *SourceGenerator {
165 | return &SourceGenerator{
166 | cfg: cfg,
167 | ccfg: ccfg,
168 | binder: cfg.NewBinder(),
169 | client: client,
170 | }
171 | }
172 |
173 | func (r *SourceGenerator) addTypenameIfInlineFragment(selectionSet *ast.SelectionSet) {
174 | for _, s := range *selectionSet {
175 | switch s.(type) {
176 | case *ast.InlineFragment:
177 | for _, s := range *selectionSet {
178 | if field, ok := s.(*ast.Field); ok {
179 | if field.Alias == "__typename" {
180 | return // Already has it
181 | }
182 | }
183 | }
184 |
185 | *selectionSet = append(ast.SelectionSet{&ast.Field{
186 | Name: "__typename",
187 | Alias: "__typename",
188 | Definition: &ast.FieldDefinition{
189 | Name: "Typename",
190 | Type: ast.NonNullNamedType("String", nil),
191 | Arguments: ast.ArgumentDefinitionList{
192 | {Name: "name", Type: ast.NonNullNamedType("String", nil)},
193 | },
194 | },
195 | }}, *selectionSet...)
196 | return
197 | }
198 | }
199 | }
200 |
201 | func (r *SourceGenerator) NewResponseFields(path FieldPath, selectionSet *ast.SelectionSet) ResponseFieldList {
202 | r.addTypenameIfInlineFragment(selectionSet)
203 |
204 | responseFields := make(ResponseFieldList, 0, len(*selectionSet))
205 | for _, selection := range *selectionSet {
206 | rf := r.NewResponseField(path, selection)
207 | responseFields = append(responseFields, rf)
208 | }
209 |
210 | return responseFields
211 | }
212 |
213 | func (r *SourceGenerator) GetNamedType(fullname string) types.Type {
214 | if gt := r.GetGenType(fullname); gt != nil {
215 | return gt.Type
216 | }
217 |
218 | if r.cfg.Models.Exists(fullname) && len(r.cfg.Models[fullname].Model) > 0 {
219 | model := r.cfg.Models[fullname].Model[0]
220 |
221 | typ, err := r.binder.FindTypeFromName(model)
222 | if err != nil {
223 | panic(fmt.Errorf("cannot get type for %v (%v): %w", fullname, model, err))
224 | }
225 |
226 | if n, is := typ.(*types.Named); is {
227 | return n.Underlying()
228 |
229 | }
230 |
231 | return typ
232 | }
233 |
234 | return nil
235 | }
236 |
237 | func (r *SourceGenerator) namedType(path FieldPath, gen func() types.Type) types.Type {
238 | fullname := path.Name()
239 |
240 | if gt := r.GetGenType(fullname); gt != nil {
241 | return gt.RefType
242 | }
243 |
244 | if r.cfg.Models.Exists(fullname) && len(r.cfg.Models[fullname].Model) > 0 {
245 | model := r.cfg.Models[fullname].Model[0]
246 | fmt.Printf("%s is already declared: %v\n", fullname, model)
247 |
248 | typ, err := r.binder.FindTypeFromName(model)
249 | if err != nil {
250 | panic(fmt.Errorf("cannot get type for %v (%v): %w", fullname, model, err))
251 | }
252 |
253 | return typ
254 | } else {
255 | genTyp := &Type{
256 | Name: fullname,
257 | Path: path,
258 | }
259 |
260 | r.RegisterGenType(fullname, genTyp)
261 |
262 | genTyp.Type = gen()
263 |
264 | return genTyp.RefType
265 | }
266 | }
267 |
268 | func (r *SourceGenerator) genFromResponseFields(path FieldPath, fieldsResponseFields ResponseFieldList) types.Type {
269 | fullname := path.Name()
270 |
271 | vars := make([]*types.Var, 0, len(fieldsResponseFields))
272 | tags := make([]string, 0, len(fieldsResponseFields))
273 | unmarshalTypes := map[string]TypeTarget{}
274 | for _, field := range fieldsResponseFields {
275 | typ := field.Type
276 | fieldName := templates.ToGo(field.Name)
277 | if field.IsInlineFragment {
278 | unmarshalTypes[field.Name] = TypeTarget{
279 | Type: typ,
280 | Name: fieldName,
281 | }
282 | typ = types.NewPointer(typ)
283 | }
284 |
285 | vars = append(vars, types.NewVar(0, nil, fieldName, typ))
286 | tags = append(tags, strings.Join(field.Tags, " "))
287 | }
288 |
289 | genType := r.GetGenType(fullname)
290 | genType.UnmarshalTypes = unmarshalTypes
291 |
292 | return types.NewStruct(vars, tags)
293 | }
294 |
295 | func (r *SourceGenerator) AstTypeToType(path FieldPath, fields ResponseFieldList, typ *ast.Type) types.Type {
296 | switch {
297 | case fields.IsBasicType():
298 | def := r.cfg.Schema.Types[typ.Name()]
299 |
300 | return r.namedType(NewFieldPath(def.Kind, def.Name), func() types.Type {
301 | return r.genFromDefinition(def)
302 | })
303 | case fields.IsFragment():
304 | // if a child field is fragment, this field type became fragment.
305 | return fields[0].Type
306 | case fields.IsStructType():
307 | return r.namedType(path, func() types.Type {
308 | return r.genFromResponseFields(path, fields)
309 | })
310 | default:
311 | // ここにきたらバグ
312 | // here is bug
313 | panic("not match type")
314 | }
315 | }
316 |
317 | func (r *SourceGenerator) NewResponseField(path FieldPath, selection ast.Selection) *ResponseField {
318 | switch selection := selection.(type) {
319 | case *ast.Field:
320 | fieldPath := path.With(selection.Name)
321 | fieldsResponseFields := r.NewResponseFields(fieldPath, &selection.SelectionSet)
322 | baseType := r.AstTypeToType(fieldPath, fieldsResponseFields, selection.Definition.Type)
323 |
324 | // GraphQLの定義がオプショナルのはtypeのポインタ型が返り、配列の定義場合はポインタのスライスの型になって返ってきます
325 | // return pointer type then optional type or slice pointer then slice type of definition in GraphQL.
326 | typ := r.binder.CopyModifiersFromAst(selection.Definition.Type, baseType)
327 |
328 | return &ResponseField{
329 | Name: selection.Alias,
330 | Type: typ,
331 | Tags: []string{
332 | fmt.Sprintf(`json:"%s"`, selection.Alias),
333 | },
334 | ResponseFields: fieldsResponseFields,
335 | }
336 |
337 | case *ast.FragmentSpread:
338 | fieldsResponseFields := r.NewResponseFields(path, &selection.Definition.SelectionSet)
339 |
340 | name := selection.Definition.Name
341 | typ := r.namedType(NewFieldPath(selection.ObjectDefinition.Kind, name), func() types.Type {
342 | panic(fmt.Sprintf("fragment %v must already be generated", name))
343 | })
344 |
345 | return &ResponseField{
346 | Name: selection.Name,
347 | Type: typ,
348 | IsFragmentSpread: true,
349 | ResponseFields: fieldsResponseFields,
350 | }
351 |
352 | case *ast.InlineFragment:
353 | // InlineFragmentは子要素をそのままstructとしてもつので、ここで、構造体の型を作成します
354 | path := path.With(selection.TypeCondition)
355 | fieldsResponseFields := r.NewResponseFields(path, &selection.SelectionSet)
356 | typ := r.namedType(path, func() types.Type {
357 | return r.genFromResponseFields(path, fieldsResponseFields)
358 | })
359 | return &ResponseField{
360 | Name: selection.TypeCondition,
361 | Type: typ,
362 | IsInlineFragment: true,
363 | ResponseFields: fieldsResponseFields,
364 | Tags: []string{`json:"-"`},
365 | }
366 | }
367 |
368 | panic("unexpected selection type")
369 | }
370 |
371 | func (r *SourceGenerator) collectPtrTypes(t types.Type, ptr bool) {
372 | if r.ptrTypes == nil {
373 | r.ptrTypes = map[types.Type]PtrType{}
374 | }
375 |
376 | if _, ok := r.ptrTypes[t]; ok {
377 | return
378 | }
379 |
380 | if p, is := t.(*types.Pointer); is {
381 | r.collectPtrTypes(p.Elem(), true)
382 | } else if n, is := t.(*types.Named); is {
383 | if ptr {
384 | r.ptrTypes[n] = PtrType{
385 | Name: n.Obj().Name(),
386 | Type: n,
387 | }
388 | }
389 |
390 | t := r.GetNamedType(n.Obj().Name())
391 | if t == nil {
392 | return
393 | }
394 |
395 | if s, is := t.(*types.Struct); is {
396 | for i := 0; i < s.NumFields(); i++ {
397 | r.collectPtrTypes(s.Field(i).Type(), false)
398 | }
399 | }
400 | } else if b, is := t.(*types.Basic); is {
401 | if ptr {
402 | r.ptrTypes[b] = PtrType{
403 | Name: b.Name(),
404 | Type: b,
405 | }
406 | }
407 | }
408 | }
409 |
410 | func (r *SourceGenerator) OperationArguments(variableDefinitions ast.VariableDefinitionList) []*Argument {
411 | argumentTypes := make([]*Argument, 0, len(variableDefinitions))
412 | for _, v := range variableDefinitions {
413 | baseType := r.namedType(NewFieldPath(v.Definition.Kind, v.Definition.Name), func() types.Type {
414 | return r.genFromDefinition(v.Definition)
415 | })
416 |
417 | typ := r.binder.CopyModifiersFromAst(v.Type, baseType)
418 |
419 | r.collectPtrTypes(typ, false)
420 |
421 | argumentTypes = append(argumentTypes, &Argument{
422 | Variable: v.Variable,
423 | Type: typ,
424 | })
425 | }
426 |
427 | return argumentTypes
428 | }
429 |
--------------------------------------------------------------------------------
/clientgen/template.go:
--------------------------------------------------------------------------------
1 | package clientgen
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/99designs/gqlgen/codegen/config"
7 | "github.com/99designs/gqlgen/codegen/templates"
8 | )
9 |
10 | func RenderTemplate(cfg *config.Config, types []*Type, ptrTypes []PtrType, operations []*Operation, generateClient bool, client config.PackageConfig) error {
11 | if err := templates.Render(templates.Options{
12 | PackageName: client.Package,
13 | Filename: client.Filename,
14 | Data: map[string]interface{}{
15 | "Types": types,
16 | "PtrTypes": ptrTypes,
17 | "Operations": operations,
18 | "GenerateClient": generateClient,
19 | },
20 | Packages: cfg.Packages,
21 | PackageDoc: "// Code generated by github.com/infiotinc/gqlgenc, DO NOT EDIT.\n",
22 | }); err != nil {
23 | return fmt.Errorf("%s generating failed: %w", client.Filename, err)
24 | }
25 |
26 | return nil
27 | }
28 |
--------------------------------------------------------------------------------
/clientgen/template.gotpl:
--------------------------------------------------------------------------------
1 | {{- if .GenerateClient }}
2 | {{ reserveImport "bytes" }}
3 | {{ reserveImport "context" }}
4 | {{ reserveImport "encoding/json" }}
5 | {{ reserveImport "fmt" }}
6 | {{ reserveImport "io" }}
7 | {{ reserveImport "io/ioutil" }}
8 | {{ reserveImport "net/http" }}
9 | {{ reserveImport "net/url" }}
10 | {{ reserveImport "path" }}
11 | {{ reserveImport "time" }}
12 |
13 | {{ reserveImport "github.com/infiotinc/gqlgenc/client" }}
14 | {{ reserveImport "github.com/infiotinc/gqlgenc/client/transport" }}
15 |
16 | type Client struct {
17 | Client *client.Client
18 | }
19 | {{- end }}
20 |
21 | {{/* Greek character used to prevent name conflicts: */}}
22 | {{/* > prefix with Ξ */}}
23 | {{/* > ctх (х in cyrillic alphabet) because it is user facing */}}
24 |
25 | {{- range $_, $element := .Types }}
26 | // {{ .Path.Kind }}: {{ .Path.String }}
27 | type {{ .Name }} {{ .Type | ref }}
28 |
29 | {{- if .IsInputMap }}
30 | func New{{ $element.Name }}({{- range $f := .MapReq }}{{$f.Name}} {{$f.Type|ref}},{{- end }}) {{ $element.Name }} {
31 | return map[string]interface{}{
32 | {{- range $f := .MapReq }}
33 | "{{$f.Name}}": {{$f.Name}},
34 | {{- end }}
35 | }
36 | }
37 |
38 | {{- range $f := .MapOpt }}
39 | func (t {{ $element.Name }}) With{{$f.Name|go}}(v {{$f.Type|ref}}) {{ $element.Name }} {
40 | t["{{$f.Name}}"] = v
41 | return t
42 | }
43 | {{- end }}
44 | {{- end }}
45 |
46 | {{- if .UnmarshalTypes }}
47 | func (t *{{ .Name }}) UnmarshalJSON(data []byte) error {
48 | type ΞAlias {{ .Name }}
49 | var r ΞAlias
50 |
51 | err := json.Unmarshal(data, &r)
52 | if err != nil {
53 | return err
54 | }
55 |
56 | *t = {{ .Name }}(r)
57 |
58 | switch r.Typename {
59 | {{- range $typename, $target := .UnmarshalTypes }}
60 | case "{{ $typename }}":
61 | var a {{ $target.Type | ref }}
62 | err = json.Unmarshal(data, &a)
63 | if err != nil {
64 | return err
65 | }
66 |
67 | t.{{ $target.Name }} = &a
68 | {{- end }}
69 | }
70 |
71 | return nil
72 | }
73 | {{- end }}
74 |
75 | {{- if .Consts }}
76 | const (
77 | {{- range $const := .Consts }}
78 | {{$const.Name}} {{$const.Type|ref}} = {{$const.Val.ExactString}}
79 | {{- end }}
80 | )
81 | {{- end }}
82 | {{- end }}
83 |
84 | // Pointer helpers
85 | {{- range $_, $element := .PtrTypes }}
86 | func {{ $element.Name|go }}Ptr(v {{ $element.Type|ref }}) *{{ $element.Type|ref }} {
87 | return &v
88 | }
89 | {{- end }}
90 |
91 | {{- range $op := .Operations }}
92 | const {{ $op.Name|go }}Document = `{{ $op.Operation }}`
93 |
94 | {{- if $.GenerateClient }}
95 | {{- if eq $op.OperationType "subscription" }}
96 | type Message{{ $op.Name|go }} struct {
97 | Data *{{ $op.ResponseType | ref }}
98 | Error error
99 | Extensions transport.RawExtensions
100 | }
101 |
102 | func (Ξc *Client) {{ $op.Name|go }} (ctх context.Context{{- range $arg := .Args }}, {{ $arg.Variable | goPrivate }} {{ $arg.Type | ref }} {{- end }}) (<-chan Message{{ $op.Name|go }}, func()) {
103 | Ξvars := map[string]interface{}{
104 | {{- range $args := .VariableDefinitions}}
105 | "{{ $args.Variable }}": {{ $args.Variable | goPrivate }},
106 | {{- end }}
107 | }
108 |
109 | { {{/* New block to prevent var names conflicts */}}
110 | res := Ξc.Client.Subscription(ctх, "{{ $op.Name }}", {{ $op.Name|go }}Document, Ξvars)
111 |
112 | ch := make(chan Message{{ $op.Name|go }})
113 |
114 | go func() {
115 | for res.Next() {
116 | opres := res.Get()
117 |
118 | var msg Message{{ $op.Name|go }}
119 | if len(opres.Errors) > 0 {
120 | msg.Error = opres.Errors
121 | }
122 |
123 | err := opres.UnmarshalData(&msg.Data)
124 | if err != nil && msg.Error == nil {
125 | msg.Error = err
126 | }
127 |
128 | msg.Extensions = opres.Extensions
129 |
130 | ch <- msg
131 | }
132 |
133 | if err := res.Err(); err != nil {
134 | ch <- Message{{ $op.Name|go }} {
135 | Error: err,
136 | }
137 | }
138 | close(ch)
139 | }()
140 |
141 | return ch, res.Close
142 | }
143 | }
144 | {{- else}}
145 | func (Ξc *Client) {{ $op.Name|go }} (ctх context.Context{{- range $arg := .Args }}, {{ $arg.Variable | goPrivate }} {{ $arg.Type | ref }} {{- end }}) (*{{ $op.ResponseType | ref }}, transport.OperationResponse, error) {
146 | Ξvars := map[string]interface{}{
147 | {{- range $args := .VariableDefinitions}}
148 | "{{ $args.Variable }}": {{ $args.Variable | goPrivate }},
149 | {{- end }}
150 | }
151 |
152 | { {{/* New block to prevent var names conflicts */}}
153 | var data {{ $op.ResponseType | ref }}
154 | res, err := Ξc.Client.{{ $op.OperationType|ucFirst }}(ctх, "{{ $op.Name }}", {{ $op.Name|go }}Document, Ξvars, &data)
155 | if err != nil {
156 | return nil, transport.OperationResponse{}, err
157 | }
158 |
159 | return &data, res, nil
160 | }
161 | }
162 | {{- end}}
163 | {{- end}}
164 | {{- end}}
165 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | // Original work from https://github.com/Yamashou/gqlgenc/blob/1ef8055d/config/config.go
4 |
5 | import (
6 | "context"
7 | "fmt"
8 | "io/ioutil"
9 | "net/http"
10 | "os"
11 | "path/filepath"
12 | "regexp"
13 | "strings"
14 |
15 | "github.com/infiotinc/gqlgenc/client"
16 | "github.com/infiotinc/gqlgenc/client/transport"
17 | "github.com/infiotinc/gqlgenc/introspection"
18 |
19 | "github.com/99designs/gqlgen/codegen/config"
20 | "github.com/vektah/gqlparser/v2"
21 | "github.com/vektah/gqlparser/v2/ast"
22 | "github.com/vektah/gqlparser/v2/validator"
23 | "gopkg.in/yaml.v2"
24 | )
25 |
26 | type Client struct {
27 | config.PackageConfig `yaml:",inline"`
28 |
29 | ExtraTypes []string `yaml:"extra_types,omitempty"`
30 | InputAsMap bool `yaml:"input_as_map"`
31 | }
32 |
33 | type TypeMapEntry struct {
34 | config.TypeMapEntry `yaml:",inline"`
35 |
36 | AsMap bool `yaml:"as_map,omitempty"`
37 | }
38 |
39 | type TypeMap map[string]TypeMapEntry
40 |
41 | // Config extends the gqlgen basic config
42 | // and represents the config file
43 | type Config struct {
44 | SchemaFilename StringList `yaml:"schema,omitempty"`
45 | Client Client `yaml:"client,omitempty"`
46 | Models TypeMap `yaml:"models,omitempty"`
47 | Endpoint *EndPointConfig `yaml:"endpoint,omitempty"`
48 | Generate *GenerateConfig `yaml:"generate,omitempty"`
49 |
50 | Query []string `yaml:"query"`
51 |
52 | // gqlgen config struct
53 | GQLConfig *config.Config `yaml:"-"`
54 | }
55 |
56 | var cfgFilenames = []string{".gqlgenc.yml", "gqlgenc.yml", "gqlgenc.yaml"}
57 |
58 | // StringList is a simple array of strings
59 | type StringList []string
60 |
61 | // Has checks if the strings array has a give value
62 | func (a StringList) Has(file string) bool {
63 | for _, existing := range a {
64 | if existing == file {
65 | return true
66 | }
67 | }
68 |
69 | return false
70 | }
71 |
72 | // LoadConfigFromDefaultLocations looks for a config file in the current directory, and all parent directories
73 | // walking up the tree. The closest config file will be returned.
74 | func LoadConfigFromDefaultLocations() (*Config, error) {
75 | cfgFile, err := findCfg()
76 | if err != nil {
77 | return nil, err
78 | }
79 |
80 | err = os.Chdir(filepath.Dir(cfgFile))
81 | if err != nil {
82 | return nil, fmt.Errorf("unable to enter config dir: %w", err)
83 | }
84 |
85 | return LoadConfig(cfgFile)
86 | }
87 |
88 | // EndPointConfig are the allowed options for the 'endpoint' config
89 | type EndPointConfig struct {
90 | URL string `yaml:"url"`
91 | Headers map[string]string `yaml:"headers,omitempty"`
92 | }
93 |
94 | // findCfg searches for the config file in this directory and all parents up the tree
95 | // looking for the closest match
96 | func findCfg() (string, error) {
97 | dir, err := os.Getwd()
98 | if err != nil {
99 | return "", fmt.Errorf("unable to get working dir to findCfg: %w", err)
100 | }
101 |
102 | cfg := findCfgInDir(dir)
103 |
104 | for cfg == "" && dir != filepath.Dir(dir) {
105 | dir = filepath.Dir(dir)
106 | cfg = findCfgInDir(dir)
107 | }
108 |
109 | if cfg == "" {
110 | return "", os.ErrNotExist
111 | }
112 |
113 | return cfg, nil
114 | }
115 |
116 | func findCfgInDir(dir string) string {
117 | for _, cfgName := range cfgFilenames {
118 | path := filepath.Join(dir, cfgName)
119 | if _, err := os.Stat(path); err == nil {
120 | return path
121 | }
122 | }
123 |
124 | return ""
125 | }
126 |
127 | var path2regex = strings.NewReplacer(
128 | `.`, `\.`,
129 | `*`, `.+`,
130 | `\`, `[\\/]`,
131 | `/`, `[\\/]`,
132 | )
133 |
134 | // LoadConfig loads and parses the config gqlgenc config
135 | func LoadConfig(filename string) (*Config, error) {
136 | var cfg Config
137 | b, err := ioutil.ReadFile(filename)
138 | if err != nil {
139 | return nil, fmt.Errorf("unable to read config: %w", err)
140 | }
141 |
142 | confContent := []byte(os.ExpandEnv(string(b)))
143 | if err := yaml.UnmarshalStrict(confContent, &cfg); err != nil {
144 | return nil, fmt.Errorf("unable to parse config: %w", err)
145 | }
146 |
147 | if cfg.SchemaFilename != nil && cfg.Endpoint != nil {
148 | return nil, fmt.Errorf("'schema' and 'endpoint' both specified. Use schema to load from a local file, use endpoint to load from a remote server (using introspection)")
149 | }
150 |
151 | if cfg.SchemaFilename == nil && cfg.Endpoint == nil {
152 | return nil, fmt.Errorf("neither 'schema' nor 'endpoint' specified. Use schema to load from a local file, use endpoint to load from a remote server (using introspection)")
153 | }
154 |
155 | // https://github.com/99designs/gqlgen/blob/3a31a752df764738b1f6e99408df3b169d514784/codegen/config/config.go#L120
156 | for _, f := range cfg.SchemaFilename {
157 | var matches []string
158 |
159 | // for ** we want to override default globbing patterns and walk all
160 | // subdirectories to match schema files.
161 | if strings.Contains(f, "**") {
162 | pathParts := strings.SplitN(f, "**", 2)
163 | rest := strings.TrimPrefix(strings.TrimPrefix(pathParts[1], `\`), `/`)
164 | // turn the rest of the glob into a regex, anchored only at the end because ** allows
165 | // for any number of dirs in between and walk will let us match against the full path name
166 | globRe := regexp.MustCompile(path2regex.Replace(rest) + `$`)
167 |
168 | if err := filepath.Walk(pathParts[0], func(path string, info os.FileInfo, err error) error {
169 | if err != nil {
170 | return err
171 | }
172 |
173 | if globRe.MatchString(strings.TrimPrefix(path, pathParts[0])) {
174 | matches = append(matches, path)
175 | }
176 |
177 | return nil
178 | }); err != nil {
179 | return nil, fmt.Errorf("failed to walk schema at root %s: %w", pathParts[0], err)
180 | }
181 | } else {
182 | matches, err = filepath.Glob(f)
183 | if err != nil {
184 | return nil, fmt.Errorf("failed to glob schema filename %s: %w", f, err)
185 | }
186 | }
187 |
188 | files := StringList{}
189 | for _, m := range matches {
190 | if !files.Has(m) {
191 | files = append(files, m)
192 | }
193 | }
194 |
195 | cfg.SchemaFilename = files
196 | }
197 |
198 | models := make(config.TypeMap)
199 | if cfg.Models != nil {
200 | for k, v := range cfg.Models {
201 | models[k] = v.TypeMapEntry
202 | }
203 | }
204 |
205 | sources := make([]*ast.Source, 0)
206 |
207 | for _, filename := range cfg.SchemaFilename {
208 | filename = filepath.ToSlash(filename)
209 | var err error
210 | var schemaRaw []byte
211 | schemaRaw, err = ioutil.ReadFile(filename)
212 | if err != nil {
213 | return nil, fmt.Errorf("unable to open schema: %w", err)
214 | }
215 |
216 | sources = append(sources, &ast.Source{Name: filename, Input: string(schemaRaw)})
217 | }
218 |
219 | cfg.GQLConfig = &config.Config{
220 | Models: models,
221 | // TODO: gqlgen must be set exec but client not used
222 | Exec: config.ExecConfig{Filename: "generated.go"},
223 | Directives: map[string]config.DirectiveConfig{},
224 | Sources: sources,
225 | }
226 |
227 | if err := cfg.Client.Check(); err != nil {
228 | return nil, fmt.Errorf("config.exec: %w", err)
229 | }
230 |
231 | return &cfg, nil
232 | }
233 |
234 | // LoadSchema load and parses the schema from a local file or a remote server
235 | func (c *Config) LoadSchema(ctx context.Context) error {
236 | var schema *ast.Schema
237 |
238 | if c.SchemaFilename != nil {
239 | s, err := c.loadLocalSchema()
240 | if err != nil {
241 | return fmt.Errorf("load local schema failed: %w", err)
242 | }
243 |
244 | schema = s
245 | } else {
246 | s, err := c.loadRemoteSchema(ctx)
247 | if err != nil {
248 | return fmt.Errorf("load remote schema failed: %w", err)
249 | }
250 |
251 | schema = s
252 | }
253 |
254 | if schema.Query == nil {
255 | schema.Query = &ast.Definition{
256 | Kind: ast.Object,
257 | Name: "Query",
258 | }
259 | schema.Types["Query"] = schema.Query
260 | }
261 |
262 | c.GQLConfig.Schema = schema
263 |
264 | return nil
265 | }
266 |
267 | func (c *Config) loadRemoteSchema(ctx context.Context) (*ast.Schema, error) {
268 | addHeaders := func(req *http.Request) {
269 | for key, value := range c.Endpoint.Headers {
270 | req.Header.Set(key, value)
271 | }
272 | }
273 |
274 | gqlClient := client.Client{
275 | Transport: &transport.Http{
276 | URL: c.Endpoint.URL,
277 | RequestOptions: []transport.HttpRequestOption{addHeaders},
278 | },
279 | }
280 |
281 | var res introspection.Query
282 | if _, err := gqlClient.Query(ctx, "", introspection.Introspection, nil, &res); err != nil {
283 | return nil, fmt.Errorf("introspection query failed: %w", err)
284 | }
285 |
286 | schema, err := validator.ValidateSchemaDocument(introspection.ParseIntrospectionQuery(c.Endpoint.URL, res))
287 | if err != nil {
288 | return nil, fmt.Errorf("validation error: %w", err)
289 | }
290 |
291 | return schema, nil
292 | }
293 |
294 | func (c *Config) loadLocalSchema() (*ast.Schema, error) {
295 | schema, err := gqlparser.LoadSchema(c.GQLConfig.Sources...)
296 | if err != nil {
297 | return nil, err
298 | }
299 |
300 | return schema, nil
301 | }
302 |
303 | type GenerateConfig struct {
304 | Prefix *NamingConfig `yaml:"prefix,omitempty"`
305 | Suffix *NamingConfig `yaml:"suffix,omitempty"`
306 | }
307 |
308 | func (c *GenerateConfig) ShouldGenerateClient() bool {
309 | return true
310 | }
311 |
312 | type NamingConfig struct {
313 | Query string `yaml:"query,omitempty"`
314 | Mutation string `yaml:"mutation,omitempty"`
315 | Subscription string `yaml:"subscription,omitempty"`
316 | }
317 |
--------------------------------------------------------------------------------
/example/.gqlgen.yml:
--------------------------------------------------------------------------------
1 | # Where are all the schema files located? globs are supported eg src/**/*.graphqls
2 | schema:
3 | - "schema.graphql"
4 |
5 | # Where should the generated server code go?
6 | exec:
7 | filename: server/generated/generated.go
8 | package: generated
9 |
10 | # Where should any generated models go?
11 | model:
12 | filename: server/model/models_gen.go
13 | package: model
14 |
15 | # Where should the resolver implementations go?
16 | resolver:
17 | layout: follow-schema
18 | dir: server
19 | package: server
20 | filename_template: "{name}.resolvers.go"
21 |
22 | # Optional: turn on use ` + "`" + `gqlgen:"fieldName"` + "`" + ` tags in your models
23 | # struct_tag: json
24 |
25 | # Optional: turn on to use []Thing instead of []*Thing
26 | # omit_slice_element_pointers: false
27 |
28 | # Optional: set to speed up generation time by not performing a final validation pass.
29 | # skip_validation: true
30 |
31 | # gqlgen will search for any type names in the schema in these go packages
32 | # if they match it will use them, otherwise it will generate them.
33 | #autobind:
34 | # - "github.com/your/app/graph/model"
35 |
36 | # This section declares type mapping between the GraphQL and go type systems
37 | #
38 | # The first line in each type will be used as defaults for resolver arguments and
39 | # modelgen, the others will be allowed when binding to fields. Configure them to
40 | # your liking
41 | models:
42 | ID:
43 | model:
44 | - github.com/99designs/gqlgen/graphql.ID
45 | - github.com/99designs/gqlgen/graphql.Int
46 | - github.com/99designs/gqlgen/graphql.Int64
47 | - github.com/99designs/gqlgen/graphql.Int32
48 | Int:
49 | model:
50 | - github.com/99designs/gqlgen/graphql.Int
51 | - github.com/99designs/gqlgen/graphql.Int64
52 | - github.com/99designs/gqlgen/graphql.Int32
53 | AsMapInput:
54 | model: map[string]interface{}
55 |
--------------------------------------------------------------------------------
/example/.gqlgenc.yml:
--------------------------------------------------------------------------------
1 | client:
2 | filename: ./client/gen_client.go
3 | package: client
4 | extra_types:
5 | - SomeExtraType
6 | - Cyclic2_1
7 | models:
8 | Int:
9 | model: github.com/99designs/gqlgen/graphql.Int64
10 | DateTime:
11 | model: github.com/99designs/gqlgen/graphql.Time
12 | GetRoomCustom:
13 | model: example/somelib.CustomRoom
14 | Upload:
15 | model: github.com/infiotinc/gqlgenc/client/transport.Upload
16 | AsMapInput:
17 | as_map: true
18 | Value1:
19 | model: example/client.Value1
20 | Value2:
21 | model: example/client.Value2
22 | schema:
23 | - schema.graphql
24 | query:
25 | - query.graphql
26 |
--------------------------------------------------------------------------------
/example/client/gen_client.go:
--------------------------------------------------------------------------------
1 | // Code generated by github.com/infiotinc/gqlgenc, DO NOT EDIT.
2 |
3 | package client
4 |
5 | import (
6 | "context"
7 | "encoding/json"
8 | "example/somelib"
9 |
10 | "github.com/infiotinc/gqlgenc/client"
11 | "github.com/infiotinc/gqlgenc/client/transport"
12 | )
13 |
14 | type Client struct {
15 | Client *client.Client
16 | }
17 |
18 | // OPERATION: AsMap
19 | type AsMap struct {
20 | AsMap string "json:\"asMap\""
21 | }
22 |
23 | // INPUT_OBJECT: AsMapInput
24 | type AsMapInput map[string]interface{}
25 |
26 | func NewAsMapInput(reqStr string, reqEp Episode) AsMapInput {
27 | return map[string]interface{}{
28 | "reqStr": reqStr,
29 | "reqEp": reqEp,
30 | }
31 | }
32 | func (t AsMapInput) WithOptStr(v *string) AsMapInput {
33 | t["optStr"] = v
34 | return t
35 | }
36 | func (t AsMapInput) WithOptEp(v *Episode) AsMapInput {
37 | t["optEp"] = v
38 | return t
39 | }
40 |
41 | // OPERATION: CreatePost
42 | type CreatePost struct {
43 | Post CreatePost_Post "json:\"post\""
44 | }
45 |
46 | // OPERATION: CreatePost.post
47 | type CreatePost_Post struct {
48 | ID string "json:\"id\""
49 | Text string "json:\"text\""
50 | }
51 |
52 | // OPERATION: Cyclic1
53 | type Cyclic1 struct {
54 | Cyclic *Cyclic1_Cyclic "json:\"cyclic\""
55 | }
56 |
57 | // OPERATION: Cyclic1.cyclic
58 | type Cyclic1_Cyclic struct {
59 | Child *Cyclic1_Cyclic_Child "json:\"child\""
60 | }
61 |
62 | // OPERATION: Cyclic1.cyclic.child
63 | type Cyclic1_Cyclic_Child struct {
64 | Child *Cyclic1_Cyclic_Child_Child "json:\"child\""
65 | }
66 |
67 | // OPERATION: Cyclic1.cyclic.child.child
68 | type Cyclic1_Cyclic_Child_Child struct {
69 | Child *Cyclic1_Cyclic_Child_Child_Child "json:\"child\""
70 | }
71 |
72 | // OPERATION: Cyclic1.cyclic.child.child.child
73 | type Cyclic1_Cyclic_Child_Child_Child struct {
74 | ID string "json:\"id\""
75 | }
76 |
77 | // OBJECT: Cyclic2_1
78 | type Cyclic2_1 struct {
79 | ID string "json:\"id\""
80 | Child *Cyclic2_2 "json:\"child\""
81 | }
82 |
83 | // OBJECT: Cyclic2_2
84 | type Cyclic2_2 struct {
85 | ID string "json:\"id\""
86 | Child *Cyclic2_1 "json:\"child\""
87 | }
88 |
89 | // ENUM: Episode
90 | type Episode string
91 |
92 | const (
93 | EpisodeNewhope Episode = "NEWHOPE"
94 | EpisodeEmpire Episode = "EMPIRE"
95 | EpisodeJedi Episode = "JEDI"
96 | )
97 |
98 | // ENUM: FooType_hash1
99 | type FooTypeHash1 string
100 |
101 | const (
102 | FooTypeHash1Hash1 FooTypeHash1 = "hash_1"
103 | FooTypeHash1Hash2 FooTypeHash1 = "hash_2"
104 | )
105 |
106 | // OPERATION: GetBooks
107 | type GetBooks struct {
108 | Books []GetBooks_Books "json:\"books\""
109 | }
110 |
111 | // OPERATION: GetBooks.books
112 | type GetBooks_Books struct {
113 | Typename string "json:\"__typename\""
114 | Title string "json:\"title\""
115 | Textbook *GetBooks_Books_Textbook "json:\"-\""
116 | ColoringBook *GetBooks_Books_ColoringBook "json:\"-\""
117 | }
118 |
119 | func (t *GetBooks_Books) UnmarshalJSON(data []byte) error {
120 | type ΞAlias GetBooks_Books
121 | var r ΞAlias
122 |
123 | err := json.Unmarshal(data, &r)
124 | if err != nil {
125 | return err
126 | }
127 |
128 | *t = GetBooks_Books(r)
129 |
130 | switch r.Typename {
131 | case "ColoringBook":
132 | var a GetBooks_Books_ColoringBook
133 | err = json.Unmarshal(data, &a)
134 | if err != nil {
135 | return err
136 | }
137 |
138 | t.ColoringBook = &a
139 | case "Textbook":
140 | var a GetBooks_Books_Textbook
141 | err = json.Unmarshal(data, &a)
142 | if err != nil {
143 | return err
144 | }
145 |
146 | t.Textbook = &a
147 | }
148 |
149 | return nil
150 | }
151 |
152 | // OPERATION: GetBooks.books.ColoringBook
153 | type GetBooks_Books_ColoringBook struct {
154 | Colors []string "json:\"colors\""
155 | }
156 |
157 | // OPERATION: GetBooks.books.Textbook
158 | type GetBooks_Books_Textbook struct {
159 | Courses []string "json:\"courses\""
160 | }
161 |
162 | // OPERATION: GetEpisodes
163 | type GetEpisodes struct {
164 | Episodes []Episode "json:\"episodes\""
165 | }
166 |
167 | // OPERATION: GetMedias
168 | type GetMedias struct {
169 | Medias []GetMedias_Medias "json:\"medias\""
170 | }
171 |
172 | // OPERATION: GetMedias.medias
173 | type GetMedias_Medias struct {
174 | Typename string "json:\"__typename\""
175 | Image *GetMedias_Medias_Image "json:\"-\""
176 | Video *GetMedias_Medias_Video "json:\"-\""
177 | }
178 |
179 | func (t *GetMedias_Medias) UnmarshalJSON(data []byte) error {
180 | type ΞAlias GetMedias_Medias
181 | var r ΞAlias
182 |
183 | err := json.Unmarshal(data, &r)
184 | if err != nil {
185 | return err
186 | }
187 |
188 | *t = GetMedias_Medias(r)
189 |
190 | switch r.Typename {
191 | case "Image":
192 | var a GetMedias_Medias_Image
193 | err = json.Unmarshal(data, &a)
194 | if err != nil {
195 | return err
196 | }
197 |
198 | t.Image = &a
199 | case "Video":
200 | var a GetMedias_Medias_Video
201 | err = json.Unmarshal(data, &a)
202 | if err != nil {
203 | return err
204 | }
205 |
206 | t.Video = &a
207 | }
208 |
209 | return nil
210 | }
211 |
212 | // OPERATION: GetMedias.medias.Image
213 | type GetMedias_Medias_Image struct {
214 | Size int64 "json:\"size\""
215 | }
216 |
217 | // OPERATION: GetMedias.medias.Video
218 | type GetMedias_Medias_Video struct {
219 | Duration int64 "json:\"duration\""
220 | }
221 |
222 | // OPERATION: GetRoom
223 | type GetRoom struct {
224 | Room *GetRoom_Room "json:\"room\""
225 | }
226 |
227 | // OPERATION: GetRoom.room
228 | type GetRoom_Room struct {
229 | Name string "json:\"name\""
230 | Hash *FooTypeHash1 "json:\"hash\""
231 | }
232 |
233 | // OPERATION: GetRoomFragment
234 | type GetRoomFragment struct {
235 | Room *RoomFragment "json:\"room\""
236 | }
237 |
238 | // OPERATION: GetRoomNonNull
239 | type GetRoomNonNull struct {
240 | RoomNonNull GetRoomNonNull_RoomNonNull "json:\"roomNonNull\""
241 | }
242 |
243 | // OPERATION: GetRoomNonNull.roomNonNull
244 | type GetRoomNonNull_RoomNonNull struct {
245 | Name string "json:\"name\""
246 | }
247 |
248 | // OPERATION: Issue8
249 | type Issue8 struct {
250 | Issue8 *Issue8_Issue8 "json:\"issue8\""
251 | }
252 |
253 | // OPERATION: Issue8.issue8
254 | type Issue8_Issue8 struct {
255 | Foo1 Issue8_Issue8_Foo1 "json:\"foo1\""
256 | Foo2 *Issue8_Issue8_Foo2 "json:\"foo2\""
257 | }
258 |
259 | // OPERATION: Issue8.issue8.foo1
260 | type Issue8_Issue8_Foo1 struct {
261 | A Issue8_Issue8_Foo1_A "json:\"a\""
262 | }
263 |
264 | // OPERATION: Issue8.issue8.foo1.a
265 | type Issue8_Issue8_Foo1_A struct {
266 | Aa string "json:\"Aa\""
267 | }
268 |
269 | // OPERATION: Issue8.issue8.foo2
270 | type Issue8_Issue8_Foo2 struct {
271 | A Issue8_Issue8_Foo2_A "json:\"a\""
272 | }
273 |
274 | // OPERATION: Issue8.issue8.foo2.a
275 | type Issue8_Issue8_Foo2_A struct {
276 | Aa string "json:\"Aa\""
277 | }
278 |
279 | // OPERATION: OptValue1
280 | type OptValue1 struct {
281 | OptValue1 *bool "json:\"optValue1\""
282 | }
283 |
284 | // OPERATION: OptValue2
285 | type OptValue2 struct {
286 | OptValue2 *bool "json:\"optValue2\""
287 | }
288 |
289 | // INPUT_OBJECT: OptionalValue1
290 | type OptionalValue1 struct {
291 | Value *Value1 "json:\"value\""
292 | }
293 |
294 | // INPUT_OBJECT: OptionalValue2
295 | type OptionalValue2 struct {
296 | Value *Value2 "json:\"value\""
297 | }
298 |
299 | // INPUT_OBJECT: PostCreateInput
300 | type PostCreateInput struct {
301 | Text string "json:\"text\""
302 | }
303 |
304 | // OBJECT: RoomFragment
305 | type RoomFragment struct {
306 | Name string "json:\"name\""
307 | }
308 |
309 | // OBJECT: SomeExtraType
310 | type SomeExtraType struct {
311 | Child SomeExtraTypeChild "json:\"child\""
312 | }
313 |
314 | // OBJECT: SomeExtraTypeChild
315 | type SomeExtraTypeChild struct {
316 | Child SomeExtraTypeChildChild "json:\"child\""
317 | }
318 |
319 | // OBJECT: SomeExtraTypeChildChild
320 | type SomeExtraTypeChildChild struct {
321 | ID string "json:\"id\""
322 | }
323 |
324 | // OPERATION: SubscribeMessageAdded
325 | type SubscribeMessageAdded struct {
326 | MessageAdded SubscribeMessageAdded_MessageAdded "json:\"messageAdded\""
327 | }
328 |
329 | // OPERATION: SubscribeMessageAdded.messageAdded
330 | type SubscribeMessageAdded_MessageAdded struct {
331 | ID string "json:\"id\""
332 | }
333 |
334 | // OPERATION: UploadFile
335 | type UploadFile struct {
336 | UploadFile UploadFile_UploadFile "json:\"uploadFile\""
337 | }
338 |
339 | // OPERATION: UploadFile.uploadFile
340 | type UploadFile_UploadFile struct {
341 | Size int64 "json:\"size\""
342 | }
343 |
344 | // OPERATION: UploadFiles
345 | type UploadFiles struct {
346 | UploadFiles []UploadFiles_UploadFiles "json:\"uploadFiles\""
347 | }
348 |
349 | // OPERATION: UploadFiles.uploadFiles
350 | type UploadFiles_UploadFiles struct {
351 | Size int64 "json:\"size\""
352 | }
353 |
354 | // OPERATION: UploadFilesMap
355 | type UploadFilesMap struct {
356 | UploadFilesMap UploadFilesMap_UploadFilesMap "json:\"uploadFilesMap\""
357 | }
358 |
359 | // OPERATION: UploadFilesMap.uploadFilesMap
360 | type UploadFilesMap_UploadFilesMap struct {
361 | Somefile UploadFilesMap_UploadFilesMap_Somefile "json:\"somefile\""
362 | }
363 |
364 | // OPERATION: UploadFilesMap.uploadFilesMap.somefile
365 | type UploadFilesMap_UploadFilesMap_Somefile struct {
366 | Size int64 "json:\"size\""
367 | }
368 |
369 | // INPUT_OBJECT: UploadFilesMapInput
370 | type UploadFilesMapInput struct {
371 | Somefile transport.Upload "json:\"somefile\""
372 | }
373 |
374 | // Pointer helpers
375 | func AsMapInputPtr(v AsMapInput) *AsMapInput {
376 | return &v
377 | }
378 | func EpisodePtr(v Episode) *Episode {
379 | return &v
380 | }
381 | func OptionalValue2Ptr(v OptionalValue2) *OptionalValue2 {
382 | return &v
383 | }
384 | func Value1Ptr(v Value1) *Value1 {
385 | return &v
386 | }
387 | func Value2Ptr(v Value2) *Value2 {
388 | return &v
389 | }
390 | func StringPtr(v string) *string {
391 | return &v
392 | }
393 |
394 | const GetRoomDocument = `query GetRoom ($name: String!) {
395 | room(name: $name) {
396 | name
397 | hash
398 | }
399 | }
400 | `
401 |
402 | func (Ξc *Client) GetRoom(ctх context.Context, name string) (*GetRoom, transport.OperationResponse, error) {
403 | Ξvars := map[string]interface{}{
404 | "name": name,
405 | }
406 |
407 | {
408 | var data GetRoom
409 | res, err := Ξc.Client.Query(ctх, "GetRoom", GetRoomDocument, Ξvars, &data)
410 | if err != nil {
411 | return nil, transport.OperationResponse{}, err
412 | }
413 |
414 | return &data, res, nil
415 | }
416 | }
417 |
418 | const GetRoomNonNullDocument = `query GetRoomNonNull ($name: String!) {
419 | roomNonNull(name: $name) {
420 | name
421 | }
422 | }
423 | `
424 |
425 | func (Ξc *Client) GetRoomNonNull(ctх context.Context, name string) (*GetRoomNonNull, transport.OperationResponse, error) {
426 | Ξvars := map[string]interface{}{
427 | "name": name,
428 | }
429 |
430 | {
431 | var data GetRoomNonNull
432 | res, err := Ξc.Client.Query(ctх, "GetRoomNonNull", GetRoomNonNullDocument, Ξvars, &data)
433 | if err != nil {
434 | return nil, transport.OperationResponse{}, err
435 | }
436 |
437 | return &data, res, nil
438 | }
439 | }
440 |
441 | const GetRoomFragmentDocument = `query GetRoomFragment ($name: String!) {
442 | room(name: $name) {
443 | ... RoomFragment
444 | }
445 | }
446 | fragment RoomFragment on Chatroom {
447 | name
448 | }
449 | `
450 |
451 | func (Ξc *Client) GetRoomFragment(ctх context.Context, name string) (*GetRoomFragment, transport.OperationResponse, error) {
452 | Ξvars := map[string]interface{}{
453 | "name": name,
454 | }
455 |
456 | {
457 | var data GetRoomFragment
458 | res, err := Ξc.Client.Query(ctх, "GetRoomFragment", GetRoomFragmentDocument, Ξvars, &data)
459 | if err != nil {
460 | return nil, transport.OperationResponse{}, err
461 | }
462 |
463 | return &data, res, nil
464 | }
465 | }
466 |
467 | const GetRoomCustomDocument = `query GetRoomCustom ($name: String!) {
468 | room(name: $name) {
469 | name
470 | }
471 | }
472 | `
473 |
474 | func (Ξc *Client) GetRoomCustom(ctх context.Context, name string) (*somelib.CustomRoom, transport.OperationResponse, error) {
475 | Ξvars := map[string]interface{}{
476 | "name": name,
477 | }
478 |
479 | {
480 | var data somelib.CustomRoom
481 | res, err := Ξc.Client.Query(ctх, "GetRoomCustom", GetRoomCustomDocument, Ξvars, &data)
482 | if err != nil {
483 | return nil, transport.OperationResponse{}, err
484 | }
485 |
486 | return &data, res, nil
487 | }
488 | }
489 |
490 | const GetMediasDocument = `query GetMedias {
491 | medias {
492 | __typename
493 | ... on Image {
494 | size
495 | }
496 | ... on Video {
497 | duration
498 | }
499 | }
500 | }
501 | `
502 |
503 | func (Ξc *Client) GetMedias(ctх context.Context) (*GetMedias, transport.OperationResponse, error) {
504 | Ξvars := map[string]interface{}{}
505 |
506 | {
507 | var data GetMedias
508 | res, err := Ξc.Client.Query(ctх, "GetMedias", GetMediasDocument, Ξvars, &data)
509 | if err != nil {
510 | return nil, transport.OperationResponse{}, err
511 | }
512 |
513 | return &data, res, nil
514 | }
515 | }
516 |
517 | const GetBooksDocument = `query GetBooks {
518 | books {
519 | __typename
520 | title
521 | ... on Textbook {
522 | courses
523 | }
524 | ... on ColoringBook {
525 | colors
526 | }
527 | }
528 | }
529 | `
530 |
531 | func (Ξc *Client) GetBooks(ctх context.Context) (*GetBooks, transport.OperationResponse, error) {
532 | Ξvars := map[string]interface{}{}
533 |
534 | {
535 | var data GetBooks
536 | res, err := Ξc.Client.Query(ctх, "GetBooks", GetBooksDocument, Ξvars, &data)
537 | if err != nil {
538 | return nil, transport.OperationResponse{}, err
539 | }
540 |
541 | return &data, res, nil
542 | }
543 | }
544 |
545 | const SubscribeMessageAddedDocument = `subscription SubscribeMessageAdded {
546 | messageAdded(roomName: "test") {
547 | id
548 | }
549 | }
550 | `
551 |
552 | type MessageSubscribeMessageAdded struct {
553 | Data *SubscribeMessageAdded
554 | Error error
555 | Extensions transport.RawExtensions
556 | }
557 |
558 | func (Ξc *Client) SubscribeMessageAdded(ctх context.Context) (<-chan MessageSubscribeMessageAdded, func()) {
559 | Ξvars := map[string]interface{}{}
560 |
561 | {
562 | res := Ξc.Client.Subscription(ctх, "SubscribeMessageAdded", SubscribeMessageAddedDocument, Ξvars)
563 |
564 | ch := make(chan MessageSubscribeMessageAdded)
565 |
566 | go func() {
567 | for res.Next() {
568 | opres := res.Get()
569 |
570 | var msg MessageSubscribeMessageAdded
571 | if len(opres.Errors) > 0 {
572 | msg.Error = opres.Errors
573 | }
574 |
575 | err := opres.UnmarshalData(&msg.Data)
576 | if err != nil && msg.Error == nil {
577 | msg.Error = err
578 | }
579 |
580 | msg.Extensions = opres.Extensions
581 |
582 | ch <- msg
583 | }
584 |
585 | if err := res.Err(); err != nil {
586 | ch <- MessageSubscribeMessageAdded{
587 | Error: err,
588 | }
589 | }
590 | close(ch)
591 | }()
592 |
593 | return ch, res.Close
594 | }
595 | }
596 |
597 | const CreatePostDocument = `mutation CreatePost ($input: PostCreateInput!) {
598 | post(input: $input) {
599 | id
600 | text
601 | }
602 | }
603 | `
604 |
605 | func (Ξc *Client) CreatePost(ctх context.Context, input PostCreateInput) (*CreatePost, transport.OperationResponse, error) {
606 | Ξvars := map[string]interface{}{
607 | "input": input,
608 | }
609 |
610 | {
611 | var data CreatePost
612 | res, err := Ξc.Client.Mutation(ctх, "CreatePost", CreatePostDocument, Ξvars, &data)
613 | if err != nil {
614 | return nil, transport.OperationResponse{}, err
615 | }
616 |
617 | return &data, res, nil
618 | }
619 | }
620 |
621 | const UploadFileDocument = `mutation UploadFile ($file: Upload!) {
622 | uploadFile(file: $file) {
623 | size
624 | }
625 | }
626 | `
627 |
628 | func (Ξc *Client) UploadFile(ctх context.Context, file transport.Upload) (*UploadFile, transport.OperationResponse, error) {
629 | Ξvars := map[string]interface{}{
630 | "file": file,
631 | }
632 |
633 | {
634 | var data UploadFile
635 | res, err := Ξc.Client.Mutation(ctх, "UploadFile", UploadFileDocument, Ξvars, &data)
636 | if err != nil {
637 | return nil, transport.OperationResponse{}, err
638 | }
639 |
640 | return &data, res, nil
641 | }
642 | }
643 |
644 | const UploadFilesDocument = `mutation UploadFiles ($files: [Upload!]!) {
645 | uploadFiles(files: $files) {
646 | size
647 | }
648 | }
649 | `
650 |
651 | func (Ξc *Client) UploadFiles(ctх context.Context, files []*transport.Upload) (*UploadFiles, transport.OperationResponse, error) {
652 | Ξvars := map[string]interface{}{
653 | "files": files,
654 | }
655 |
656 | {
657 | var data UploadFiles
658 | res, err := Ξc.Client.Mutation(ctх, "UploadFiles", UploadFilesDocument, Ξvars, &data)
659 | if err != nil {
660 | return nil, transport.OperationResponse{}, err
661 | }
662 |
663 | return &data, res, nil
664 | }
665 | }
666 |
667 | const UploadFilesMapDocument = `mutation UploadFilesMap ($files: UploadFilesMapInput!) {
668 | uploadFilesMap(files: $files) {
669 | somefile {
670 | size
671 | }
672 | }
673 | }
674 | `
675 |
676 | func (Ξc *Client) UploadFilesMap(ctх context.Context, files UploadFilesMapInput) (*UploadFilesMap, transport.OperationResponse, error) {
677 | Ξvars := map[string]interface{}{
678 | "files": files,
679 | }
680 |
681 | {
682 | var data UploadFilesMap
683 | res, err := Ξc.Client.Mutation(ctх, "UploadFilesMap", UploadFilesMapDocument, Ξvars, &data)
684 | if err != nil {
685 | return nil, transport.OperationResponse{}, err
686 | }
687 |
688 | return &data, res, nil
689 | }
690 | }
691 |
692 | const Issue8Document = `query Issue8 {
693 | issue8 {
694 | foo1 {
695 | a {
696 | Aa
697 | }
698 | }
699 | foo2 {
700 | a {
701 | Aa
702 | }
703 | }
704 | }
705 | }
706 | `
707 |
708 | func (Ξc *Client) Issue8(ctх context.Context) (*Issue8, transport.OperationResponse, error) {
709 | Ξvars := map[string]interface{}{}
710 |
711 | {
712 | var data Issue8
713 | res, err := Ξc.Client.Query(ctх, "Issue8", Issue8Document, Ξvars, &data)
714 | if err != nil {
715 | return nil, transport.OperationResponse{}, err
716 | }
717 |
718 | return &data, res, nil
719 | }
720 | }
721 |
722 | const GetEpisodesDocument = `query GetEpisodes {
723 | episodes
724 | }
725 | `
726 |
727 | func (Ξc *Client) GetEpisodes(ctх context.Context) (*GetEpisodes, transport.OperationResponse, error) {
728 | Ξvars := map[string]interface{}{}
729 |
730 | {
731 | var data GetEpisodes
732 | res, err := Ξc.Client.Query(ctх, "GetEpisodes", GetEpisodesDocument, Ξvars, &data)
733 | if err != nil {
734 | return nil, transport.OperationResponse{}, err
735 | }
736 |
737 | return &data, res, nil
738 | }
739 | }
740 |
741 | const Cyclic1Document = `query Cyclic1 {
742 | cyclic {
743 | child {
744 | child {
745 | child {
746 | id
747 | }
748 | }
749 | }
750 | }
751 | }
752 | `
753 |
754 | func (Ξc *Client) Cyclic1(ctх context.Context) (*Cyclic1, transport.OperationResponse, error) {
755 | Ξvars := map[string]interface{}{}
756 |
757 | {
758 | var data Cyclic1
759 | res, err := Ξc.Client.Query(ctх, "Cyclic1", Cyclic1Document, Ξvars, &data)
760 | if err != nil {
761 | return nil, transport.OperationResponse{}, err
762 | }
763 |
764 | return &data, res, nil
765 | }
766 | }
767 |
768 | const AsMapDocument = `query AsMap ($req: AsMapInput!, $opt: AsMapInput) {
769 | asMap(req: $req, opt: $opt)
770 | }
771 | `
772 |
773 | func (Ξc *Client) AsMap(ctх context.Context, req AsMapInput, opt *AsMapInput) (*AsMap, transport.OperationResponse, error) {
774 | Ξvars := map[string]interface{}{
775 | "req": req,
776 | "opt": opt,
777 | }
778 |
779 | {
780 | var data AsMap
781 | res, err := Ξc.Client.Query(ctх, "AsMap", AsMapDocument, Ξvars, &data)
782 | if err != nil {
783 | return nil, transport.OperationResponse{}, err
784 | }
785 |
786 | return &data, res, nil
787 | }
788 | }
789 |
790 | const OptValue1Document = `query OptValue1 ($v: OptionalValue1!) {
791 | optValue1(req: $v)
792 | }
793 | `
794 |
795 | func (Ξc *Client) OptValue1(ctх context.Context, v OptionalValue1) (*OptValue1, transport.OperationResponse, error) {
796 | Ξvars := map[string]interface{}{
797 | "v": v,
798 | }
799 |
800 | {
801 | var data OptValue1
802 | res, err := Ξc.Client.Query(ctх, "OptValue1", OptValue1Document, Ξvars, &data)
803 | if err != nil {
804 | return nil, transport.OperationResponse{}, err
805 | }
806 |
807 | return &data, res, nil
808 | }
809 | }
810 |
811 | const OptValue2Document = `query OptValue2 ($v: OptionalValue2) {
812 | optValue2(opt: $v)
813 | }
814 | `
815 |
816 | func (Ξc *Client) OptValue2(ctх context.Context, v *OptionalValue2) (*OptValue2, transport.OperationResponse, error) {
817 | Ξvars := map[string]interface{}{
818 | "v": v,
819 | }
820 |
821 | {
822 | var data OptValue2
823 | res, err := Ξc.Client.Query(ctх, "OptValue2", OptValue2Document, Ξvars, &data)
824 | if err != nil {
825 | return nil, transport.OperationResponse{}, err
826 | }
827 |
828 | return &data, res, nil
829 | }
830 | }
831 |
--------------------------------------------------------------------------------
/example/client/scalars.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | type Value1 bool
4 | type Value2 bool
5 |
--------------------------------------------------------------------------------
/example/client_apq_test.go:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import (
4 | "context"
5 | "github.com/99designs/gqlgen/graphql/handler/extension"
6 | "github.com/infiotinc/gqlgenc/client/extensions"
7 | "github.com/infiotinc/gqlgenc/client/transport"
8 | "github.com/stretchr/testify/assert"
9 | "testing"
10 | )
11 |
12 | func TestHttpAPQQuery(t *testing.T) {
13 | ctx := context.Background()
14 |
15 | cli, teardown := httpcli(ctx)
16 | defer teardown()
17 |
18 | cli.Use(&extensions.APQ{})
19 |
20 | runAssertQuery(t, ctx, cli, func(opres transport.OperationResponse, data RoomQueryResponse) {
21 | var stats extension.ApqStats
22 | err := opres.Extensions.Unmarshal("apqStats", &stats)
23 | if err != nil {
24 | assert.Fail(t, err.Error())
25 | }
26 | assert.NotNil(t, stats)
27 | assert.True(t, stats.SentQuery)
28 | })
29 |
30 | runAssertQuery(t, ctx, cli, func(opres transport.OperationResponse, data RoomQueryResponse) {
31 | var stats extension.ApqStats
32 | err := opres.Extensions.Unmarshal("apqStats", &stats)
33 | if err != nil {
34 | assert.Fail(t, err.Error())
35 | }
36 | assert.NotNil(t, stats)
37 | assert.False(t, stats.SentQuery)
38 | })
39 | }
40 |
41 | func TestSplitAPQQuery(t *testing.T) {
42 | ctx := context.Background()
43 |
44 | cli, teardown, _ := splitcli(ctx)
45 | defer teardown()
46 |
47 | cli.Use(&extensions.APQ{})
48 | }
49 |
50 | func TestSplitAPQSubscription(t *testing.T) {
51 | ctx := context.Background()
52 |
53 | cli, teardown, _ := splitcli(ctx)
54 | defer teardown()
55 |
56 | cli.Use(&extensions.APQ{})
57 |
58 | runAssertSub(t, ctx, cli, func(opres transport.OperationResponse, data MessagesSubResponse) {
59 | var stats extension.ApqStats
60 | err := opres.Extensions.Unmarshal("apqStats", &stats)
61 | if err != nil {
62 | assert.Fail(t, err.Error())
63 | }
64 | assert.NotNil(t, stats)
65 | assert.True(t, stats.SentQuery)
66 | })
67 |
68 | runAssertSub(t, ctx, cli, func(opres transport.OperationResponse, data MessagesSubResponse) {
69 | var stats extension.ApqStats
70 | err := opres.Extensions.Unmarshal("apqStats", &stats)
71 | if err != nil {
72 | assert.Fail(t, err.Error())
73 | }
74 | assert.NotNil(t, stats)
75 | assert.False(t, stats.SentQuery)
76 | })
77 | }
78 |
--------------------------------------------------------------------------------
/example/client_http_test.go:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "github.com/infiotinc/gqlgenc/client"
7 | "github.com/infiotinc/gqlgenc/client/transport"
8 | "github.com/stretchr/testify/assert"
9 | "net/http"
10 | "net/http/httptest"
11 | "testing"
12 | )
13 |
14 | func httpcli(ctx context.Context) (*client.Client, func()) {
15 | return clifactory(ctx, func(ts *httptest.Server) (transport.Transport, func()) {
16 | return httptr(ctx, ts.URL), nil
17 | })
18 | }
19 |
20 | func TestRawHttpQuery(t *testing.T) {
21 | ctx := context.Background()
22 |
23 | cli, teardown := httpcli(ctx)
24 | defer teardown()
25 |
26 | runAssertQuery(t, ctx, cli)
27 | }
28 |
29 | func TestRawHttpQueryError(t *testing.T) {
30 | ctx := context.Background()
31 |
32 | cli, teardown := httpcli(ctx)
33 | defer teardown()
34 |
35 | var opres RoomQueryResponse
36 | _, err := cli.Query(ctx, "", RoomQuery, map[string]interface{}{"name": "error"}, &opres)
37 | assert.EqualError(t, err, "input: room that's an invalid room\n")
38 | }
39 |
40 | func TestRawHttpError(t *testing.T) {
41 | ctx := context.Background()
42 |
43 | cli, teardown := clifactorywith(ctx, func(ts *httptest.Server) (transport.Transport, func()) {
44 | return httptr(ctx, ts.URL), nil
45 | }, func(h http.Handler) http.Handler {
46 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
47 | b, _ := json.Marshal(map[string]string{
48 | "error": "Yeah that went wrong",
49 | })
50 |
51 | w.WriteHeader(http.StatusForbidden)
52 | w.Write(b)
53 | })
54 | })
55 | defer teardown()
56 |
57 | var opres RoomQueryResponse
58 | _, err := cli.Query(ctx, "", RoomQuery, map[string]interface{}{"name": "name"}, &opres)
59 | assert.EqualError(t, err, `no data nor errors, got 403: {"error":"Yeah that went wrong"}`)
60 | }
61 |
--------------------------------------------------------------------------------
/example/client_split_test.go:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import (
4 | "context"
5 | "github.com/infiotinc/gqlgenc/client"
6 | "github.com/infiotinc/gqlgenc/client/transport"
7 | "github.com/stretchr/testify/assert"
8 | "net/http/httptest"
9 | "testing"
10 | )
11 |
12 | type splitStats map[string]int
13 |
14 | func (ss splitStats) wrapTr(name string, tr transport.Transport) transport.Transport {
15 | return transport.Func(func(req transport.Request) transport.Response {
16 | if _, ok := ss[name]; !ok {
17 | ss[name] = 0
18 | }
19 |
20 | ss[name]++
21 |
22 | return tr.Request(req)
23 | })
24 | }
25 |
26 | func (ss splitStats) get(name string) int {
27 | if v, ok := ss[name]; ok {
28 | return v
29 | }
30 |
31 | return 0
32 | }
33 |
34 | func splitcli(ctx context.Context) (*client.Client, func(), *splitStats) {
35 | ss := &splitStats{}
36 |
37 | c, td := clifactory(ctx, func(ts *httptest.Server) (transport.Transport, func()) {
38 | wstr := wstr(ctx, ts.URL)
39 | httptr := httptr(ctx, ts.URL)
40 |
41 | s_wstr := ss.wrapTr("ws", wstr)
42 | s_httptr := ss.wrapTr("http", httptr)
43 |
44 | tr := transport.SplitSubscription(s_wstr, s_httptr)
45 |
46 | return tr, func() {
47 | wstr.Close()
48 | }
49 | })
50 |
51 | return c, td, ss
52 | }
53 |
54 | func TestRawSplitQuery(t *testing.T) {
55 | ctx := context.Background()
56 |
57 | cli, teardown, ss := splitcli(ctx)
58 | defer teardown()
59 |
60 | runAssertQuery(t, ctx, cli)
61 |
62 | t.Log(ss)
63 |
64 | assert.Equal(t, 1, ss.get("http"))
65 | assert.Equal(t, 0, ss.get("ws"))
66 | }
67 |
68 | func TestRawSplitSubscription(t *testing.T) {
69 | ctx := context.Background()
70 |
71 | cli, teardown, ss := splitcli(ctx)
72 | defer teardown()
73 |
74 | runAssertSub(t, ctx, cli)
75 |
76 | t.Log(ss)
77 |
78 | assert.Equal(t, 0, ss.get("http"))
79 | assert.Equal(t, 1, ss.get("ws"))
80 | }
81 |
--------------------------------------------------------------------------------
/example/client_unstable_ws_test.go:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/infiotinc/gqlgenc/client"
7 | "github.com/infiotinc/gqlgenc/client/transport"
8 | "net/http/httptest"
9 | "nhooyr.io/websocket"
10 | "testing"
11 | "time"
12 | )
13 |
14 | type unstableWebsocketConn struct {
15 | ctx context.Context
16 | wsconn *transport.WebsocketHandler
17 | }
18 |
19 | func (u *unstableWebsocketConn) dropConn() {
20 | fmt.Println("## DROP CONN")
21 | _ = u.wsconn.Conn.Close(websocket.StatusProtocolError, "conn drop")
22 | }
23 |
24 | func (u *unstableWebsocketConn) ReadJSON(v interface{}) error {
25 | return u.wsconn.ReadJSON(v)
26 | }
27 |
28 | func (u *unstableWebsocketConn) WriteJSON(v interface{}) error {
29 | return u.wsconn.WriteJSON(v)
30 | }
31 |
32 | func (u *unstableWebsocketConn) Close() error {
33 | return u.wsconn.Close()
34 | }
35 |
36 | func (u *unstableWebsocketConn) SetReadLimit(limit int64) {
37 | u.wsconn.SetReadLimit(limit)
38 | }
39 |
40 | func newUnstableConn(ctx context.Context, URL string) (transport.WebsocketConn, error) {
41 | wsconn, err := transport.DefaultWebsocketConnProvider(time.Second)(ctx, URL)
42 | if err != nil {
43 | return nil, err
44 | }
45 |
46 | return &unstableWebsocketConn{
47 | ctx: ctx,
48 | wsconn: wsconn.(*transport.WebsocketHandler),
49 | }, nil
50 | }
51 |
52 | func unstablewscli(ctx context.Context, newWebsocketConn transport.WebsocketConnProvider) (*client.Client, func()) {
53 | return clifactory(ctx, func(ts *httptest.Server) (transport.Transport, func()) {
54 | tr := cwstr(ctx, ts.URL, newWebsocketConn)
55 |
56 | return tr, func() {
57 | tr.Close()
58 | }
59 | })
60 | }
61 |
62 | func TestRawWSUnstableQuery(t *testing.T) {
63 | ctx := context.Background()
64 |
65 | cli, teardown := unstablewscli(ctx, newUnstableConn)
66 | defer teardown()
67 | tr := cli.Transport.(*transport.Ws)
68 |
69 | for i := 0; i < 5; i++ {
70 | fmt.Println("> Attempt", i)
71 | tr.GetConn().(*unstableWebsocketConn).dropConn()
72 |
73 | tr.WaitFor(transport.StatusReady, time.Second)
74 |
75 | runAssertQuery(t, ctx, cli)
76 | }
77 | }
78 |
79 | func TestRawWSUnstableSubscription(t *testing.T) {
80 | ctx := context.Background()
81 |
82 | cli, teardown := unstablewscli(ctx, newUnstableConn)
83 | defer teardown()
84 | tr := cli.Transport.(*transport.Ws)
85 |
86 | for i := 0; i < 5; i++ {
87 | fmt.Println("> Attempt", i)
88 | tr.GetConn().(*unstableWebsocketConn).dropConn()
89 |
90 | tr.WaitFor(transport.StatusReady, time.Second)
91 |
92 | runAssertSub(t, ctx, cli)
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/example/client_ws_test.go:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import (
4 | "context"
5 | "github.com/infiotinc/gqlgenc/client"
6 | "github.com/infiotinc/gqlgenc/client/transport"
7 | "net/http/httptest"
8 | "testing"
9 | "time"
10 | )
11 |
12 | func wscli(ctx context.Context) (*client.Client, func()) {
13 | return clifactory(ctx, func(ts *httptest.Server) (transport.Transport, func()) {
14 | tr := wstr(ctx, ts.URL)
15 |
16 | return tr, func() {
17 | tr.Close()
18 | }
19 | })
20 | }
21 |
22 | func TestRawWSQuery(t *testing.T) {
23 | ctx := context.Background()
24 |
25 | cli, teardown := wscli(ctx)
26 | defer teardown()
27 |
28 | runAssertQuery(t, ctx, cli)
29 | }
30 |
31 | func TestRawWSSubscription(t *testing.T) {
32 | ctx := context.Background()
33 |
34 | cli, teardown := wscli(ctx)
35 | defer teardown()
36 |
37 | runAssertSub(t, ctx, cli)
38 | }
39 |
40 | func TestWSCtxCancel1(t *testing.T) {
41 | ctx, cancel := context.WithCancel(context.Background())
42 |
43 | cli, teardown := wscli(ctx)
44 |
45 | runAssertQuery(t, ctx, cli)
46 |
47 | teardown()
48 | time.Sleep(time.Second)
49 | cancel()
50 | }
51 |
52 | func TestWSCtxCancel2(t *testing.T) {
53 | ctx, cancel := context.WithCancel(context.Background())
54 |
55 | cli, teardown := wscli(ctx)
56 |
57 | runAssertQuery(t, ctx, cli)
58 |
59 | cancel()
60 | time.Sleep(time.Second)
61 | teardown()
62 | }
63 |
--------------------------------------------------------------------------------
/example/cmd/memleak.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "example"
6 | "example/server"
7 | "example/server/generated"
8 | _ "expvar" // Register the expvar handlers
9 | "fmt"
10 | "github.com/99designs/gqlgen/graphql"
11 | "github.com/99designs/gqlgen/graphql/handler"
12 | "github.com/99designs/gqlgen/graphql/handler/extension"
13 | htransport "github.com/99designs/gqlgen/graphql/handler/transport"
14 | "github.com/99designs/gqlgen/graphql/playground"
15 | "github.com/gorilla/websocket"
16 | "github.com/infiotinc/gqlgenc/client"
17 | "github.com/infiotinc/gqlgenc/client/extensions"
18 | "github.com/infiotinc/gqlgenc/client/transport"
19 | "net/http"
20 | _ "net/http/pprof" // Register the pprof handlers
21 | "runtime"
22 | "sync"
23 | "sync/atomic"
24 | "time"
25 | )
26 |
27 | func round(cli *client.Client) {
28 | var v interface{}
29 | _, err := cli.Query(context.Background(), "", example.RoomQuery, map[string]interface{}{"name": "test"}, &v)
30 | if err != nil {
31 | fmt.Println("ERROR QUERY: ", err)
32 | }
33 |
34 | res := cli.Subscription(context.Background(), "", example.MessagesSub, nil)
35 | if err != nil {
36 | fmt.Println("ERROR SUB: ", err)
37 | }
38 | defer res.Close()
39 |
40 | for res.Next() {
41 | // Consume res
42 | }
43 |
44 | if res.Err() != nil {
45 | fmt.Println("ERROR SUB RES: ", res.Err())
46 | }
47 | }
48 |
49 | func main() {
50 | srv := handler.New(generated.NewExecutableSchema(generated.Config{
51 | Resolvers: &server.Resolver{},
52 | }))
53 |
54 | srv.AddTransport(htransport.POST{})
55 | srv.AddTransport(htransport.Websocket{
56 | KeepAlivePingInterval: 500 * time.Millisecond,
57 | Upgrader: websocket.Upgrader{
58 | CheckOrigin: func(r *http.Request) bool {
59 | return true
60 | },
61 | },
62 | })
63 | srv.Use(extension.AutomaticPersistedQuery{Cache: graphql.MapCache{}})
64 | srv.Use(extension.Introspection{})
65 |
66 | http.Handle("/playground", playground.Handler("Playground", "/"))
67 | http.Handle("/", srv)
68 |
69 | go func() {
70 | err := http.ListenAndServe(":8080", nil)
71 | if err != nil {
72 | panic(err)
73 | }
74 | }()
75 |
76 | httptr := &transport.Http{
77 | URL: "http://localhost:8080",
78 | }
79 | wstr := &transport.Ws{
80 | URL: "ws://localhost:8080",
81 | WebsocketConnProvider: transport.DefaultWebsocketConnProvider(time.Second),
82 | }
83 | wstr.Start(context.Background())
84 | defer wstr.Close()
85 |
86 | tr := transport.SplitSubscription(wstr, httptr)
87 |
88 | cli := &client.Client{
89 | Transport: tr,
90 | }
91 | cli.Use(&extensions.APQ{})
92 |
93 | pm := collectMemStats()
94 |
95 | fmt.Println("Starting queries")
96 |
97 | var wg sync.WaitGroup
98 | ch := make(chan struct{}, 5) // Concurrency
99 | var di int64
100 | for i := 0; i < 100_000; i++ {
101 | wg.Add(1)
102 |
103 | ch <- struct{}{}
104 | go func() {
105 | round(cli)
106 | di := atomic.AddInt64(&di, 1)
107 | if di%1000 == 0 {
108 | fmt.Println(di)
109 | }
110 | <-ch
111 | wg.Done()
112 | }()
113 | }
114 |
115 | wg.Wait()
116 |
117 | time.Sleep(2 * time.Second)
118 |
119 | m := collectMemStats()
120 |
121 | printMemStats("Before", pm)
122 | printMemStats("After ", m)
123 | }
124 |
125 | func collectMemStats() runtime.MemStats {
126 | fmt.Println("Running GC")
127 | runtime.GC()
128 |
129 | fmt.Println("Collecting MemStats")
130 | var m runtime.MemStats
131 | runtime.ReadMemStats(&m)
132 | return m
133 | }
134 |
135 | func printMemStats(s string, m runtime.MemStats) {
136 | fmt.Printf("%v: ", s)
137 | fmt.Printf("Alloc = %v MiB", bToMb(m.Alloc))
138 | fmt.Printf("\tTotalAlloc = %v MiB", bToMb(m.TotalAlloc))
139 | fmt.Printf("\tSys = %v MiB", bToMb(m.Sys))
140 | fmt.Printf("\tNumGC = %v\n", m.NumGC)
141 | }
142 |
143 | func bToMb(b uint64) uint64 {
144 | return b / 1024 / 1024
145 | }
146 |
--------------------------------------------------------------------------------
/example/codegen_test.go:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import (
4 | "context"
5 | "example/client"
6 | client2 "github.com/infiotinc/gqlgenc/client"
7 | "github.com/infiotinc/gqlgenc/client/transport"
8 | "github.com/stretchr/testify/assert"
9 | "io/ioutil"
10 | "net/http/httptest"
11 | "os"
12 | "reflect"
13 | "testing"
14 | )
15 |
16 | func TestSubscription(t *testing.T) {
17 | t.Parallel()
18 |
19 | ctx := context.Background()
20 |
21 | cli, td, _ := splitcli(ctx)
22 | defer td()
23 |
24 | gql := &client.Client{
25 | Client: cli,
26 | }
27 |
28 | ch, stop := gql.SubscribeMessageAdded(ctx)
29 | defer stop()
30 |
31 | ids := make([]string, 0)
32 |
33 | for msg := range ch {
34 | if msg.Error != nil {
35 | t.Fatal(msg.Error)
36 | }
37 |
38 | ids = append(ids, msg.Data.MessageAdded.ID)
39 | }
40 |
41 | assert.Len(t, ids, 3)
42 | }
43 |
44 | func isPointer(v interface{}) bool {
45 | return reflect.ValueOf(v).Kind() == reflect.Ptr
46 | }
47 |
48 | func TestQuery(t *testing.T) {
49 | t.Parallel()
50 |
51 | ctx := context.Background()
52 |
53 | cli, td, _ := splitcli(ctx)
54 | defer td()
55 |
56 | gql := &client.Client{
57 | Client: cli,
58 | }
59 |
60 | room, _, err := gql.GetRoom(ctx, "test")
61 | if err != nil {
62 | t.Fatal(err)
63 | }
64 |
65 | assert.Equal(t, "test", room.Room.Name)
66 | assert.True(t, isPointer(room.Room), "room must be a pointer")
67 | }
68 |
69 | func TestQueryNonNull(t *testing.T) {
70 | t.Parallel()
71 |
72 | ctx := context.Background()
73 |
74 | cli, td, _ := splitcli(ctx)
75 | defer td()
76 |
77 | gql := &client.Client{
78 | Client: cli,
79 | }
80 |
81 | room, _, err := gql.GetRoomNonNull(ctx, "test")
82 | if err != nil {
83 | t.Fatal(err)
84 | }
85 |
86 | assert.Equal(t, "test", room.RoomNonNull.Name)
87 | assert.False(t, isPointer(room.RoomNonNull), "room must not be a pointer")
88 | }
89 |
90 | func TestQueryCustomType(t *testing.T) {
91 | t.Parallel()
92 |
93 | ctx := context.Background()
94 |
95 | cli, td, _ := splitcli(ctx)
96 | defer td()
97 |
98 | gql := &client.Client{
99 | Client: cli,
100 | }
101 |
102 | room, _, err := gql.GetRoomCustom(ctx, "test")
103 | if err != nil {
104 | t.Fatal(err)
105 | }
106 |
107 | assert.Equal(t, "Room: test", room.String())
108 | }
109 |
110 | func TestQueryFragment(t *testing.T) {
111 | t.Parallel()
112 |
113 | ctx := context.Background()
114 |
115 | cli, td, _ := splitcli(ctx)
116 | defer td()
117 |
118 | gql := &client.Client{
119 | Client: cli,
120 | }
121 |
122 | res, _, err := gql.GetRoomFragment(ctx, "test")
123 | if err != nil {
124 | t.Fatal(err)
125 | }
126 |
127 | assert.Equal(t, "test", res.Room.Name)
128 | }
129 |
130 | func TestQueryUnion(t *testing.T) {
131 | t.Parallel()
132 |
133 | ctx := context.Background()
134 |
135 | cli, td, _ := splitcli(ctx)
136 | defer td()
137 |
138 | gql := &client.Client{
139 | Client: cli,
140 | }
141 |
142 | res, _, err := gql.GetMedias(ctx)
143 | if err != nil {
144 | t.Fatal(err)
145 | }
146 |
147 | assert.Len(t, res.Medias, 2)
148 |
149 | assert.Equal(t, int64(100), res.Medias[0].Image.Size)
150 | assert.Equal(t, int64(200), res.Medias[1].Video.Duration)
151 | }
152 |
153 | func TestQueryInterface(t *testing.T) {
154 | t.Parallel()
155 |
156 | ctx := context.Background()
157 |
158 | cli, td, _ := splitcli(ctx)
159 | defer td()
160 |
161 | gql := &client.Client{
162 | Client: cli,
163 | }
164 |
165 | res, _, err := gql.GetBooks(ctx)
166 | if err != nil {
167 | t.Fatal(err)
168 | }
169 |
170 | assert.Len(t, res.Books, 2)
171 |
172 | assert.Equal(t, "Some textbook", res.Books[0].Title)
173 | assert.Equal(t, []string{"course 1", "course 2"}, res.Books[0].Textbook.Courses)
174 |
175 | assert.Equal(t, "Some Coloring Book", res.Books[1].Title)
176 | assert.Equal(t, []string{"red", "blue"}, res.Books[1].ColoringBook.Colors)
177 | }
178 |
179 | func TestMutationInput(t *testing.T) {
180 | t.Parallel()
181 |
182 | ctx := context.Background()
183 |
184 | cli, td, _ := splitcli(ctx)
185 | defer td()
186 |
187 | gql := &client.Client{
188 | Client: cli,
189 | }
190 |
191 | res, _, err := gql.CreatePost(ctx, client.PostCreateInput{
192 | Text: "some text",
193 | })
194 | if err != nil {
195 | t.Fatal(err)
196 | }
197 |
198 | assert.Equal(t, "some text", res.Post.Text)
199 | }
200 |
201 | func uploadcli(ctx context.Context) (*client2.Client, func()) {
202 | return clifactory(ctx, func(ts *httptest.Server) (transport.Transport, func()) {
203 | tr := httptr(ctx, ts.URL)
204 | tr.UseFormMultipart = true
205 |
206 | return tr, nil
207 | })
208 | }
209 |
210 | func createUploadFile(t *testing.T) (transport.Upload, int64, func()) {
211 | f, err := ioutil.TempFile("", "")
212 | if err != nil {
213 | t.Fatal(err)
214 | }
215 |
216 | _, err = f.WriteString("some content")
217 | if err != nil {
218 | t.Fatal(err)
219 | }
220 |
221 | err = f.Sync()
222 | if err != nil {
223 | t.Fatal(err)
224 | }
225 |
226 | _, err = f.Seek(0, 0)
227 | if err != nil {
228 | t.Fatal(err)
229 | }
230 |
231 | up := transport.NewUpload(f)
232 |
233 | return up, 12, func() {
234 | os.Remove(f.Name())
235 | }
236 | }
237 |
238 | func TestMutationUploadFile(t *testing.T) {
239 | t.Parallel()
240 |
241 | ctx := context.Background()
242 |
243 | cli, td := uploadcli(ctx)
244 | defer td()
245 |
246 | gql := &client.Client{
247 | Client: cli,
248 | }
249 |
250 | up, l, rm := createUploadFile(t)
251 | defer rm()
252 |
253 | res, _, err := gql.UploadFile(ctx, up)
254 | if err != nil {
255 | t.Fatal(err)
256 | }
257 |
258 | assert.Equal(t, l, res.UploadFile.Size)
259 | }
260 |
261 | func TestMutationUploadFiles(t *testing.T) {
262 | t.Parallel()
263 |
264 | ctx := context.Background()
265 |
266 | cli, td := uploadcli(ctx)
267 | defer td()
268 |
269 | gql := &client.Client{
270 | Client: cli,
271 | }
272 |
273 | up, l, rm := createUploadFile(t)
274 | defer rm()
275 |
276 | res, _, err := gql.UploadFiles(ctx, []*transport.Upload{&up})
277 | if err != nil {
278 | t.Fatal(err)
279 | }
280 |
281 | assert.Equal(t, l, res.UploadFiles[0].Size)
282 | }
283 |
284 | func TestMutationUploadFilesMap(t *testing.T) {
285 | t.Parallel()
286 |
287 | ctx := context.Background()
288 |
289 | cli, td := uploadcli(ctx)
290 | defer td()
291 |
292 | gql := &client.Client{
293 | Client: cli,
294 | }
295 |
296 | up, l, rm := createUploadFile(t)
297 | defer rm()
298 |
299 | res, _, err := gql.UploadFilesMap(ctx, client.UploadFilesMapInput{
300 | Somefile: up,
301 | })
302 | if err != nil {
303 | t.Fatal(err)
304 | }
305 |
306 | assert.Equal(t, l, res.UploadFilesMap.Somefile.Size)
307 | }
308 |
309 | func TestIssue8(t *testing.T) {
310 | t.Parallel()
311 |
312 | ctx := context.Background()
313 |
314 | cli, td := uploadcli(ctx)
315 | defer td()
316 |
317 | gql := &client.Client{
318 | Client: cli,
319 | }
320 |
321 | res, _, err := gql.Issue8(ctx)
322 | if err != nil {
323 | t.Fatal(err)
324 | }
325 |
326 | assert.False(t, isPointer(res.Issue8.Foo1))
327 | assert.True(t, isPointer(res.Issue8.Foo2))
328 |
329 | assert.Equal(t, "foo1", res.Issue8.Foo1.A.Aa)
330 | assert.Equal(t, "foo2", res.Issue8.Foo2.A.Aa)
331 | }
332 |
333 | func TestEnum(t *testing.T) {
334 | t.Parallel()
335 |
336 | ctx := context.Background()
337 |
338 | cli, td := uploadcli(ctx)
339 | defer td()
340 |
341 | gql := &client.Client{
342 | Client: cli,
343 | }
344 |
345 | res, _, err := gql.GetEpisodes(ctx)
346 | if err != nil {
347 | t.Fatal(err)
348 | }
349 |
350 | assert.Equal(t, client.EpisodeJedi, res.Episodes[0])
351 | assert.Equal(t, client.EpisodeNewhope, res.Episodes[1])
352 | assert.Equal(t, client.EpisodeEmpire, res.Episodes[2])
353 | }
354 |
355 | func TestInputAsMapReq(t *testing.T) {
356 | t.Parallel()
357 |
358 | ctx := context.Background()
359 |
360 | cli, td := uploadcli(ctx)
361 | defer td()
362 |
363 | gql := &client.Client{
364 | Client: cli,
365 | }
366 |
367 | res, _, err := gql.AsMap(
368 | ctx,
369 | client.NewAsMapInput("str1", client.EpisodeJedi).WithOptEp(nil),
370 | nil,
371 | )
372 | if err != nil {
373 | t.Fatal(err)
374 | }
375 |
376 | assert.Equal(t, "req: map[optEp: reqEp:JEDI reqStr:str1] opt: map[]", res.AsMap)
377 | }
378 |
379 | func TestInputAsMapOpt(t *testing.T) {
380 | t.Parallel()
381 |
382 | ctx := context.Background()
383 |
384 | cli, td := uploadcli(ctx)
385 | defer td()
386 |
387 | gql := &client.Client{
388 | Client: cli,
389 | }
390 |
391 | res, _, err := gql.AsMap(
392 | ctx,
393 | client.NewAsMapInput("str1", client.EpisodeJedi),
394 | client.AsMapInputPtr(
395 | client.NewAsMapInput("str2", client.EpisodeEmpire).
396 | WithOptStr(client.StringPtr("str3")).
397 | WithOptEp(client.EpisodePtr(client.EpisodeNewhope)),
398 | ),
399 | )
400 | if err != nil {
401 | t.Fatal(err)
402 | }
403 |
404 | assert.Equal(t, "req: map[reqEp:JEDI reqStr:str1] opt: map[optEp:NEWHOPE optStr:str3 reqEp:EMPIRE reqStr:str2]", res.AsMap)
405 | }
406 |
407 | func TestGenExtraType(t *testing.T) {
408 | t.Parallel()
409 |
410 | // This should fail compiling if the types are missing
411 | _ = client.SomeExtraType{}
412 | _ = client.SomeExtraTypeChild{}
413 | _ = client.SomeExtraTypeChildChild{}
414 | }
415 |
416 | func TestGenHelpers(t *testing.T) {
417 | t.Parallel()
418 |
419 | // This should fail compiling if the helpers are missing
420 | _ = client.StringPtr
421 | _ = client.EpisodePtr
422 | _ = client.AsMapInputPtr
423 |
424 | _ = client.OptionalValue2Ptr
425 | _ = client.Value1Ptr
426 | _ = client.Value2Ptr
427 | }
428 |
--------------------------------------------------------------------------------
/example/common_test.go:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import (
4 | "context"
5 | "example/server"
6 | "example/server/generated"
7 | "fmt"
8 | "github.com/99designs/gqlgen/graphql"
9 | "github.com/99designs/gqlgen/graphql/handler"
10 | "github.com/99designs/gqlgen/graphql/handler/extension"
11 | htransport "github.com/99designs/gqlgen/graphql/handler/transport"
12 | "github.com/99designs/gqlgen/graphql/playground"
13 | "github.com/gorilla/websocket"
14 | "github.com/infiotinc/gqlgenc/client"
15 | "github.com/infiotinc/gqlgenc/client/transport"
16 | "github.com/stretchr/testify/assert"
17 | "log"
18 | "net/http"
19 | "net/http/httptest"
20 | "os"
21 | "strings"
22 | "testing"
23 | "time"
24 | )
25 |
26 | func wstr(ctx context.Context, u string) *transport.Ws {
27 | return cwstr(
28 | ctx,
29 | u,
30 | nil,
31 | )
32 | }
33 |
34 | func cwstr(ctx context.Context, u string, newWebsocketConn transport.WebsocketConnProvider) *transport.Ws {
35 | _ = os.Setenv("GQLGENC_WS_LOG", "1")
36 |
37 | if strings.HasPrefix(u, "http") {
38 | u = "ws" + strings.TrimPrefix(u, "http")
39 | }
40 |
41 | tr := &transport.Ws{
42 | URL: u,
43 | WebsocketConnProvider: newWebsocketConn,
44 | }
45 | errCh := tr.Start(ctx)
46 | go func() {
47 | for err := range errCh {
48 | log.Println("Ws Transport error: ", err)
49 | }
50 | }()
51 |
52 | tr.WaitFor(transport.StatusReady, time.Second)
53 |
54 | return tr
55 | }
56 |
57 | func httptr(ctx context.Context, u string) *transport.Http {
58 | tr := &transport.Http{
59 | URL: u,
60 | }
61 |
62 | return tr
63 | }
64 |
65 | func clifactory(ctx context.Context, trf func(server *httptest.Server) (transport.Transport, func())) (*client.Client, func()) {
66 | return clifactorywith(ctx, trf, func(h http.Handler) http.Handler {
67 | return h
68 | })
69 | }
70 |
71 | func clifactorywith(ctx context.Context, trf func(server *httptest.Server) (transport.Transport, func()), hw func(http.Handler) http.Handler) (*client.Client, func()) {
72 | h := handler.New(generated.NewExecutableSchema(generated.Config{
73 | Resolvers: &server.Resolver{},
74 | }))
75 |
76 | h.AddTransport(htransport.POST{})
77 | h.AddTransport(htransport.MultipartForm{})
78 | h.AddTransport(htransport.Websocket{
79 | KeepAlivePingInterval: 500 * time.Millisecond,
80 | Upgrader: websocket.Upgrader{
81 | CheckOrigin: func(r *http.Request) bool {
82 | return true
83 | },
84 | },
85 | InitFunc: func(ctx context.Context, initPayload htransport.InitPayload) (context.Context, error) {
86 | fmt.Println("WS Server init received")
87 |
88 | return ctx, nil
89 | },
90 | })
91 | h.Use(extension.AutomaticPersistedQuery{Cache: graphql.MapCache{}})
92 | h.Use(extension.Introspection{})
93 |
94 | h.AroundResponses(func(ctx context.Context, next graphql.ResponseHandler) *graphql.Response {
95 | stats := extension.GetApqStats(ctx)
96 |
97 | if stats != nil {
98 | graphql.RegisterExtension(ctx, "apqStats", stats)
99 | }
100 |
101 | return next(ctx)
102 | })
103 |
104 | srv := http.NewServeMux()
105 | srv.Handle("/playground", playground.Handler("Playground", "/"))
106 | srv.Handle("/", hw(h))
107 |
108 | ts := httptest.NewServer(srv)
109 |
110 | fmt.Println("TS URL: ", ts.URL)
111 |
112 | tr, trteardown := trf(ts)
113 |
114 | return &client.Client{
115 | Transport: tr,
116 | }, func() {
117 | if trteardown != nil {
118 | fmt.Println("CLOSE TR")
119 | trteardown()
120 | }
121 |
122 | if ts != nil {
123 | fmt.Println("CLOSE HTTPTEST")
124 | ts.Close()
125 | }
126 | }
127 | }
128 |
129 | type QueryAsserter func(transport.OperationResponse, RoomQueryResponse)
130 |
131 | func runAssertQuery(t *testing.T, ctx context.Context, cli *client.Client, asserters ...QueryAsserter) {
132 | fmt.Println("ASSERT QUERY")
133 | var data RoomQueryResponse
134 | opres, err := cli.Query(ctx, "", RoomQuery, map[string]interface{}{"name": "test"}, &data)
135 | if err != nil {
136 | t.Fatal(err)
137 | }
138 |
139 | assert.Equal(t, "test", data.Room.Name)
140 |
141 | for _, a := range asserters {
142 | a(opres, data)
143 | }
144 | }
145 |
146 | type SubAsserter func(transport.OperationResponse, MessagesSubResponse)
147 |
148 | func runAssertSub(t *testing.T, ctx context.Context, cli *client.Client, asserters ...SubAsserter) {
149 | fmt.Println("ASSERT SUB")
150 | res := cli.Subscription(ctx, "", MessagesSub, nil)
151 |
152 | ids := make([]string, 0)
153 |
154 | for res.Next() {
155 | opres := res.Get()
156 |
157 | var data MessagesSubResponse
158 | err := opres.UnmarshalData(&data)
159 | if err != nil {
160 | t.Fatal(err)
161 | }
162 | ids = append(ids, data.MessageAdded.ID)
163 |
164 | for _, a := range asserters {
165 | a(opres, data)
166 | }
167 | }
168 |
169 | if err := res.Err(); err != nil {
170 | t.Fatal(err)
171 | }
172 |
173 | assert.Len(t, ids, 3)
174 | }
175 |
--------------------------------------------------------------------------------
/example/go.mod:
--------------------------------------------------------------------------------
1 | module example
2 |
3 | replace github.com/infiotinc/gqlgenc => ../
4 |
5 | go 1.15
6 |
7 | require (
8 | github.com/99designs/gqlgen v0.16.0
9 | github.com/gorilla/websocket v1.4.2
10 | github.com/infiotinc/gqlgenc v0.0.0-00010101000000-000000000000
11 | github.com/pkg/errors v0.9.1
12 | github.com/stretchr/testify v1.4.0
13 | github.com/urfave/cli/v2 v2.3.0
14 | github.com/vektah/gqlparser/v2 v2.2.0
15 | golang.org/x/tools v0.1.5
16 | nhooyr.io/websocket v1.8.7
17 | )
18 |
--------------------------------------------------------------------------------
/example/go.sum:
--------------------------------------------------------------------------------
1 | github.com/99designs/gqlgen v0.16.0 h1:7Qc4Ll3mfN3doAyUWOgtGLcBGu+KDgK48HdkBGLZVFs=
2 | github.com/99designs/gqlgen v0.16.0/go.mod h1:nbeSjFkqphIqpZsYe1ULVz0yfH8hjpJdJIQoX/e0G2I=
3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
4 | github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
5 | github.com/agnivade/levenshtein v1.1.0 h1:n6qGwyHG61v3ABce1rPVZklEYRT8NFpCMrpZdBUbYGM=
6 | github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
7 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
8 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
9 | github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
10 | github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
11 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
12 | github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
13 | github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
14 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
15 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
16 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
17 | github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
18 | github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
19 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
20 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
21 | github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
22 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
23 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
24 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
25 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
26 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
27 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
28 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
29 | github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
30 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
31 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
32 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
33 | github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
34 | github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
35 | github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
36 | github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
37 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
38 | github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
39 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
40 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
41 | github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
42 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
43 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
44 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
45 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
46 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
47 | github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
48 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
49 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
50 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
51 | github.com/kevinmbeaulieu/eq-go v1.0.0/go.mod h1:G3S8ajA56gKBZm4UB9AOyoOS37JO3roToPzKNM8dtdM=
52 | github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
53 | github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
54 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
55 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
56 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
57 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
58 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
59 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
60 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
61 | github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc=
62 | github.com/matryer/moq v0.2.3/go.mod h1:9RtPYjTnH1bSBIkpvtHkFN7nbWAnO7oRpdJkEIn6UtE=
63 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
64 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
65 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
66 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
67 | github.com/mitchellh/mapstructure v1.2.3 h1:f/MjBEBDLttYCGfRaKBbKSRVF5aV2O6fnBpzknuE3jU=
68 | github.com/mitchellh/mapstructure v1.2.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
69 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
70 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
71 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
72 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
73 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
74 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
75 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
76 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
77 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
78 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
79 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
80 | github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
81 | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
82 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
83 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
84 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
85 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
86 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
87 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
88 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
89 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
90 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
91 | github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
92 | github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
93 | github.com/vektah/gqlparser/v2 v2.2.0 h1:bAc3slekAAJW6sZTi07aGq0OrfaCjj4jxARAaC7g2EM=
94 | github.com/vektah/gqlparser/v2 v2.2.0/go.mod h1:i3mQIGIrbK2PD1RrCeMTlVbkF2FJ6WkU1KJlJlC+3F4=
95 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
96 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
97 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
98 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
99 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
100 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
101 | golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
102 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
103 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
104 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
105 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
106 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
107 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
108 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
109 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
110 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
111 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
112 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
113 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
114 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
115 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
116 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
117 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
118 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
119 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
120 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
121 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
122 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
123 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
124 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
125 | golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
126 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
127 | golang.org/x/tools v0.0.0-20200815165600-90abf76919f3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
128 | golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
129 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
130 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
131 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
132 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
133 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
134 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
135 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
136 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
137 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
138 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
139 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
140 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
141 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
142 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
143 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
144 | nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
145 | nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
146 |
--------------------------------------------------------------------------------
/example/query.go:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | const RoomQuery = `
4 | query query($name: String!) {
5 | room(name: $name) {
6 | name
7 | }
8 | }
9 | `
10 |
11 | type RoomQueryResponse struct {
12 | Room struct {
13 | Name string `json:"name"`
14 | } `json:"room"`
15 | }
16 |
17 | const MessagesSub = `
18 | subscription query{
19 | messageAdded(roomName: "test") {
20 | id
21 | }
22 | }
23 | `
24 |
25 | type MessagesSubResponse struct {
26 | MessageAdded struct {
27 | ID string `json:"id"`
28 | } `json:"messageAdded"`
29 | }
30 |
--------------------------------------------------------------------------------
/example/query.graphql:
--------------------------------------------------------------------------------
1 | query GetRoom($name: String!) {
2 | room(name: $name) {
3 | name
4 | hash
5 | }
6 | }
7 |
8 | query GetRoomNonNull($name: String!) {
9 | roomNonNull(name: $name) {
10 | name
11 | }
12 | }
13 |
14 | fragment RoomFragment on Chatroom {
15 | name
16 | }
17 |
18 | query GetRoomFragment($name: String!) {
19 | room(name: $name) {
20 | ... RoomFragment
21 | }
22 | }
23 |
24 | query GetRoomCustom($name: String!) {
25 | room(name: $name) {
26 | name
27 | }
28 | }
29 |
30 | query GetMedias {
31 | medias {
32 | ... on Image {
33 | size
34 | }
35 | ... on Video {
36 | duration
37 | }
38 | }
39 | }
40 |
41 | query GetBooks {
42 | books {
43 | title
44 | ... on Textbook {
45 | courses
46 | }
47 | ... on ColoringBook {
48 | colors
49 | }
50 | }
51 | }
52 |
53 | subscription SubscribeMessageAdded {
54 | messageAdded(roomName: "test") {
55 | id
56 | }
57 | }
58 |
59 | mutation CreatePost($input: PostCreateInput!) {
60 | post(input: $input) {
61 | id
62 | text
63 | }
64 | }
65 |
66 | mutation UploadFile($file: Upload!) {
67 | uploadFile(file: $file) {
68 | size
69 | }
70 | }
71 |
72 | mutation UploadFiles($files: [Upload!]!) {
73 | uploadFiles(files: $files) {
74 | size
75 | }
76 | }
77 |
78 | mutation UploadFilesMap($files: UploadFilesMapInput!) {
79 | uploadFilesMap(files: $files) {
80 | somefile {
81 | size
82 | }
83 | }
84 | }
85 |
86 | query Issue8 {
87 | issue8 {
88 | foo1 {
89 | a {
90 | Aa
91 | }
92 | }
93 | foo2 {
94 | a {
95 | Aa
96 | }
97 | }
98 | }
99 | }
100 |
101 | query GetEpisodes {
102 | episodes
103 | }
104 |
105 | query Cyclic1 {
106 | cyclic {
107 | child {
108 | child {
109 | child {
110 | id
111 | }
112 | }
113 | }
114 | }
115 | }
116 |
117 | query AsMap($req: AsMapInput!, $opt: AsMapInput) {
118 | asMap(req: $req, opt: $opt)
119 | }
120 |
121 | query OptValue1($v: OptionalValue1!) {
122 | optValue1(req: $v)
123 | }
124 |
125 | query OptValue2($v: OptionalValue2) {
126 | optValue2(opt: $v)
127 | }
128 |
--------------------------------------------------------------------------------
/example/schema.graphql:
--------------------------------------------------------------------------------
1 | scalar Time
2 | scalar Upload
3 |
4 | union Media = Image | Video
5 |
6 | type Image {
7 | size: Int!
8 | }
9 |
10 | type Video {
11 | duration: Int!
12 | }
13 |
14 | interface Book {
15 | title: String!
16 | }
17 |
18 | type Textbook implements Book {
19 | title: String!
20 | courses: [String!]!
21 | }
22 |
23 | type ColoringBook implements Book {
24 | title: String!
25 | colors: [String!]!
26 | }
27 |
28 | type Chatroom {
29 | name: String!
30 | messages: [Message!]!
31 | hash: FooType_hash1
32 | }
33 |
34 | type Message {
35 | id: ID!
36 | text: String!
37 | createdBy: String!
38 | createdAt: Time!
39 | }
40 |
41 | scalar Value1
42 | input OptionalValue1 {
43 | value: Value1
44 | }
45 |
46 | scalar Value2
47 | input OptionalValue2 {
48 | value: Value2
49 | }
50 |
51 | type Query {
52 | room(name:String!): Chatroom
53 | roomNonNull(name:String!): Chatroom!
54 | medias: [Media!]!
55 | books: [Book!]!
56 | issue8: Issue8Payload
57 | cyclic: Cyclic1_1
58 | episodes: [Episode!]!
59 | asMap(req: AsMapInput!, opt: AsMapInput): String!
60 | optValue1(req: OptionalValue1!): Boolean
61 | optValue2(opt: OptionalValue2): Boolean
62 | }
63 |
64 | type Issue8Payload {
65 | foo1: Issue8PayloadFoo!
66 | foo2: Issue8PayloadFoo
67 | }
68 |
69 | type Issue8PayloadFoo {
70 | a: Issue8PayloadFooA!
71 | }
72 |
73 | type Issue8PayloadFooA {
74 | Aa: String!
75 | }
76 |
77 | input PostCreateInput {
78 | text: String!
79 | }
80 |
81 | type UploadData {
82 | size: Int!
83 | }
84 |
85 |
86 | input UploadFilesMapInput {
87 | somefile: Upload!
88 | }
89 |
90 | type UploadFilesMap {
91 | somefile: UploadData!
92 | }
93 |
94 | type Mutation {
95 | post(input: PostCreateInput!): Message!
96 | uploadFile(file: Upload!): UploadData!
97 | uploadFiles(files: [Upload!]!): [UploadData!]
98 | uploadFilesMap(files: UploadFilesMapInput!): UploadFilesMap!
99 | }
100 |
101 | type Subscription {
102 | messageAdded(roomName: String!): Message!
103 | }
104 |
105 | # Should be generated by the config
106 | type SomeExtraType {
107 | child: SomeExtraTypeChild!
108 | }
109 |
110 | type SomeExtraTypeChild {
111 | child: SomeExtraTypeChildChild!
112 | }
113 |
114 | type SomeExtraTypeChildChild {
115 | id: String!
116 | }
117 |
118 | enum Episode {
119 | NEWHOPE
120 | EMPIRE
121 | JEDI
122 | }
123 |
124 | type Cyclic1_1 {
125 | id: String!
126 | child: Cyclic1_2
127 | }
128 |
129 | type Cyclic1_2 {
130 | id: String!
131 | child: Cyclic1_1
132 | }
133 |
134 | type Cyclic2_1 {
135 | id: String!
136 | child: Cyclic2_2
137 | }
138 |
139 | type Cyclic2_2 {
140 | id: String!
141 | child: Cyclic2_1
142 | }
143 |
144 | input AsMapInput {
145 | reqStr: String!
146 | optStr: String
147 |
148 | reqEp: Episode!
149 | optEp: Episode
150 | }
151 |
152 | input InputIssue14 {
153 | ids: [String!]
154 | }
155 |
156 | enum FooType_hash1 {
157 | hash_1
158 | hash_2
159 | }
160 |
--------------------------------------------------------------------------------
/example/server/model/models_gen.go:
--------------------------------------------------------------------------------
1 | // Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
2 |
3 | package model
4 |
5 | import (
6 | "fmt"
7 | "io"
8 | "strconv"
9 | "time"
10 |
11 | "github.com/99designs/gqlgen/graphql"
12 | )
13 |
14 | type Book interface {
15 | IsBook()
16 | }
17 |
18 | type Media interface {
19 | IsMedia()
20 | }
21 |
22 | type Chatroom struct {
23 | Name string `json:"name"`
24 | Messages []*Message `json:"messages"`
25 | Hash *FooTypeHash1 `json:"hash"`
26 | }
27 |
28 | type ColoringBook struct {
29 | Title string `json:"title"`
30 | Colors []string `json:"colors"`
31 | }
32 |
33 | func (ColoringBook) IsBook() {}
34 |
35 | type Cyclic1_1 struct {
36 | ID string `json:"id"`
37 | Child *Cyclic1_2 `json:"child"`
38 | }
39 |
40 | type Cyclic1_2 struct {
41 | ID string `json:"id"`
42 | Child *Cyclic1_1 `json:"child"`
43 | }
44 |
45 | type Cyclic2_1 struct {
46 | ID string `json:"id"`
47 | Child *Cyclic2_2 `json:"child"`
48 | }
49 |
50 | type Cyclic2_2 struct {
51 | ID string `json:"id"`
52 | Child *Cyclic2_1 `json:"child"`
53 | }
54 |
55 | type Image struct {
56 | Size int `json:"size"`
57 | }
58 |
59 | func (Image) IsMedia() {}
60 |
61 | type InputIssue14 struct {
62 | Ids []string `json:"ids"`
63 | }
64 |
65 | type Issue8Payload struct {
66 | Foo1 *Issue8PayloadFoo `json:"foo1"`
67 | Foo2 *Issue8PayloadFoo `json:"foo2"`
68 | }
69 |
70 | type Issue8PayloadFoo struct {
71 | A *Issue8PayloadFooA `json:"a"`
72 | }
73 |
74 | type Issue8PayloadFooA struct {
75 | Aa string `json:"Aa"`
76 | }
77 |
78 | type Message struct {
79 | ID string `json:"id"`
80 | Text string `json:"text"`
81 | CreatedBy string `json:"createdBy"`
82 | CreatedAt time.Time `json:"createdAt"`
83 | }
84 |
85 | type OptionalValue1 struct {
86 | Value *string `json:"value"`
87 | }
88 |
89 | type OptionalValue2 struct {
90 | Value *string `json:"value"`
91 | }
92 |
93 | type PostCreateInput struct {
94 | Text string `json:"text"`
95 | }
96 |
97 | type SomeExtraType struct {
98 | Child *SomeExtraTypeChild `json:"child"`
99 | }
100 |
101 | type SomeExtraTypeChild struct {
102 | Child *SomeExtraTypeChildChild `json:"child"`
103 | }
104 |
105 | type SomeExtraTypeChildChild struct {
106 | ID string `json:"id"`
107 | }
108 |
109 | type Textbook struct {
110 | Title string `json:"title"`
111 | Courses []string `json:"courses"`
112 | }
113 |
114 | func (Textbook) IsBook() {}
115 |
116 | type UploadData struct {
117 | Size int `json:"size"`
118 | }
119 |
120 | type UploadFilesMap struct {
121 | Somefile *UploadData `json:"somefile"`
122 | }
123 |
124 | type UploadFilesMapInput struct {
125 | Somefile graphql.Upload `json:"somefile"`
126 | }
127 |
128 | type Video struct {
129 | Duration int `json:"duration"`
130 | }
131 |
132 | func (Video) IsMedia() {}
133 |
134 | type Episode string
135 |
136 | const (
137 | EpisodeNewhope Episode = "NEWHOPE"
138 | EpisodeEmpire Episode = "EMPIRE"
139 | EpisodeJedi Episode = "JEDI"
140 | )
141 |
142 | var AllEpisode = []Episode{
143 | EpisodeNewhope,
144 | EpisodeEmpire,
145 | EpisodeJedi,
146 | }
147 |
148 | func (e Episode) IsValid() bool {
149 | switch e {
150 | case EpisodeNewhope, EpisodeEmpire, EpisodeJedi:
151 | return true
152 | }
153 | return false
154 | }
155 |
156 | func (e Episode) String() string {
157 | return string(e)
158 | }
159 |
160 | func (e *Episode) UnmarshalGQL(v interface{}) error {
161 | str, ok := v.(string)
162 | if !ok {
163 | return fmt.Errorf("enums must be strings")
164 | }
165 |
166 | *e = Episode(str)
167 | if !e.IsValid() {
168 | return fmt.Errorf("%s is not a valid Episode", str)
169 | }
170 | return nil
171 | }
172 |
173 | func (e Episode) MarshalGQL(w io.Writer) {
174 | fmt.Fprint(w, strconv.Quote(e.String()))
175 | }
176 |
177 | type FooTypeHash1 string
178 |
179 | const (
180 | FooTypeHash1Hash1 FooTypeHash1 = "hash_1"
181 | FooTypeHash1Hash2 FooTypeHash1 = "hash_2"
182 | )
183 |
184 | var AllFooTypeHash1 = []FooTypeHash1{
185 | FooTypeHash1Hash1,
186 | FooTypeHash1Hash2,
187 | }
188 |
189 | func (e FooTypeHash1) IsValid() bool {
190 | switch e {
191 | case FooTypeHash1Hash1, FooTypeHash1Hash2:
192 | return true
193 | }
194 | return false
195 | }
196 |
197 | func (e FooTypeHash1) String() string {
198 | return string(e)
199 | }
200 |
201 | func (e *FooTypeHash1) UnmarshalGQL(v interface{}) error {
202 | str, ok := v.(string)
203 | if !ok {
204 | return fmt.Errorf("enums must be strings")
205 | }
206 |
207 | *e = FooTypeHash1(str)
208 | if !e.IsValid() {
209 | return fmt.Errorf("%s is not a valid FooType_hash1", str)
210 | }
211 | return nil
212 | }
213 |
214 | func (e FooTypeHash1) MarshalGQL(w io.Writer) {
215 | fmt.Fprint(w, strconv.Quote(e.String()))
216 | }
217 |
--------------------------------------------------------------------------------
/example/server/resolver.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | // This file will not be regenerated automatically.
4 | //
5 | // It serves as dependency injection for your app, add any dependencies you require here.
6 |
7 | type Resolver struct{}
8 |
--------------------------------------------------------------------------------
/example/server/schema.resolvers.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | // This file will be automatically regenerated based on the schema, any resolver implementations
4 | // will be copied through when generating and any unknown code will be moved to the end.
5 |
6 | import (
7 | "context"
8 | "errors"
9 | "example/server/generated"
10 | "example/server/model"
11 | "fmt"
12 | "os"
13 | "strconv"
14 |
15 | "github.com/99designs/gqlgen/graphql"
16 | )
17 |
18 | func (r *mutationResolver) Post(ctx context.Context, input model.PostCreateInput) (*model.Message, error) {
19 | return &model.Message{
20 | Text: input.Text,
21 | }, nil
22 | }
23 |
24 | func (r *mutationResolver) UploadFile(ctx context.Context, file graphql.Upload) (*model.UploadData, error) {
25 | return &model.UploadData{
26 | Size: int(file.Size),
27 | }, nil
28 | }
29 |
30 | func (r *mutationResolver) UploadFiles(ctx context.Context, files []*graphql.Upload) ([]*model.UploadData, error) {
31 | ds := make([]*model.UploadData, 0)
32 | for _, f := range files {
33 | ds = append(ds, &model.UploadData{
34 | Size: int(f.Size),
35 | })
36 | }
37 | return ds, nil
38 | }
39 |
40 | func (r *mutationResolver) UploadFilesMap(ctx context.Context, files model.UploadFilesMapInput) (*model.UploadFilesMap, error) {
41 | return &model.UploadFilesMap{
42 | Somefile: &model.UploadData{
43 | Size: int(files.Somefile.Size),
44 | },
45 | }, nil
46 | }
47 |
48 | func (r *queryResolver) Room(ctx context.Context, name string) (*model.Chatroom, error) {
49 | if name == "error" {
50 | return nil, errors.New("that's an invalid room")
51 | }
52 |
53 | return &model.Chatroom{
54 | Name: name,
55 | Messages: nil,
56 | }, nil
57 | }
58 |
59 | func (r *queryResolver) RoomNonNull(ctx context.Context, name string) (*model.Chatroom, error) {
60 | return r.Room(ctx, name)
61 | }
62 |
63 | func (r *queryResolver) Medias(ctx context.Context) ([]model.Media, error) {
64 | return []model.Media{
65 | &model.Image{
66 | Size: 100,
67 | },
68 | &model.Video{
69 | Duration: 200,
70 | },
71 | }, nil
72 | }
73 |
74 | func (r *queryResolver) Books(ctx context.Context) ([]model.Book, error) {
75 | return []model.Book{
76 | &model.Textbook{
77 | Title: "Some textbook",
78 | Courses: []string{"course 1", "course 2"},
79 | },
80 | &model.ColoringBook{
81 | Title: "Some Coloring Book",
82 | Colors: []string{"red", "blue"},
83 | },
84 | }, nil
85 | }
86 |
87 | func (r *queryResolver) Issue8(ctx context.Context) (*model.Issue8Payload, error) {
88 | return &model.Issue8Payload{
89 | Foo1: &model.Issue8PayloadFoo{A: &model.Issue8PayloadFooA{Aa: "foo1"}},
90 | Foo2: &model.Issue8PayloadFoo{A: &model.Issue8PayloadFooA{Aa: "foo2"}},
91 | }, nil
92 | }
93 |
94 | func (r *queryResolver) Cyclic(ctx context.Context) (*model.Cyclic1_1, error) {
95 | panic(fmt.Errorf("not implemented"))
96 | }
97 |
98 | func (r *queryResolver) Episodes(ctx context.Context) ([]model.Episode, error) {
99 | return []model.Episode{
100 | model.EpisodeJedi,
101 | model.EpisodeNewhope,
102 | model.EpisodeEmpire,
103 | }, nil
104 | }
105 |
106 | func (r *queryResolver) AsMap(ctx context.Context, req map[string]interface{}, opt map[string]interface{}) (string, error) {
107 | return fmt.Sprintf("req: %+v opt: %+v", req, opt), nil
108 | }
109 |
110 | func (r *queryResolver) OptValue1(ctx context.Context, req model.OptionalValue1) (*bool, error) {
111 | panic(fmt.Errorf("not implemented"))
112 | }
113 |
114 | func (r *queryResolver) OptValue2(ctx context.Context, opt *model.OptionalValue2) (*bool, error) {
115 | panic(fmt.Errorf("not implemented"))
116 | }
117 |
118 | func (r *subscriptionResolver) MessageAdded(ctx context.Context, roomName string) (<-chan *model.Message, error) {
119 | ch := make(chan *model.Message)
120 | debug, _ := strconv.ParseBool(os.Getenv("GQLGENC_WS_LOG"))
121 |
122 | debugPrint := func(a ...interface{}) {
123 | if debug {
124 | fmt.Println(a...)
125 | }
126 | }
127 |
128 | debugPrint("MESSAGE ADDED")
129 |
130 | go func() {
131 | i := 0
132 | for {
133 | if i == 3 {
134 | close(ch)
135 | debugPrint("DONE MESSAGE ADDED")
136 | return
137 | }
138 |
139 | msg := &model.Message{
140 | ID: fmt.Sprintf("msg%v", i),
141 | }
142 |
143 | select {
144 | case <-ctx.Done():
145 | close(ch)
146 | debugPrint("DONE ctx")
147 | return
148 | case ch <- msg:
149 | debugPrint("SEND")
150 | i++
151 | }
152 | }
153 | }()
154 |
155 | return ch, nil
156 | }
157 |
158 | // Mutation returns generated.MutationResolver implementation.
159 | func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
160 |
161 | // Query returns generated.QueryResolver implementation.
162 | func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
163 |
164 | // Subscription returns generated.SubscriptionResolver implementation.
165 | func (r *Resolver) Subscription() generated.SubscriptionResolver { return &subscriptionResolver{r} }
166 |
167 | type mutationResolver struct{ *Resolver }
168 | type queryResolver struct{ *Resolver }
169 | type subscriptionResolver struct{ *Resolver }
170 |
--------------------------------------------------------------------------------
/example/server/tools.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | // See https://github.com/golang/go/issues/44129#issuecomment-815008489
4 | import (
5 | // GQLGen generate command dependencies.
6 | _ "github.com/pkg/errors"
7 | _ "github.com/urfave/cli/v2"
8 | _ "golang.org/x/tools/go/ast/astutil"
9 | _ "golang.org/x/tools/go/packages"
10 | _ "golang.org/x/tools/imports"
11 | )
12 |
--------------------------------------------------------------------------------
/example/somelib/lib.go:
--------------------------------------------------------------------------------
1 | package somelib
2 |
3 | import "encoding/json"
4 |
5 | type CustomRoom string
6 |
7 | func (b *CustomRoom) UnmarshalJSON(data []byte) error {
8 | var v struct {
9 | Room struct {
10 | Name string `json:"name"`
11 | } `json:"room"`
12 | }
13 | if err := json.Unmarshal(data, &v); err != nil {
14 | return err
15 | }
16 |
17 | *b = CustomRoom("Room: " + v.Room.Name)
18 |
19 | return nil
20 | }
21 |
22 | func (b CustomRoom) String() string {
23 | return string(b)
24 | }
25 |
--------------------------------------------------------------------------------
/generator/generator.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // Original work from https://github.com/Yamashou/gqlgenc/blob/1ef8055d/generator/generater.go
4 |
5 | import (
6 | "context"
7 | "fmt"
8 | "github.com/infiotinc/gqlgenc/config"
9 |
10 | "github.com/99designs/gqlgen/api"
11 | "github.com/99designs/gqlgen/plugin"
12 | )
13 |
14 | func Generate(ctx context.Context, cfg *config.Config, option ...api.Option) error {
15 | var plugins []plugin.Plugin
16 | for _, o := range option {
17 | o(cfg.GQLConfig, &plugins)
18 | }
19 |
20 | if err := cfg.LoadSchema(ctx); err != nil {
21 | return fmt.Errorf("failed to load schema: %w", err)
22 | }
23 |
24 | if err := cfg.GQLConfig.Init(); err != nil {
25 | return fmt.Errorf("generating core failed: %w", err)
26 | }
27 |
28 | for _, p := range plugins {
29 | if mut, ok := p.(plugin.ConfigMutator); ok {
30 | err := mut.MutateConfig(cfg.GQLConfig)
31 | if err != nil {
32 | return fmt.Errorf("%s failed: %w", p.Name(), err)
33 | }
34 | }
35 | }
36 |
37 | return nil
38 | }
39 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/infiotinc/gqlgenc
2 |
3 | go 1.15
4 |
5 | require (
6 | github.com/99designs/gqlgen v0.16.0
7 | github.com/google/go-cmp v0.5.4 // indirect
8 | github.com/stretchr/testify v1.4.0
9 | github.com/vektah/gqlparser/v2 v2.2.0
10 | gopkg.in/yaml.v2 v2.3.0
11 | nhooyr.io/websocket v1.8.7
12 | )
13 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/99designs/gqlgen v0.16.0 h1:7Qc4Ll3mfN3doAyUWOgtGLcBGu+KDgK48HdkBGLZVFs=
2 | github.com/99designs/gqlgen v0.16.0/go.mod h1:nbeSjFkqphIqpZsYe1ULVz0yfH8hjpJdJIQoX/e0G2I=
3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
4 | github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
5 | github.com/agnivade/levenshtein v1.1.0 h1:n6qGwyHG61v3ABce1rPVZklEYRT8NFpCMrpZdBUbYGM=
6 | github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
7 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
8 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
9 | github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
10 | github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
11 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
12 | github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
13 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
14 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
15 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
16 | github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
17 | github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
18 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
19 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
20 | github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
21 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
22 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
23 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
24 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
25 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
26 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
27 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
28 | github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
29 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
30 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
31 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
32 | github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
33 | github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
34 | github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
35 | github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
36 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
37 | github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
38 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
39 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
40 | github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
41 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
42 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
43 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
44 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
45 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
46 | github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
47 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
48 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
49 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
50 | github.com/kevinmbeaulieu/eq-go v1.0.0/go.mod h1:G3S8ajA56gKBZm4UB9AOyoOS37JO3roToPzKNM8dtdM=
51 | github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
52 | github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
53 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
54 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
55 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
56 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
57 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
58 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
59 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
60 | github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc=
61 | github.com/matryer/moq v0.2.3/go.mod h1:9RtPYjTnH1bSBIkpvtHkFN7nbWAnO7oRpdJkEIn6UtE=
62 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
63 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
64 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
65 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
66 | github.com/mitchellh/mapstructure v1.2.3 h1:f/MjBEBDLttYCGfRaKBbKSRVF5aV2O6fnBpzknuE3jU=
67 | github.com/mitchellh/mapstructure v1.2.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
68 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
69 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
70 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
71 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
72 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
73 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
74 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
75 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
76 | github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
77 | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
78 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
79 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
80 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
81 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
82 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
83 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
84 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
85 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
86 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
87 | github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
88 | github.com/vektah/gqlparser/v2 v2.2.0 h1:bAc3slekAAJW6sZTi07aGq0OrfaCjj4jxARAaC7g2EM=
89 | github.com/vektah/gqlparser/v2 v2.2.0/go.mod h1:i3mQIGIrbK2PD1RrCeMTlVbkF2FJ6WkU1KJlJlC+3F4=
90 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
91 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
92 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
93 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
94 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
95 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
96 | golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
97 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
98 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
99 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
100 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
101 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
102 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
103 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
104 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
105 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
106 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
107 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
108 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
109 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
110 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
111 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
112 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
113 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
114 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
115 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
116 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
117 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
118 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
119 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
120 | golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
121 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
122 | golang.org/x/tools v0.0.0-20200815165600-90abf76919f3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
123 | golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
124 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
125 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
126 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
127 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
128 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
129 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
130 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
131 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
132 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
133 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
134 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
135 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
136 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
137 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
138 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
139 | nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
140 | nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
141 |
--------------------------------------------------------------------------------
/introspection/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Yamashou
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/introspection/parse.go:
--------------------------------------------------------------------------------
1 | package introspection
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/vektah/gqlparser/v2/ast"
7 | )
8 |
9 | func ParseIntrospectionQuery(url string, query Query) *ast.SchemaDocument {
10 | parser := parser{
11 | sharedPosition: &ast.Position{Src: &ast.Source{
12 | Name: "remote",
13 | BuiltIn: false,
14 | }},
15 | }
16 |
17 | if url != "" {
18 | parser.sharedPosition.Src.Name = url
19 | }
20 |
21 | return parser.parseIntrospectionQuery(query)
22 | }
23 |
24 | type parser struct {
25 | sharedPosition *ast.Position
26 | }
27 |
28 | func (p parser) parseIntrospectionQuery(query Query) *ast.SchemaDocument {
29 | var doc ast.SchemaDocument
30 | typeMap := query.Schema.Types.NameMap()
31 |
32 | doc.Schema = append(doc.Schema, p.parseSchemaDefinition(query, typeMap))
33 | doc.Position = p.sharedPosition
34 |
35 | for _, typeVale := range typeMap {
36 | doc.Definitions = append(doc.Definitions, p.parseTypeSystemDefinition(typeVale))
37 | }
38 |
39 | for _, directiveValue := range query.Schema.Directives {
40 | doc.Directives = append(doc.Directives, p.parseDirectiveDefinition(directiveValue))
41 | }
42 |
43 | return &doc
44 | }
45 |
46 | func (p parser) parseSchemaDefinition(query Query, typeMap map[string]*FullType) *ast.SchemaDefinition {
47 | def := ast.SchemaDefinition{}
48 | def.Position = p.sharedPosition
49 |
50 | if query.Schema.QueryType.Name != nil {
51 | def.OperationTypes = append(def.OperationTypes,
52 | p.parseOperationTypeDefinitionForQuery(typeMap[*query.Schema.QueryType.Name]),
53 | )
54 | }
55 |
56 | if query.Schema.MutationType != nil {
57 | def.OperationTypes = append(def.OperationTypes,
58 | p.parseOperationTypeDefinitionForMutation(typeMap[*query.Schema.MutationType.Name]),
59 | )
60 | }
61 |
62 | if query.Schema.SubscriptionType != nil {
63 | def.OperationTypes = append(def.OperationTypes,
64 | p.parseOperationTypeDefinitionForSubscription(typeMap[*query.Schema.SubscriptionType.Name]),
65 | )
66 | }
67 |
68 | return &def
69 | }
70 |
71 | func (p parser) parseOperationTypeDefinitionForQuery(fullType *FullType) *ast.OperationTypeDefinition {
72 | var op ast.OperationTypeDefinition
73 | op.Operation = ast.Query
74 | op.Type = *fullType.Name
75 | op.Position = p.sharedPosition
76 |
77 | return &op
78 | }
79 |
80 | func (p parser) parseOperationTypeDefinitionForMutation(fullType *FullType) *ast.OperationTypeDefinition {
81 | var op ast.OperationTypeDefinition
82 | op.Operation = ast.Mutation
83 | op.Type = *fullType.Name
84 | op.Position = p.sharedPosition
85 |
86 | return &op
87 | }
88 |
89 | func (p parser) parseOperationTypeDefinitionForSubscription(fullType *FullType) *ast.OperationTypeDefinition {
90 | var op ast.OperationTypeDefinition
91 | op.Operation = ast.Subscription
92 | op.Type = *fullType.Name
93 | op.Position = p.sharedPosition
94 |
95 | return &op
96 | }
97 |
98 | func (p parser) parseDirectiveDefinition(directiveValue *DirectiveType) *ast.DirectiveDefinition {
99 | args := make(ast.ArgumentDefinitionList, 0, len(directiveValue.Args))
100 | for _, arg := range directiveValue.Args {
101 | argumentDefinition := p.buildInputValue(arg)
102 | args = append(args, argumentDefinition)
103 | }
104 | locations := make([]ast.DirectiveLocation, 0, len(directiveValue.Locations))
105 | for _, locationValue := range directiveValue.Locations {
106 | locations = append(locations, ast.DirectiveLocation(locationValue))
107 | }
108 |
109 | return &ast.DirectiveDefinition{
110 | Description: pointerString(directiveValue.Description),
111 | Name: directiveValue.Name,
112 | Arguments: args,
113 | Locations: locations,
114 | Position: p.sharedPosition,
115 | }
116 | }
117 |
118 | func (p parser) parseObjectFields(typeVale *FullType) ast.FieldList {
119 | fieldList := make(ast.FieldList, 0, len(typeVale.Fields))
120 | for _, field := range typeVale.Fields {
121 | typ := p.getType(&field.Type)
122 | args := make(ast.ArgumentDefinitionList, 0, len(field.Args))
123 | for _, arg := range field.Args {
124 | argumentDefinition := p.buildInputValue(arg)
125 | args = append(args, argumentDefinition)
126 | }
127 |
128 | fieldDefinition := &ast.FieldDefinition{
129 | Description: pointerString(field.Description),
130 | Name: field.Name,
131 | Arguments: args,
132 | Type: typ,
133 | Position: p.sharedPosition,
134 | }
135 | fieldList = append(fieldList, fieldDefinition)
136 | }
137 |
138 | return fieldList
139 | }
140 |
141 | func (p parser) parseInputObjectFields(typeVale *FullType) ast.FieldList {
142 | fieldList := make(ast.FieldList, 0, len(typeVale.InputFields))
143 | for _, field := range typeVale.InputFields {
144 | typ := p.getType(&field.Type)
145 | fieldDefinition := &ast.FieldDefinition{
146 | Description: pointerString(field.Description),
147 | Name: field.Name,
148 | Type: typ,
149 | Position: p.sharedPosition,
150 | }
151 | fieldList = append(fieldList, fieldDefinition)
152 | }
153 |
154 | return fieldList
155 | }
156 |
157 | func (p parser) parseObjectTypeDefinition(typeVale *FullType) *ast.Definition {
158 | fieldList := p.parseObjectFields(typeVale)
159 | interfaces := make([]string, 0, len(typeVale.Interfaces))
160 | for _, intf := range typeVale.Interfaces {
161 | interfaces = append(interfaces, pointerString(intf.Name))
162 | }
163 |
164 | enums := make(ast.EnumValueList, 0, len(typeVale.EnumValues))
165 | for _, enum := range typeVale.EnumValues {
166 | enumValue := &ast.EnumValueDefinition{
167 | Description: pointerString(enum.Description),
168 | Name: enum.Name,
169 | Position: p.sharedPosition,
170 | }
171 | enums = append(enums, enumValue)
172 | }
173 |
174 | return &ast.Definition{
175 | Kind: ast.Object,
176 | Description: pointerString(typeVale.Description),
177 | Name: pointerString(typeVale.Name),
178 | Interfaces: interfaces,
179 | Fields: fieldList,
180 | EnumValues: enums,
181 | Position: p.sharedPosition,
182 | BuiltIn: true,
183 | }
184 | }
185 |
186 | func (p parser) parseInterfaceTypeDefinition(typeVale *FullType) *ast.Definition {
187 | fieldList := p.parseObjectFields(typeVale)
188 | interfaces := make([]string, 0, len(typeVale.Interfaces))
189 | for _, intf := range typeVale.Interfaces {
190 | interfaces = append(interfaces, pointerString(intf.Name))
191 | }
192 |
193 | return &ast.Definition{
194 | Kind: ast.Interface,
195 | Description: pointerString(typeVale.Description),
196 | Name: pointerString(typeVale.Name),
197 | Interfaces: interfaces,
198 | Fields: fieldList,
199 | Position: p.sharedPosition,
200 | BuiltIn: true,
201 | }
202 | }
203 |
204 | func (p parser) parseInputObjectTypeDefinition(typeVale *FullType) *ast.Definition {
205 | fieldList := p.parseInputObjectFields(typeVale)
206 | interfaces := make([]string, 0, len(typeVale.Interfaces))
207 | for _, intf := range typeVale.Interfaces {
208 | interfaces = append(interfaces, pointerString(intf.Name))
209 | }
210 |
211 | return &ast.Definition{
212 | Kind: ast.InputObject,
213 | Description: pointerString(typeVale.Description),
214 | Name: pointerString(typeVale.Name),
215 | Interfaces: interfaces,
216 | Fields: fieldList,
217 | Position: p.sharedPosition,
218 | BuiltIn: true,
219 | }
220 | }
221 |
222 | func (p parser) parseUnionTypeDefinition(typeVale *FullType) *ast.Definition {
223 | unions := make([]string, 0, len(typeVale.PossibleTypes))
224 | for _, unionValue := range typeVale.PossibleTypes {
225 | unions = append(unions, *unionValue.Name)
226 | }
227 |
228 | return &ast.Definition{
229 | Kind: ast.Union,
230 | Description: pointerString(typeVale.Description),
231 | Name: pointerString(typeVale.Name),
232 | Types: unions,
233 | Position: p.sharedPosition,
234 | BuiltIn: true,
235 | }
236 | }
237 |
238 | func (p parser) parseEnumTypeDefinition(typeVale *FullType) *ast.Definition {
239 | enums := make(ast.EnumValueList, 0, len(typeVale.EnumValues))
240 | for _, enum := range typeVale.EnumValues {
241 | enumValue := &ast.EnumValueDefinition{
242 | Description: pointerString(enum.Description),
243 | Name: enum.Name,
244 | Position: p.sharedPosition,
245 | }
246 | enums = append(enums, enumValue)
247 | }
248 |
249 | return &ast.Definition{
250 | Kind: ast.Enum,
251 | Description: pointerString(typeVale.Description),
252 | Name: pointerString(typeVale.Name),
253 | EnumValues: enums,
254 | Position: p.sharedPosition,
255 | BuiltIn: true,
256 | }
257 | }
258 |
259 | func (p parser) parseScalarTypeExtension(typeVale *FullType) *ast.Definition {
260 | return &ast.Definition{
261 | Kind: ast.Scalar,
262 | Description: pointerString(typeVale.Description),
263 | Name: pointerString(typeVale.Name),
264 | Position: p.sharedPosition,
265 | BuiltIn: true,
266 | }
267 | }
268 |
269 | func (p parser) parseTypeSystemDefinition(typeVale *FullType) *ast.Definition {
270 | switch typeVale.Kind {
271 | case TypeKindScalar:
272 | return p.parseScalarTypeExtension(typeVale)
273 | case TypeKindInterface:
274 | return p.parseInterfaceTypeDefinition(typeVale)
275 | case TypeKindEnum:
276 | return p.parseEnumTypeDefinition(typeVale)
277 | case TypeKindUnion:
278 | return p.parseUnionTypeDefinition(typeVale)
279 | case TypeKindObject:
280 | return p.parseObjectTypeDefinition(typeVale)
281 | case TypeKindInputObject:
282 | return p.parseInputObjectTypeDefinition(typeVale)
283 | case TypeKindList, TypeKindNonNull:
284 | panic(fmt.Sprintf("not match Kind: %s", typeVale.Kind))
285 | }
286 |
287 | panic(fmt.Sprintf("not match Kind: %s", typeVale.Kind))
288 | }
289 |
290 | func (p parser) buildInputValue(input *InputValue) *ast.ArgumentDefinition {
291 | typ := p.getType(&input.Type)
292 |
293 | var defaultValue *ast.Value
294 | if input.DefaultValue != nil {
295 | defaultValue = &ast.Value{
296 | Raw: pointerString(input.DefaultValue),
297 | Kind: ast.Variable,
298 | Position: p.sharedPosition,
299 | }
300 | }
301 |
302 | return &ast.ArgumentDefinition{
303 | Description: pointerString(input.Description),
304 | Name: input.Name,
305 | DefaultValue: defaultValue,
306 | Type: typ,
307 | Position: p.sharedPosition,
308 | }
309 | }
310 |
311 | func (p parser) getType(typeRef *TypeRef) *ast.Type {
312 | if typeRef.Kind == TypeKindList {
313 | itemRef := typeRef.OfType
314 | if itemRef == nil {
315 | panic("Decorated type deeper than introspection query.")
316 | }
317 |
318 | return ast.ListType(p.getType(itemRef), p.sharedPosition)
319 | }
320 |
321 | if typeRef.Kind == TypeKindNonNull {
322 | nullableRef := typeRef.OfType
323 | if nullableRef == nil {
324 | panic("Decorated type deeper than introspection query.")
325 | }
326 | nullableType := p.getType(nullableRef)
327 | nullableType.NonNull = true
328 |
329 | return nullableType
330 | }
331 |
332 | return ast.NamedType(pointerString(typeRef.Name), p.sharedPosition)
333 | }
334 |
335 | func pointerString(s *string) string {
336 | if s == nil {
337 | return ""
338 | }
339 |
340 | return *s
341 | }
342 |
--------------------------------------------------------------------------------
/introspection/parser_test.go:
--------------------------------------------------------------------------------
1 | package introspection
2 |
3 | import (
4 | "encoding/json"
5 | "io/ioutil"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func TestParseIntrospectionQuery_Parse(t *testing.T) {
12 | t.Parallel()
13 |
14 | tests := []struct {
15 | name string
16 | filename string
17 | expectedErr error
18 | }{
19 | {"no mutation in schema", "testdata/introspection_result_no_mutation.json", nil},
20 | }
21 |
22 | for _, testCase := range tests {
23 | test := testCase
24 | t.Run(test.name, func(t *testing.T) {
25 | t.Parallel()
26 | query := readQueryResult(t, test.filename)
27 |
28 | if test.expectedErr == nil {
29 | require.NotPanics(t, func() {
30 | ast := ParseIntrospectionQuery("test", query)
31 | require.NotNil(t, ast)
32 | })
33 | } else {
34 | require.PanicsWithValue(t, test.expectedErr, func() {
35 | ast := ParseIntrospectionQuery("test", query)
36 | require.Nil(t, ast)
37 | })
38 | }
39 | })
40 | }
41 | }
42 |
43 | func readQueryResult(t *testing.T, filename string) Query {
44 | t.Helper()
45 |
46 | data, err := ioutil.ReadFile(filename)
47 | require.NoError(t, err)
48 |
49 | query := Query{}
50 | err = json.Unmarshal(data, &query)
51 | require.NoError(t, err)
52 |
53 | return query
54 | }
55 |
--------------------------------------------------------------------------------
/introspection/query.go:
--------------------------------------------------------------------------------
1 | package introspection
2 |
3 | const Introspection = `query Query {
4 | __schema {
5 | queryType { name }
6 | mutationType { name }
7 | subscriptionType { name }
8 | types {
9 | ...FullType
10 | }
11 | directives {
12 | name
13 | description
14 | locations
15 | args {
16 | ...InputValue
17 | }
18 | }
19 | }
20 | }
21 |
22 | fragment FullType on __Type {
23 | kind
24 | name
25 | description
26 | fields(includeDeprecated: true) {
27 | name
28 | description
29 | args {
30 | ...InputValue
31 | }
32 | type {
33 | ...TypeRef
34 | }
35 | isDeprecated
36 | deprecationReason
37 | }
38 | inputFields {
39 | ...InputValue
40 | }
41 | interfaces {
42 | ...TypeRef
43 | }
44 | enumValues(includeDeprecated: true) {
45 | name
46 | description
47 | isDeprecated
48 | deprecationReason
49 | }
50 | possibleTypes {
51 | ...TypeRef
52 | }
53 | }
54 |
55 | fragment InputValue on __InputValue {
56 | name
57 | description
58 | type { ...TypeRef }
59 | defaultValue
60 | }
61 |
62 | fragment TypeRef on __Type {
63 | kind
64 | name
65 | ofType {
66 | kind
67 | name
68 | ofType {
69 | kind
70 | name
71 | ofType {
72 | kind
73 | name
74 | ofType {
75 | kind
76 | name
77 | ofType {
78 | kind
79 | name
80 | ofType {
81 | kind
82 | name
83 | ofType {
84 | kind
85 | name
86 | }
87 | }
88 | }
89 | }
90 | }
91 | }
92 | }
93 | }`
94 |
--------------------------------------------------------------------------------
/introspection/testdata/introspection_result_no_mutation.json:
--------------------------------------------------------------------------------
1 | {
2 | "__schema": {
3 | "queryType": {
4 | "name": "Query"
5 | },
6 | "mutationType": null,
7 | "subscriptionType": null,
8 | "types": [
9 | {
10 | "kind": "OBJECT",
11 | "name": "Query"
12 | }
13 | ]
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/introspection/type.go:
--------------------------------------------------------------------------------
1 | package introspection
2 |
3 | type TypeKind string
4 |
5 | const (
6 | TypeKindScalar TypeKind = "SCALAR"
7 | TypeKindObject TypeKind = "OBJECT"
8 | TypeKindInterface TypeKind = "INTERFACE"
9 | TypeKindUnion TypeKind = "UNION"
10 | TypeKindEnum TypeKind = "ENUM"
11 | TypeKindInputObject TypeKind = "INPUT_OBJECT"
12 | TypeKindList TypeKind = "LIST"
13 | TypeKindNonNull TypeKind = "NON_NULL"
14 | )
15 |
16 | type FullTypes []*FullType
17 |
18 | func (fs FullTypes) NameMap() map[string]*FullType {
19 | typeMap := make(map[string]*FullType)
20 | for _, typ := range fs {
21 | typeMap[*typ.Name] = typ
22 | }
23 |
24 | return typeMap
25 | }
26 |
27 | type FullType struct {
28 | Kind TypeKind
29 | Name *string
30 | Description *string
31 | Fields []*FieldValue
32 | InputFields []*InputValue
33 | Interfaces []*TypeRef
34 | EnumValues []*struct {
35 | Name string
36 | Description *string
37 | IsDeprecated bool
38 | DeprecationReason *string
39 | }
40 | PossibleTypes []*TypeRef
41 | }
42 |
43 | type FieldValue struct {
44 | Name string
45 | Description *string
46 | Args []*InputValue
47 | Type TypeRef
48 | IsDeprecated bool
49 | DeprecationReason *string
50 | }
51 |
52 | type InputValue struct {
53 | Name string
54 | Description *string
55 | Type TypeRef
56 | DefaultValue *string
57 | }
58 |
59 | type TypeRef struct {
60 | Kind TypeKind
61 | Name *string
62 | OfType *TypeRef
63 | }
64 |
65 | type Query struct {
66 | Schema struct {
67 | QueryType struct{ Name *string }
68 | MutationType *struct{ Name *string }
69 | SubscriptionType *struct{ Name *string }
70 | Types FullTypes
71 | Directives []*DirectiveType
72 | } `json:"__schema"`
73 | }
74 |
75 | type DirectiveType struct {
76 | Name string
77 | Description *string
78 | Locations []string
79 | Args []*InputValue
80 | }
81 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/99designs/gqlgen/api"
7 | "github.com/infiotinc/gqlgenc/clientgen"
8 | "github.com/infiotinc/gqlgenc/config"
9 | "github.com/infiotinc/gqlgenc/generator"
10 | "os"
11 | )
12 |
13 | func main() {
14 | ctx := context.Background()
15 | cfg, err := config.LoadConfigFromDefaultLocations()
16 | if err != nil {
17 | fmt.Fprintf(os.Stderr, "cfg: %+v", err.Error())
18 | os.Exit(2)
19 | }
20 |
21 | clientGen := api.AddPlugin(clientgen.New(cfg))
22 |
23 | if err := generator.Generate(ctx, cfg, clientGen); err != nil {
24 | fmt.Fprintf(os.Stderr, "generate: %+v", err.Error())
25 | os.Exit(4)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------