Skip to content

Commit 3e40774

Browse files
authored
chore(loo-4737): typed errors (#4)
1 parent 4f725ca commit 3e40774

3 files changed

Lines changed: 51 additions & 30 deletions

File tree

internal/api/api_key.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,8 @@ func (c *Client) GetAPIKey() (*APIKeyResponse, error) {
2222
}
2323
defer resp.Body.Close()
2424

25-
if resp.StatusCode == http.StatusUnauthorized {
26-
return nil, fmt.Errorf("invalid API key")
27-
}
28-
2925
if resp.StatusCode != http.StatusOK {
30-
return nil, fmt.Errorf("unexpected status: %d", resp.StatusCode)
26+
return nil, errorFromResponse(resp)
3127
}
3228

3329
var result APIKeyResponse

internal/api/api_key_test.go

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,45 @@
11
package api
22

33
import (
4+
"errors"
45
"net/http"
56
"net/http/httptest"
7+
"strings"
68
"testing"
79
)
810

911
func TestGetAPIKey(t *testing.T) {
1012
tests := []struct {
11-
name string
12-
statusCode int
13-
body string
14-
wantErr string
15-
wantTeam string
13+
name string
14+
statusCode int
15+
body string
16+
wantAPIErr *APIError
17+
wantErrMsg string
18+
wantTeam string
1619
}{
1720
{
1821
name: "success",
1922
statusCode: http.StatusOK,
20-
body: `{"success":true,"teamName":"Acme"}`,
23+
body: `{"teamName":"Acme"}`,
2124
wantTeam: "Acme",
2225
},
2326
{
2427
name: "unauthorized",
2528
statusCode: http.StatusUnauthorized,
2629
body: `{"success":false,"error":"Invalid API key"}`,
27-
wantErr: "invalid API key",
30+
wantAPIErr: &APIError{StatusCode: http.StatusUnauthorized, Message: "Invalid API key"},
2831
},
2932
{
3033
name: "unexpected status",
3134
statusCode: http.StatusInternalServerError,
3235
body: ``,
33-
wantErr: "unexpected status: 500",
36+
wantAPIErr: &APIError{StatusCode: http.StatusInternalServerError},
3437
},
3538
{
3639
name: "invalid json",
3740
statusCode: http.StatusOK,
3841
body: `not json`,
39-
wantErr: "failed to decode response",
42+
wantErrMsg: "failed to decode response",
4043
},
4144
}
4245

@@ -51,12 +54,26 @@ func TestGetAPIKey(t *testing.T) {
5154
client := NewClient(server.URL, "test-key")
5255
result, err := client.GetAPIKey()
5356

54-
if tt.wantErr != "" {
57+
if tt.wantAPIErr != nil {
58+
var apiErr *APIError
59+
if !errors.As(err, &apiErr) {
60+
t.Fatalf("expected *APIError, got %T: %v", err, err)
61+
}
62+
if apiErr.StatusCode != tt.wantAPIErr.StatusCode {
63+
t.Errorf("StatusCode = %d, want %d", apiErr.StatusCode, tt.wantAPIErr.StatusCode)
64+
}
65+
if tt.wantAPIErr.Message != "" && apiErr.Message != tt.wantAPIErr.Message {
66+
t.Errorf("Message = %q, want %q", apiErr.Message, tt.wantAPIErr.Message)
67+
}
68+
return
69+
}
70+
71+
if tt.wantErrMsg != "" {
5572
if err == nil {
56-
t.Fatalf("expected error containing %q, got nil", tt.wantErr)
73+
t.Fatalf("expected error containing %q, got nil", tt.wantErrMsg)
5774
}
58-
if !contains(err.Error(), tt.wantErr) {
59-
t.Errorf("error = %q, want it to contain %q", err.Error(), tt.wantErr)
75+
if !strings.Contains(err.Error(), tt.wantErrMsg) {
76+
t.Errorf("error = %q, want it to contain %q", err.Error(), tt.wantErrMsg)
6077
}
6178
return
6279
}
@@ -70,15 +87,3 @@ func TestGetAPIKey(t *testing.T) {
7087
})
7188
}
7289
}
73-
74-
func contains(s, substr string) bool {
75-
return len(s) >= len(substr) && (s == substr || len(substr) == 0 ||
76-
func() bool {
77-
for i := 0; i <= len(s)-len(substr); i++ {
78-
if s[i:i+len(substr)] == substr {
79-
return true
80-
}
81-
}
82-
return false
83-
}())
84-
}

internal/api/client.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
package api
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"net/http"
67
"time"
78
)
89

10+
type APIError struct {
11+
StatusCode int
12+
Message string
13+
}
14+
15+
func (e *APIError) Error() string {
16+
return e.Message
17+
}
18+
919
type Client struct {
1020
baseURL string
1121
apiKey string
@@ -20,6 +30,16 @@ func NewClient(baseURL, apiKey string) *Client {
2030
}
2131
}
2232

33+
func errorFromResponse(resp *http.Response) *APIError {
34+
var body struct {
35+
Error string `json:"error"`
36+
}
37+
if err := json.NewDecoder(resp.Body).Decode(&body); err == nil && body.Error != "" {
38+
return &APIError{StatusCode: resp.StatusCode, Message: body.Error}
39+
}
40+
return &APIError{StatusCode: resp.StatusCode, Message: fmt.Sprintf("unexpected status: %d", resp.StatusCode)}
41+
}
42+
2343
func (c *Client) newRequest(method, path string) (*http.Request, error) {
2444
url := fmt.Sprintf("%s%s", c.baseURL, path)
2545
req, err := http.NewRequest(method, url, nil)

0 commit comments

Comments
 (0)