...

Source file src/github.com/cli/go-gh/v2/pkg/api/rest_client.go

Documentation: github.com/cli/go-gh/v2/pkg/api

     1  package api
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"strings"
    10  
    11  	"github.com/cli/go-gh/v2/pkg/auth"
    12  )
    13  
    14  // RESTClient wraps methods for the different types of
    15  // API requests that are supported by the server.
    16  type RESTClient struct {
    17  	client *http.Client
    18  	host   string
    19  }
    20  
    21  func DefaultRESTClient() (*RESTClient, error) {
    22  	return NewRESTClient(ClientOptions{})
    23  }
    24  
    25  // RESTClient builds a client to send requests to GitHub REST API endpoints.
    26  // As part of the configuration a hostname, auth token, default set of headers,
    27  // and unix domain socket are resolved from the gh environment configuration.
    28  // These behaviors can be overridden using the opts argument.
    29  func NewRESTClient(opts ClientOptions) (*RESTClient, error) {
    30  	if optionsNeedResolution(opts) {
    31  		var err error
    32  		opts, err = resolveOptions(opts)
    33  		if err != nil {
    34  			return nil, err
    35  		}
    36  	}
    37  
    38  	client, err := NewHTTPClient(opts)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  
    43  	return &RESTClient{
    44  		client: client,
    45  		host:   opts.Host,
    46  	}, nil
    47  }
    48  
    49  // RequestWithContext issues a request with type specified by method to the
    50  // specified path with the specified body.
    51  // The response is returned rather than being populated
    52  // into a response argument.
    53  func (c *RESTClient) RequestWithContext(ctx context.Context, method string, path string, body io.Reader) (*http.Response, error) {
    54  	url := restURL(c.host, path)
    55  	req, err := http.NewRequestWithContext(ctx, method, url, body)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	resp, err := c.client.Do(req)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	success := resp.StatusCode >= 200 && resp.StatusCode < 300
    66  	if !success {
    67  		defer resp.Body.Close()
    68  		return nil, HandleHTTPError(resp)
    69  	}
    70  
    71  	return resp, err
    72  }
    73  
    74  // Request wraps RequestWithContext with context.Background.
    75  func (c *RESTClient) Request(method string, path string, body io.Reader) (*http.Response, error) {
    76  	return c.RequestWithContext(context.Background(), method, path, body)
    77  }
    78  
    79  // DoWithContext issues a request with type specified by method to the
    80  // specified path with the specified body.
    81  // The response is populated into the response argument.
    82  func (c *RESTClient) DoWithContext(ctx context.Context, method string, path string, body io.Reader, response interface{}) error {
    83  	url := restURL(c.host, path)
    84  	req, err := http.NewRequestWithContext(ctx, method, url, body)
    85  	if err != nil {
    86  		return err
    87  	}
    88  
    89  	resp, err := c.client.Do(req)
    90  	if err != nil {
    91  		return err
    92  	}
    93  
    94  	success := resp.StatusCode >= 200 && resp.StatusCode < 300
    95  	if !success {
    96  		defer resp.Body.Close()
    97  		return HandleHTTPError(resp)
    98  	}
    99  
   100  	if resp.StatusCode == http.StatusNoContent {
   101  		return nil
   102  	}
   103  	defer resp.Body.Close()
   104  
   105  	b, err := io.ReadAll(resp.Body)
   106  	if err != nil {
   107  		return err
   108  	}
   109  
   110  	err = json.Unmarshal(b, &response)
   111  	if err != nil {
   112  		return err
   113  	}
   114  
   115  	return nil
   116  }
   117  
   118  // Do wraps DoWithContext with context.Background.
   119  func (c *RESTClient) Do(method string, path string, body io.Reader, response interface{}) error {
   120  	return c.DoWithContext(context.Background(), method, path, body, response)
   121  }
   122  
   123  // Delete issues a DELETE request to the specified path.
   124  // The response is populated into the response argument.
   125  func (c *RESTClient) Delete(path string, resp interface{}) error {
   126  	return c.Do(http.MethodDelete, path, nil, resp)
   127  }
   128  
   129  // Get issues a GET request to the specified path.
   130  // The response is populated into the response argument.
   131  func (c *RESTClient) Get(path string, resp interface{}) error {
   132  	return c.Do(http.MethodGet, path, nil, resp)
   133  }
   134  
   135  // Patch issues a PATCH request to the specified path with the specified body.
   136  // The response is populated into the response argument.
   137  func (c *RESTClient) Patch(path string, body io.Reader, resp interface{}) error {
   138  	return c.Do(http.MethodPatch, path, body, resp)
   139  }
   140  
   141  // Post issues a POST request to the specified path with the specified body.
   142  // The response is populated into the response argument.
   143  func (c *RESTClient) Post(path string, body io.Reader, resp interface{}) error {
   144  	return c.Do(http.MethodPost, path, body, resp)
   145  }
   146  
   147  // Put issues a PUT request to the specified path with the specified body.
   148  // The response is populated into the response argument.
   149  func (c *RESTClient) Put(path string, body io.Reader, resp interface{}) error {
   150  	return c.Do(http.MethodPut, path, body, resp)
   151  }
   152  
   153  func restURL(hostname string, pathOrURL string) string {
   154  	if strings.HasPrefix(pathOrURL, "https://") || strings.HasPrefix(pathOrURL, "http://") {
   155  		return pathOrURL
   156  	}
   157  	return restPrefix(hostname) + pathOrURL
   158  }
   159  
   160  func restPrefix(hostname string) string {
   161  	if isGarage(hostname) {
   162  		return fmt.Sprintf("https://%s/api/v3/", hostname)
   163  	}
   164  	hostname = auth.NormalizeHostname(hostname)
   165  	if auth.IsEnterprise(hostname) {
   166  		return fmt.Sprintf("https://%s/api/v3/", hostname)
   167  	}
   168  	if strings.EqualFold(hostname, localhost) {
   169  		return fmt.Sprintf("http://api.%s/", hostname)
   170  	}
   171  	return fmt.Sprintf("https://api.%s/", hostname)
   172  }
   173  

View as plain text