github.com/abdfnx/gh-api@v0.0.0-20210414084727-f5432eec23b8/pkg/cmd/api/http.go (about)

     1  package api
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"net/url"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/abdfnx/gh-api/internal/ghinstance"
    14  )
    15  
    16  func httpRequest(client *http.Client, hostname string, method string, p string, params interface{}, headers []string) (*http.Response, error) {
    17  	isGraphQL := p == "graphql"
    18  	var requestURL string
    19  	if strings.Contains(p, "://") {
    20  		requestURL = p
    21  	} else if isGraphQL {
    22  		requestURL = ghinstance.GraphQLEndpoint(hostname)
    23  	} else {
    24  		requestURL = ghinstance.RESTPrefix(hostname) + strings.TrimPrefix(p, "/")
    25  	}
    26  
    27  	var body io.Reader
    28  	var bodyIsJSON bool
    29  
    30  	switch pp := params.(type) {
    31  	case map[string]interface{}:
    32  		if strings.EqualFold(method, "GET") {
    33  			requestURL = addQuery(requestURL, pp)
    34  		} else {
    35  			for key, value := range pp {
    36  				switch vv := value.(type) {
    37  				case []byte:
    38  					pp[key] = string(vv)
    39  				}
    40  			}
    41  			if isGraphQL {
    42  				pp = groupGraphQLVariables(pp)
    43  			}
    44  			b, err := json.Marshal(pp)
    45  			if err != nil {
    46  				return nil, fmt.Errorf("error serializing parameters: %w", err)
    47  			}
    48  			body = bytes.NewBuffer(b)
    49  			bodyIsJSON = true
    50  		}
    51  	case io.Reader:
    52  		body = pp
    53  	case nil:
    54  		body = nil
    55  	default:
    56  		return nil, fmt.Errorf("unrecognized parameters type: %v", params)
    57  	}
    58  
    59  	req, err := http.NewRequest(method, requestURL, body)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  
    64  	for _, h := range headers {
    65  		idx := strings.IndexRune(h, ':')
    66  		if idx == -1 {
    67  			return nil, fmt.Errorf("header %q requires a value separated by ':'", h)
    68  		}
    69  		name, value := h[0:idx], strings.TrimSpace(h[idx+1:])
    70  		if strings.EqualFold(name, "Content-Length") {
    71  			length, err := strconv.ParseInt(value, 10, 0)
    72  			if err != nil {
    73  				return nil, err
    74  			}
    75  			req.ContentLength = length
    76  		} else {
    77  			req.Header.Add(name, value)
    78  		}
    79  	}
    80  	if bodyIsJSON && req.Header.Get("Content-Type") == "" {
    81  		req.Header.Set("Content-Type", "application/json; charset=utf-8")
    82  	}
    83  
    84  	return client.Do(req)
    85  }
    86  
    87  func groupGraphQLVariables(params map[string]interface{}) map[string]interface{} {
    88  	topLevel := make(map[string]interface{})
    89  	variables := make(map[string]interface{})
    90  
    91  	for key, val := range params {
    92  		switch key {
    93  		case "query", "operationName":
    94  			topLevel[key] = val
    95  		default:
    96  			variables[key] = val
    97  		}
    98  	}
    99  
   100  	if len(variables) > 0 {
   101  		topLevel["variables"] = variables
   102  	}
   103  	return topLevel
   104  }
   105  
   106  func addQuery(path string, params map[string]interface{}) string {
   107  	if len(params) == 0 {
   108  		return path
   109  	}
   110  
   111  	query := url.Values{}
   112  	for key, value := range params {
   113  		switch v := value.(type) {
   114  		case string:
   115  			query.Add(key, v)
   116  		case []byte:
   117  			query.Add(key, string(v))
   118  		case nil:
   119  			query.Add(key, "")
   120  		case int:
   121  			query.Add(key, fmt.Sprintf("%d", v))
   122  		case bool:
   123  			query.Add(key, fmt.Sprintf("%v", v))
   124  		default:
   125  			panic(fmt.Sprintf("unknown type %v", v))
   126  		}
   127  	}
   128  
   129  	sep := "?"
   130  	if strings.ContainsRune(path, '?') {
   131  		sep = "&"
   132  	}
   133  	return path + sep + query.Encode()
   134  }