package rudder import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "net/url" "strings" ) type transport struct { baseURL string apiKey string projectID string httpClient *http.Client } func (t *transport) do(ctx context.Context, method, path string, query url.Values, body any, out any) error { endpoint := strings.TrimSuffix(t.baseURL, "/") + "/game/v1/projects/" + t.projectID + path if len(query) > 0 { endpoint += "?" + query.Encode() } var reader io.Reader if body != nil { data, err := json.Marshal(body) if err != nil { return fmt.Errorf("rudder: encode request body: %w", err) } reader = bytes.NewReader(data) } req, err := http.NewRequestWithContext(ctx, method, endpoint, reader) if err != nil { return fmt.Errorf("rudder: build request: %w", err) } req.Header.Set("X-API-Key", t.apiKey) if body != nil { req.Header.Set("Content-Type", "application/json") } resp, err := t.httpClient.Do(req) if err != nil { return fmt.Errorf("rudder: %s %s: %w", method, path, err) } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("rudder: read response body: %w", err) } if resp.StatusCode >= 400 { apiErr := &APIError{Status: resp.StatusCode} var payload struct { Error string `json:"error"` Code string `json:"code"` RequestID string `json:"requestId"` } if json.Unmarshal(data, &payload) == nil { apiErr.Code = payload.Code apiErr.Message = payload.Error apiErr.RequestID = payload.RequestID } if apiErr.Message == "" { apiErr.Message = http.StatusText(resp.StatusCode) } return apiErr } if out == nil || len(data) == 0 { return nil } if err := json.Unmarshal(data, out); err != nil { return fmt.Errorf("rudder: decode response: %w", err) } return nil }