Skip to content

Commit c5d8c36

Browse files
authored
Add fields to ApiError struct (#32)
* Add fields to ApiError struct * Add documentation comments
1 parent 95eec81 commit c5d8c36

4 files changed

Lines changed: 73 additions & 34 deletions

File tree

apierror.go

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,78 @@ package replicate
33
import (
44
"encoding/json"
55
"fmt"
6+
"net/http"
7+
"strings"
68
)
79

810
// APIError represents an error returned by the Replicate API
911
type APIError struct {
10-
Detail string `json:"detail"`
12+
// Type is a URI that identifies the error type.
13+
Type string `json:"type,omitempty"`
14+
15+
// Title is a short human-readable summary of the error.
16+
Title string `json:"title,omitempty"`
17+
18+
// Status is the HTTP status code.
19+
Status int `json:"status,omitempty"`
20+
21+
// Detail is a human-readable explanation of the error.
22+
Detail string `json:"detail,omitempty"`
23+
24+
// Instance is a URI that identifies the specific occurrence of the error.
25+
Instance string `json:"instance,omitempty"`
1126
}
1227

13-
func unmarshalAPIError(data []byte) *APIError {
14-
apiError := &APIError{}
15-
err := json.Unmarshal(data, apiError)
28+
func unmarshalAPIError(resp *http.Response, data []byte) *APIError {
29+
apiError := APIError{}
30+
err := json.Unmarshal(data, &apiError)
1631
if err != nil {
1732
apiError.Detail = fmt.Sprintf("Unknown error: %s", err)
1833
}
1934

20-
return apiError
35+
if apiError.Status == 0 && resp != nil {
36+
apiError.Status = resp.StatusCode
37+
}
38+
39+
return &apiError
2140
}
2241

23-
// Error implements the error interface
2442
func (e APIError) Error() string {
25-
return fmt.Sprintf("Replicate API error: %s", e.Detail)
43+
components := []string{}
44+
if e.Type != "" {
45+
components = append(components, e.Type)
46+
}
47+
48+
if e.Title != "" {
49+
components = append(components, e.Title)
50+
}
51+
52+
if e.Detail != "" {
53+
components = append(components, e.Detail)
54+
}
55+
56+
output := strings.Join(components, ": ")
57+
if output == "" {
58+
output = "Unknown error"
59+
}
60+
61+
if e.Instance != "" {
62+
output = fmt.Sprintf("%s (%s)", output, e.Instance)
63+
}
64+
65+
return output
66+
}
67+
68+
func (e *APIError) WriteHTTPResponse(w http.ResponseWriter) {
69+
status := http.StatusBadGateway
70+
if e.Status != 0 {
71+
status = e.Status
72+
}
73+
74+
w.WriteHeader(status)
75+
err := json.NewEncoder(w).Encode(e)
76+
if err != nil {
77+
err = fmt.Errorf("failed to write error response: %w", err)
78+
http.Error(w, err.Error(), http.StatusInternalServerError)
79+
}
2680
}

client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ func (r *Client) request(ctx context.Context, method, path string, body interfac
186186
}
187187

188188
if response.StatusCode < 200 || response.StatusCode >= 400 {
189-
apiError = unmarshalAPIError(responseBytes)
189+
apiError = unmarshalAPIError(response, responseBytes)
190190
if !r.shouldRetry(response, method) {
191191
return apiError
192192
}

client_test.go

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"time"
1212

1313
"github.com/replicate/replicate-go"
14+
1415
"github.com/stretchr/testify/assert"
1516
"github.com/stretchr/testify/require"
1617
)
@@ -1148,19 +1149,11 @@ func TestAutomaticallyRetryGetRequests(t *testing.T) {
11481149
w.Header().Set("Retry-After", "0")
11491150
w.WriteHeader(status)
11501151

1151-
if status == http.StatusInternalServerError {
1152-
err := &replicate.APIError{
1153-
Detail: "Internal server error",
1154-
}
1155-
body, _ := json.Marshal(err)
1156-
w.Write(body)
1157-
} else if status == http.StatusTooManyRequests {
1158-
err := &replicate.APIError{
1159-
Detail: "Too many requests",
1160-
}
1161-
body, _ := json.Marshal(err)
1162-
w.Write(body)
1152+
err := replicate.APIError{
1153+
Detail: http.StatusText(status),
11631154
}
1155+
body, _ := json.Marshal(err)
1156+
w.Write(body)
11641157
}
11651158
}))
11661159
defer mockServer.Close()
@@ -1191,19 +1184,11 @@ func TestAutomaticallyRetryPostRequests(t *testing.T) {
11911184
w.Header().Set("Retry-After", "0")
11921185
w.WriteHeader(status)
11931186

1194-
if status == http.StatusInternalServerError {
1195-
err := &replicate.APIError{
1196-
Detail: "Internal server error",
1197-
}
1198-
body, _ := json.Marshal(err)
1199-
w.Write(body)
1200-
} else if status == http.StatusTooManyRequests {
1201-
err := &replicate.APIError{
1202-
Detail: "Too many requests",
1203-
}
1204-
body, _ := json.Marshal(err)
1205-
w.Write(body)
1187+
err := replicate.APIError{
1188+
Detail: http.StatusText(status),
12061189
}
1190+
body, _ := json.Marshal(err)
1191+
w.Write(body)
12071192
}))
12081193
defer mockServer.Close()
12091194

@@ -1224,7 +1209,7 @@ func TestAutomaticallyRetryPostRequests(t *testing.T) {
12241209
version := "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa"
12251210
_, err = client.CreatePrediction(ctx, version, input, &webhook, true)
12261211

1227-
assert.ErrorContains(t, err, "Internal server error")
1212+
assert.ErrorContains(t, err, http.StatusText(http.StatusInternalServerError))
12281213
}
12291214

12301215
func TestStream(t *testing.T) {

stream.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ func (r *Client) streamPrediction(ctx context.Context, prediction *Prediction, l
179179

180180
switch event.Type {
181181
case "error":
182-
errChan <- unmarshalAPIError([]byte(event.Data))
182+
errChan <- unmarshalAPIError(nil, []byte(event.Data))
183183
case "done":
184184
close(done)
185185
return

0 commit comments

Comments
 (0)