(ctx context.Context, method, path string, body, result interface{})
| 62 | } |
| 63 | |
| 64 | func (c *Client) doRequest(ctx context.Context, method, path string, body, result interface{}) error { |
| 65 | sentry.AddBreadcrumb(&sentry.Breadcrumb{ |
| 66 | Category: "api", |
| 67 | Message: method + " " + path, |
| 68 | Level: sentry.LevelInfo, |
| 69 | }) |
| 70 | |
| 71 | var bodyReader io.Reader |
| 72 | if body != nil { |
| 73 | data, err := json.Marshal(body) |
| 74 | if err != nil { |
| 75 | return fmt.Errorf("failed to marshal request: %w", err) |
| 76 | } |
| 77 | bodyReader = bytes.NewReader(data) |
| 78 | } |
| 79 | |
| 80 | req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, bodyReader) |
| 81 | if err != nil { |
| 82 | return fmt.Errorf("failed to create request: %w", err) |
| 83 | } |
| 84 | |
| 85 | req.Header.Set("Authorization", "Bearer "+c.token) |
| 86 | req.Header.Set("Content-Type", "application/json") |
| 87 | req.Header.Set("Thunder-Client", "GO-CLI") |
| 88 | req.Header.Set("Version", version.BuildVersion) |
| 89 | if platform := cliPlatform(); platform != "" { |
| 90 | req.Header.Set("Platform", platform) |
| 91 | } |
| 92 | |
| 93 | resp, err := c.httpClient.Do(req) |
| 94 | if err != nil { |
| 95 | return fmt.Errorf("%w: failed to make request: %w", ErrTransport, err) |
| 96 | } |
| 97 | defer resp.Body.Close() |
| 98 | |
| 99 | if resp.StatusCode >= 500 { |
| 100 | sentry.WithScope(func(scope *sentry.Scope) { |
| 101 | scope.SetTag("api_method", method) |
| 102 | scope.SetTag("api_path", path) |
| 103 | scope.SetTag("status_code", fmt.Sprintf("%d", resp.StatusCode)) |
| 104 | scope.SetLevel(sentry.LevelError) |
| 105 | sentry.CaptureMessage(fmt.Sprintf("API server error: %s %s returned %d", method, path, resp.StatusCode)) |
| 106 | }) |
| 107 | } |
| 108 | |
| 109 | respBody, err := io.ReadAll(resp.Body) |
| 110 | if err != nil { |
| 111 | return fmt.Errorf("failed to read response: %w", err) |
| 112 | } |
| 113 | |
| 114 | if resp.StatusCode >= 400 { |
| 115 | if resp.StatusCode == 401 { |
| 116 | return &APIError{StatusCode: 401, Message: "authentication failed: invalid token"} |
| 117 | } |
| 118 | retryAfter, hasRetryAfter := parseRetryAfter(resp.Header.Get("Retry-After"), time.Now()) |
| 119 | apiErr := &APIError{ |
| 120 | StatusCode: resp.StatusCode, |
| 121 | RetryAfter: retryAfter, |
no test coverage detected