diff --git a/client/nginx.go b/client/nginx.go index 4a44d98c..ebe59212 100644 --- a/client/nginx.go +++ b/client/nginx.go @@ -52,6 +52,15 @@ var ( ErrPlusVersionNotFound = errors.New("plus version not found in the input string") ) +// StatusError is an interface that defines our API with consumers of the plus client errors. +// The error will return a http status code and an NGINX error code. +type StatusError interface { + Status() int + Code() string +} + +var _ StatusError = (*internalError)(nil) + // NginxClient lets you access NGINX Plus API. type NginxClient struct { httpClient *http.Client @@ -112,8 +121,18 @@ type apiError struct { } type internalError struct { - err string - apiError + err string + apiError apiError +} + +// Status returns the HTTP status code of the error. +func (internalError *internalError) Status() int { + return internalError.apiError.Status +} + +// Status returns the NGINX error code on the response. +func (internalError *internalError) Code() string { + return internalError.apiError.Code } // Error allows internalError to match the Error interface. @@ -1782,7 +1801,7 @@ func (client *NginxClient) GetStreamServerZones(ctx context.Context) (*StreamSer if err != nil { var ie *internalError if errors.As(err, &ie) { - if ie.Code == pathNotFoundCode { + if ie.Code() == pathNotFoundCode { return &zones, nil } } @@ -1808,7 +1827,7 @@ func (client *NginxClient) GetStreamUpstreams(ctx context.Context) (*StreamUpstr if err != nil { var ie *internalError if errors.As(err, &ie) { - if ie.Code == pathNotFoundCode { + if ie.Code() == pathNotFoundCode { return &upstreams, nil } } @@ -1824,7 +1843,7 @@ func (client *NginxClient) GetStreamZoneSync(ctx context.Context) (*StreamZoneSy if err != nil { var ie *internalError if errors.As(err, &ie) { - if ie.Code == pathNotFoundCode { + if ie.Code() == pathNotFoundCode { return nil, nil } } @@ -2137,7 +2156,7 @@ func (client *NginxClient) GetStreamConnectionsLimit(ctx context.Context) (*Stre if err != nil { var ie *internalError if errors.As(err, &ie) { - if ie.Code == pathNotFoundCode { + if ie.Code() == pathNotFoundCode { return &limitConns, nil } } diff --git a/client/nginx_test.go b/client/nginx_test.go index 46467477..8d565cae 100644 --- a/client/nginx_test.go +++ b/client/nginx_test.go @@ -3,6 +3,7 @@ package client import ( "context" "encoding/json" + "errors" "net/http" "net/http/httptest" "reflect" @@ -1438,6 +1439,70 @@ func TestUpdateStreamServers(t *testing.T) { } } +func TestInternalError(t *testing.T) { + t.Parallel() + + // mimic a user-defined interface type + type TestStatusError interface { + Status() int + Code() string + } + + //nolint // ignore golangci-lint err113 sugggestion to create package level static error + anotherErr := errors.New("another error") + + notFoundErr := &internalError{ + err: "not found error", + apiError: apiError{ + Text: "not found error", + Status: http.StatusNotFound, + Code: "not found code", + }, + } + + testcases := map[string]struct { + inputErr error + expectedCode string + expectedStatus int + }{ + "simple not found": { + inputErr: notFoundErr, + expectedStatus: http.StatusNotFound, + expectedCode: "not found code", + }, + "not found joined with another error": { + inputErr: errors.Join(notFoundErr, anotherErr), + expectedStatus: http.StatusNotFound, + expectedCode: "not found code", + }, + "not found wrapped with another error": { + inputErr: notFoundErr.Wrap("some error"), + expectedStatus: http.StatusNotFound, + expectedCode: "not found code", + }, + } + + for name, tc := range testcases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + var se TestStatusError + ok := errors.As(tc.inputErr, &se) + if !ok { + t.Fatalf("could not cast error %v as StatusError", tc.inputErr) + } + + if se.Status() != tc.expectedStatus { + t.Fatalf("expected status %d, got status %d", tc.expectedStatus, se.Status()) + } + + if se.Code() != tc.expectedCode { + t.Fatalf("expected code %s, got code %s", tc.expectedCode, se.Code()) + } + }) + } +} + type response struct { servers interface{} statusCode int