diff --git a/api.go b/api.go index 4c137ec..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() @@ -357,7 +356,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 +374,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..c373af5 100644 --- a/errors.go +++ b/errors.go @@ -2,13 +2,22 @@ package deriv import ( "encoding/json" + "fmt" +) + +var ( + 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. 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. @@ -16,18 +25,27 @@ func (e *APIError) Error() string { return e.Message } -// APIErrorResponse represents an error response returned by the Deriv API service. -type APIErrorResponse struct { +// 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. 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 0ad96a3..dcc0a6d 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 @@ -22,11 +24,11 @@ func TestAPIError_Error(t *testing.T) { } func TestParseError_ValidResponse(t *testing.T) { - errorResponse := APIErrorResponse{ + errorResponse := apiErrorResponse{ Error: APIError{ Code: "test-code", Message: "test-message", - Details: map[string]interface{}{"test-key": "test-value"}, + Details: &expectedDetails, }, } @@ -65,7 +67,7 @@ func TestParseError_EmptyErrorResponse(t *testing.T) { } func TestParseError_EmptyAPIError(t *testing.T) { - errorResponse := APIErrorResponse{ + errorResponse := apiErrorResponse{ Error: APIError{}, } @@ -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) + } +} diff --git a/subscriptions.go b/subscriptions.go index ab55b11..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 @@ -125,7 +124,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) diff --git a/subscriptions_test.go b/subscriptions_test.go index 89ea0a5..b4c5614 100644 --- a/subscriptions_test.go +++ b/subscriptions_test.go @@ -2,8 +2,6 @@ package deriv import ( "context" - "errors" - "fmt" "reflect" "testing" "time" @@ -57,15 +55,14 @@ func TestParseSubscription_EmptyInput(t *testing.T) { func TestParseSubscription_EmptySubscriptionData(t *testing.T) { input := []byte(`{}`) - expectedErr := fmt.Errorf("subscription ID is empty") _, 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) } }