From 0a7c02bdec6cdf2a8818dd047b8e13554d616efd Mon Sep 17 00:00:00 2001 From: Kirill Sysoev Date: Mon, 19 Aug 2024 20:16:42 +0800 Subject: [PATCH 1/5] Adds error for connection closed case --- api.go | 6 +++--- errors.go | 5 +++++ subscriptions.go | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/api.go b/api.go index 4c137ec..4a2bb22 100644 --- a/api.go +++ b/api.go @@ -357,7 +357,7 @@ func (api *Client) Send(ctx context.Context, reqID int, request any) (chan []byt case <-ctx.Done(): return nil, ctx.Err() case <-api.ctx.Done(): - return nil, fmt.Errorf("connection closed") + return nil, ErrConnectionClosed case api.reqChan <- req: return respChan, nil } @@ -375,13 +375,13 @@ func (api *Client) SendRequest(ctx context.Context, reqID int, request, response select { case <-api.ctx.Done(): - return fmt.Errorf("connection closed") + return ErrConnectionClosed case <-ctx.Done(): return ctx.Err() case responseJSON, ok := <-respChan: if !ok { api.logDebugf("Connection closed while waiting for response for request %d", reqID) - return fmt.Errorf("connection closed") + return ErrConnectionClosed } if err := parseError(responseJSON); err != nil { diff --git a/errors.go b/errors.go index d8d6703..08f65c1 100644 --- a/errors.go +++ b/errors.go @@ -2,6 +2,11 @@ package deriv import ( "encoding/json" + "fmt" +) + +var ( + ErrConnectionClosed = fmt.Errorf("connection closed") ) // APIError represents an error returned by the Deriv API service. diff --git a/subscriptions.go b/subscriptions.go index ab55b11..8ecdc22 100644 --- a/subscriptions.go +++ b/subscriptions.go @@ -125,7 +125,7 @@ func (s *Subsciption[initResp, Resp]) Start(reqID int, request any) (initResp, e if !ok { s.API.logDebugf("Connection closed while waiting for response for request %d", reqID) - return resp, fmt.Errorf("connection closed") + return resp, ErrConnectionClosed } subResp, err := parseSubsciption(initResponse) From 6f7529fd1ed4e13dcf33ddfcc4bbf92934f17af2 Mon Sep 17 00:00:00 2001 From: Kirill Sysoev Date: Mon, 19 Aug 2024 20:33:07 +0800 Subject: [PATCH 2/5] Define errors as public variables --- api.go | 7 +++---- errors.go | 6 +++++- subscriptions.go | 3 +-- subscriptions_test.go | 3 +-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/api.go b/api.go index 4a2bb22..2e7d385 100644 --- a/api.go +++ b/api.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "encoding/json" - "fmt" "log" "net/url" "strconv" @@ -86,15 +85,15 @@ func NewDerivAPI(endpoint string, appID int, lang, origin string, opts ...APIOpt } if urlEnpoint.Scheme != "wss" && urlEnpoint.Scheme != "ws" { - return nil, fmt.Errorf("invalid endpoint scheme") + return nil, ErrInvalidSchema } if appID < 1 { - return nil, fmt.Errorf("invalid app id") + return nil, ErrInvalidAppID } if lang == "" || len(lang) != 2 { - return nil, fmt.Errorf("invalid language") + return nil, ErrInvalidLanguage } query := urlEnpoint.Query() diff --git a/errors.go b/errors.go index 08f65c1..4d30a1c 100644 --- a/errors.go +++ b/errors.go @@ -6,7 +6,11 @@ import ( ) var ( - ErrConnectionClosed = fmt.Errorf("connection closed") + ErrConnectionClosed = fmt.Errorf("connection closed") + ErrEmptySubscriptionID = fmt.Errorf("subscription ID is empty") + ErrInvalidSchema = fmt.Errorf("invalid endpoint scheme") + ErrInvalidAppID = fmt.Errorf("invalid app ID") + ErrInvalidLanguage = fmt.Errorf("invalid language") ) // APIError represents an error returned by the Deriv API service. diff --git a/subscriptions.go b/subscriptions.go index 8ecdc22..c513bd9 100644 --- a/subscriptions.go +++ b/subscriptions.go @@ -5,7 +5,6 @@ package deriv import ( "context" "encoding/json" - "fmt" "sync" "github.com/ksysoev/deriv-api/schema" @@ -49,7 +48,7 @@ func parseSubsciption(rawResponse []byte) (SubscriptionResponse, error) { } if sub.Subscription.ID == "" { - return sub, fmt.Errorf("subscription ID is empty") + return sub, ErrEmptySubscriptionID } return sub, nil diff --git a/subscriptions_test.go b/subscriptions_test.go index 89ea0a5..e36668a 100644 --- a/subscriptions_test.go +++ b/subscriptions_test.go @@ -3,7 +3,6 @@ package deriv import ( "context" "errors" - "fmt" "reflect" "testing" "time" @@ -57,7 +56,7 @@ func TestParseSubscription_EmptyInput(t *testing.T) { func TestParseSubscription_EmptySubscriptionData(t *testing.T) { input := []byte(`{}`) - expectedErr := fmt.Errorf("subscription ID is empty") + expectedErr := ErrEmptySubscriptionID _, err := parseSubsciption(input) if err == nil { From 96d5f932278c113ab34dcbc521d40eda0722549a Mon Sep 17 00:00:00 2001 From: Kirill Sysoev Date: Mon, 19 Aug 2024 20:49:39 +0800 Subject: [PATCH 3/5] Adds method for parsing API error details --- errors.go | 15 ++++++++++++--- errors_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/errors.go b/errors.go index 4d30a1c..2ee9d71 100644 --- a/errors.go +++ b/errors.go @@ -15,9 +15,9 @@ var ( // APIError represents an error returned by the Deriv API service. type APIError struct { - Details map[string]interface{} `json:"details"` - Code string `json:"code"` - Message string `json:"message"` + Details *json.RawMessage `json:"details"` + Code string `json:"code"` + Message string `json:"message"` } // Error returns the error message associated with the APIError. @@ -25,6 +25,15 @@ func (e *APIError) Error() string { return e.Message } +// ParseDetails parses the details field of the APIError into the provided value. +func (e *APIError) ParseDetails(v any) error { + if e.Details == nil { + return nil + } + + return json.Unmarshal(*e.Details, v) +} + // APIErrorResponse represents an error response returned by the Deriv API service. type APIErrorResponse struct { // Error is the APIError associated with the response. diff --git a/errors_test.go b/errors_test.go index 0ad96a3..ec71b00 100644 --- a/errors_test.go +++ b/errors_test.go @@ -6,11 +6,13 @@ import ( "testing" ) +var expectedDetails json.RawMessage = []byte(`{"TestKey":"TestValue"}`) + func TestAPIError_Error(t *testing.T) { err := &APIError{ Code: "test-code", Message: "test-message", - Details: map[string]interface{}{"test-key": "test-value"}, + Details: &expectedDetails, } expected := err.Message @@ -26,7 +28,7 @@ func TestParseError_ValidResponse(t *testing.T) { Error: APIError{ Code: "test-code", Message: "test-message", - Details: map[string]interface{}{"test-key": "test-value"}, + Details: &expectedDetails, }, } @@ -80,3 +82,44 @@ func TestParseError_EmptyAPIError(t *testing.T) { t.Errorf("parseError() returned %v, expected %v", actual, nil) } } +func TestAPIError_ParseDetails_ValidDetails(t *testing.T) { + details := struct { + TestKey string `json:"TestKey"` + }{} + + apiErr := &APIError{ + Code: "test-code", + Message: "test-message", + Details: &expectedDetails, + } + + if err := apiErr.ParseDetails(&details); err != nil { + t.Errorf("Expected no error, got %v", err) + } + + if details.TestKey != "TestValue" { + t.Errorf("ParseDetails() did not parse details correctly, expected %q, got %q", "test-value", details.TestKey) + } +} + +func TestAPIError_ParseDetails_EmptyDetails(t *testing.T) { + details := struct { + TestKey string `json:"TestKey"` + }{} + + apiErr := &APIError{ + Code: "test-code", + Message: "test-message", + Details: nil, + } + + err := apiErr.ParseDetails(&details) + + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + + if details.TestKey != "" { + t.Errorf("ParseDetails() did not handle empty details correctly, expected %q, got %q", "", details.TestKey) + } +} From a357bdb924ed6b4856a6e0254208a8087feec5ba Mon Sep 17 00:00:00 2001 From: Kirill Sysoev Date: Mon, 19 Aug 2024 20:54:43 +0800 Subject: [PATCH 4/5] Updates test fir empty subscription id --- subscriptions_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/subscriptions_test.go b/subscriptions_test.go index e36668a..b4c5614 100644 --- a/subscriptions_test.go +++ b/subscriptions_test.go @@ -2,7 +2,6 @@ package deriv import ( "context" - "errors" "reflect" "testing" "time" @@ -56,15 +55,14 @@ func TestParseSubscription_EmptyInput(t *testing.T) { func TestParseSubscription_EmptySubscriptionData(t *testing.T) { input := []byte(`{}`) - expectedErr := ErrEmptySubscriptionID _, err := parseSubsciption(input) if err == nil { t.Errorf("Expected an error, but got nil") } - if errors.Is(err, expectedErr) { - t.Errorf("Expected %+v, but got %+v", expectedErr, err) + if err != ErrEmptySubscriptionID { + t.Errorf("Expected '%+v', but got '%+v'", ErrEmptySubscriptionID, err) } } From 79a18664b25d9664ea97fea215d792aef759cdb2 Mon Sep 17 00:00:00 2001 From: Kirill Sysoev Date: Mon, 19 Aug 2024 21:00:28 +0800 Subject: [PATCH 5/5] makes apiErrorResponse private --- errors.go | 10 +++++----- errors_test.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/errors.go b/errors.go index 2ee9d71..c373af5 100644 --- a/errors.go +++ b/errors.go @@ -34,18 +34,18 @@ func (e *APIError) ParseDetails(v any) error { return json.Unmarshal(*e.Details, v) } -// APIErrorResponse represents an error response returned by the Deriv API service. -type APIErrorResponse struct { +// apiErrorResponse represents an error response returned by the Deriv API service. +type apiErrorResponse struct { // Error is the APIError associated with the response. Error APIError `json:"error"` } // parseError parses a JSON error response from the Deriv API service into an error. -// If the response is not a valid JSON-encoded APIErrorResponse, an error is returned. -// If the APIErrorResponse contains a non-empty APIError, it is returned as an error. +// If the response is not a valid JSON-encoded apiErrorResponse, an error is returned. +// If the apiErrorResponse contains a non-empty APIError, it is returned as an error. // Otherwise, nil is returned. func parseError(rawResponse []byte) error { - var errorResponse APIErrorResponse + var errorResponse apiErrorResponse err := json.Unmarshal(rawResponse, &errorResponse) if err != nil { diff --git a/errors_test.go b/errors_test.go index ec71b00..dcc0a6d 100644 --- a/errors_test.go +++ b/errors_test.go @@ -24,7 +24,7 @@ func TestAPIError_Error(t *testing.T) { } func TestParseError_ValidResponse(t *testing.T) { - errorResponse := APIErrorResponse{ + errorResponse := apiErrorResponse{ Error: APIError{ Code: "test-code", Message: "test-message", @@ -67,7 +67,7 @@ func TestParseError_EmptyErrorResponse(t *testing.T) { } func TestParseError_EmptyAPIError(t *testing.T) { - errorResponse := APIErrorResponse{ + errorResponse := apiErrorResponse{ Error: APIError{}, }