github.com/chelnak/go-gh@v0.0.2/internal/api/gql_client.go (about) 1 package api 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "io" 9 "net/http" 10 "strings" 11 12 "github.com/chelnak/go-gh/pkg/api" 13 graphql "github.com/cli/shurcooL-graphql" 14 ) 15 16 // Implements api.GQLClient interface. 17 type gqlClient struct { 18 client *graphql.Client 19 host string 20 httpClient *http.Client 21 } 22 23 func NewGQLClient(host string, opts *api.ClientOptions) api.GQLClient { 24 httpClient := newHTTPClient(opts) 25 26 if isEnterprise(host) { 27 host = fmt.Sprintf("https://%s/api/graphql", host) 28 } else { 29 host = "https://api.github.com/graphql" 30 } 31 32 return gqlClient{ 33 client: graphql.NewClient(host, &httpClient), 34 host: host, 35 httpClient: &httpClient, 36 } 37 } 38 39 // Do executes a single GraphQL query request and populates the response into the data argument. 40 func (c gqlClient) Do(query string, variables map[string]interface{}, response interface{}) error { 41 reqBody, err := json.Marshal(map[string]interface{}{"query": query, "variables": variables}) 42 if err != nil { 43 return err 44 } 45 46 req, err := http.NewRequest("POST", c.host, bytes.NewBuffer(reqBody)) 47 if err != nil { 48 return err 49 } 50 51 resp, err := c.httpClient.Do(req) 52 if err != nil { 53 return err 54 } 55 defer resp.Body.Close() 56 57 success := resp.StatusCode >= 200 && resp.StatusCode < 300 58 if !success { 59 return handleHTTPError(resp) 60 } 61 62 if resp.StatusCode == http.StatusNoContent { 63 return nil 64 } 65 66 body, err := io.ReadAll(resp.Body) 67 if err != nil { 68 return err 69 } 70 71 gr := &gqlResponse{Data: response} 72 err = json.Unmarshal(body, &gr) 73 if err != nil { 74 return err 75 } 76 77 if len(gr.Errors) > 0 { 78 return &gqlErrorResponse{Errors: gr.Errors} 79 } 80 81 return nil 82 } 83 84 // Mutate executes a single GraphQL mutation request, 85 // with a mutation derived from m, populating the response into it. 86 // "m" should be a pointer to struct that corresponds to the GitHub GraphQL schema. 87 func (c gqlClient) Mutate(name string, m interface{}, variables map[string]interface{}) error { 88 return c.client.MutateNamed(context.Background(), name, m, variables) 89 } 90 91 // Query executes a single GraphQL query request, 92 // with a query derived from q, populating the response into it. 93 // "q" should be a pointer to struct that corresponds to the GitHub GraphQL schema. 94 func (c gqlClient) Query(name string, q interface{}, variables map[string]interface{}) error { 95 return c.client.QueryNamed(context.Background(), name, q, variables) 96 } 97 98 type gqlResponse struct { 99 Data interface{} 100 Errors []gqlError 101 } 102 103 type gqlError struct { 104 Type string 105 Message string 106 } 107 108 type gqlErrorResponse struct { 109 Errors []gqlError 110 } 111 112 func (gr gqlErrorResponse) Error() string { 113 errorMessages := make([]string, 0, len(gr.Errors)) 114 for _, e := range gr.Errors { 115 errorMessages = append(errorMessages, e.Message) 116 } 117 return fmt.Sprintf("GQL error: %s", strings.Join(errorMessages, "\n")) 118 }