Replace IsErrXXX functions with regular Go errors (#407)

close #220
This commit is contained in:
Roman Khimov 2023-05-05 10:47:42 +03:00 committed by GitHub
commit d7a12a4846
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 123 additions and 126 deletions

View file

@ -31,14 +31,13 @@ import (
// Each method which produces a NeoFS API call may return a server response.
// Status responses are returned in the result structure, and can be cast
// to built-in error instance (or in the returned error if the client is
// configured accordingly). Certain statuses can be checked using `apistatus`
// and standard `errors` packages. Note that package provides some helper
// functions to work with status returns (e.g. IsErrContainerNotFound).
// configured accordingly). Certain statuses can be checked using [apistatus]
// and standard [errors] packages.
// All possible responses are documented in methods, however, some may be
// returned from all of them (pay attention to the presence of the pointer sign):
// - *apistatus.ServerInternal on internal server error;
// - *apistatus.NodeUnderMaintenance if a server is under maintenance;
// - *apistatus.SuccessDefaultV2 on default success.
// - *[apistatus.ServerInternal] on internal server error;
// - *[apistatus.NodeUnderMaintenance] if a server is under maintenance;
// - *[apistatus.SuccessDefaultV2] on default success.
//
// Client MUST NOT be copied by value: use pointer to Client instead.
//

View file

@ -1,99 +1,9 @@
package client
import (
"errors"
"fmt"
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
)
// unwraps err using errors.Unwrap and returns the result.
func unwrapErr(err error) error {
for e := errors.Unwrap(err); e != nil; e = errors.Unwrap(err) {
err = e
}
return err
}
// IsErrContainerNotFound checks if err corresponds to NeoFS status
// return corresponding to missing container. Supports wrapped errors.
func IsErrContainerNotFound(err error) bool {
switch unwrapErr(err).(type) {
default:
return false
case
apistatus.ContainerNotFound,
*apistatus.ContainerNotFound:
return true
}
}
// IsErrEACLNotFound checks if err corresponds to NeoFS status
// return corresponding to missing eACL table. Supports wrapped errors.
func IsErrEACLNotFound(err error) bool {
switch unwrapErr(err).(type) {
default:
return false
case
apistatus.EACLNotFound,
*apistatus.EACLNotFound:
return true
}
}
// IsErrObjectNotFound checks if err corresponds to NeoFS status
// return corresponding to missing object. Supports wrapped errors.
func IsErrObjectNotFound(err error) bool {
switch unwrapErr(err).(type) {
default:
return false
case
apistatus.ObjectNotFound,
*apistatus.ObjectNotFound:
return true
}
}
// IsErrObjectAlreadyRemoved checks if err corresponds to NeoFS status
// return corresponding to already removed object. Supports wrapped errors.
func IsErrObjectAlreadyRemoved(err error) bool {
switch unwrapErr(err).(type) {
default:
return false
case
apistatus.ObjectAlreadyRemoved,
*apistatus.ObjectAlreadyRemoved:
return true
}
}
// IsErrSessionExpired checks if err corresponds to NeoFS status return
// corresponding to expired session. Supports wrapped errors.
func IsErrSessionExpired(err error) bool {
switch unwrapErr(err).(type) {
default:
return false
case
apistatus.SessionTokenExpired,
*apistatus.SessionTokenExpired:
return true
}
}
// IsErrSessionNotFound checks if err corresponds to NeoFS status return
// corresponding to missing session. Supports wrapped errors.
func IsErrSessionNotFound(err error) bool {
switch unwrapErr(err).(type) {
default:
return false
case
apistatus.SessionTokenNotFound,
*apistatus.SessionTokenNotFound:
return true
}
}
// returns error describing missing field with the given name.
func newErrMissingResponseField(name string) error {
return fmt.Errorf("missing %s field in the response", name)

View file

@ -4,65 +4,68 @@ import (
"fmt"
"testing"
"github.com/nspcc-dev/neofs-sdk-go/client"
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
"github.com/stretchr/testify/require"
)
func TestErrors(t *testing.T) {
for _, tc := range []struct {
check func(error) bool
errs []error
errs []error
errVariable error
}{
{
check: client.IsErrContainerNotFound,
errs: []error{
apistatus.ContainerNotFound{},
new(apistatus.ContainerNotFound),
},
errVariable: apistatus.ErrContainerNotFound,
},
{
check: client.IsErrEACLNotFound,
errs: []error{
apistatus.EACLNotFound{},
new(apistatus.EACLNotFound),
},
errVariable: apistatus.ErrEACLNotFound,
},
{
check: client.IsErrObjectNotFound,
errs: []error{
apistatus.ObjectNotFound{},
new(apistatus.ObjectNotFound),
},
errVariable: apistatus.ErrObjectNotFound,
},
{
check: client.IsErrObjectAlreadyRemoved,
errs: []error{
apistatus.ObjectAlreadyRemoved{},
new(apistatus.ObjectAlreadyRemoved),
},
errVariable: apistatus.ErrObjectAlreadyRemoved,
},
{
check: client.IsErrSessionExpired,
errs: []error{
apistatus.SessionTokenExpired{},
new(apistatus.SessionTokenExpired),
},
errVariable: apistatus.ErrSessionTokenExpired,
}, {
check: client.IsErrSessionNotFound,
errs: []error{
apistatus.SessionTokenNotFound{},
new(apistatus.SessionTokenNotFound),
},
errVariable: apistatus.ErrSessionTokenNotFound,
},
} {
require.NotEmpty(t, tc.errs)
require.NotNil(t, tc.errVariable)
for i := range tc.errs {
require.True(t, tc.check(tc.errs[i]), tc.errs[i])
require.True(t, tc.check(fmt.Errorf("top-level context: :%w",
fmt.Errorf("inner context: %w", tc.errs[i])),
), tc.errs[i])
require.ErrorIs(t, tc.errs[i], tc.errVariable)
wrapped := fmt.Errorf("some message %w", tc.errs[i])
require.ErrorIs(t, wrapped, tc.errVariable)
wrappedTwice := fmt.Errorf("another message %w", wrapped)
require.ErrorIs(t, wrappedTwice, tc.errVariable)
}
}
}

View file

@ -7,7 +7,7 @@ import (
)
// ServerInternal describes failure statuses related to internal server errors.
// Instances provide Status and StatusV2 interfaces.
// Instances provide [Status], [StatusV2] and error interfaces.
//
// The status is purely informative, the client should not go into details of the error except for debugging needs.
type ServerInternal struct {
@ -57,7 +57,7 @@ func WriteInternalServerErr(x *ServerInternal, err error) {
}
// WrongMagicNumber describes failure status related to incorrect network magic.
// Instances provide Status and StatusV2 interfaces.
// Instances provide [Status], [StatusV2] and error interfaces.
type WrongMagicNumber struct {
v2 status.Status
}
@ -125,7 +125,7 @@ func (x WrongMagicNumber) CorrectMagic() (magic uint64, ok int8) {
}
// SignatureVerification describes failure status related to signature verification.
// Instances provide Status and StatusV2 interfaces.
// Instances provide [Status], [StatusV2] and error interfaces.
type SignatureVerification struct {
v2 status.Status
}
@ -183,7 +183,7 @@ func (x SignatureVerification) Message() string {
}
// NodeUnderMaintenance describes failure status for nodes being under maintenance.
// Instances provide Status and StatusV2 interfaces.
// Instances provide [Status], [StatusV2] and error interfaces.
type NodeUnderMaintenance struct {
v2 status.Status
}

View file

@ -5,8 +5,17 @@ import (
"github.com/nspcc-dev/neofs-api-go/v2/status"
)
var (
// ErrEACLNotFound is an instance of EACLNotFound error status. It's expected to be used for [errors.Is]
// and MUST NOT be changed.
ErrEACLNotFound EACLNotFound
// ErrContainerNotFound is an instance of ContainerNotFound error status. It's expected to be used for [errors.Is]
// and MUST NOT be changed.
ErrContainerNotFound ContainerNotFound
)
// ContainerNotFound describes status of the failure because of the missing container.
// Instances provide Status and StatusV2 interfaces.
// Instances provide [Status], [StatusV2] and error interfaces.
type ContainerNotFound struct {
v2 status.Status
}
@ -25,6 +34,16 @@ func (x ContainerNotFound) Error() string {
)
}
// Is implements interface for correct checking current error type with [errors.Is].
func (x ContainerNotFound) Is(target error) bool {
switch target.(type) {
default:
return false
case ContainerNotFound, *ContainerNotFound:
return true
}
}
// implements local interface defined in FromStatusV2 func.
func (x *ContainerNotFound) fromStatusV2(st *status.Status) {
x.v2 = *st
@ -44,7 +63,7 @@ func (x ContainerNotFound) ToStatusV2() *status.Status {
// EACLNotFound describes status of the failure because of the missing eACL
// table.
// Instances provide Status and StatusV2 interfaces.
// Instances provide [Status], [StatusV2] and error interfaces.
type EACLNotFound struct {
v2 status.Status
}
@ -63,6 +82,16 @@ func (x EACLNotFound) Error() string {
)
}
// Is implements interface for correct checking current error type with [errors.Is].
func (x EACLNotFound) Is(target error) bool {
switch target.(type) {
default:
return false
case EACLNotFound, *EACLNotFound:
return true
}
}
// implements local interface defined in FromStatusV2 func.
func (x *EACLNotFound) fromStatusV2(st *status.Status) {
x.v2 = *st

View file

@ -5,8 +5,17 @@ import (
"github.com/nspcc-dev/neofs-api-go/v2/status"
)
var (
// ErrObjectAlreadyRemoved is an instance of ObjectAlreadyRemoved error status. It's expected to be used for [errors.Is]
// and MUST NOT be changed.
ErrObjectAlreadyRemoved ObjectAlreadyRemoved
// ErrObjectNotFound is an instance of ObjectNotFound error status. It's expected to be used for [errors.Is]
// and MUST NOT be changed.
ErrObjectNotFound ObjectNotFound
)
// ObjectLocked describes status of the failure because of the locked object.
// Instances provide Status and StatusV2 interfaces.
// Instances provide [Status], [StatusV2] and error interfaces.
type ObjectLocked struct {
v2 status.Status
}
@ -43,7 +52,7 @@ func (x ObjectLocked) ToStatusV2() *status.Status {
}
// LockNonRegularObject describes status returned on locking the non-regular object.
// Instances provide Status and StatusV2 interfaces.
// Instances provide [Status], [StatusV2] and error interfaces.
type LockNonRegularObject struct {
v2 status.Status
}
@ -80,7 +89,7 @@ func (x LockNonRegularObject) ToStatusV2() *status.Status {
}
// ObjectAccessDenied describes status of the failure because of the access control violation.
// Instances provide Status and StatusV2 interfaces.
// Instances provide [Status], [StatusV2] and error interfaces.
type ObjectAccessDenied struct {
v2 status.Status
}
@ -128,7 +137,7 @@ func (x ObjectAccessDenied) Reason() string {
}
// ObjectNotFound describes status of the failure because of the missing object.
// Instances provide Status and StatusV2 interfaces.
// Instances provide [Status], [StatusV2] and error interfaces.
type ObjectNotFound struct {
v2 status.Status
}
@ -147,6 +156,16 @@ func (x ObjectNotFound) Error() string {
)
}
// Is implements interface for correct checking current error type with [errors.Is].
func (x ObjectNotFound) Is(target error) bool {
switch target.(type) {
default:
return false
case ObjectNotFound, *ObjectNotFound:
return true
}
}
// implements local interface defined in FromStatusV2 func.
func (x *ObjectNotFound) fromStatusV2(st *status.Status) {
x.v2 = *st
@ -184,6 +203,16 @@ func (x ObjectAlreadyRemoved) Error() string {
)
}
// Is implements interface for correct checking current error type with [errors.Is].
func (x ObjectAlreadyRemoved) Is(target error) bool {
switch target.(type) {
default:
return false
case ObjectAlreadyRemoved, *ObjectAlreadyRemoved:
return true
}
}
// implements local interface defined in FromStatusV2 func.
func (x *ObjectAlreadyRemoved) fromStatusV2(st *status.Status) {
x.v2 = *st
@ -203,7 +232,7 @@ func (x ObjectAlreadyRemoved) ToStatusV2() *status.Status {
// ObjectOutOfRange describes status of the failure because of the incorrect
// provided object ranges.
// Instances provide Status and StatusV2 interfaces.
// Instances provide [Status], [StatusV2] and error interfaces.
type ObjectOutOfRange struct {
v2 status.Status
}

View file

@ -5,8 +5,17 @@ import (
"github.com/nspcc-dev/neofs-api-go/v2/status"
)
var (
// ErrSessionTokenNotFound is an instance of SessionTokenNotFound error status. It's expected to be used for [errors.Is]
// and MUST NOT be changed.
ErrSessionTokenNotFound SessionTokenNotFound
// ErrSessionTokenExpired is an instance of SessionTokenExpired error status. It's expected to be used for [errors.Is]
// and MUST NOT be changed.
ErrSessionTokenExpired SessionTokenExpired
)
// SessionTokenNotFound describes status of the failure because of the missing session token.
// Instances provide Status and StatusV2 interfaces.
// Instances provide [Status], [StatusV2] and error interfaces.
type SessionTokenNotFound struct {
v2 status.Status
}
@ -25,6 +34,16 @@ func (x SessionTokenNotFound) Error() string {
)
}
// Is implements interface for correct checking current error type with [errors.Is].
func (x SessionTokenNotFound) Is(target error) bool {
switch target.(type) {
default:
return false
case SessionTokenNotFound, *SessionTokenNotFound:
return true
}
}
// implements local interface defined in FromStatusV2 func.
func (x *SessionTokenNotFound) fromStatusV2(st *status.Status) {
x.v2 = *st
@ -43,7 +62,7 @@ func (x SessionTokenNotFound) ToStatusV2() *status.Status {
}
// SessionTokenExpired describes status of the failure because of the expired session token.
// Instances provide Status and StatusV2 interfaces.
// Instances provide [Status], [StatusV2] and error interfaces.
type SessionTokenExpired struct {
v2 status.Status
}
@ -62,6 +81,16 @@ func (x SessionTokenExpired) Error() string {
)
}
// Is implements interface for correct checking current error type with [errors.Is].
func (x SessionTokenExpired) Is(target error) bool {
switch target.(type) {
default:
return false
case SessionTokenExpired, *SessionTokenExpired:
return true
}
}
// implements local interface defined in FromStatusV2 func.
func (x *SessionTokenExpired) fromStatusV2(st *status.Status) {
x.v2 = *st

View file

@ -1527,8 +1527,6 @@ type resCreateSession struct {
// Each method which produces a NeoFS API call may return an error.
// Status of underlying server response is casted to built-in error instance.
// Certain statuses can be checked using `sdkClient` and standard `errors` packages.
// Note that package provides some helper functions to work with status returns
// (e.g. sdkClient.IsErrContainerNotFound, sdkClient.IsErrObjectNotFound).
//
// See pool package overview to get some examples.
type Pool struct {
@ -1860,7 +1858,7 @@ func (p *Pool) checkSessionTokenErr(err error, address string) bool {
return false
}
if sdkClient.IsErrSessionNotFound(err) || sdkClient.IsErrSessionExpired(err) {
if errors.Is(err, apistatus.ErrSessionTokenNotFound) || errors.Is(err, apistatus.ErrSessionTokenExpired) {
p.cache.DeleteByPrefix(address)
return true
}
@ -2458,7 +2456,7 @@ func waitForContainerRemoved(ctx context.Context, cli client, cnrID *cid.ID, wai
return waitFor(ctx, waitParams, func(ctx context.Context) bool {
_, err := cli.containerGet(ctx, prm)
return sdkClient.IsErrContainerNotFound(err)
return errors.Is(err, apistatus.ErrContainerNotFound)
})
}