From 3b50701d9bac12bfabe3bd0919db376963b9e060 Mon Sep 17 00:00:00 2001 From: Marc Haisenko Date: Mon, 13 Feb 2023 16:24:04 +0100 Subject: [PATCH 1/3] Provide APIError and use Go's error wrapping --- api.go | 3 ++- api_test.go | 27 +++++++++++++++++++++++++++ error.go | 19 +++++++++++++------ 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/api.go b/api.go index c339afeb4..2f47835d3 100644 --- a/api.go +++ b/api.go @@ -68,7 +68,8 @@ func (c *Client) sendRequest(req *http.Request, v interface{}) error { if err != nil || errRes.Error == nil { return fmt.Errorf("error, status code: %d", res.StatusCode) } - return fmt.Errorf("error, status code: %d, message: %s", res.StatusCode, errRes.Error.Message) + errRes.Error.StatusCode = res.StatusCode + return fmt.Errorf("error, status code: %d, message: %w", res.StatusCode, errRes.Error) } if v != nil { diff --git a/api_test.go b/api_test.go index 7843bef61..d7a20e235 100644 --- a/api_test.go +++ b/api_test.go @@ -81,6 +81,33 @@ func TestAPI(t *testing.T) { } } +func TestAPIError(t *testing.T) { + apiToken := os.Getenv("OPENAI_TOKEN") + if apiToken == "" { + t.Skip("Skipping testing against production OpenAI API. Set OPENAI_TOKEN environment variable to enable it.") + } + + var err error + c := NewClient(apiToken + "_invalid") + ctx := context.Background() + _, err = c.ListEngines(ctx) + if err == nil { + t.Fatal("ListEngines did not fail with invalid token") + } + + var apiErr *APIError + if !errors.As(err, &apiErr) { + t.Fatalf("Request error is not an APIError: %+v", err) + } + + if apiErr.StatusCode != 401 { + t.Fatalf("Unexpected API error status code: %d", apiErr.StatusCode) + } + if *apiErr.Code != "invalid_api_key" { + t.Fatalf("Unexpected API error code: %s", *apiErr.Code) + } +} + // numTokens Returns the number of GPT-3 encoded tokens in the given text. // This function approximates based on the rule of thumb stated by OpenAI: // https://beta.openai.com/tokenizer diff --git a/error.go b/error.go index 4d0a32404..6451a1004 100644 --- a/error.go +++ b/error.go @@ -1,10 +1,17 @@ package gogpt +type APIError struct { + Code *string `json:"code,omitempty"` + Message string `json:"message"` + Param *string `json:"param,omitempty"` + Type string `json:"type"` + StatusCode int `json:"-"` +} + type ErrorResponse struct { - Error *struct { - Code *int `json:"code,omitempty"` - Message string `json:"message"` - Param *string `json:"param,omitempty"` - Type string `json:"type"` - } `json:"error,omitempty"` + Error *APIError `json:"error,omitempty"` +} + +func (er *APIError) Error() string { + return er.Message } From 687dfa4e95510cb338aaf1c61e1e5084d0478633 Mon Sep 17 00:00:00 2001 From: Marc Haisenko Date: Mon, 13 Feb 2023 17:08:24 +0100 Subject: [PATCH 2/3] Add generic request error --- api.go | 6 +++++- api_test.go | 24 ++++++++++++++++++++++-- error.go | 24 ++++++++++++++++++++++-- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/api.go b/api.go index 2f47835d3..c43f0b143 100644 --- a/api.go +++ b/api.go @@ -66,7 +66,11 @@ func (c *Client) sendRequest(req *http.Request, v interface{}) error { var errRes ErrorResponse err = json.NewDecoder(res.Body).Decode(&errRes) if err != nil || errRes.Error == nil { - return fmt.Errorf("error, status code: %d", res.StatusCode) + reqErr := RequestError { + StatusCode: res.StatusCode, + Err: err, + } + return fmt.Errorf("error, %w", &reqErr) } errRes.Error.StatusCode = res.StatusCode return fmt.Errorf("error, status code: %d, message: %w", res.StatusCode, errRes.Error) diff --git a/api_test.go b/api_test.go index d7a20e235..4c8732f72 100644 --- a/api_test.go +++ b/api_test.go @@ -92,12 +92,12 @@ func TestAPIError(t *testing.T) { ctx := context.Background() _, err = c.ListEngines(ctx) if err == nil { - t.Fatal("ListEngines did not fail with invalid token") + t.Fatal("ListEngines did not fail") } var apiErr *APIError if !errors.As(err, &apiErr) { - t.Fatalf("Request error is not an APIError: %+v", err) + t.Fatalf("Error is not an APIError: %+v", err) } if apiErr.StatusCode != 401 { @@ -108,6 +108,26 @@ func TestAPIError(t *testing.T) { } } +func TestRequestError(t *testing.T) { + var err error + c := NewClient("dummy") + c.BaseURL = "https://httpbin.org/status/418?" + ctx := context.Background() + _, err = c.ListEngines(ctx) + if err == nil { + t.Fatal("ListEngines request did not fail") + } + + var reqErr *RequestError + if !errors.As(err, &reqErr) { + t.Fatalf("Error is not a RequestError: %+v", err) + } + + if reqErr.StatusCode != 418 { + t.Fatalf("Unexpected request error status code: %d", reqErr.StatusCode) + } +} + // numTokens Returns the number of GPT-3 encoded tokens in the given text. // This function approximates based on the rule of thumb stated by OpenAI: // https://beta.openai.com/tokenizer diff --git a/error.go b/error.go index 6451a1004..927fafd45 100644 --- a/error.go +++ b/error.go @@ -1,5 +1,8 @@ package gogpt +import "fmt" + +// APIError provides error information returned by the OpenAI API. type APIError struct { Code *string `json:"code,omitempty"` Message string `json:"message"` @@ -8,10 +11,27 @@ type APIError struct { StatusCode int `json:"-"` } +// RequestError provides informations about generic request errors. +type RequestError struct { + StatusCode int + Err error +} + type ErrorResponse struct { Error *APIError `json:"error,omitempty"` } -func (er *APIError) Error() string { - return er.Message +func (e *APIError) Error() string { + return e.Message +} + +func (e *RequestError) Error() string { + if e.Err != nil { + return e.Err.Error() + } + return fmt.Sprintf("status code %d", e.StatusCode) +} + +func (e *RequestError) Unwrap() error { + return e.Err } From e3defc713c0406d22a0cd8a135351f4960038f23 Mon Sep 17 00:00:00 2001 From: Marc Haisenko Date: Mon, 13 Feb 2023 17:33:54 +0100 Subject: [PATCH 3/3] Fix code formatting --- api.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api.go b/api.go index c43f0b143..0a7bf1584 100644 --- a/api.go +++ b/api.go @@ -66,9 +66,9 @@ func (c *Client) sendRequest(req *http.Request, v interface{}) error { var errRes ErrorResponse err = json.NewDecoder(res.Body).Decode(&errRes) if err != nil || errRes.Error == nil { - reqErr := RequestError { + reqErr := RequestError{ StatusCode: res.StatusCode, - Err: err, + Err: err, } return fmt.Errorf("error, %w", &reqErr) }