Drop ResolveNeoFSFailures (#413)

close #406 
fixes #405
This commit is contained in:
Roman Khimov 2023-05-19 12:13:24 +03:00 committed by GitHub
commit cfdd870755
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 589 additions and 821 deletions

View file

@ -42,7 +42,6 @@ Contains client for working with NeoFS.
```go ```go
var prmInit client.PrmInit var prmInit client.PrmInit
prmInit.SetDefaultPrivateKey(key) // private key for request signing prmInit.SetDefaultPrivateKey(key) // private key for request signing
prmInit.ResolveNeoFSFailures() // enable erroneous status parsing
var c client.Client var c client.Client
c.Init(prmInit) c.Init(prmInit)
@ -77,8 +76,7 @@ if needed and perform any desired action. In the case above we may want to repor
these details to the user as well as retry an operation, possibly with different parameters. these details to the user as well as retry an operation, possibly with different parameters.
Status wire-format is extendable and each node can report any set of details it wants. Status wire-format is extendable and each node can report any set of details it wants.
The set of reserved status codes can be found in The set of reserved status codes can be found in
[NeoFS API](https://github.com/nspcc-dev/neofs-api/blob/master/status/types.proto). There is also [NeoFS API](https://github.com/nspcc-dev/neofs-api/blob/master/status/types.proto).
a `client.PrmInit.ResolveNeoFSFailures()` to seamlessly convert erroneous statuses into Go error type.
### policy ### policy
Contains helpers allowing conversion of placing policy from/to JSON representation Contains helpers allowing conversion of placing policy from/to JSON representation

View file

@ -28,8 +28,6 @@ func (x *PrmBalanceGet) SetAccount(id user.ID) {
// ResBalanceGet groups resulting values of BalanceGet operation. // ResBalanceGet groups resulting values of BalanceGet operation.
type ResBalanceGet struct { type ResBalanceGet struct {
statusRes
amount accounting.Decimal amount accounting.Decimal
} }
@ -40,17 +38,11 @@ func (x ResBalanceGet) Amount() accounting.Decimal {
// BalanceGet requests current balance of the NeoFS account. // BalanceGet requests current balance of the NeoFS account.
// //
// Exactly one return value is non-nil. By default, server status is returned in res structure. // Any errors (local or remote, including returned status codes) are returned as Go errors,
// Any client's internal or transport errors are returned as `error`, // see [apistatus] package for NeoFS-specific error types.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// //
// Immediately panics if parameters are set incorrectly (see PrmBalanceGet docs). // Immediately panics if parameters are set incorrectly (see PrmBalanceGet docs).
// Context is required and must not be nil. It is used for network communication. // Context is required and must not be nil. It is used for network communication.
//
// Return statuses:
// - global (see Client docs).
func (c *Client) BalanceGet(ctx context.Context, prm PrmBalanceGet) (*ResBalanceGet, error) { func (c *Client) BalanceGet(ctx context.Context, prm PrmBalanceGet) (*ResBalanceGet, error) {
switch { switch {
case ctx == nil: case ctx == nil:
@ -81,7 +73,6 @@ func (c *Client) BalanceGet(ctx context.Context, prm PrmBalanceGet) (*ResBalance
c.initCallContext(&cc) c.initCallContext(&cc)
cc.meta = prm.prmCommonMeta cc.meta = prm.prmCommonMeta
cc.req = &req cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) { cc.call = func() (responseV2, error) {
return rpcapi.Balance(&c.c, &req, client.WithContext(ctx)) return rpcapi.Balance(&c.c, &req, client.WithContext(ctx))
} }

View file

@ -145,8 +145,6 @@ func (c *Client) Close() error {
// //
// See also Init. // See also Init.
type PrmInit struct { type PrmInit struct {
resolveNeoFSErrors bool
signer neofscrypto.Signer signer neofscrypto.Signer
cbRespInfo func(ResponseMetaInfo) error cbRespInfo func(ResponseMetaInfo) error
@ -162,14 +160,6 @@ func (x *PrmInit) SetDefaultSigner(signer neofscrypto.Signer) {
x.signer = signer x.signer = signer
} }
// ResolveNeoFSFailures makes the Client to resolve failure statuses of the
// NeoFS protocol into Go built-in errors. These errors are returned from
// each protocol operation. By default, statuses aren't resolved and written
// to the resulting structure (see corresponding Res* docs).
func (x *PrmInit) ResolveNeoFSFailures() {
x.resolveNeoFSErrors = true
}
// SetResponseInfoCallback makes the Client to pass ResponseMetaInfo from each // SetResponseInfoCallback makes the Client to pass ResponseMetaInfo from each
// NeoFS server response to f. Nil (default) means ignore response meta info. // NeoFS server response to f. Nil (default) means ignore response meta info.
func (x *PrmInit) SetResponseInfoCallback(f func(ResponseMetaInfo) error) { func (x *PrmInit) SetResponseInfoCallback(f func(ResponseMetaInfo) error) {

View file

@ -19,11 +19,6 @@ func init() {
statusErr.SetMessage("test status error") statusErr.SetMessage("test status error")
} }
func assertStatusErr(tb testing.TB, res interface{ Status() apistatus.Status }) {
require.IsType(tb, &statusErr, res.Status())
require.Equal(tb, statusErr.Message(), res.Status().(*apistatus.ServerInternal).Message())
}
func newClient(signer neofscrypto.Signer, server neoFSAPIServer) *Client { func newClient(signer neofscrypto.Signer, server neoFSAPIServer) *Client {
var prm PrmInit var prm PrmInit
prm.SetDefaultSigner(signer) prm.SetDefaultSigner(signer)

View file

@ -11,28 +11,6 @@ import (
"github.com/nspcc-dev/neofs-sdk-go/version" "github.com/nspcc-dev/neofs-sdk-go/version"
) )
// common interface of resulting structures with API status.
type resCommon interface {
setStatus(apistatus.Status)
}
// structure is embedded to all resulting types in order to inherit status-related methods.
type statusRes struct {
st apistatus.Status
}
// setStatus implements resCommon interface method.
func (x *statusRes) setStatus(st apistatus.Status) {
x.st = st
}
// Status returns server's status return.
//
// Use apistatus package functionality to handle the status.
func (x statusRes) Status() apistatus.Status {
return x.st
}
// groups meta parameters shared between all Client operations. // groups meta parameters shared between all Client operations.
type prmCommonMeta struct { type prmCommonMeta struct {
// NeoFS request X-Headers // NeoFS request X-Headers
@ -97,9 +75,6 @@ type contextCall struct {
// callback prior to processing the response by the client // callback prior to processing the response by the client
callbackResp func(ResponseMetaInfo) error callbackResp func(ResponseMetaInfo) error
// if set, protocol errors will be expanded into a final error
resolveAPIFailures bool
// NeoFS network magic // NeoFS network magic
netMagic uint64 netMagic uint64
@ -109,9 +84,6 @@ type contextCall struct {
// ================================================== // ==================================================
// custom call parameters // custom call parameters
// structure of the call result
statusRes resCommon
// request to be signed with a signer and sent // request to be signed with a signer and sent
req request req request
@ -235,20 +207,13 @@ func (x *contextCall) processResponse() bool {
// get result status // get result status
st := apistatus.FromStatusV2(x.resp.GetMetaHeader().GetStatus()) st := apistatus.FromStatusV2(x.resp.GetMetaHeader().GetStatus())
// unwrap unsuccessful status and return it
// as error if client has been configured so
successfulStatus := apistatus.IsSuccessful(st) successfulStatus := apistatus.IsSuccessful(st)
if x.resolveAPIFailures {
x.err = apistatus.ErrFromStatus(st) x.err = apistatus.ErrFromStatus(st)
} else {
x.statusRes.setStatus(st)
}
return successfulStatus return successfulStatus
} }
// processResponse verifies response signature and converts status to an error if needed. // processResponse verifies response signature.
func (c *Client) processResponse(resp responseV2) (apistatus.Status, error) { func (c *Client) processResponse(resp responseV2) (apistatus.Status, error) {
err := verifyServiceMessage(resp) err := verifyServiceMessage(resp)
if err != nil { if err != nil {
@ -256,14 +221,11 @@ func (c *Client) processResponse(resp responseV2) (apistatus.Status, error) {
} }
st := apistatus.FromStatusV2(resp.GetMetaHeader().GetStatus()) st := apistatus.FromStatusV2(resp.GetMetaHeader().GetStatus())
if c.prm.resolveNeoFSErrors {
return st, apistatus.ErrFromStatus(st) return st, apistatus.ErrFromStatus(st)
} }
return st, nil
}
// reads response (if rResp is set) and processes it. Result means success. // reads response (if rResp is set) and processes it. Result means success.
// If failed, contextCall.err (or statusRes if resolveAPIFailures is set) contains the reason. // If failed, contextCall.err contains the reason.
func (x *contextCall) readResponse() bool { func (x *contextCall) readResponse() bool {
if x.rResp != nil { if x.rResp != nil {
x.err = x.rResp() x.err = x.rResp()
@ -329,7 +291,6 @@ func (x *contextCall) processCall() bool {
// initializes static cross-call parameters inherited from client. // initializes static cross-call parameters inherited from client.
func (c *Client) initCallContext(ctx *contextCall) { func (c *Client) initCallContext(ctx *contextCall) {
ctx.signer = c.prm.signer ctx.signer = c.prm.signer
ctx.resolveAPIFailures = c.prm.resolveNeoFSErrors
ctx.callbackResp = c.prm.cbRespInfo ctx.callbackResp = c.prm.cbRespInfo
ctx.netMagic = c.prm.netMagic ctx.netMagic = c.prm.netMagic
} }

View file

@ -60,8 +60,6 @@ func (x *PrmContainerPut) WithinSession(s session.Container) {
// ResContainerPut groups resulting values of ContainerPut operation. // ResContainerPut groups resulting values of ContainerPut operation.
type ResContainerPut struct { type ResContainerPut struct {
statusRes
id cid.ID id cid.ID
} }
@ -78,11 +76,8 @@ func (c *Client) defaultSigner() neofscrypto.Signer {
// ContainerPut sends request to save container in NeoFS. // ContainerPut sends request to save container in NeoFS.
// //
// Exactly one return value is non-nil. By default, server status is returned in res structure. // Any errors (local or remote, including returned status codes) are returned as Go errors,
// Any client's internal or transport errors are returned as `error`. // see [apistatus] package for NeoFS-specific error types.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// //
// Operation is asynchronous and no guaranteed even in the absence of errors. // Operation is asynchronous and no guaranteed even in the absence of errors.
// The required time is also not predictable. // The required time is also not predictable.
@ -91,9 +86,6 @@ func (c *Client) defaultSigner() neofscrypto.Signer {
// //
// Immediately panics if parameters are set incorrectly (see PrmContainerPut docs). // Immediately panics if parameters are set incorrectly (see PrmContainerPut docs).
// Context is required and must not be nil. It is used for network communication. // Context is required and must not be nil. It is used for network communication.
//
// Return statuses:
// - global (see Client docs).
func (c *Client) ContainerPut(ctx context.Context, prm PrmContainerPut) (*ResContainerPut, error) { func (c *Client) ContainerPut(ctx context.Context, prm PrmContainerPut) (*ResContainerPut, error) {
// check parameters // check parameters
switch { switch {
@ -154,7 +146,6 @@ func (c *Client) ContainerPut(ctx context.Context, prm PrmContainerPut) (*ResCon
c.initCallContext(&cc) c.initCallContext(&cc)
cc.req = &req cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) { cc.call = func() (responseV2, error) {
return rpcapi.PutContainer(&c.c, &req, client.WithContext(ctx)) return rpcapi.PutContainer(&c.c, &req, client.WithContext(ctx))
} }
@ -200,8 +191,6 @@ func (x *PrmContainerGet) SetContainer(id cid.ID) {
// ResContainerGet groups resulting values of ContainerGet operation. // ResContainerGet groups resulting values of ContainerGet operation.
type ResContainerGet struct { type ResContainerGet struct {
statusRes
cnr container.Container cnr container.Container
} }
@ -214,18 +203,11 @@ func (x ResContainerGet) Container() container.Container {
// ContainerGet reads NeoFS container by ID. // ContainerGet reads NeoFS container by ID.
// //
// Exactly one return value is non-nil. By default, server status is returned in res structure. // Any errors (local or remote, including returned status codes) are returned as Go errors,
// Any client's internal or transport errors are returned as `error`. // see [apistatus] package for NeoFS-specific error types.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// //
// Immediately panics if parameters are set incorrectly (see PrmContainerGet docs). // Immediately panics if parameters are set incorrectly (see PrmContainerGet docs).
// Context is required and must not be nil. It is used for network communication. // Context is required and must not be nil. It is used for network communication.
//
// Return statuses:
// - global (see Client docs);
// - *apistatus.ContainerNotFound.
func (c *Client) ContainerGet(ctx context.Context, prm PrmContainerGet) (*ResContainerGet, error) { func (c *Client) ContainerGet(ctx context.Context, prm PrmContainerGet) (*ResContainerGet, error) {
switch { switch {
case ctx == nil: case ctx == nil:
@ -256,7 +238,6 @@ func (c *Client) ContainerGet(ctx context.Context, prm PrmContainerGet) (*ResCon
c.initCallContext(&cc) c.initCallContext(&cc)
cc.meta = prm.prmCommonMeta cc.meta = prm.prmCommonMeta
cc.req = &req cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) { cc.call = func() (responseV2, error) {
return rpcapi.GetContainer(&c.c, &req, client.WithContext(ctx)) return rpcapi.GetContainer(&c.c, &req, client.WithContext(ctx))
} }
@ -300,8 +281,6 @@ func (x *PrmContainerList) SetAccount(id user.ID) {
// ResContainerList groups resulting values of ContainerList operation. // ResContainerList groups resulting values of ContainerList operation.
type ResContainerList struct { type ResContainerList struct {
statusRes
ids []cid.ID ids []cid.ID
} }
@ -314,17 +293,11 @@ func (x ResContainerList) Containers() []cid.ID {
// ContainerList requests identifiers of the account-owned containers. // ContainerList requests identifiers of the account-owned containers.
// //
// Exactly one return value is non-nil. By default, server status is returned in res structure. // Any errors (local or remote, including returned status codes) are returned as Go errors,
// Any client's internal or transport errors are returned as `error`. // see [apistatus] package for NeoFS-specific error types.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// //
// Immediately panics if parameters are set incorrectly (see PrmContainerList docs). // Immediately panics if parameters are set incorrectly (see PrmContainerList docs).
// Context is required and must not be nil. It is used for network communication. // Context is required and must not be nil. It is used for network communication.
//
// Return statuses:
// - global (see Client docs).
func (c *Client) ContainerList(ctx context.Context, prm PrmContainerList) (*ResContainerList, error) { func (c *Client) ContainerList(ctx context.Context, prm PrmContainerList) (*ResContainerList, error) {
// check parameters // check parameters
switch { switch {
@ -356,7 +329,6 @@ func (c *Client) ContainerList(ctx context.Context, prm PrmContainerList) (*ResC
c.initCallContext(&cc) c.initCallContext(&cc)
cc.meta = prm.prmCommonMeta cc.meta = prm.prmCommonMeta
cc.req = &req cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) { cc.call = func() (responseV2, error) {
return rpcapi.ListContainers(&c.c, &req, client.WithContext(ctx)) return rpcapi.ListContainers(&c.c, &req, client.WithContext(ctx))
} }
@ -420,18 +392,10 @@ func (x *PrmContainerDelete) WithinSession(tok session.Container) {
x.tokSet = true x.tokSet = true
} }
// ResContainerDelete groups resulting values of ContainerDelete operation.
type ResContainerDelete struct {
statusRes
}
// ContainerDelete sends request to remove the NeoFS container. // ContainerDelete sends request to remove the NeoFS container.
// //
// Exactly one return value is non-nil. By default, server status is returned in res structure. // Any errors (local or remote, including returned status codes) are returned as Go errors,
// Any client's internal or transport errors are returned as `error`. // see [apistatus] package for NeoFS-specific error types.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// //
// Operation is asynchronous and no guaranteed even in the absence of errors. // Operation is asynchronous and no guaranteed even in the absence of errors.
// The required time is also not predictable. // The required time is also not predictable.
@ -441,12 +405,8 @@ type ResContainerDelete struct {
// Immediately panics if parameters are set incorrectly (see PrmContainerDelete docs). // Immediately panics if parameters are set incorrectly (see PrmContainerDelete docs).
// Context is required and must not be nil. It is used for network communication. // Context is required and must not be nil. It is used for network communication.
// //
// Exactly one return value is non-nil. Server status return is returned in ResContainerDelete.
// Reflects all internal errors in second return value (transport problems, response processing, etc.). // Reflects all internal errors in second return value (transport problems, response processing, etc.).
// func (c *Client) ContainerDelete(ctx context.Context, prm PrmContainerDelete) error {
// Return statuses:
// - global (see Client docs).
func (c *Client) ContainerDelete(ctx context.Context, prm PrmContainerDelete) (*ResContainerDelete, error) {
// check parameters // check parameters
switch { switch {
case ctx == nil: case ctx == nil:
@ -471,7 +431,7 @@ func (c *Client) ContainerDelete(ctx context.Context, prm PrmContainerDelete) (*
err := sig.Calculate(signer, data) err := sig.Calculate(signer, data)
if err != nil { if err != nil {
return nil, fmt.Errorf("calculate signature: %w", err) return fmt.Errorf("calculate signature: %w", err)
} }
var sigv2 refs.Signature var sigv2 refs.Signature
@ -504,22 +464,20 @@ func (c *Client) ContainerDelete(ctx context.Context, prm PrmContainerDelete) (*
var ( var (
cc contextCall cc contextCall
res ResContainerDelete
) )
c.initCallContext(&cc) c.initCallContext(&cc)
cc.req = &req cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) { cc.call = func() (responseV2, error) {
return rpcapi.DeleteContainer(&c.c, &req, client.WithContext(ctx)) return rpcapi.DeleteContainer(&c.c, &req, client.WithContext(ctx))
} }
// process call // process call
if !cc.processCall() { if !cc.processCall() {
return nil, cc.err return cc.err
} }
return &res, nil return nil
} }
// PrmContainerEACL groups parameters of ContainerEACL operation. // PrmContainerEACL groups parameters of ContainerEACL operation.
@ -539,8 +497,6 @@ func (x *PrmContainerEACL) SetContainer(id cid.ID) {
// ResContainerEACL groups resulting values of ContainerEACL operation. // ResContainerEACL groups resulting values of ContainerEACL operation.
type ResContainerEACL struct { type ResContainerEACL struct {
statusRes
table eacl.Table table eacl.Table
} }
@ -551,19 +507,11 @@ func (x ResContainerEACL) Table() eacl.Table {
// ContainerEACL reads eACL table of the NeoFS container. // ContainerEACL reads eACL table of the NeoFS container.
// //
// Exactly one return value is non-nil. By default, server status is returned in res structure. // Any errors (local or remote, including returned status codes) are returned as Go errors,
// Any client's internal or transport errors are returned as `error`. // see [apistatus] package for NeoFS-specific error types.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// //
// Immediately panics if parameters are set incorrectly (see PrmContainerEACL docs). // Immediately panics if parameters are set incorrectly (see PrmContainerEACL docs).
// Context is required and must not be nil. It is used for network communication. // Context is required and must not be nil. It is used for network communication.
//
// Return statuses:
// - global (see Client docs);
// - *apistatus.ContainerNotFound;
// - *apistatus.EACLNotFound.
func (c *Client) ContainerEACL(ctx context.Context, prm PrmContainerEACL) (*ResContainerEACL, error) { func (c *Client) ContainerEACL(ctx context.Context, prm PrmContainerEACL) (*ResContainerEACL, error) {
// check parameters // check parameters
switch { switch {
@ -595,7 +543,6 @@ func (c *Client) ContainerEACL(ctx context.Context, prm PrmContainerEACL) (*ResC
c.initCallContext(&cc) c.initCallContext(&cc)
cc.meta = prm.prmCommonMeta cc.meta = prm.prmCommonMeta
cc.req = &req cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) { cc.call = func() (responseV2, error) {
return rpcapi.GetEACL(&c.c, &req, client.WithContext(ctx)) return rpcapi.GetEACL(&c.c, &req, client.WithContext(ctx))
} }
@ -662,18 +609,10 @@ func (x *PrmContainerSetEACL) WithinSession(s session.Container) {
x.sessionSet = true x.sessionSet = true
} }
// ResContainerSetEACL groups resulting values of ContainerSetEACL operation.
type ResContainerSetEACL struct {
statusRes
}
// ContainerSetEACL sends request to update eACL table of the NeoFS container. // ContainerSetEACL sends request to update eACL table of the NeoFS container.
// //
// Exactly one return value is non-nil. By default, server status is returned in res structure. // Any errors (local or remote, including returned status codes) are returned as Go errors,
// Any client's internal or transport errors are returned as `error`. // see [apistatus] package for NeoFS-specific error types.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// //
// Operation is asynchronous and no guaranteed even in the absence of errors. // Operation is asynchronous and no guaranteed even in the absence of errors.
// The required time is also not predictable. // The required time is also not predictable.
@ -682,10 +621,7 @@ type ResContainerSetEACL struct {
// //
// Immediately panics if parameters are set incorrectly (see PrmContainerSetEACL docs). // Immediately panics if parameters are set incorrectly (see PrmContainerSetEACL docs).
// Context is required and must not be nil. It is used for network communication. // Context is required and must not be nil. It is used for network communication.
// func (c *Client) ContainerSetEACL(ctx context.Context, prm PrmContainerSetEACL) error {
// Return statuses:
// - global (see Client docs).
func (c *Client) ContainerSetEACL(ctx context.Context, prm PrmContainerSetEACL) (*ResContainerSetEACL, error) {
// check parameters // check parameters
switch { switch {
case ctx == nil: case ctx == nil:
@ -710,7 +646,7 @@ func (c *Client) ContainerSetEACL(ctx context.Context, prm PrmContainerSetEACL)
err := sig.Calculate(signer, eaclV2.StableMarshal(nil)) err := sig.Calculate(signer, eaclV2.StableMarshal(nil))
if err != nil { if err != nil {
return nil, fmt.Errorf("calculate signature: %w", err) return fmt.Errorf("calculate signature: %w", err)
} }
var sigv2 refs.Signature var sigv2 refs.Signature
@ -743,22 +679,20 @@ func (c *Client) ContainerSetEACL(ctx context.Context, prm PrmContainerSetEACL)
var ( var (
cc contextCall cc contextCall
res ResContainerSetEACL
) )
c.initCallContext(&cc) c.initCallContext(&cc)
cc.req = &req cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) { cc.call = func() (responseV2, error) {
return rpcapi.SetEACL(&c.c, &req, client.WithContext(ctx)) return rpcapi.SetEACL(&c.c, &req, client.WithContext(ctx))
} }
// process call // process call
if !cc.processCall() { if !cc.processCall() {
return nil, cc.err return cc.err
} }
return &res, nil return nil
} }
// PrmAnnounceSpace groups parameters of ContainerAnnounceUsedSpace operation. // PrmAnnounceSpace groups parameters of ContainerAnnounceUsedSpace operation.
@ -776,18 +710,10 @@ func (x *PrmAnnounceSpace) SetValues(vs []container.SizeEstimation) {
x.announcements = vs x.announcements = vs
} }
// ResAnnounceSpace groups resulting values of ContainerAnnounceUsedSpace operation.
type ResAnnounceSpace struct {
statusRes
}
// ContainerAnnounceUsedSpace sends request to announce volume of the space used for the container objects. // ContainerAnnounceUsedSpace sends request to announce volume of the space used for the container objects.
// //
// Exactly one return value is non-nil. By default, server status is returned in res structure. // Any errors (local or remote, including returned status codes) are returned as Go errors,
// Any client's internal or transport errors are returned as `error`. // see [apistatus] package for NeoFS-specific error types.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// //
// Operation is asynchronous and no guaranteed even in the absence of errors. // Operation is asynchronous and no guaranteed even in the absence of errors.
// The required time is also not predictable. // The required time is also not predictable.
@ -796,10 +722,7 @@ type ResAnnounceSpace struct {
// //
// Immediately panics if parameters are set incorrectly (see PrmAnnounceSpace docs). // Immediately panics if parameters are set incorrectly (see PrmAnnounceSpace docs).
// Context is required and must not be nil. It is used for network communication. // Context is required and must not be nil. It is used for network communication.
// func (c *Client) ContainerAnnounceUsedSpace(ctx context.Context, prm PrmAnnounceSpace) error {
// Return statuses:
// - global (see Client docs).
func (c *Client) ContainerAnnounceUsedSpace(ctx context.Context, prm PrmAnnounceSpace) (*ResAnnounceSpace, error) {
// check parameters // check parameters
switch { switch {
case ctx == nil: case ctx == nil:
@ -827,23 +750,21 @@ func (c *Client) ContainerAnnounceUsedSpace(ctx context.Context, prm PrmAnnounce
var ( var (
cc contextCall cc contextCall
res ResAnnounceSpace
) )
c.initCallContext(&cc) c.initCallContext(&cc)
cc.meta = prm.prmCommonMeta cc.meta = prm.prmCommonMeta
cc.req = &req cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) { cc.call = func() (responseV2, error) {
return rpcapi.AnnounceUsedSpace(&c.c, &req, client.WithContext(ctx)) return rpcapi.AnnounceUsedSpace(&c.c, &req, client.WithContext(ctx))
} }
// process call // process call
if !cc.processCall() { if !cc.processCall() {
return nil, cc.err return cc.err
} }
return &res, nil return nil
} }
// SyncContainerWithNetwork requests network configuration using passed client // SyncContainerWithNetwork requests network configuration using passed client

View file

@ -1,71 +0,0 @@
package client_test
import (
"fmt"
"testing"
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
"github.com/stretchr/testify/require"
)
func TestErrors(t *testing.T) {
for _, tc := range []struct {
errs []error
errVariable error
}{
{
errs: []error{
apistatus.ContainerNotFound{},
new(apistatus.ContainerNotFound),
},
errVariable: apistatus.ErrContainerNotFound,
},
{
errs: []error{
apistatus.EACLNotFound{},
new(apistatus.EACLNotFound),
},
errVariable: apistatus.ErrEACLNotFound,
},
{
errs: []error{
apistatus.ObjectNotFound{},
new(apistatus.ObjectNotFound),
},
errVariable: apistatus.ErrObjectNotFound,
},
{
errs: []error{
apistatus.ObjectAlreadyRemoved{},
new(apistatus.ObjectAlreadyRemoved),
},
errVariable: apistatus.ErrObjectAlreadyRemoved,
},
{
errs: []error{
apistatus.SessionTokenExpired{},
new(apistatus.SessionTokenExpired),
},
errVariable: apistatus.ErrSessionTokenExpired,
}, {
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.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

@ -36,7 +36,6 @@ func ExampleClient_ContainerPut() {
// prepare client // prepare client
var prmInit client.PrmInit var prmInit client.PrmInit
prmInit.SetDefaultSigner(signer) // private signer for request signing prmInit.SetDefaultSigner(signer) // private signer for request signing
prmInit.ResolveNeoFSFailures() // enable erroneous status parsing
var c client.Client var c client.Client
c.Init(prmInit) c.Init(prmInit)

View file

@ -8,7 +8,6 @@ import (
rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc" rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc"
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client" "github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
v2session "github.com/nspcc-dev/neofs-api-go/v2/session" v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
"github.com/nspcc-dev/neofs-sdk-go/netmap" "github.com/nspcc-dev/neofs-sdk-go/netmap"
"github.com/nspcc-dev/neofs-sdk-go/version" "github.com/nspcc-dev/neofs-sdk-go/version"
) )
@ -20,8 +19,6 @@ type PrmEndpointInfo struct {
// ResEndpointInfo group resulting values of EndpointInfo operation. // ResEndpointInfo group resulting values of EndpointInfo operation.
type ResEndpointInfo struct { type ResEndpointInfo struct {
statusRes
version version.Version version version.Version
ni netmap.NodeInfo ni netmap.NodeInfo
@ -41,19 +38,14 @@ func (x ResEndpointInfo) NodeInfo() netmap.NodeInfo {
// //
// Method can be used as a health check to see if node is alive and responds to requests. // Method can be used as a health check to see if node is alive and responds to requests.
// //
// Any client's internal or transport errors are returned as `error`. // Any client's internal or transport errors are returned as `error`,
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful // see [apistatus] package for NeoFS-specific error types.
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// //
// Immediately panics if parameters are set incorrectly (see PrmEndpointInfo docs). // Immediately panics if parameters are set incorrectly (see PrmEndpointInfo docs).
// Context is required and must not be nil. It is used for network communication. // Context is required and must not be nil. It is used for network communication.
// //
// Exactly one return value is non-nil. Server status return is returned in ResEndpointInfo. // Exactly one return value is non-nil. Server status return is returned in ResEndpointInfo.
// Reflects all internal errors in second return value (transport problems, response processing, etc.). // Reflects all internal errors in second return value (transport problems, response processing, etc.).
//
// Return statuses:
// - global (see Client docs).
func (c *Client) EndpointInfo(ctx context.Context, prm PrmEndpointInfo) (*ResEndpointInfo, error) { func (c *Client) EndpointInfo(ctx context.Context, prm PrmEndpointInfo) (*ResEndpointInfo, error) {
// check context // check context
if ctx == nil { if ctx == nil {
@ -73,7 +65,6 @@ func (c *Client) EndpointInfo(ctx context.Context, prm PrmEndpointInfo) (*ResEnd
c.initCallContext(&cc) c.initCallContext(&cc)
cc.meta = prm.prmCommonMeta cc.meta = prm.prmCommonMeta
cc.req = &req cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) { cc.call = func() (responseV2, error) {
return rpcapi.LocalNodeInfo(&c.c, &req, client.WithContext(ctx)) return rpcapi.LocalNodeInfo(&c.c, &req, client.WithContext(ctx))
} }
@ -126,8 +117,6 @@ type PrmNetworkInfo struct {
// ResNetworkInfo groups resulting values of NetworkInfo operation. // ResNetworkInfo groups resulting values of NetworkInfo operation.
type ResNetworkInfo struct { type ResNetworkInfo struct {
statusRes
info netmap.NetworkInfo info netmap.NetworkInfo
} }
@ -138,19 +127,14 @@ func (x ResNetworkInfo) Info() netmap.NetworkInfo {
// NetworkInfo requests information about the NeoFS network of which the remote server is a part. // NetworkInfo requests information about the NeoFS network of which the remote server is a part.
// //
// Any client's internal or transport errors are returned as `error`. // Any client's internal or transport errors are returned as `error`,
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful // see [apistatus] package for NeoFS-specific error types.
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// //
// Immediately panics if parameters are set incorrectly (see PrmNetworkInfo docs). // Immediately panics if parameters are set incorrectly (see PrmNetworkInfo docs).
// Context is required and must not be nil. It is used for network communication. // Context is required and must not be nil. It is used for network communication.
// //
// Exactly one return value is non-nil. Server status return is returned in ResNetworkInfo. // Exactly one return value is non-nil. Server status return is returned in ResNetworkInfo.
// Reflects all internal errors in second return value (transport problems, response processing, etc.). // Reflects all internal errors in second return value (transport problems, response processing, etc.).
//
// Return statuses:
// - global (see Client docs).
func (c *Client) NetworkInfo(ctx context.Context, prm PrmNetworkInfo) (*ResNetworkInfo, error) { func (c *Client) NetworkInfo(ctx context.Context, prm PrmNetworkInfo) (*ResNetworkInfo, error) {
// check context // check context
if ctx == nil { if ctx == nil {
@ -170,7 +154,6 @@ func (c *Client) NetworkInfo(ctx context.Context, prm PrmNetworkInfo) (*ResNetwo
c.initCallContext(&cc) c.initCallContext(&cc)
cc.meta = prm.prmCommonMeta cc.meta = prm.prmCommonMeta
cc.req = &req cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) { cc.call = func() (responseV2, error) {
return rpcapi.NetworkInfo(&c.c, &req, client.WithContext(ctx)) return rpcapi.NetworkInfo(&c.c, &req, client.WithContext(ctx))
} }
@ -206,8 +189,6 @@ type PrmNetMapSnapshot struct {
// ResNetMapSnapshot groups resulting values of NetMapSnapshot operation. // ResNetMapSnapshot groups resulting values of NetMapSnapshot operation.
type ResNetMapSnapshot struct { type ResNetMapSnapshot struct {
statusRes
netMap netmap.NetMap netMap netmap.NetMap
} }
@ -218,18 +199,13 @@ func (x ResNetMapSnapshot) NetMap() netmap.NetMap {
// NetMapSnapshot requests current network view of the remote server. // NetMapSnapshot requests current network view of the remote server.
// //
// Any client's internal or transport errors are returned as `error`. // Any client's internal or transport errors are returned as `error`,
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful // see [apistatus] package for NeoFS-specific error types.
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// //
// Context is required and MUST NOT be nil. It is used for network communication. // Context is required and MUST NOT be nil. It is used for network communication.
// //
// Exactly one return value is non-nil. Server status return is returned in ResNetMapSnapshot. // Exactly one return value is non-nil. Server status return is returned in ResNetMapSnapshot.
// Reflects all internal errors in second return value (transport problems, response processing, etc.). // Reflects all internal errors in second return value (transport problems, response processing, etc.).
//
// Return statuses:
// - global (see Client docs).
func (c *Client) NetMapSnapshot(ctx context.Context, _ PrmNetMapSnapshot) (*ResNetMapSnapshot, error) { func (c *Client) NetMapSnapshot(ctx context.Context, _ PrmNetMapSnapshot) (*ResNetMapSnapshot, error) {
// check context // check context
if ctx == nil { if ctx == nil {
@ -258,15 +234,11 @@ func (c *Client) NetMapSnapshot(ctx context.Context, _ PrmNetMapSnapshot) (*ResN
} }
var res ResNetMapSnapshot var res ResNetMapSnapshot
res.st, err = c.processResponse(resp) _, err = c.processResponse(resp)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !apistatus.IsSuccessful(res.st) {
return &res, nil
}
const fieldNetMap = "network map" const fieldNetMap = "network map"
netMapV2 := resp.GetBody().NetMap() netMapV2 := resp.GetBody().NetMap()

View file

@ -102,10 +102,10 @@ func TestClient_NetMapSnapshot(t *testing.T) {
srv.signResponse = true srv.signResponse = true
// status failure // failure error
res, err = c.NetMapSnapshot(ctx, prm) _, err = c.NetMapSnapshot(ctx, prm)
require.NoError(t, err) require.Error(t, err)
assertStatusErr(t, res) require.ErrorIs(t, err, apistatus.ErrServerInternal)
srv.statusOK = true srv.statusOK = true
@ -145,6 +145,5 @@ func TestClient_NetMapSnapshot(t *testing.T) {
res, err = c.NetMapSnapshot(ctx, prm) res, err = c.NetMapSnapshot(ctx, prm)
require.NoError(t, err) require.NoError(t, err)
require.True(t, apistatus.IsSuccessful(res.Status()))
require.Equal(t, netMap, res.NetMap()) require.Equal(t, netMap, res.NetMap())
} }

View file

@ -11,7 +11,6 @@ import (
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client" "github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
v2session "github.com/nspcc-dev/neofs-api-go/v2/session" v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
"github.com/nspcc-dev/neofs-sdk-go/bearer" "github.com/nspcc-dev/neofs-sdk-go/bearer"
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id" cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
oid "github.com/nspcc-dev/neofs-sdk-go/object/id" oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
@ -95,8 +94,6 @@ func (x *PrmObjectDelete) WithXHeaders(hs ...string) {
// ResObjectDelete groups resulting values of ObjectDelete operation. // ResObjectDelete groups resulting values of ObjectDelete operation.
type ResObjectDelete struct { type ResObjectDelete struct {
statusRes
tomb oid.ID tomb oid.ID
} }
@ -115,19 +112,17 @@ func (x ResObjectDelete) Tombstone() oid.ID {
// //
// Exactly one return value is non-nil. By default, server status is returned in res structure. // Exactly one return value is non-nil. By default, server status is returned in res structure.
// Any client's internal or transport errors are returned as `error`, // Any client's internal or transport errors are returned as `error`,
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful // see [apistatus] package for NeoFS-specific error types.
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// //
// Immediately panics if parameters are set incorrectly (see PrmObjectDelete docs). // Immediately panics if parameters are set incorrectly (see PrmObjectDelete docs).
// Context is required and must not be nil. It is used for network communication. // Context is required and must not be nil. It is used for network communication.
// //
// Return statuses: // Return errors:
// - global (see Client docs) // - global (see Client docs)
// - *apistatus.ContainerNotFound; // - [apistatus.ErrContainerNotFound];
// - *apistatus.ObjectAccessDenied; // - [apistatus.ErrObjectAccessDenied];
// - *apistatus.ObjectLocked; // - [apistatus.ErrObjectLocked];
// - *apistatus.SessionTokenExpired. // - [apistatus.ErrSessionTokenExpired].
func (c *Client) ObjectDelete(ctx context.Context, prm PrmObjectDelete) (*ResObjectDelete, error) { func (c *Client) ObjectDelete(ctx context.Context, prm PrmObjectDelete) (*ResObjectDelete, error) {
switch { switch {
case ctx == nil: case ctx == nil:
@ -162,15 +157,11 @@ func (c *Client) ObjectDelete(ctx context.Context, prm PrmObjectDelete) (*ResObj
} }
var res ResObjectDelete var res ResObjectDelete
res.st, err = c.processResponse(resp) _, err = c.processResponse(resp)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !apistatus.IsSuccessful(res.st) {
return &res, nil
}
const fieldTombstone = "tombstone" const fieldTombstone = "tombstone"
idTombV2 := resp.GetBody().GetTombstone().GetObjectID() idTombV2 := resp.GetBody().GetTombstone().GetObjectID()

View file

@ -13,7 +13,6 @@ import (
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client" "github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
v2session "github.com/nspcc-dev/neofs-api-go/v2/session" v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
"github.com/nspcc-dev/neofs-sdk-go/bearer" "github.com/nspcc-dev/neofs-sdk-go/bearer"
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id" cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
"github.com/nspcc-dev/neofs-sdk-go/object" "github.com/nspcc-dev/neofs-sdk-go/object"
@ -100,11 +99,6 @@ type PrmObjectGet struct {
signer neofscrypto.Signer signer neofscrypto.Signer
} }
// ResObjectGet groups the final result values of ObjectGetInit operation.
type ResObjectGet struct {
statusRes
}
// ObjectReader is designed to read one object from NeoFS system. // ObjectReader is designed to read one object from NeoFS system.
// //
// Must be initialized using Client.ObjectGetInit, any other // Must be initialized using Client.ObjectGetInit, any other
@ -117,7 +111,6 @@ type ObjectReader struct {
Read(resp *v2object.GetResponse) error Read(resp *v2object.GetResponse) error
} }
res ResObjectGet
err error err error
tailPayload []byte tailPayload []byte
@ -140,8 +133,8 @@ func (x *ObjectReader) ReadHeader(dst *object.Object) bool {
return false return false
} }
x.res.st, x.err = x.client.processResponse(&resp) _, x.err = x.client.processResponse(&resp)
if x.err != nil || !apistatus.IsSuccessful(x.res.st) { if x.err != nil {
return false return false
} }
@ -193,8 +186,8 @@ func (x *ObjectReader) readChunk(buf []byte) (int, bool) {
return read, false return read, false
} }
x.res.st, x.err = x.client.processResponse(&resp) _, x.err = x.client.processResponse(&resp)
if x.err != nil || !apistatus.IsSuccessful(x.res.st) { if x.err != nil {
return read, false return read, false
} }
@ -233,44 +226,40 @@ func (x *ObjectReader) ReadChunk(buf []byte) (int, bool) {
return x.readChunk(buf) return x.readChunk(buf)
} }
func (x *ObjectReader) close(ignoreEOF bool) (*ResObjectGet, error) { func (x *ObjectReader) close(ignoreEOF bool) error {
defer x.cancelCtxStream() defer x.cancelCtxStream()
if x.err != nil { if x.err != nil {
if !errors.Is(x.err, io.EOF) { if !errors.Is(x.err, io.EOF) {
return nil, x.err return x.err
} else if !ignoreEOF { } else if !ignoreEOF {
if x.remainingPayloadLen > 0 { if x.remainingPayloadLen > 0 {
return nil, io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
return nil, io.EOF return io.EOF
} }
} }
return &x.res, nil return nil
} }
// Close ends reading the object and returns the result of the operation // Close ends reading the object and returns the result of the operation
// along with the final results. Must be called after using the ObjectReader. // along with the final results. Must be called after using the ObjectReader.
// //
// Exactly one return value is non-nil. By default, server status is returned in res structure.
// Any client's internal or transport errors are returned as Go built-in error. // Any client's internal or transport errors are returned as Go built-in error.
// If Client is tuned to resolve NeoFS API statuses, then NeoFS failures // If Client is tuned to resolve NeoFS API statuses, then NeoFS failures
// codes are returned as error. // codes are returned as error.
// //
// Return errors: // Return errors:
//
// *object.SplitInfoError (returned on virtual objects with PrmObjectGet.MakeRaw).
//
// Return statuses:
// - global (see Client docs); // - global (see Client docs);
// - *apistatus.ContainerNotFound; // - *[object.SplitInfoError] (returned on virtual objects with PrmObjectGet.MakeRaw).
// - *apistatus.ObjectNotFound; // - [apistatus.ErrContainerNotFound];
// - *apistatus.ObjectAccessDenied; // - [apistatus.ErrObjectNotFound];
// - *apistatus.ObjectAlreadyRemoved; // - [apistatus.ErrObjectAccessDenied];
// - *apistatus.SessionTokenExpired. // - [apistatus.ErrObjectAlreadyRemoved];
func (x *ObjectReader) Close() (*ResObjectGet, error) { // - [apistatus.ErrSessionTokenExpired].
func (x *ObjectReader) Close() error {
return x.close(true) return x.close(true)
} }
@ -281,12 +270,11 @@ func (x *ObjectReader) Read(p []byte) (int, error) {
x.remainingPayloadLen -= n x.remainingPayloadLen -= n
if !ok { if !ok {
res, err := x.close(false) if err := x.close(false); err != nil {
if err != nil {
return n, err return n, err
} }
return n, apistatus.ErrFromStatus(res.Status()) return n, x.err
} }
if x.remainingPayloadLen < 0 { if x.remainingPayloadLen < 0 {
@ -367,8 +355,6 @@ func (x *PrmObjectHead) UseSigner(signer neofscrypto.Signer) {
// ResObjectHead groups resulting values of ObjectHead operation. // ResObjectHead groups resulting values of ObjectHead operation.
type ResObjectHead struct { type ResObjectHead struct {
statusRes
// requested object (response doesn't carry the ID) // requested object (response doesn't carry the ID)
idObj oid.ID idObj oid.ID
@ -399,24 +385,19 @@ func (x *ResObjectHead) ReadHeader(dst *object.Object) bool {
// //
// Exactly one return value is non-nil. By default, server status is returned in res structure. // Exactly one return value is non-nil. By default, server status is returned in res structure.
// Any client's internal or transport errors are returned as `error`, // Any client's internal or transport errors are returned as `error`,
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful // see [apistatus] package for NeoFS-specific error types.
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// //
// Immediately panics if parameters are set incorrectly (see PrmObjectHead docs). // Immediately panics if parameters are set incorrectly (see PrmObjectHead docs).
// Context is required and must not be nil. It is used for network communication. // Context is required and must not be nil. It is used for network communication.
// //
// Return errors: // Return errors:
//
// *object.SplitInfoError (returned on virtual objects with PrmObjectHead.MakeRaw).
//
// Return statuses:
// - global (see Client docs); // - global (see Client docs);
// - *apistatus.ContainerNotFound; // - *[object.SplitInfoError] (returned on virtual objects with PrmObjectHead.MakeRaw).
// - *apistatus.ObjectNotFound; // - [apistatus.ErrContainerNotFound];
// - *apistatus.ObjectAccessDenied; // - [apistatus.ErrObjectNotFound];
// - *apistatus.ObjectAlreadyRemoved; // - [apistatus.ErrObjectAccessDenied];
// - *apistatus.SessionTokenExpired. // - [apistatus.ErrObjectAlreadyRemoved];
// - [apistatus.ErrSessionTokenExpired].
func (c *Client) ObjectHead(ctx context.Context, prm PrmObjectHead) (*ResObjectHead, error) { func (c *Client) ObjectHead(ctx context.Context, prm PrmObjectHead) (*ResObjectHead, error) {
switch { switch {
case ctx == nil: case ctx == nil:
@ -452,15 +433,11 @@ func (c *Client) ObjectHead(ctx context.Context, prm PrmObjectHead) (*ResObjectH
} }
var res ResObjectHead var res ResObjectHead
res.st, err = c.processResponse(resp) _, err = c.processResponse(resp)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !apistatus.IsSuccessful(res.st) {
return &res, nil
}
_ = res.idObj.ReadFromV2(*prm.addr.GetObjectID()) _ = res.idObj.ReadFromV2(*prm.addr.GetObjectID())
switch v := resp.GetBody().GetHeaderPart().(type) { switch v := resp.GetBody().GetHeaderPart().(type) {
@ -508,11 +485,6 @@ func (x *PrmObjectRange) UseSigner(signer neofscrypto.Signer) {
x.signer = signer x.signer = signer
} }
// ResObjectRange groups the final result values of ObjectRange operation.
type ResObjectRange struct {
statusRes
}
// ObjectRangeReader is designed to read payload range of one object // ObjectRangeReader is designed to read payload range of one object
// from NeoFS system. // from NeoFS system.
// //
@ -523,7 +495,6 @@ type ObjectRangeReader struct {
client *Client client *Client
res ResObjectRange
err error err error
stream interface { stream interface {
@ -558,8 +529,8 @@ func (x *ObjectRangeReader) readChunk(buf []byte) (int, bool) {
return read, false return read, false
} }
x.res.st, x.err = x.client.processResponse(&resp) _, x.err = x.client.processResponse(&resp)
if x.err != nil || !apistatus.IsSuccessful(x.res.st) { if x.err != nil {
return read, false return read, false
} }
@ -602,45 +573,41 @@ func (x *ObjectRangeReader) ReadChunk(buf []byte) (int, bool) {
return x.readChunk(buf) return x.readChunk(buf)
} }
func (x *ObjectRangeReader) close(ignoreEOF bool) (*ResObjectRange, error) { func (x *ObjectRangeReader) close(ignoreEOF bool) error {
defer x.cancelCtxStream() defer x.cancelCtxStream()
if x.err != nil { if x.err != nil {
if !errors.Is(x.err, io.EOF) { if !errors.Is(x.err, io.EOF) {
return nil, x.err return x.err
} else if !ignoreEOF { } else if !ignoreEOF {
if x.remainingPayloadLen > 0 { if x.remainingPayloadLen > 0 {
return nil, io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
return nil, io.EOF return io.EOF
} }
} }
return &x.res, nil return nil
} }
// Close ends reading the payload range and returns the result of the operation // Close ends reading the payload range and returns the result of the operation
// along with the final results. Must be called after using the ObjectRangeReader. // along with the final results. Must be called after using the ObjectRangeReader.
// //
// Exactly one return value is non-nil. By default, server status is returned in res structure.
// Any client's internal or transport errors are returned as Go built-in error. // Any client's internal or transport errors are returned as Go built-in error.
// If Client is tuned to resolve NeoFS API statuses, then NeoFS failures // If Client is tuned to resolve NeoFS API statuses, then NeoFS failures
// codes are returned as error. // codes are returned as error.
// //
// Return errors: // Return errors:
//
// *object.SplitInfoError (returned on virtual objects with PrmObjectRange.MakeRaw).
//
// Return statuses:
// - global (see Client docs); // - global (see Client docs);
// - *apistatus.ContainerNotFound; // - *[object.SplitInfoError] (returned on virtual objects with PrmObjectRange.MakeRaw).
// - *apistatus.ObjectNotFound; // - [apistatus.ErrContainerNotFound];
// - *apistatus.ObjectAccessDenied; // - [apistatus.ErrObjectNotFound];
// - *apistatus.ObjectAlreadyRemoved; // - [apistatus.ErrObjectAccessDenied];
// - *apistatus.ObjectOutOfRange; // - [apistatus.ErrObjectAlreadyRemoved];
// - *apistatus.SessionTokenExpired. // - [apistatus.ErrObjectOutOfRange];
func (x *ObjectRangeReader) Close() (*ResObjectRange, error) { // - [apistatus.ErrSessionTokenExpired].
func (x *ObjectRangeReader) Close() error {
return x.close(true) return x.close(true)
} }
@ -651,12 +618,12 @@ func (x *ObjectRangeReader) Read(p []byte) (int, error) {
x.remainingPayloadLen -= n x.remainingPayloadLen -= n
if !ok { if !ok {
res, err := x.close(false) err := x.close(false)
if err != nil { if err != nil {
return n, err return n, err
} }
return n, apistatus.ErrFromStatus(res.Status()) return n, x.err
} }
if x.remainingPayloadLen < 0 { if x.remainingPayloadLen < 0 {

View file

@ -11,7 +11,6 @@ import (
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client" "github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
v2session "github.com/nspcc-dev/neofs-api-go/v2/session" v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
"github.com/nspcc-dev/neofs-sdk-go/bearer" "github.com/nspcc-dev/neofs-sdk-go/bearer"
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id" cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
oid "github.com/nspcc-dev/neofs-sdk-go/object/id" oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
@ -135,8 +134,6 @@ func (x *PrmObjectHash) WithXHeaders(hs ...string) {
// ResObjectHash groups resulting values of ObjectHash operation. // ResObjectHash groups resulting values of ObjectHash operation.
type ResObjectHash struct { type ResObjectHash struct {
statusRes
checksums [][]byte checksums [][]byte
} }
@ -153,20 +150,10 @@ func (x ResObjectHash) Checksums() [][]byte {
// //
// Exactly one return value is non-nil. By default, server status is returned in res structure. // Exactly one return value is non-nil. By default, server status is returned in res structure.
// Any client's internal or transport errors are returned as `error`, // Any client's internal or transport errors are returned as `error`,
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful // see [apistatus] package for NeoFS-specific error types.
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// //
// Immediately panics if parameters are set incorrectly (see PrmObjectHash docs). // Immediately panics if parameters are set incorrectly (see PrmObjectHash docs).
// Context is required and must not be nil. It is used for network communication. // Context is required and must not be nil. It is used for network communication.
//
// Return statuses:
// - global (see Client docs);
// - *apistatus.ContainerNotFound;
// - *apistatus.ObjectNotFound;
// - *apistatus.ObjectAccessDenied;
// - *apistatus.ObjectOutOfRange;
// - *apistatus.SessionTokenExpired.
func (c *Client) ObjectHash(ctx context.Context, prm PrmObjectHash) (*ResObjectHash, error) { func (c *Client) ObjectHash(ctx context.Context, prm PrmObjectHash) (*ResObjectHash, error) {
switch { switch {
case ctx == nil: case ctx == nil:
@ -206,15 +193,11 @@ func (c *Client) ObjectHash(ctx context.Context, prm PrmObjectHash) (*ResObjectH
} }
var res ResObjectHash var res ResObjectHash
res.st, err = c.processResponse(resp) _, err = c.processResponse(resp)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !apistatus.IsSuccessful(res.st) {
return &res, nil
}
res.checksums = resp.GetBody().GetHashList() res.checksums = resp.GetBody().GetHashList()
if len(res.checksums) == 0 { if len(res.checksums) == 0 {
return nil, newErrMissingResponseField("hash list") return nil, newErrMissingResponseField("hash list")

View file

@ -36,8 +36,6 @@ func (x *PrmObjectPutInit) SetCopiesNumber(copiesNumber uint32) {
// ResObjectPut groups the final result values of ObjectPutInit operation. // ResObjectPut groups the final result values of ObjectPutInit operation.
type ResObjectPut struct { type ResObjectPut struct {
statusRes
obj oid.ID obj oid.ID
} }
@ -186,14 +184,14 @@ func (x *ObjectWriter) WritePayloadChunk(chunk []byte) bool {
// If Client is tuned to resolve NeoFS API statuses, then NeoFS failures // If Client is tuned to resolve NeoFS API statuses, then NeoFS failures
// codes are returned as error. // codes are returned as error.
// //
// Return statuses: // Return errors:
// - global (see Client docs); // - global (see Client docs);
// - *apistatus.ContainerNotFound; // - [apistatus.ErrContainerNotFound];
// - *apistatus.ObjectAccessDenied; // - [apistatus.ErrObjectAccessDenied];
// - *apistatus.ObjectLocked; // - [apistatus.ErrObjectLocked];
// - *apistatus.LockNonRegularObject; // - [apistatus.ErrLockNonRegularObject];
// - *apistatus.SessionTokenNotFound; // - [apistatus.ErrSessionTokenNotFound];
// - *apistatus.SessionTokenExpired. // - [apistatus.ErrSessionTokenExpired].
func (x *ObjectWriter) Close() (*ResObjectPut, error) { func (x *ObjectWriter) Close() (*ResObjectPut, error) {
defer x.cancelCtxStream() defer x.cancelCtxStream()
@ -208,15 +206,11 @@ func (x *ObjectWriter) Close() (*ResObjectPut, error) {
return nil, x.err return nil, x.err
} }
x.res.st, x.err = x.client.processResponse(&x.respV2) _, x.err = x.client.processResponse(&x.respV2)
if x.err != nil { if x.err != nil {
return nil, x.err return nil, x.err
} }
if !apistatus.IsSuccessful(x.res.st) {
return &x.res, nil
}
const fieldID = "ID" const fieldID = "ID"
idV2 := x.respV2.GetBody().GetObjectID() idV2 := x.respV2.GetBody().GetObjectID()
@ -291,7 +285,7 @@ func (x *objectWriter) InitDataStream(header object.Object) (io.Writer, error) {
return nil, err return nil, err
} }
return nil, apistatus.ErrFromStatus(res.Status()) return nil, apistatus.ErrFromStatus(res)
} }
type payloadWriter struct { type payloadWriter struct {
@ -312,7 +306,7 @@ func (x *payloadWriter) Close() error {
return err return err
} }
return apistatus.ErrFromStatus(res.Status()) return apistatus.ErrFromStatus(res)
} }
// CreateObject creates new NeoFS object with given payload data and stores it // CreateObject creates new NeoFS object with given payload data and stores it

View file

@ -13,7 +13,6 @@ import (
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client" "github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
v2session "github.com/nspcc-dev/neofs-api-go/v2/session" v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
"github.com/nspcc-dev/neofs-sdk-go/bearer" "github.com/nspcc-dev/neofs-sdk-go/bearer"
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id" cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
"github.com/nspcc-dev/neofs-sdk-go/object" "github.com/nspcc-dev/neofs-sdk-go/object"
@ -88,11 +87,6 @@ func (x *PrmObjectSearch) SetFilters(filters object.SearchFilters) {
x.filters = filters x.filters = filters
} }
// ResObjectSearch groups the final result values of ObjectSearch operation.
type ResObjectSearch struct {
statusRes
}
// ObjectListReader is designed to read list of object identifiers from NeoFS system. // ObjectListReader is designed to read list of object identifiers from NeoFS system.
// //
// Must be initialized using Client.ObjectSearch, any other usage is unsafe. // Must be initialized using Client.ObjectSearch, any other usage is unsafe.
@ -100,7 +94,6 @@ type ObjectListReader struct {
client *Client client *Client
cancelCtxStream context.CancelFunc cancelCtxStream context.CancelFunc
err error err error
res ResObjectSearch
stream interface { stream interface {
Read(resp *v2object.SearchResponse) error Read(resp *v2object.SearchResponse) error
} }
@ -132,8 +125,8 @@ func (x *ObjectListReader) Read(buf []oid.ID) (int, bool) {
return read, false return read, false
} }
x.res.st, x.err = x.client.processResponse(&resp) _, x.err = x.client.processResponse(&resp)
if x.err != nil || !apistatus.IsSuccessful(x.res.st) { if x.err != nil {
return read, false return read, false
} }
@ -176,11 +169,7 @@ func (x *ObjectListReader) Iterate(f func(oid.ID) bool) error {
// so false means nothing was read. // so false means nothing was read.
_, ok := x.Read(buf) _, ok := x.Read(buf)
if !ok { if !ok {
res, err := x.Close() return x.Close()
if err != nil {
return err
}
return apistatus.ErrFromStatus(res.Status())
} }
if f(buf[0]) { if f(buf[0]) {
return nil return nil
@ -191,24 +180,23 @@ func (x *ObjectListReader) Iterate(f func(oid.ID) bool) error {
// Close ends reading list of the matched objects and returns the result of the operation // Close ends reading list of the matched objects and returns the result of the operation
// along with the final results. Must be called after using the ObjectListReader. // along with the final results. Must be called after using the ObjectListReader.
// //
// Exactly one return value is non-nil. By default, server status is returned in res structure.
// Any client's internal or transport errors are returned as Go built-in error. // Any client's internal or transport errors are returned as Go built-in error.
// If Client is tuned to resolve NeoFS API statuses, then NeoFS failures // If Client is tuned to resolve NeoFS API statuses, then NeoFS failures
// codes are returned as error. // codes are returned as error.
// //
// Return statuses: // Return errors:
// - global (see Client docs); // - global (see Client docs);
// - *apistatus.ContainerNotFound; // - [apistatus.ErrContainerNotFound];
// - *apistatus.ObjectAccessDenied; // - [apistatus.ErrObjectAccessDenied];
// - *apistatus.SessionTokenExpired. // - [apistatus.ErrSessionTokenExpired].
func (x *ObjectListReader) Close() (*ResObjectSearch, error) { func (x *ObjectListReader) Close() error {
defer x.cancelCtxStream() defer x.cancelCtxStream()
if x.err != nil && !errors.Is(x.err, io.EOF) { if x.err != nil && !errors.Is(x.err, io.EOF) {
return nil, x.err return x.err
} }
return &x.res, nil return nil
} }
// ObjectSearchInit initiates object selection through a remote server using NeoFS API protocol. // ObjectSearchInit initiates object selection through a remote server using NeoFS API protocol.

View file

@ -32,25 +32,14 @@ func (x *PrmAnnounceLocalTrust) SetValues(trusts []reputation.Trust) {
x.trusts = trusts x.trusts = trusts
} }
// ResAnnounceLocalTrust groups results of AnnounceLocalTrust operation.
type ResAnnounceLocalTrust struct {
statusRes
}
// AnnounceLocalTrust sends client's trust values to the NeoFS network participants. // AnnounceLocalTrust sends client's trust values to the NeoFS network participants.
// //
// Exactly one return value is non-nil. By default, server status is returned in res structure. // Any errors (local or remote, including returned status codes) are returned as Go errors,
// Any client's internal or transport errors are returned as `error`. // see [apistatus] package for NeoFS-specific error types.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// //
// Immediately panics if parameters are set incorrectly (see PrmAnnounceLocalTrust docs). // Immediately panics if parameters are set incorrectly (see PrmAnnounceLocalTrust docs).
// Context is required and must not be nil. It is used for network communication. // Context is required and must not be nil. It is used for network communication.
// func (c *Client) AnnounceLocalTrust(ctx context.Context, prm PrmAnnounceLocalTrust) error {
// Return statuses:
// - global (see Client docs).
func (c *Client) AnnounceLocalTrust(ctx context.Context, prm PrmAnnounceLocalTrust) (*ResAnnounceLocalTrust, error) {
// check parameters // check parameters
switch { switch {
case ctx == nil: case ctx == nil:
@ -82,23 +71,21 @@ func (c *Client) AnnounceLocalTrust(ctx context.Context, prm PrmAnnounceLocalTru
var ( var (
cc contextCall cc contextCall
res ResAnnounceLocalTrust
) )
c.initCallContext(&cc) c.initCallContext(&cc)
cc.meta = prm.prmCommonMeta cc.meta = prm.prmCommonMeta
cc.req = &req cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) { cc.call = func() (responseV2, error) {
return rpcapi.AnnounceLocalTrust(&c.c, &req, client.WithContext(ctx)) return rpcapi.AnnounceLocalTrust(&c.c, &req, client.WithContext(ctx))
} }
// process call // process call
if !cc.processCall() { if !cc.processCall() {
return nil, cc.err return cc.err
} }
return &res, nil return nil
} }
// PrmAnnounceIntermediateTrust groups parameters of AnnounceIntermediateTrust operation. // PrmAnnounceIntermediateTrust groups parameters of AnnounceIntermediateTrust operation.
@ -132,26 +119,15 @@ func (x *PrmAnnounceIntermediateTrust) SetCurrentValue(trust reputation.PeerToPe
x.trustSet = true x.trustSet = true
} }
// ResAnnounceIntermediateTrust groups results of AnnounceIntermediateTrust operation.
type ResAnnounceIntermediateTrust struct {
statusRes
}
// AnnounceIntermediateTrust sends global trust values calculated for the specified NeoFS network participants // AnnounceIntermediateTrust sends global trust values calculated for the specified NeoFS network participants
// at some stage of client's calculation algorithm. // at some stage of client's calculation algorithm.
// //
// Exactly one return value is non-nil. By default, server status is returned in res structure. // Any errors (local or remote, including returned status codes) are returned as Go errors,
// Any client's internal or transport errors are returned as `error`. // see [apistatus] package for NeoFS-specific error types.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// //
// Immediately panics if parameters are set incorrectly (see PrmAnnounceIntermediateTrust docs). // Immediately panics if parameters are set incorrectly (see PrmAnnounceIntermediateTrust docs).
// Context is required and must not be nil. It is used for network communication. // Context is required and must not be nil. It is used for network communication.
// func (c *Client) AnnounceIntermediateTrust(ctx context.Context, prm PrmAnnounceIntermediateTrust) error {
// Return statuses:
// - global (see Client docs).
func (c *Client) AnnounceIntermediateTrust(ctx context.Context, prm PrmAnnounceIntermediateTrust) (*ResAnnounceIntermediateTrust, error) {
// check parameters // check parameters
switch { switch {
case ctx == nil: case ctx == nil:
@ -180,21 +156,19 @@ func (c *Client) AnnounceIntermediateTrust(ctx context.Context, prm PrmAnnounceI
var ( var (
cc contextCall cc contextCall
res ResAnnounceIntermediateTrust
) )
c.initCallContext(&cc) c.initCallContext(&cc)
cc.meta = prm.prmCommonMeta cc.meta = prm.prmCommonMeta
cc.req = &req cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) { cc.call = func() (responseV2, error) {
return rpcapi.AnnounceIntermediateResult(&c.c, &req, client.WithContext(ctx)) return rpcapi.AnnounceIntermediateResult(&c.c, &req, client.WithContext(ctx))
} }
// process call // process call
if !cc.processCall() { if !cc.processCall() {
return nil, cc.err return cc.err
} }
return &res, nil return nil
} }

View file

@ -32,8 +32,6 @@ func (x *PrmSessionCreate) UseSigner(signer neofscrypto.Signer) {
// ResSessionCreate groups resulting values of SessionCreate operation. // ResSessionCreate groups resulting values of SessionCreate operation.
type ResSessionCreate struct { type ResSessionCreate struct {
statusRes
id []byte id []byte
sessionKey []byte sessionKey []byte
@ -63,17 +61,11 @@ func (x ResSessionCreate) PublicKey() []byte {
// The session lifetime coincides with the server lifetime. Results can be written // The session lifetime coincides with the server lifetime. Results can be written
// to session token which can be later attached to the requests. // to session token which can be later attached to the requests.
// //
// Exactly one return value is non-nil. By default, server status is returned in res structure. // Any errors (local or remote, including returned status codes) are returned as Go errors,
// Any client's internal or transport errors are returned as `error`. // see [apistatus] package for NeoFS-specific error types.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// //
// Immediately panics if parameters are set incorrectly (see PrmSessionCreate docs). // Immediately panics if parameters are set incorrectly (see PrmSessionCreate docs).
// Context is required and must not be nil. It is used for network communication. // Context is required and must not be nil. It is used for network communication.
//
// Return statuses:
// - global (see Client docs).
func (c *Client) SessionCreate(ctx context.Context, prm PrmSessionCreate) (*ResSessionCreate, error) { func (c *Client) SessionCreate(ctx context.Context, prm PrmSessionCreate) (*ResSessionCreate, error) {
// check context // check context
if ctx == nil { if ctx == nil {
@ -113,7 +105,6 @@ func (c *Client) SessionCreate(ctx context.Context, prm PrmSessionCreate) (*ResS
cc.meta = prm.prmCommonMeta cc.meta = prm.prmCommonMeta
cc.req = &req cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) { cc.call = func() (responseV2, error) {
return c.server.createSession(&c.c, &req, client.WithContext(ctx)) return c.server.createSession(&c.c, &req, client.WithContext(ctx))
} }

View file

@ -41,7 +41,6 @@ func TestClient_SessionCreate(t *testing.T) {
var prmInit PrmInit var prmInit PrmInit
prmInit.SetDefaultSigner(signer) prmInit.SetDefaultSigner(signer)
prmInit.ResolveNeoFSFailures()
var c Client var c Client
c.Init(prmInit) c.Init(prmInit)

View file

@ -2,10 +2,30 @@ package apistatus
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"github.com/nspcc-dev/neofs-api-go/v2/status" "github.com/nspcc-dev/neofs-api-go/v2/status"
) )
// Error describes common error which is a grouping type for any [apistatus] errors. Any [apistatus] error may be checked
// explicitly via it's type of just check the group via errors.Is(err, [apistatus.Error]).
var Error = errors.New("api error")
var (
// ErrServerInternal is an instance of ServerInternal error status. It's expected to be used for [errors.Is]
// and MUST NOT be changed.
ErrServerInternal ServerInternal
// ErrWrongMagicNumber is an instance of WrongMagicNumber error status. It's expected to be used for [errors.Is]
// and MUST NOT be changed.
ErrWrongMagicNumber WrongMagicNumber
// ErrSignatureVerification is an instance of SignatureVerification error status. It's expected to be used for [errors.Is]
// and MUST NOT be changed.
ErrSignatureVerification SignatureVerification
// ErrNodeUnderMaintenance is an instance of NodeUnderMaintenance error status. It's expected to be used for [errors.Is]
// and MUST NOT be changed.
ErrNodeUnderMaintenance NodeUnderMaintenance
)
// ServerInternal describes failure statuses related to internal server errors. // ServerInternal describes failure statuses related to internal server errors.
// Instances provide [Status], [StatusV2] and error interfaces. // Instances provide [Status], [StatusV2] and error interfaces.
// //
@ -21,6 +41,16 @@ func (x ServerInternal) Error() string {
) )
} }
// Is implements interface for correct checking current error type with [errors.Is].
func (x ServerInternal) Is(target error) bool {
switch target.(type) {
default:
return errors.Is(Error, target)
case ServerInternal, *ServerInternal:
return true
}
}
// implements local interface defined in FromStatusV2 func. // implements local interface defined in FromStatusV2 func.
func (x *ServerInternal) fromStatusV2(st *status.Status) { func (x *ServerInternal) fromStatusV2(st *status.Status) {
x.v2 = *st x.v2 = *st
@ -69,6 +99,16 @@ func (x WrongMagicNumber) Error() string {
) )
} }
// Is implements interface for correct checking current error type with [errors.Is].
func (x WrongMagicNumber) Is(target error) bool {
switch target.(type) {
default:
return errors.Is(Error, target)
case WrongMagicNumber, *WrongMagicNumber:
return true
}
}
// implements local interface defined in FromStatusV2 func. // implements local interface defined in FromStatusV2 func.
func (x *WrongMagicNumber) fromStatusV2(st *status.Status) { func (x *WrongMagicNumber) fromStatusV2(st *status.Status) {
x.v2 = *st x.v2 = *st
@ -144,6 +184,16 @@ func (x SignatureVerification) Error() string {
) )
} }
// Is implements interface for correct checking current error type with [errors.Is].
func (x SignatureVerification) Is(target error) bool {
switch target.(type) {
default:
return errors.Is(Error, target)
case SignatureVerification, *SignatureVerification:
return true
}
}
// implements local interface defined in FromStatusV2 func. // implements local interface defined in FromStatusV2 func.
func (x *SignatureVerification) fromStatusV2(st *status.Status) { func (x *SignatureVerification) fromStatusV2(st *status.Status) {
x.v2 = *st x.v2 = *st
@ -203,6 +253,16 @@ func (x NodeUnderMaintenance) Error() string {
) )
} }
// Is implements interface for correct checking current error type with [errors.Is].
func (x NodeUnderMaintenance) Is(target error) bool {
switch target.(type) {
default:
return errors.Is(Error, target)
case NodeUnderMaintenance, *NodeUnderMaintenance:
return true
}
}
func (x *NodeUnderMaintenance) fromStatusV2(st *status.Status) { func (x *NodeUnderMaintenance) fromStatusV2(st *status.Status) {
x.v2 = *st x.v2 = *st
} }

View file

@ -1,6 +1,8 @@
package apistatus package apistatus
import ( import (
"errors"
"github.com/nspcc-dev/neofs-api-go/v2/container" "github.com/nspcc-dev/neofs-api-go/v2/container"
"github.com/nspcc-dev/neofs-api-go/v2/status" "github.com/nspcc-dev/neofs-api-go/v2/status"
) )
@ -38,7 +40,7 @@ func (x ContainerNotFound) Error() string {
func (x ContainerNotFound) Is(target error) bool { func (x ContainerNotFound) Is(target error) bool {
switch target.(type) { switch target.(type) {
default: default:
return false return errors.Is(Error, target)
case ContainerNotFound, *ContainerNotFound: case ContainerNotFound, *ContainerNotFound:
return true return true
} }
@ -86,7 +88,7 @@ func (x EACLNotFound) Error() string {
func (x EACLNotFound) Is(target error) bool { func (x EACLNotFound) Is(target error) bool {
switch target.(type) { switch target.(type) {
default: default:
return false return errors.Is(Error, target)
case EACLNotFound, *EACLNotFound: case EACLNotFound, *EACLNotFound:
return true return true
} }

View file

@ -0,0 +1,93 @@
package apistatus
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
)
func TestErrors(t *testing.T) {
for _, tc := range []struct {
errs []error
errVariable error
}{
{
errs: []error{ServerInternal{}, new(ServerInternal)},
errVariable: ErrServerInternal,
},
{
errs: []error{WrongMagicNumber{}, new(WrongMagicNumber)},
errVariable: ErrWrongMagicNumber,
},
{
errs: []error{SignatureVerification{}, new(SignatureVerification)},
errVariable: ErrSignatureVerification,
},
{
errs: []error{NodeUnderMaintenance{}, new(NodeUnderMaintenance)},
errVariable: ErrNodeUnderMaintenance,
},
{
errs: []error{ObjectLocked{}, new(ObjectLocked)},
errVariable: ErrObjectLocked,
},
{
errs: []error{LockNonRegularObject{}, new(LockNonRegularObject)},
errVariable: ErrLockNonRegularObject,
},
{
errs: []error{ObjectAccessDenied{}, new(ObjectAccessDenied)},
errVariable: ErrObjectAccessDenied,
},
{
errs: []error{ObjectNotFound{}, new(ObjectNotFound)},
errVariable: ErrObjectNotFound,
},
{
errs: []error{ObjectAlreadyRemoved{}, new(ObjectAlreadyRemoved)},
errVariable: ErrObjectAlreadyRemoved,
},
{
errs: []error{ObjectOutOfRange{}, new(ObjectOutOfRange)},
errVariable: ErrObjectOutOfRange,
},
{
errs: []error{ContainerNotFound{}, new(ContainerNotFound)},
errVariable: ErrContainerNotFound,
},
{
errs: []error{EACLNotFound{}, new(EACLNotFound)},
errVariable: ErrEACLNotFound,
},
{
errs: []error{SessionTokenExpired{}, new(SessionTokenExpired)},
errVariable: ErrSessionTokenExpired,
},
{
errs: []error{SessionTokenNotFound{}, new(SessionTokenNotFound)},
errVariable: ErrSessionTokenNotFound,
},
{
errs: []error{UnrecognizedStatusV2{}, new(UnrecognizedStatusV2)},
errVariable: ErrUnrecognizedStatusV2,
},
} {
require.NotEmpty(t, tc.errs)
require.NotNil(t, tc.errVariable)
for i := range tc.errs {
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

@ -1,17 +1,31 @@
package apistatus package apistatus
import ( import (
"errors"
"github.com/nspcc-dev/neofs-api-go/v2/object" "github.com/nspcc-dev/neofs-api-go/v2/object"
"github.com/nspcc-dev/neofs-api-go/v2/status" "github.com/nspcc-dev/neofs-api-go/v2/status"
) )
var ( var (
// ErrObjectLocked is an instance of ObjectLocked error status. It's expected to be used for [errors.Is]
// and MUST NOT be changed.
ErrObjectLocked ObjectLocked
// ErrObjectAlreadyRemoved is an instance of ObjectAlreadyRemoved error status. It's expected to be used for [errors.Is] // ErrObjectAlreadyRemoved is an instance of ObjectAlreadyRemoved error status. It's expected to be used for [errors.Is]
// and MUST NOT be changed. // and MUST NOT be changed.
ErrObjectAlreadyRemoved ObjectAlreadyRemoved ErrObjectAlreadyRemoved ObjectAlreadyRemoved
// ErrLockNonRegularObject is an instance of LockNonRegularObject error status. It's expected to be used for [errors.Is]
// and MUST NOT be changed.
ErrLockNonRegularObject LockNonRegularObject
// ErrObjectAccessDenied is an instance of ObjectAccessDenied error status. It's expected to be used for [errors.Is]
// and MUST NOT be changed.
ErrObjectAccessDenied ObjectAccessDenied
// ErrObjectNotFound is an instance of ObjectNotFound error status. It's expected to be used for [errors.Is] // ErrObjectNotFound is an instance of ObjectNotFound error status. It's expected to be used for [errors.Is]
// and MUST NOT be changed. // and MUST NOT be changed.
ErrObjectNotFound ObjectNotFound ErrObjectNotFound ObjectNotFound
// ErrObjectOutOfRange is an instance of ObjectOutOfRange error status. It's expected to be used for [errors.Is]
// and MUST NOT be changed.
ErrObjectOutOfRange ObjectOutOfRange
) )
// ObjectLocked describes status of the failure because of the locked object. // ObjectLocked describes status of the failure because of the locked object.
@ -34,6 +48,16 @@ func (x ObjectLocked) Error() string {
) )
} }
// Is implements interface for correct checking current error type with [errors.Is].
func (x ObjectLocked) Is(target error) bool {
switch target.(type) {
default:
return errors.Is(Error, target)
case ObjectLocked, *ObjectLocked:
return true
}
}
// implements local interface defined in FromStatusV2 func. // implements local interface defined in FromStatusV2 func.
func (x *ObjectLocked) fromStatusV2(st *status.Status) { func (x *ObjectLocked) fromStatusV2(st *status.Status) {
x.v2 = *st x.v2 = *st
@ -71,6 +95,16 @@ func (x LockNonRegularObject) Error() string {
) )
} }
// Is implements interface for correct checking current error type with [errors.Is].
func (x LockNonRegularObject) Is(target error) bool {
switch target.(type) {
default:
return errors.Is(Error, target)
case LockNonRegularObject, *LockNonRegularObject:
return true
}
}
// implements local interface defined in FromStatusV2 func. // implements local interface defined in FromStatusV2 func.
func (x *LockNonRegularObject) fromStatusV2(st *status.Status) { func (x *LockNonRegularObject) fromStatusV2(st *status.Status) {
x.v2 = *st x.v2 = *st
@ -108,6 +142,16 @@ func (x ObjectAccessDenied) Error() string {
) )
} }
// Is implements interface for correct checking current error type with [errors.Is].
func (x ObjectAccessDenied) Is(target error) bool {
switch target.(type) {
default:
return errors.Is(Error, target)
case ObjectAccessDenied, *ObjectAccessDenied:
return true
}
}
// implements local interface defined in FromStatusV2 func. // implements local interface defined in FromStatusV2 func.
func (x *ObjectAccessDenied) fromStatusV2(st *status.Status) { func (x *ObjectAccessDenied) fromStatusV2(st *status.Status) {
x.v2 = *st x.v2 = *st
@ -160,7 +204,7 @@ func (x ObjectNotFound) Error() string {
func (x ObjectNotFound) Is(target error) bool { func (x ObjectNotFound) Is(target error) bool {
switch target.(type) { switch target.(type) {
default: default:
return false return errors.Is(Error, target)
case ObjectNotFound, *ObjectNotFound: case ObjectNotFound, *ObjectNotFound:
return true return true
} }
@ -207,7 +251,7 @@ func (x ObjectAlreadyRemoved) Error() string {
func (x ObjectAlreadyRemoved) Is(target error) bool { func (x ObjectAlreadyRemoved) Is(target error) bool {
switch target.(type) { switch target.(type) {
default: default:
return false return errors.Is(Error, target)
case ObjectAlreadyRemoved, *ObjectAlreadyRemoved: case ObjectAlreadyRemoved, *ObjectAlreadyRemoved:
return true return true
} }
@ -251,6 +295,16 @@ func (x ObjectOutOfRange) Error() string {
) )
} }
// Is implements interface for correct checking current error type with [errors.Is].
func (x ObjectOutOfRange) Is(target error) bool {
switch target.(type) {
default:
return errors.Is(Error, target)
case ObjectOutOfRange, *ObjectOutOfRange:
return true
}
}
// implements local interface defined in FromStatusV2 func. // implements local interface defined in FromStatusV2 func.
func (x *ObjectOutOfRange) fromStatusV2(st *status.Status) { func (x *ObjectOutOfRange) fromStatusV2(st *status.Status) {
x.v2 = *st x.v2 = *st

View file

@ -1,6 +1,8 @@
package apistatus package apistatus
import ( import (
"errors"
"github.com/nspcc-dev/neofs-api-go/v2/session" "github.com/nspcc-dev/neofs-api-go/v2/session"
"github.com/nspcc-dev/neofs-api-go/v2/status" "github.com/nspcc-dev/neofs-api-go/v2/status"
) )
@ -38,7 +40,7 @@ func (x SessionTokenNotFound) Error() string {
func (x SessionTokenNotFound) Is(target error) bool { func (x SessionTokenNotFound) Is(target error) bool {
switch target.(type) { switch target.(type) {
default: default:
return false return errors.Is(Error, target)
case SessionTokenNotFound, *SessionTokenNotFound: case SessionTokenNotFound, *SessionTokenNotFound:
return true return true
} }
@ -85,7 +87,7 @@ func (x SessionTokenExpired) Error() string {
func (x SessionTokenExpired) Is(target error) bool { func (x SessionTokenExpired) Is(target error) bool {
switch target.(type) { switch target.(type) {
default: default:
return false return errors.Is(Error, target)
case SessionTokenExpired, *SessionTokenExpired: case SessionTokenExpired, *SessionTokenExpired:
return true return true
} }

View file

@ -19,7 +19,7 @@ func TestErrors(t *testing.T) {
res := apistatus.ErrFromStatus(st) res := apistatus.ErrFromStatus(st)
require.Equal(t, err, res) require.ErrorIs(t, res, err)
}) })
t.Run("non-error source", func(t *testing.T) { t.Run("non-error source", func(t *testing.T) {

View file

@ -4,15 +4,31 @@ import (
"github.com/nspcc-dev/neofs-api-go/v2/status" "github.com/nspcc-dev/neofs-api-go/v2/status"
) )
type unrecognizedStatusV2 struct { // ErrUnrecognizedStatusV2 is an instance of UnrecognizedStatusV2 error status. It's expected to be used for [errors.Is]
// and MUST NOT be changed.
var ErrUnrecognizedStatusV2 UnrecognizedStatusV2
// UnrecognizedStatusV2 describes status of the uncertain failure.
// Instances provide [Status], [StatusV2] and error interfaces.
type UnrecognizedStatusV2 struct {
v2 status.Status v2 status.Status
} }
func (x unrecognizedStatusV2) Error() string { func (x UnrecognizedStatusV2) Error() string {
return errMessageStatusV2("unrecognized", x.v2.Message()) return errMessageStatusV2("unrecognized", x.v2.Message())
} }
// Is implements interface for correct checking current error type with [errors.Is].
func (x UnrecognizedStatusV2) Is(target error) bool {
switch target.(type) {
default:
return false
case UnrecognizedStatusV2, *UnrecognizedStatusV2:
return true
}
}
// implements local interface defined in FromStatusV2 func. // implements local interface defined in FromStatusV2 func.
func (x *unrecognizedStatusV2) fromStatusV2(st *status.Status) { func (x *UnrecognizedStatusV2) fromStatusV2(st *status.Status) {
x.v2 = *st x.v2 = *st
} }

View file

@ -28,16 +28,29 @@ type StatusV2 interface {
// Note: notice if the return type is a pointer. // Note: notice if the return type is a pointer.
// //
// Successes: // Successes:
// - status.OK: *SuccessDefaultV2 (this also includes nil argument). // - [status.OK]: *[SuccessDefaultV2] (this also includes nil argument).
// //
// Common failures: // Common failures:
// - status.Internal: *ServerInternal; // - [status.Internal]: *[ServerInternal];
// - status.SignatureVerificationFail: *SignatureVerification. // - [status.SignatureVerificationFail]: *[SignatureVerification].
// - [status.WrongMagicNumber]: *[WrongMagicNumber].
// - [status.NodeUnderMaintenance]: *[NodeUnderMaintenance].
// //
// Object failures: // Object failures:
// - object.StatusLocked: *ObjectLocked; // - [object.StatusLocked]: *[ObjectLocked];
// - object.StatusLockNonRegularObject: *LockNonRegularObject. // - [object.StatusLockNonRegularObject]: *[LockNonRegularObject].
// - object.StatusAccessDenied: *ObjectAccessDenied. // - [object.StatusAccessDenied]: *[ObjectAccessDenied].
// - [object.StatusNotFound]: *[ObjectNotFound].
// - [object.StatusAlreadyRemoved]: *[ObjectAlreadyRemoved].
// - [object.StatusOutOfRange]: *[ObjectOutOfRange].
//
// Container failures:
// - [container.StatusNotFound]: *[ContainerNotFound];
// - [container.StatusEACLNotFound]: *[EACLNotFound];
//
// Session failures:
// - [session.StatusTokenNotFound]: *[SessionTokenNotFound];
// - [session.StatusTokenExpired]: *[SessionTokenExpired];
func FromStatusV2(st *status.Status) Status { func FromStatusV2(st *status.Status) Status {
var decoder interface { var decoder interface {
fromStatusV2(*status.Status) fromStatusV2(*status.Status)
@ -95,7 +108,7 @@ func FromStatusV2(st *status.Status) Status {
} }
if decoder == nil { if decoder == nil {
decoder = new(unrecognizedStatusV2) decoder = new(UnrecognizedStatusV2)
} }
decoder.fromStatusV2(st) decoder.fromStatusV2(st)

View file

@ -8,159 +8,6 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestToStatusV2(t *testing.T) {
type statusConstructor func() apistatus.Status
for _, testItem := range [...]struct {
status any // Status or statusConstructor
codeV2 uint64
messageV2 string
}{
{
status: errors.New("some error"),
codeV2: 1024,
messageV2: "some error",
},
{
status: 1,
codeV2: 0,
},
{
status: "text",
codeV2: 0,
},
{
status: true,
codeV2: 0,
},
{
status: true,
codeV2: 0,
},
{
status: nil,
codeV2: 0,
},
{
status: (statusConstructor)(func() apistatus.Status {
var st apistatus.ServerInternal
st.SetMessage("internal error message")
return st
}),
codeV2: 1024,
},
{
status: (statusConstructor)(func() apistatus.Status {
var st apistatus.WrongMagicNumber
st.WriteCorrectMagic(322)
return st
}),
codeV2: 1025,
},
{
status: (statusConstructor)(func() apistatus.Status {
return new(apistatus.ObjectLocked)
}),
codeV2: 2050,
},
{
status: (statusConstructor)(func() apistatus.Status {
return new(apistatus.LockNonRegularObject)
}),
codeV2: 2051,
},
{
status: (statusConstructor)(func() apistatus.Status {
var st apistatus.ObjectAccessDenied
st.WriteReason("any reason")
return st
}),
codeV2: 2048,
},
{
status: (statusConstructor)(func() apistatus.Status {
return new(apistatus.ObjectNotFound)
}),
codeV2: 2049,
},
{
status: (statusConstructor)(func() apistatus.Status {
return new(apistatus.ObjectAlreadyRemoved)
}),
codeV2: 2052,
},
{
status: (statusConstructor)(func() apistatus.Status {
return new(apistatus.ObjectOutOfRange)
}),
codeV2: 2053,
},
{
status: (statusConstructor)(func() apistatus.Status {
return new(apistatus.ContainerNotFound)
}),
codeV2: 3072,
},
{
status: (statusConstructor)(func() apistatus.Status {
return new(apistatus.EACLNotFound)
}),
codeV2: 3073,
},
{
status: (statusConstructor)(func() apistatus.Status {
return new(apistatus.SessionTokenNotFound)
}),
codeV2: 4096,
},
{
status: (statusConstructor)(func() apistatus.Status {
return new(apistatus.SessionTokenExpired)
}),
codeV2: 4097,
},
{
status: (statusConstructor)(func() apistatus.Status {
return new(apistatus.NodeUnderMaintenance)
}),
codeV2: 1027,
},
} {
var st apistatus.Status
if cons, ok := testItem.status.(statusConstructor); ok {
st = cons()
} else {
st = testItem.status
}
stv2 := apistatus.ToStatusV2(st)
// must generate the same status.Status message
require.EqualValues(t, testItem.codeV2, stv2.Code())
if len(testItem.messageV2) > 0 {
require.Equal(t, testItem.messageV2, stv2.Message())
}
_, ok := st.(apistatus.StatusV2)
if ok {
// restore and convert again
restored := apistatus.FromStatusV2(stv2)
res := apistatus.ToStatusV2(restored)
// must generate the same status.Status message
require.Equal(t, stv2, res)
}
}
}
func TestFromStatusV2(t *testing.T) { func TestFromStatusV2(t *testing.T) {
type statusConstructor func() apistatus.Status type statusConstructor func() apistatus.Status
@ -168,6 +15,8 @@ func TestFromStatusV2(t *testing.T) {
status any // Status or statusConstructor status any // Status or statusConstructor
codeV2 uint64 codeV2 uint64
messageV2 string messageV2 string
compatibleErrs []error
checkAsErr func(error) bool
}{ }{
{ {
status: errors.New("some error"), status: errors.New("some error"),
@ -183,7 +32,7 @@ func TestFromStatusV2(t *testing.T) {
codeV2: 0, codeV2: 0,
}, },
{ {
status: true, status: false,
codeV2: 0, codeV2: 0,
}, },
{ {
@ -196,93 +45,155 @@ func TestFromStatusV2(t *testing.T) {
}, },
{ {
status: (statusConstructor)(func() apistatus.Status { status: (statusConstructor)(func() apistatus.Status {
var st apistatus.ServerInternal st := new(apistatus.ServerInternal)
st.SetMessage("internal error message") st.SetMessage("internal error message")
return st return st
}), }),
codeV2: 1024, codeV2: 1024,
compatibleErrs: []error{apistatus.ErrServerInternal, apistatus.ServerInternal{}, &apistatus.ServerInternal{}, apistatus.Error},
checkAsErr: func(err error) bool {
var target *apistatus.ServerInternal
return errors.As(err, &target)
},
}, },
{ {
status: (statusConstructor)(func() apistatus.Status { status: (statusConstructor)(func() apistatus.Status {
var st apistatus.WrongMagicNumber st := new(apistatus.WrongMagicNumber)
st.WriteCorrectMagic(322) st.WriteCorrectMagic(322)
return st return st
}), }),
codeV2: 1025, codeV2: 1025,
compatibleErrs: []error{apistatus.ErrWrongMagicNumber, apistatus.WrongMagicNumber{}, &apistatus.WrongMagicNumber{}, apistatus.Error},
checkAsErr: func(err error) bool {
var target *apistatus.WrongMagicNumber
return errors.As(err, &target)
},
}, },
{ {
status: (statusConstructor)(func() apistatus.Status { status: (statusConstructor)(func() apistatus.Status {
return new(apistatus.ObjectLocked) return new(apistatus.ObjectLocked)
}), }),
codeV2: 2050, codeV2: 2050,
compatibleErrs: []error{apistatus.ErrObjectLocked, apistatus.ObjectLocked{}, &apistatus.ObjectLocked{}, apistatus.Error},
checkAsErr: func(err error) bool {
var target *apistatus.ObjectLocked
return errors.As(err, &target)
},
}, },
{ {
status: (statusConstructor)(func() apistatus.Status { status: (statusConstructor)(func() apistatus.Status {
return new(apistatus.LockNonRegularObject) return new(apistatus.LockNonRegularObject)
}), }),
codeV2: 2051, codeV2: 2051,
compatibleErrs: []error{apistatus.ErrLockNonRegularObject, apistatus.LockNonRegularObject{}, &apistatus.LockNonRegularObject{}, apistatus.Error},
checkAsErr: func(err error) bool {
var target *apistatus.LockNonRegularObject
return errors.As(err, &target)
},
}, },
{ {
status: (statusConstructor)(func() apistatus.Status { status: (statusConstructor)(func() apistatus.Status {
var st apistatus.ObjectAccessDenied st := new(apistatus.ObjectAccessDenied)
st.WriteReason("any reason") st.WriteReason("any reason")
return st return st
}), }),
codeV2: 2048, codeV2: 2048,
compatibleErrs: []error{apistatus.ErrObjectAccessDenied, apistatus.ObjectAccessDenied{}, &apistatus.ObjectAccessDenied{}, apistatus.Error},
checkAsErr: func(err error) bool {
var target *apistatus.ObjectAccessDenied
return errors.As(err, &target)
},
}, },
{ {
status: (statusConstructor)(func() apistatus.Status { status: (statusConstructor)(func() apistatus.Status {
return new(apistatus.ObjectNotFound) return new(apistatus.ObjectNotFound)
}), }),
codeV2: 2049, codeV2: 2049,
compatibleErrs: []error{apistatus.ErrObjectNotFound, apistatus.ObjectNotFound{}, &apistatus.ObjectNotFound{}, apistatus.Error},
checkAsErr: func(err error) bool {
var target *apistatus.ObjectNotFound
return errors.As(err, &target)
},
}, },
{ {
status: (statusConstructor)(func() apistatus.Status { status: (statusConstructor)(func() apistatus.Status {
return new(apistatus.ObjectAlreadyRemoved) return new(apistatus.ObjectAlreadyRemoved)
}), }),
codeV2: 2052, codeV2: 2052,
compatibleErrs: []error{apistatus.ErrObjectAlreadyRemoved, apistatus.ObjectAlreadyRemoved{}, &apistatus.ObjectAlreadyRemoved{}, apistatus.Error},
checkAsErr: func(err error) bool {
var target *apistatus.ObjectAlreadyRemoved
return errors.As(err, &target)
},
}, },
{ {
status: statusConstructor(func() apistatus.Status { status: statusConstructor(func() apistatus.Status {
return new(apistatus.ObjectOutOfRange) return new(apistatus.ObjectOutOfRange)
}), }),
codeV2: 2053, codeV2: 2053,
compatibleErrs: []error{apistatus.ErrObjectOutOfRange, apistatus.ObjectOutOfRange{}, &apistatus.ObjectOutOfRange{}, apistatus.Error},
checkAsErr: func(err error) bool {
var target *apistatus.ObjectOutOfRange
return errors.As(err, &target)
},
}, },
{ {
status: (statusConstructor)(func() apistatus.Status { status: (statusConstructor)(func() apistatus.Status {
return new(apistatus.ContainerNotFound) return new(apistatus.ContainerNotFound)
}), }),
codeV2: 3072, codeV2: 3072,
compatibleErrs: []error{apistatus.ErrContainerNotFound, apistatus.ContainerNotFound{}, &apistatus.ContainerNotFound{}, apistatus.Error},
checkAsErr: func(err error) bool {
var target *apistatus.ContainerNotFound
return errors.As(err, &target)
},
}, },
{ {
status: (statusConstructor)(func() apistatus.Status { status: (statusConstructor)(func() apistatus.Status {
return new(apistatus.EACLNotFound) return new(apistatus.EACLNotFound)
}), }),
codeV2: 3073, codeV2: 3073,
compatibleErrs: []error{apistatus.ErrEACLNotFound, apistatus.EACLNotFound{}, &apistatus.EACLNotFound{}, apistatus.Error},
checkAsErr: func(err error) bool {
var target *apistatus.EACLNotFound
return errors.As(err, &target)
},
}, },
{ {
status: (statusConstructor)(func() apistatus.Status { status: (statusConstructor)(func() apistatus.Status {
return new(apistatus.SessionTokenNotFound) return new(apistatus.SessionTokenNotFound)
}), }),
codeV2: 4096, codeV2: 4096,
compatibleErrs: []error{apistatus.ErrSessionTokenNotFound, apistatus.SessionTokenNotFound{}, &apistatus.SessionTokenNotFound{}, apistatus.Error},
checkAsErr: func(err error) bool {
var target *apistatus.SessionTokenNotFound
return errors.As(err, &target)
},
}, },
{ {
status: (statusConstructor)(func() apistatus.Status { status: (statusConstructor)(func() apistatus.Status {
return new(apistatus.SessionTokenExpired) return new(apistatus.SessionTokenExpired)
}), }),
codeV2: 4097, codeV2: 4097,
compatibleErrs: []error{apistatus.ErrSessionTokenExpired, apistatus.SessionTokenExpired{}, &apistatus.SessionTokenExpired{}, apistatus.Error},
checkAsErr: func(err error) bool {
var target *apistatus.SessionTokenExpired
return errors.As(err, &target)
},
}, },
{ {
status: (statusConstructor)(func() apistatus.Status { status: (statusConstructor)(func() apistatus.Status {
return new(apistatus.NodeUnderMaintenance) return new(apistatus.NodeUnderMaintenance)
}), }),
codeV2: 1027, codeV2: 1027,
compatibleErrs: []error{apistatus.ErrNodeUnderMaintenance, apistatus.NodeUnderMaintenance{}, &apistatus.NodeUnderMaintenance{}, apistatus.Error},
checkAsErr: func(err error) bool {
var target *apistatus.NodeUnderMaintenance
return errors.As(err, &target)
},
}, },
} { } {
var st apistatus.Status var st apistatus.Status
@ -311,5 +222,17 @@ func TestFromStatusV2(t *testing.T) {
// must generate the same status.Status message // must generate the same status.Status message
require.Equal(t, stv2, res) require.Equal(t, stv2, res)
} }
randomError := errors.New("garbage")
errFromStatus := apistatus.ErrFromStatus(st)
for _, err := range testItem.compatibleErrs {
require.ErrorIs(t, errFromStatus, err)
require.NotErrorIs(t, randomError, err)
}
if testItem.checkAsErr != nil {
require.True(t, testItem.checkAsErr(errFromStatus))
}
} }
} }

View file

@ -28,12 +28,10 @@ FILTER City EQ SPB AND SSD EQ true OR City EQ SPB AND Rating GE 5 AS SPBSSD`,
var p PlacementPolicy var p PlacementPolicy
for _, testCase := range testCases { for _, testCase := range testCases {
require.NoError(t, p.DecodeString(testCase)) require.NoError(t, p.DecodeString(testCase))
var b strings.Builder var b strings.Builder
require.NoError(t, p.WriteStringTo(&b)) require.NoError(t, p.WriteStringTo(&b))
require.Equal(t, testCase, b.String()) require.Equal(t, testCase, b.String())
} }

View file

@ -107,7 +107,9 @@ func (m *mockClient) endpointInfo(context.Context, prmEndpointInfo) (netmap.Node
var ni netmap.NodeInfo var ni netmap.NodeInfo
if m.errorOnEndpointInfo { if m.errorOnEndpointInfo {
return ni, m.handleError(nil, errors.New("error")) err := errors.New("endpoint info")
m.updateErrorRate(err)
return ni, err
} }
ni.SetNetworkEndpoints(m.addr) ni.SetNetworkEndpoints(m.addr)
@ -118,7 +120,9 @@ func (m *mockClient) networkInfo(context.Context, prmNetworkInfo) (netmap.Networ
var ni netmap.NetworkInfo var ni netmap.NetworkInfo
if m.errorOnNetworkInfo { if m.errorOnNetworkInfo {
return ni, m.handleError(nil, errors.New("error")) err := errors.New("network info")
m.updateErrorRate(err)
return ni, err
} }
return ni, nil return ni, nil
@ -139,8 +143,9 @@ func (m *mockClient) objectGet(context.Context, PrmObjectGet) (ResGetObject, err
return res, nil return res, nil
} }
status := apistatus.ErrFromStatus(m.stOnGetObject) err := apistatus.ErrFromStatus(m.stOnGetObject)
return res, m.handleError(status, nil) m.updateErrorRate(err)
return res, err
} }
func (m *mockClient) objectHead(context.Context, PrmObjectHead) (object.Object, error) { func (m *mockClient) objectHead(context.Context, PrmObjectHead) (object.Object, error) {
@ -157,7 +162,9 @@ func (m *mockClient) objectSearch(context.Context, PrmObjectSearch) (ResObjectSe
func (m *mockClient) sessionCreate(context.Context, prmCreateSession) (resCreateSession, error) { func (m *mockClient) sessionCreate(context.Context, prmCreateSession) (resCreateSession, error) {
if m.errorOnCreateSession { if m.errorOnCreateSession {
return resCreateSession{}, m.handleError(nil, errors.New("error")) err := errors.New("create session")
m.updateErrorRate(err)
return resCreateSession{}, err
} }
tok := newToken(m.signer) tok := newToken(m.signer)

View file

@ -374,11 +374,8 @@ func (c *clientWrapper) balanceGet(ctx context.Context, prm PrmBalanceGet) (acco
start := time.Now() start := time.Now()
res, err := cl.BalanceGet(ctx, cliPrm) res, err := cl.BalanceGet(ctx, cliPrm)
c.incRequests(time.Since(start), methodBalanceGet) c.incRequests(time.Since(start), methodBalanceGet)
var st apistatus.Status c.updateErrorRate(err)
if res != nil { if err != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
return accounting.Decimal{}, fmt.Errorf("balance get on client: %w", err) return accounting.Decimal{}, fmt.Errorf("balance get on client: %w", err)
} }
@ -396,11 +393,8 @@ func (c *clientWrapper) containerPut(ctx context.Context, prm PrmContainerPut) (
start := time.Now() start := time.Now()
res, err := cl.ContainerPut(ctx, prm.prmClient) res, err := cl.ContainerPut(ctx, prm.prmClient)
c.incRequests(time.Since(start), methodContainerPut) c.incRequests(time.Since(start), methodContainerPut)
var st apistatus.Status c.updateErrorRate(err)
if res != nil { if err != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
return cid.ID{}, fmt.Errorf("container put on client: %w", err) return cid.ID{}, fmt.Errorf("container put on client: %w", err)
} }
@ -411,7 +405,8 @@ func (c *clientWrapper) containerPut(ctx context.Context, prm PrmContainerPut) (
idCnr := res.ID() idCnr := res.ID()
err = waitForContainerPresence(ctx, c, idCnr, &prm.waitParams) err = waitForContainerPresence(ctx, c, idCnr, &prm.waitParams)
if err = c.handleError(nil, err); err != nil { c.updateErrorRate(err)
if err != nil {
return cid.ID{}, fmt.Errorf("wait container presence on client: %w", err) return cid.ID{}, fmt.Errorf("wait container presence on client: %w", err)
} }
@ -431,11 +426,8 @@ func (c *clientWrapper) containerGet(ctx context.Context, prm PrmContainerGet) (
start := time.Now() start := time.Now()
res, err := cl.ContainerGet(ctx, cliPrm) res, err := cl.ContainerGet(ctx, cliPrm)
c.incRequests(time.Since(start), methodContainerGet) c.incRequests(time.Since(start), methodContainerGet)
var st apistatus.Status c.updateErrorRate(err)
if res != nil { if err != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
return container.Container{}, fmt.Errorf("container get on client: %w", err) return container.Container{}, fmt.Errorf("container get on client: %w", err)
} }
@ -455,11 +447,8 @@ func (c *clientWrapper) containerList(ctx context.Context, prm PrmContainerList)
start := time.Now() start := time.Now()
res, err := cl.ContainerList(ctx, cliPrm) res, err := cl.ContainerList(ctx, cliPrm)
c.incRequests(time.Since(start), methodContainerList) c.incRequests(time.Since(start), methodContainerList)
var st apistatus.Status c.updateErrorRate(err)
if res != nil { if err != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
return nil, fmt.Errorf("container list on client: %w", err) return nil, fmt.Errorf("container list on client: %w", err)
} }
return res.Containers(), nil return res.Containers(), nil
@ -480,13 +469,10 @@ func (c *clientWrapper) containerDelete(ctx context.Context, prm PrmContainerDel
} }
start := time.Now() start := time.Now()
res, err := cl.ContainerDelete(ctx, cliPrm) err = cl.ContainerDelete(ctx, cliPrm)
c.incRequests(time.Since(start), methodContainerDelete) c.incRequests(time.Since(start), methodContainerDelete)
var st apistatus.Status c.updateErrorRate(err)
if res != nil { if err != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
return fmt.Errorf("container delete on client: %w", err) return fmt.Errorf("container delete on client: %w", err)
} }
@ -510,11 +496,8 @@ func (c *clientWrapper) containerEACL(ctx context.Context, prm PrmContainerEACL)
start := time.Now() start := time.Now()
res, err := cl.ContainerEACL(ctx, cliPrm) res, err := cl.ContainerEACL(ctx, cliPrm)
c.incRequests(time.Since(start), methodContainerEACL) c.incRequests(time.Since(start), methodContainerEACL)
var st apistatus.Status c.updateErrorRate(err)
if res != nil { if err != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
return eacl.Table{}, fmt.Errorf("get eacl on client: %w", err) return eacl.Table{}, fmt.Errorf("get eacl on client: %w", err)
} }
@ -537,13 +520,10 @@ func (c *clientWrapper) containerSetEACL(ctx context.Context, prm PrmContainerSe
} }
start := time.Now() start := time.Now()
res, err := cl.ContainerSetEACL(ctx, cliPrm) err = cl.ContainerSetEACL(ctx, cliPrm)
c.incRequests(time.Since(start), methodContainerSetEACL) c.incRequests(time.Since(start), methodContainerSetEACL)
var st apistatus.Status c.updateErrorRate(err)
if res != nil { if err != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
return fmt.Errorf("set eacl on client: %w", err) return fmt.Errorf("set eacl on client: %w", err)
} }
@ -557,7 +537,8 @@ func (c *clientWrapper) containerSetEACL(ctx context.Context, prm PrmContainerSe
} }
err = waitForEACLPresence(ctx, c, cIDp, &prm.table, &prm.waitParams) err = waitForEACLPresence(ctx, c, cIDp, &prm.table, &prm.waitParams)
if err = c.handleError(nil, err); err != nil { c.updateErrorRate(err)
if err != nil {
return fmt.Errorf("wait eacl presence on client: %w", err) return fmt.Errorf("wait eacl presence on client: %w", err)
} }
@ -574,11 +555,8 @@ func (c *clientWrapper) endpointInfo(ctx context.Context, _ prmEndpointInfo) (ne
start := time.Now() start := time.Now()
res, err := cl.EndpointInfo(ctx, sdkClient.PrmEndpointInfo{}) res, err := cl.EndpointInfo(ctx, sdkClient.PrmEndpointInfo{})
c.incRequests(time.Since(start), methodEndpointInfo) c.incRequests(time.Since(start), methodEndpointInfo)
var st apistatus.Status c.updateErrorRate(err)
if res != nil { if err != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
return netmap.NodeInfo{}, fmt.Errorf("endpoint info on client: %w", err) return netmap.NodeInfo{}, fmt.Errorf("endpoint info on client: %w", err)
} }
@ -595,11 +573,8 @@ func (c *clientWrapper) networkInfo(ctx context.Context, _ prmNetworkInfo) (netm
start := time.Now() start := time.Now()
res, err := cl.NetworkInfo(ctx, sdkClient.PrmNetworkInfo{}) res, err := cl.NetworkInfo(ctx, sdkClient.PrmNetworkInfo{})
c.incRequests(time.Since(start), methodNetworkInfo) c.incRequests(time.Since(start), methodNetworkInfo)
var st apistatus.Status c.updateErrorRate(err)
if res != nil { if err != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
return netmap.NetworkInfo{}, fmt.Errorf("network info on client: %w", err) return netmap.NetworkInfo{}, fmt.Errorf("network info on client: %w", err)
} }
@ -628,7 +603,8 @@ func (c *clientWrapper) objectPut(ctx context.Context, prm PrmObjectPut) (oid.ID
start := time.Now() start := time.Now()
wObj, err := cl.ObjectPutInit(ctx, cliPrm) wObj, err := cl.ObjectPutInit(ctx, cliPrm)
c.incRequests(time.Since(start), methodObjectPut) c.incRequests(time.Since(start), methodObjectPut)
if err = c.handleError(nil, err); err != nil { c.updateErrorRate(err)
if err != nil {
return oid.ID{}, fmt.Errorf("init writing on API client: %w", err) return oid.ID{}, fmt.Errorf("init writing on API client: %w", err)
} }
@ -672,17 +648,15 @@ func (c *clientWrapper) objectPut(ctx context.Context, prm PrmObjectPut) (oid.ID
break break
} }
return oid.ID{}, fmt.Errorf("read payload: %w", c.handleError(nil, err)) c.updateErrorRate(err)
return oid.ID{}, fmt.Errorf("read payload: %w", err)
} }
} }
} }
res, err := wObj.Close() res, err := wObj.Close()
var st apistatus.Status c.updateErrorRate(err)
if res != nil { if err != nil { // here err already carries both status and client errors
st = res.Status()
}
if err = c.handleError(st, err); err != nil { // here err already carries both status and client errors
return oid.ID{}, fmt.Errorf("client failure: %w", err) return oid.ID{}, fmt.Errorf("client failure: %w", err)
} }
@ -712,13 +686,10 @@ func (c *clientWrapper) objectDelete(ctx context.Context, prm PrmObjectDelete) e
} }
start := time.Now() start := time.Now()
res, err := cl.ObjectDelete(ctx, cliPrm) _, err = cl.ObjectDelete(ctx, cliPrm)
c.incRequests(time.Since(start), methodObjectDelete) c.incRequests(time.Since(start), methodObjectDelete)
var st apistatus.Status c.updateErrorRate(err)
if res != nil { if err != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
return fmt.Errorf("delete object on client: %w", err) return fmt.Errorf("delete object on client: %w", err)
} }
return nil return nil
@ -749,7 +720,8 @@ func (c *clientWrapper) objectGet(ctx context.Context, prm PrmObjectGet) (ResGet
var res ResGetObject var res ResGetObject
rObj, err := cl.ObjectGetInit(ctx, cliPrm) rObj, err := cl.ObjectGetInit(ctx, cliPrm)
if err = c.handleError(nil, err); err != nil { c.updateErrorRate(err)
if err != nil {
return ResGetObject{}, fmt.Errorf("init object reading on client: %w", err) return ResGetObject{}, fmt.Errorf("init object reading on client: %w", err)
} }
@ -757,12 +729,8 @@ func (c *clientWrapper) objectGet(ctx context.Context, prm PrmObjectGet) (ResGet
successReadHeader := rObj.ReadHeader(&res.Header) successReadHeader := rObj.ReadHeader(&res.Header)
c.incRequests(time.Since(start), methodObjectGet) c.incRequests(time.Since(start), methodObjectGet)
if !successReadHeader { if !successReadHeader {
rObjRes, err := rObj.Close() err = rObj.Close()
var st apistatus.Status c.updateErrorRate(err)
if rObjRes != nil {
st = rObjRes.Status()
}
err = c.handleError(st, err)
return res, fmt.Errorf("read header: %w", err) return res, fmt.Errorf("read header: %w", err)
} }
@ -806,11 +774,8 @@ func (c *clientWrapper) objectHead(ctx context.Context, prm PrmObjectHead) (obje
start := time.Now() start := time.Now()
res, err := cl.ObjectHead(ctx, cliPrm) res, err := cl.ObjectHead(ctx, cliPrm)
c.incRequests(time.Since(start), methodObjectHead) c.incRequests(time.Since(start), methodObjectHead)
var st apistatus.Status c.updateErrorRate(err)
if res != nil { if err != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
return obj, fmt.Errorf("read object header via client: %w", err) return obj, fmt.Errorf("read object header via client: %w", err)
} }
if !res.ReadHeader(&obj) { if !res.ReadHeader(&obj) {
@ -846,7 +811,8 @@ func (c *clientWrapper) objectRange(ctx context.Context, prm PrmObjectRange) (Re
start := time.Now() start := time.Now()
res, err := cl.ObjectRangeInit(ctx, cliPrm) res, err := cl.ObjectRangeInit(ctx, cliPrm)
c.incRequests(time.Since(start), methodObjectRange) c.incRequests(time.Since(start), methodObjectRange)
if err = c.handleError(nil, err); err != nil { c.updateErrorRate(err)
if err != nil {
return ResObjectRange{}, fmt.Errorf("init payload range reading on client: %w", err) return ResObjectRange{}, fmt.Errorf("init payload range reading on client: %w", err)
} }
@ -883,7 +849,8 @@ func (c *clientWrapper) objectSearch(ctx context.Context, prm PrmObjectSearch) (
} }
res, err := cl.ObjectSearchInit(ctx, cliPrm) res, err := cl.ObjectSearchInit(ctx, cliPrm)
if err = c.handleError(nil, err); err != nil { c.updateErrorRate(err)
if err != nil {
return ResObjectSearch{}, fmt.Errorf("init object searching on client: %w", err) return ResObjectSearch{}, fmt.Errorf("init object searching on client: %w", err)
} }
@ -904,11 +871,8 @@ func (c *clientWrapper) sessionCreate(ctx context.Context, prm prmCreateSession)
start := time.Now() start := time.Now()
res, err := cl.SessionCreate(ctx, cliPrm) res, err := cl.SessionCreate(ctx, cliPrm)
c.incRequests(time.Since(start), methodSessionCreate) c.incRequests(time.Since(start), methodSessionCreate)
var st apistatus.Status c.updateErrorRate(err)
if res != nil { if err != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
return resCreateSession{}, fmt.Errorf("session creation on client: %w", err) return resCreateSession{}, fmt.Errorf("session creation on client: %w", err)
} }
@ -978,8 +942,25 @@ func (c *clientWrapper) incRequests(elapsed time.Duration, method MethodIndex) {
} }
} }
func (c *clientStatusMonitor) handleError(st apistatus.Status, err error) error { func (c *clientStatusMonitor) updateErrorRate(err error) {
if err != nil { if err == nil {
return
}
// count only this API errors
if errors.Is(err, apistatus.ErrServerInternal) ||
errors.Is(err, apistatus.ErrWrongMagicNumber) ||
errors.Is(err, apistatus.ErrSignatureVerification) ||
errors.Is(err, apistatus.ErrNodeUnderMaintenance) {
c.incErrorRate()
return
}
// don't count another API errors
if errors.Is(err, apistatus.Error) {
return
}
// non-status logic error that could be returned // non-status logic error that could be returned
// from the SDK client; should not be considered // from the SDK client; should not be considered
// as a connection error // as a connection error
@ -987,20 +968,6 @@ func (c *clientStatusMonitor) handleError(st apistatus.Status, err error) error
if !errors.As(err, &siErr) { if !errors.As(err, &siErr) {
c.incErrorRate() c.incErrorRate()
} }
return err
}
err = apistatus.ErrFromStatus(st)
switch err.(type) {
case apistatus.ServerInternal, *apistatus.ServerInternal,
apistatus.WrongMagicNumber, *apistatus.WrongMagicNumber,
apistatus.SignatureVerification, *apistatus.SignatureVerification,
apistatus.NodeUnderMaintenance, *apistatus.NodeUnderMaintenance:
c.incErrorRate()
}
return err
} }
// clientBuilder is a type alias of client constructors which open connection // clientBuilder is a type alias of client constructors which open connection
@ -2133,8 +2100,7 @@ func (x *objectReadCloser) Read(p []byte) (int, error) {
// Close implements io.Closer of the object payload. // Close implements io.Closer of the object payload.
func (x *objectReadCloser) Close() error { func (x *objectReadCloser) Close() error {
_, err := x.reader.Close() return x.reader.Close()
return err
} }
// ResGetObject is designed to provide object header nad read one object payload from NeoFS system. // ResGetObject is designed to provide object header nad read one object payload from NeoFS system.
@ -2212,8 +2178,7 @@ func (x *ResObjectRange) Read(p []byte) (int, error) {
// Close ends reading the payload range and returns the result of the operation // Close ends reading the payload range and returns the result of the operation
// along with the final results. Must be called after using the ResObjectRange. // along with the final results. Must be called after using the ResObjectRange.
func (x *ResObjectRange) Close() error { func (x *ResObjectRange) Close() error {
_, err := x.payload.Close() return x.payload.Close()
return err
} }
// ObjectRange initiates reading an object's payload range through a remote // ObjectRange initiates reading an object's payload range through a remote
@ -2251,7 +2216,7 @@ type ResObjectSearch struct {
func (x *ResObjectSearch) Read(buf []oid.ID) (int, error) { func (x *ResObjectSearch) Read(buf []oid.ID) (int, error) {
n, ok := x.r.Read(buf) n, ok := x.r.Read(buf)
if !ok { if !ok {
_, err := x.r.Close() err := x.r.Close()
if err == nil { if err == nil {
return n, io.EOF return n, io.EOF
} }
@ -2273,7 +2238,7 @@ func (x *ResObjectSearch) Iterate(f func(oid.ID) bool) error {
// Close ends reading list of the matched objects and returns the result of the operation // Close ends reading list of the matched objects and returns the result of the operation
// along with the final results. Must be called after using the ResObjectSearch. // along with the final results. Must be called after using the ResObjectSearch.
func (x *ResObjectSearch) Close() { func (x *ResObjectSearch) Close() {
_, _ = x.r.Close() _ = x.r.Close()
} }
// SearchObjects initiates object selection through a remote server using NeoFS API protocol. // SearchObjects initiates object selection through a remote server using NeoFS API protocol.

View file

@ -521,79 +521,68 @@ func TestHandleError(t *testing.T) {
monitor := newClientStatusMonitor("", 10) monitor := newClientStatusMonitor("", 10)
for i, tc := range []struct { for i, tc := range []struct {
status apistatus.Status
err error err error
expectedError bool expectedError bool
countError bool countError bool
}{ }{
{ {
status: nil,
err: nil, err: nil,
expectedError: false, expectedError: false,
countError: false, countError: false,
}, },
{ {
status: apistatus.SuccessDefaultV2{},
err: nil, err: nil,
expectedError: false, expectedError: false,
countError: false, countError: false,
}, },
{ {
status: apistatus.SuccessDefaultV2{},
err: errors.New("error"), err: errors.New("error"),
expectedError: true, expectedError: true,
countError: true, countError: true,
}, },
{ {
status: nil,
err: errors.New("error"), err: errors.New("error"),
expectedError: true, expectedError: true,
countError: true, countError: true,
}, },
{ {
status: apistatus.ObjectNotFound{}, err: apistatus.ObjectNotFound{},
err: nil,
expectedError: true, expectedError: true,
countError: false, countError: false,
}, },
{ {
status: apistatus.ServerInternal{}, err: apistatus.ServerInternal{},
err: nil,
expectedError: true, expectedError: true,
countError: true, countError: true,
}, },
{ {
status: apistatus.WrongMagicNumber{}, err: apistatus.WrongMagicNumber{},
err: nil,
expectedError: true, expectedError: true,
countError: true, countError: true,
}, },
{ {
status: apistatus.SignatureVerification{}, err: apistatus.SignatureVerification{},
err: nil,
expectedError: true, expectedError: true,
countError: true, countError: true,
}, },
{ {
status: &apistatus.SignatureVerification{}, err: apistatus.SignatureVerification{},
err: nil,
expectedError: true, expectedError: true,
countError: true, countError: true,
}, },
{ {
status: apistatus.NodeUnderMaintenance{}, err: apistatus.NodeUnderMaintenance{},
err: nil,
expectedError: true, expectedError: true,
countError: true, countError: true,
}, },
} { } {
t.Run(strconv.Itoa(i), func(t *testing.T) { t.Run(strconv.Itoa(i), func(t *testing.T) {
errCount := monitor.currentErrorRate() errCount := monitor.currentErrorRate()
err := monitor.handleError(tc.status, tc.err) monitor.updateErrorRate(tc.err)
if tc.expectedError { if tc.expectedError {
require.Error(t, err) require.Error(t, tc.err)
} else { } else {
require.NoError(t, err) require.NoError(t, tc.err)
} }
if tc.countError { if tc.countError {
errCount++ errCount++

View file

@ -183,12 +183,16 @@ func TestStorageGroup_SetMembers_DoubleSetting(t *testing.T) {
var sg storagegroup.StorageGroup var sg storagegroup.StorageGroup
mm := []oid.ID{oidtest.ID(), oidtest.ID(), oidtest.ID()} // cap is 3 at least mm := []oid.ID{oidtest.ID(), oidtest.ID(), oidtest.ID()} // cap is 3 at least
require.NotPanics(t, func() {
sg.SetMembers(mm) sg.SetMembers(mm)
})
require.NotPanics(t, func() {
// the previous cap is more that a new length; // the previous cap is more that a new length;
// slicing should not lead to `out of range` // slicing should not lead to `out of range`
// and apply update correctly // and apply update correctly
sg.SetMembers(mm[:1]) sg.SetMembers(mm[:1])
})
} }
func TestStorageGroupFromObject(t *testing.T) { func TestStorageGroupFromObject(t *testing.T) {