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 }