Skip to content

Commit ee3df71

Browse files
authored
Improve error reporting (#68)
* Provide APIError and use Go's error wrapping * Add generic request error * Fix code formatting
1 parent 8fd81bc commit ee3df71

File tree

3 files changed

+87
-8
lines changed

3 files changed

+87
-8
lines changed

api.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,14 @@ func (c *Client) sendRequest(req *http.Request, v interface{}) error {
6666
var errRes ErrorResponse
6767
err = json.NewDecoder(res.Body).Decode(&errRes)
6868
if err != nil || errRes.Error == nil {
69-
return fmt.Errorf("error, status code: %d", res.StatusCode)
69+
reqErr := RequestError{
70+
StatusCode: res.StatusCode,
71+
Err: err,
72+
}
73+
return fmt.Errorf("error, %w", &reqErr)
7074
}
71-
return fmt.Errorf("error, status code: %d, message: %s", res.StatusCode, errRes.Error.Message)
75+
errRes.Error.StatusCode = res.StatusCode
76+
return fmt.Errorf("error, status code: %d, message: %w", res.StatusCode, errRes.Error)
7277
}
7378

7479
if v != nil {

api_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,53 @@ func TestAPI(t *testing.T) {
8181
}
8282
}
8383

84+
func TestAPIError(t *testing.T) {
85+
apiToken := os.Getenv("OPENAI_TOKEN")
86+
if apiToken == "" {
87+
t.Skip("Skipping testing against production OpenAI API. Set OPENAI_TOKEN environment variable to enable it.")
88+
}
89+
90+
var err error
91+
c := NewClient(apiToken + "_invalid")
92+
ctx := context.Background()
93+
_, err = c.ListEngines(ctx)
94+
if err == nil {
95+
t.Fatal("ListEngines did not fail")
96+
}
97+
98+
var apiErr *APIError
99+
if !errors.As(err, &apiErr) {
100+
t.Fatalf("Error is not an APIError: %+v", err)
101+
}
102+
103+
if apiErr.StatusCode != 401 {
104+
t.Fatalf("Unexpected API error status code: %d", apiErr.StatusCode)
105+
}
106+
if *apiErr.Code != "invalid_api_key" {
107+
t.Fatalf("Unexpected API error code: %s", *apiErr.Code)
108+
}
109+
}
110+
111+
func TestRequestError(t *testing.T) {
112+
var err error
113+
c := NewClient("dummy")
114+
c.BaseURL = "https://httpbin.org/status/418?"
115+
ctx := context.Background()
116+
_, err = c.ListEngines(ctx)
117+
if err == nil {
118+
t.Fatal("ListEngines request did not fail")
119+
}
120+
121+
var reqErr *RequestError
122+
if !errors.As(err, &reqErr) {
123+
t.Fatalf("Error is not a RequestError: %+v", err)
124+
}
125+
126+
if reqErr.StatusCode != 418 {
127+
t.Fatalf("Unexpected request error status code: %d", reqErr.StatusCode)
128+
}
129+
}
130+
84131
// numTokens Returns the number of GPT-3 encoded tokens in the given text.
85132
// This function approximates based on the rule of thumb stated by OpenAI:
86133
// https://beta.openai.com/tokenizer

error.go

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,37 @@
11
package gogpt
22

3+
import "fmt"
4+
5+
// APIError provides error information returned by the OpenAI API.
6+
type APIError struct {
7+
Code *string `json:"code,omitempty"`
8+
Message string `json:"message"`
9+
Param *string `json:"param,omitempty"`
10+
Type string `json:"type"`
11+
StatusCode int `json:"-"`
12+
}
13+
14+
// RequestError provides informations about generic request errors.
15+
type RequestError struct {
16+
StatusCode int
17+
Err error
18+
}
19+
320
type ErrorResponse struct {
4-
Error *struct {
5-
Code *int `json:"code,omitempty"`
6-
Message string `json:"message"`
7-
Param *string `json:"param,omitempty"`
8-
Type string `json:"type"`
9-
} `json:"error,omitempty"`
21+
Error *APIError `json:"error,omitempty"`
22+
}
23+
24+
func (e *APIError) Error() string {
25+
return e.Message
26+
}
27+
28+
func (e *RequestError) Error() string {
29+
if e.Err != nil {
30+
return e.Err.Error()
31+
}
32+
return fmt.Sprintf("status code %d", e.StatusCode)
33+
}
34+
35+
func (e *RequestError) Unwrap() error {
36+
return e.Err
1037
}

0 commit comments

Comments
 (0)