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  }