1 package api
2
3 import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "io"
10 "net/http"
11 "strings"
12
13 "github.com/cli/go-gh/v2/pkg/auth"
14 graphql "github.com/cli/shurcooL-graphql"
15 )
16
17
18
19 type GraphQLClient struct {
20 client *graphql.Client
21 host string
22 httpClient *http.Client
23 }
24
25 func DefaultGraphQLClient() (*GraphQLClient, error) {
26 return NewGraphQLClient(ClientOptions{})
27 }
28
29
30
31
32
33 func NewGraphQLClient(opts ClientOptions) (*GraphQLClient, error) {
34 if optionsNeedResolution(opts) {
35 var err error
36 opts, err = resolveOptions(opts)
37 if err != nil {
38 return nil, err
39 }
40 }
41
42 httpClient, err := NewHTTPClient(opts)
43 if err != nil {
44 return nil, err
45 }
46
47 endpoint := graphQLEndpoint(opts.Host)
48
49 return &GraphQLClient{
50 client: graphql.NewClient(endpoint, httpClient),
51 host: endpoint,
52 httpClient: httpClient,
53 }, nil
54 }
55
56
57
58 func (c *GraphQLClient) DoWithContext(ctx context.Context, query string, variables map[string]interface{}, response interface{}) error {
59 reqBody, err := json.Marshal(map[string]interface{}{"query": query, "variables": variables})
60 if err != nil {
61 return err
62 }
63
64 req, err := http.NewRequestWithContext(ctx, "POST", c.host, bytes.NewBuffer(reqBody))
65 if err != nil {
66 return err
67 }
68
69 resp, err := c.httpClient.Do(req)
70 if err != nil {
71 return err
72 }
73 defer resp.Body.Close()
74
75 success := resp.StatusCode >= 200 && resp.StatusCode < 300
76 if !success {
77 return HandleHTTPError(resp)
78 }
79
80 if resp.StatusCode == http.StatusNoContent {
81 return nil
82 }
83
84 body, err := io.ReadAll(resp.Body)
85 if err != nil {
86 return err
87 }
88
89 gr := graphQLResponse{Data: response}
90 err = json.Unmarshal(body, &gr)
91 if err != nil {
92 return err
93 }
94
95 if len(gr.Errors) > 0 {
96 return &GraphQLError{Errors: gr.Errors}
97 }
98
99 return nil
100 }
101
102
103 func (c *GraphQLClient) Do(query string, variables map[string]interface{}, response interface{}) error {
104 return c.DoWithContext(context.Background(), query, variables, response)
105 }
106
107
108
109
110
111
112
113 func (c *GraphQLClient) MutateWithContext(ctx context.Context, name string, m interface{}, variables map[string]interface{}) error {
114 err := c.client.MutateNamed(ctx, name, m, variables)
115 var graphQLErrs graphql.Errors
116 if err != nil && errors.As(err, &graphQLErrs) {
117 items := make([]GraphQLErrorItem, len(graphQLErrs))
118 for i, e := range graphQLErrs {
119 items[i] = GraphQLErrorItem{
120 Message: e.Message,
121 Locations: e.Locations,
122 Path: e.Path,
123 Extensions: e.Extensions,
124 Type: e.Type,
125 }
126 }
127 err = &GraphQLError{items}
128 }
129 return err
130 }
131
132
133 func (c *GraphQLClient) Mutate(name string, m interface{}, variables map[string]interface{}) error {
134 return c.MutateWithContext(context.Background(), name, m, variables)
135 }
136
137
138
139
140
141
142 func (c *GraphQLClient) QueryWithContext(ctx context.Context, name string, q interface{}, variables map[string]interface{}) error {
143 err := c.client.QueryNamed(ctx, name, q, variables)
144 var graphQLErrs graphql.Errors
145 if err != nil && errors.As(err, &graphQLErrs) {
146 items := make([]GraphQLErrorItem, len(graphQLErrs))
147 for i, e := range graphQLErrs {
148 items[i] = GraphQLErrorItem{
149 Message: e.Message,
150 Locations: e.Locations,
151 Path: e.Path,
152 Extensions: e.Extensions,
153 Type: e.Type,
154 }
155 }
156 err = &GraphQLError{items}
157 }
158 return err
159 }
160
161
162 func (c *GraphQLClient) Query(name string, q interface{}, variables map[string]interface{}) error {
163 return c.QueryWithContext(context.Background(), name, q, variables)
164 }
165
166 type graphQLResponse struct {
167 Data interface{}
168 Errors []GraphQLErrorItem
169 }
170
171 func graphQLEndpoint(host string) string {
172 if isGarage(host) {
173 return fmt.Sprintf("https://%s/api/graphql", host)
174 }
175 host = auth.NormalizeHostname(host)
176 if auth.IsEnterprise(host) {
177 return fmt.Sprintf("https://%s/api/graphql", host)
178 }
179 if strings.EqualFold(host, localhost) {
180 return fmt.Sprintf("http://api.%s/graphql", host)
181 }
182 return fmt.Sprintf("https://api.%s/graphql", host)
183 }
184
View as plain text