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
var prmInit client.PrmInit
prmInit.SetDefaultPrivateKey(key) // private key for request signing
prmInit.ResolveNeoFSFailures() // enable erroneous status parsing
var c client.Client
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.
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
[NeoFS API](https://github.com/nspcc-dev/neofs-api/blob/master/status/types.proto). There is also
a `client.PrmInit.ResolveNeoFSFailures()` to seamlessly convert erroneous statuses into Go error type.
[NeoFS API](https://github.com/nspcc-dev/neofs-api/blob/master/status/types.proto).
### policy
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.
type ResBalanceGet struct {
statusRes
amount accounting.Decimal
}
@ -40,17 +38,11 @@ func (x ResBalanceGet) Amount() accounting.Decimal {
// 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 client's internal or transport errors are returned as `error`,
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// Any errors (local or remote, including returned status codes) are returned as Go errors,
// see [apistatus] package for NeoFS-specific error types.
//
// Immediately panics if parameters are set incorrectly (see PrmBalanceGet docs).
// 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) {
switch {
case ctx == nil:
@ -81,7 +73,6 @@ func (c *Client) BalanceGet(ctx context.Context, prm PrmBalanceGet) (*ResBalance
c.initCallContext(&cc)
cc.meta = prm.prmCommonMeta
cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) {
return rpcapi.Balance(&c.c, &req, client.WithContext(ctx))
}

View file

@ -145,8 +145,6 @@ func (c *Client) Close() error {
//
// See also Init.
type PrmInit struct {
resolveNeoFSErrors bool
signer neofscrypto.Signer
cbRespInfo func(ResponseMetaInfo) error
@ -162,14 +160,6 @@ func (x *PrmInit) SetDefaultSigner(signer neofscrypto.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
// NeoFS server response to f. Nil (default) means ignore response meta info.
func (x *PrmInit) SetResponseInfoCallback(f func(ResponseMetaInfo) error) {

View file

@ -19,11 +19,6 @@ func init() {
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 {
var prm PrmInit
prm.SetDefaultSigner(signer)

View file

@ -11,28 +11,6 @@ import (
"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.
type prmCommonMeta struct {
// NeoFS request X-Headers
@ -97,9 +75,6 @@ type contextCall struct {
// callback prior to processing the response by the client
callbackResp func(ResponseMetaInfo) error
// if set, protocol errors will be expanded into a final error
resolveAPIFailures bool
// NeoFS network magic
netMagic uint64
@ -109,9 +84,6 @@ type contextCall struct {
// ==================================================
// custom call parameters
// structure of the call result
statusRes resCommon
// request to be signed with a signer and sent
req request
@ -235,20 +207,13 @@ func (x *contextCall) processResponse() bool {
// get result status
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)
if x.resolveAPIFailures {
x.err = apistatus.ErrFromStatus(st)
} else {
x.statusRes.setStatus(st)
}
x.err = apistatus.ErrFromStatus(st)
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) {
err := verifyServiceMessage(resp)
if err != nil {
@ -256,14 +221,11 @@ func (c *Client) processResponse(resp responseV2) (apistatus.Status, error) {
}
st := apistatus.FromStatusV2(resp.GetMetaHeader().GetStatus())
if c.prm.resolveNeoFSErrors {
return st, apistatus.ErrFromStatus(st)
}
return st, nil
return st, apistatus.ErrFromStatus(st)
}
// 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 {
if x.rResp != nil {
x.err = x.rResp()
@ -329,7 +291,6 @@ func (x *contextCall) processCall() bool {
// initializes static cross-call parameters inherited from client.
func (c *Client) initCallContext(ctx *contextCall) {
ctx.signer = c.prm.signer
ctx.resolveAPIFailures = c.prm.resolveNeoFSErrors
ctx.callbackResp = c.prm.cbRespInfo
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.
type ResContainerPut struct {
statusRes
id cid.ID
}
@ -78,11 +76,8 @@ func (c *Client) defaultSigner() neofscrypto.Signer {
// 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 client's internal or transport errors are returned as `error`.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// Any errors (local or remote, including returned status codes) are returned as Go errors,
// see [apistatus] package for NeoFS-specific error types.
//
// Operation is asynchronous and no guaranteed even in the absence of errors.
// 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).
// 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) {
// check parameters
switch {
@ -154,7 +146,6 @@ func (c *Client) ContainerPut(ctx context.Context, prm PrmContainerPut) (*ResCon
c.initCallContext(&cc)
cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) {
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.
type ResContainerGet struct {
statusRes
cnr container.Container
}
@ -214,18 +203,11 @@ func (x ResContainerGet) Container() container.Container {
// ContainerGet reads NeoFS container by ID.
//
// 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`.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// Any errors (local or remote, including returned status codes) are returned as Go errors,
// see [apistatus] package for NeoFS-specific error types.
//
// Immediately panics if parameters are set incorrectly (see PrmContainerGet docs).
// 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) {
switch {
case ctx == nil:
@ -256,7 +238,6 @@ func (c *Client) ContainerGet(ctx context.Context, prm PrmContainerGet) (*ResCon
c.initCallContext(&cc)
cc.meta = prm.prmCommonMeta
cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) {
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.
type ResContainerList struct {
statusRes
ids []cid.ID
}
@ -314,17 +293,11 @@ func (x ResContainerList) Containers() []cid.ID {
// 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 client's internal or transport errors are returned as `error`.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// Any errors (local or remote, including returned status codes) are returned as Go errors,
// see [apistatus] package for NeoFS-specific error types.
//
// Immediately panics if parameters are set incorrectly (see PrmContainerList docs).
// 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) {
// check parameters
switch {
@ -356,7 +329,6 @@ func (c *Client) ContainerList(ctx context.Context, prm PrmContainerList) (*ResC
c.initCallContext(&cc)
cc.meta = prm.prmCommonMeta
cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) {
return rpcapi.ListContainers(&c.c, &req, client.WithContext(ctx))
}
@ -420,18 +392,10 @@ func (x *PrmContainerDelete) WithinSession(tok session.Container) {
x.tokSet = true
}
// ResContainerDelete groups resulting values of ContainerDelete operation.
type ResContainerDelete struct {
statusRes
}
// 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 client's internal or transport errors are returned as `error`.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// Any errors (local or remote, including returned status codes) are returned as Go errors,
// see [apistatus] package for NeoFS-specific error types.
//
// Operation is asynchronous and no guaranteed even in the absence of errors.
// The required time is also not predictable.
@ -441,12 +405,8 @@ type ResContainerDelete struct {
// Immediately panics if parameters are set incorrectly (see PrmContainerDelete docs).
// 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.).
//
// Return statuses:
// - global (see Client docs).
func (c *Client) ContainerDelete(ctx context.Context, prm PrmContainerDelete) (*ResContainerDelete, error) {
func (c *Client) ContainerDelete(ctx context.Context, prm PrmContainerDelete) error {
// check parameters
switch {
case ctx == nil:
@ -471,7 +431,7 @@ func (c *Client) ContainerDelete(ctx context.Context, prm PrmContainerDelete) (*
err := sig.Calculate(signer, data)
if err != nil {
return nil, fmt.Errorf("calculate signature: %w", err)
return fmt.Errorf("calculate signature: %w", err)
}
var sigv2 refs.Signature
@ -503,23 +463,21 @@ func (c *Client) ContainerDelete(ctx context.Context, prm PrmContainerDelete) (*
// init call context
var (
cc contextCall
res ResContainerDelete
cc contextCall
)
c.initCallContext(&cc)
cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) {
return rpcapi.DeleteContainer(&c.c, &req, client.WithContext(ctx))
}
// process call
if !cc.processCall() {
return nil, cc.err
return cc.err
}
return &res, nil
return nil
}
// PrmContainerEACL groups parameters of ContainerEACL operation.
@ -539,8 +497,6 @@ func (x *PrmContainerEACL) SetContainer(id cid.ID) {
// ResContainerEACL groups resulting values of ContainerEACL operation.
type ResContainerEACL struct {
statusRes
table eacl.Table
}
@ -551,19 +507,11 @@ func (x ResContainerEACL) Table() eacl.Table {
// 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 client's internal or transport errors are returned as `error`.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// Any errors (local or remote, including returned status codes) are returned as Go errors,
// see [apistatus] package for NeoFS-specific error types.
//
// Immediately panics if parameters are set incorrectly (see PrmContainerEACL docs).
// 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) {
// check parameters
switch {
@ -595,7 +543,6 @@ func (c *Client) ContainerEACL(ctx context.Context, prm PrmContainerEACL) (*ResC
c.initCallContext(&cc)
cc.meta = prm.prmCommonMeta
cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) {
return rpcapi.GetEACL(&c.c, &req, client.WithContext(ctx))
}
@ -662,18 +609,10 @@ func (x *PrmContainerSetEACL) WithinSession(s session.Container) {
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.
//
// 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`.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// Any errors (local or remote, including returned status codes) are returned as Go errors,
// see [apistatus] package for NeoFS-specific error types.
//
// Operation is asynchronous and no guaranteed even in the absence of errors.
// The required time is also not predictable.
@ -682,10 +621,7 @@ type ResContainerSetEACL struct {
//
// Immediately panics if parameters are set incorrectly (see PrmContainerSetEACL docs).
// Context is required and must not be nil. It is used for network communication.
//
// Return statuses:
// - global (see Client docs).
func (c *Client) ContainerSetEACL(ctx context.Context, prm PrmContainerSetEACL) (*ResContainerSetEACL, error) {
func (c *Client) ContainerSetEACL(ctx context.Context, prm PrmContainerSetEACL) error {
// check parameters
switch {
case ctx == nil:
@ -710,7 +646,7 @@ func (c *Client) ContainerSetEACL(ctx context.Context, prm PrmContainerSetEACL)
err := sig.Calculate(signer, eaclV2.StableMarshal(nil))
if err != nil {
return nil, fmt.Errorf("calculate signature: %w", err)
return fmt.Errorf("calculate signature: %w", err)
}
var sigv2 refs.Signature
@ -742,23 +678,21 @@ func (c *Client) ContainerSetEACL(ctx context.Context, prm PrmContainerSetEACL)
// init call context
var (
cc contextCall
res ResContainerSetEACL
cc contextCall
)
c.initCallContext(&cc)
cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) {
return rpcapi.SetEACL(&c.c, &req, client.WithContext(ctx))
}
// process call
if !cc.processCall() {
return nil, cc.err
return cc.err
}
return &res, nil
return nil
}
// PrmAnnounceSpace groups parameters of ContainerAnnounceUsedSpace operation.
@ -776,18 +710,10 @@ func (x *PrmAnnounceSpace) SetValues(vs []container.SizeEstimation) {
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.
//
// 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`.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// Any errors (local or remote, including returned status codes) are returned as Go errors,
// see [apistatus] package for NeoFS-specific error types.
//
// Operation is asynchronous and no guaranteed even in the absence of errors.
// The required time is also not predictable.
@ -796,10 +722,7 @@ type ResAnnounceSpace struct {
//
// Immediately panics if parameters are set incorrectly (see PrmAnnounceSpace docs).
// Context is required and must not be nil. It is used for network communication.
//
// Return statuses:
// - global (see Client docs).
func (c *Client) ContainerAnnounceUsedSpace(ctx context.Context, prm PrmAnnounceSpace) (*ResAnnounceSpace, error) {
func (c *Client) ContainerAnnounceUsedSpace(ctx context.Context, prm PrmAnnounceSpace) error {
// check parameters
switch {
case ctx == nil:
@ -826,24 +749,22 @@ func (c *Client) ContainerAnnounceUsedSpace(ctx context.Context, prm PrmAnnounce
// init call context
var (
cc contextCall
res ResAnnounceSpace
cc contextCall
)
c.initCallContext(&cc)
cc.meta = prm.prmCommonMeta
cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) {
return rpcapi.AnnounceUsedSpace(&c.c, &req, client.WithContext(ctx))
}
// process call
if !cc.processCall() {
return nil, cc.err
return cc.err
}
return &res, nil
return nil
}
// 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
var prmInit client.PrmInit
prmInit.SetDefaultSigner(signer) // private signer for request signing
prmInit.ResolveNeoFSFailures() // enable erroneous status parsing
var c client.Client
c.Init(prmInit)

View file

@ -8,7 +8,6 @@ import (
rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc"
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
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/version"
)
@ -20,8 +19,6 @@ type PrmEndpointInfo struct {
// ResEndpointInfo group resulting values of EndpointInfo operation.
type ResEndpointInfo struct {
statusRes
version version.Version
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.
//
// Any client's internal or transport errors are returned as `error`.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// Any client's internal or transport errors are returned as `error`,
// see [apistatus] package for NeoFS-specific error types.
//
// Immediately panics if parameters are set incorrectly (see PrmEndpointInfo docs).
// 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.
// 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) {
// check context
if ctx == nil {
@ -73,7 +65,6 @@ func (c *Client) EndpointInfo(ctx context.Context, prm PrmEndpointInfo) (*ResEnd
c.initCallContext(&cc)
cc.meta = prm.prmCommonMeta
cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) {
return rpcapi.LocalNodeInfo(&c.c, &req, client.WithContext(ctx))
}
@ -126,8 +117,6 @@ type PrmNetworkInfo struct {
// ResNetworkInfo groups resulting values of NetworkInfo operation.
type ResNetworkInfo struct {
statusRes
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.
//
// Any client's internal or transport errors are returned as `error`.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// Any client's internal or transport errors are returned as `error`,
// see [apistatus] package for NeoFS-specific error types.
//
// Immediately panics if parameters are set incorrectly (see PrmNetworkInfo docs).
// 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.
// 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) {
// check context
if ctx == nil {
@ -170,7 +154,6 @@ func (c *Client) NetworkInfo(ctx context.Context, prm PrmNetworkInfo) (*ResNetwo
c.initCallContext(&cc)
cc.meta = prm.prmCommonMeta
cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) {
return rpcapi.NetworkInfo(&c.c, &req, client.WithContext(ctx))
}
@ -206,8 +189,6 @@ type PrmNetMapSnapshot struct {
// ResNetMapSnapshot groups resulting values of NetMapSnapshot operation.
type ResNetMapSnapshot struct {
statusRes
netMap netmap.NetMap
}
@ -218,18 +199,13 @@ func (x ResNetMapSnapshot) NetMap() netmap.NetMap {
// NetMapSnapshot requests current network view of the remote server.
//
// Any client's internal or transport errors are returned as `error`.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// Any client's internal or transport errors are returned as `error`,
// see [apistatus] package for NeoFS-specific error types.
//
// 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.
// 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) {
// check context
if ctx == nil {
@ -258,15 +234,11 @@ func (c *Client) NetMapSnapshot(ctx context.Context, _ PrmNetMapSnapshot) (*ResN
}
var res ResNetMapSnapshot
res.st, err = c.processResponse(resp)
_, err = c.processResponse(resp)
if err != nil {
return nil, err
}
if !apistatus.IsSuccessful(res.st) {
return &res, nil
}
const fieldNetMap = "network map"
netMapV2 := resp.GetBody().NetMap()

View file

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

View file

@ -11,7 +11,6 @@ import (
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
"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"
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
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.
type ResObjectDelete struct {
statusRes
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.
// Any client's internal or transport errors are returned as `error`,
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// see [apistatus] package for NeoFS-specific error types.
//
// Immediately panics if parameters are set incorrectly (see PrmObjectDelete docs).
// Context is required and must not be nil. It is used for network communication.
//
// Return statuses:
// Return errors:
// - global (see Client docs)
// - *apistatus.ContainerNotFound;
// - *apistatus.ObjectAccessDenied;
// - *apistatus.ObjectLocked;
// - *apistatus.SessionTokenExpired.
// - [apistatus.ErrContainerNotFound];
// - [apistatus.ErrObjectAccessDenied];
// - [apistatus.ErrObjectLocked];
// - [apistatus.ErrSessionTokenExpired].
func (c *Client) ObjectDelete(ctx context.Context, prm PrmObjectDelete) (*ResObjectDelete, error) {
switch {
case ctx == nil:
@ -162,15 +157,11 @@ func (c *Client) ObjectDelete(ctx context.Context, prm PrmObjectDelete) (*ResObj
}
var res ResObjectDelete
res.st, err = c.processResponse(resp)
_, err = c.processResponse(resp)
if err != nil {
return nil, err
}
if !apistatus.IsSuccessful(res.st) {
return &res, nil
}
const fieldTombstone = "tombstone"
idTombV2 := resp.GetBody().GetTombstone().GetObjectID()

View file

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

View file

@ -11,7 +11,6 @@ import (
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
"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"
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
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.
type ResObjectHash struct {
statusRes
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.
// Any client's internal or transport errors are returned as `error`,
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// see [apistatus] package for NeoFS-specific error types.
//
// Immediately panics if parameters are set incorrectly (see PrmObjectHash docs).
// 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) {
switch {
case ctx == nil:
@ -206,15 +193,11 @@ func (c *Client) ObjectHash(ctx context.Context, prm PrmObjectHash) (*ResObjectH
}
var res ResObjectHash
res.st, err = c.processResponse(resp)
_, err = c.processResponse(resp)
if err != nil {
return nil, err
}
if !apistatus.IsSuccessful(res.st) {
return &res, nil
}
res.checksums = resp.GetBody().GetHashList()
if len(res.checksums) == 0 {
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.
type ResObjectPut struct {
statusRes
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
// codes are returned as error.
//
// Return statuses:
// Return errors:
// - global (see Client docs);
// - *apistatus.ContainerNotFound;
// - *apistatus.ObjectAccessDenied;
// - *apistatus.ObjectLocked;
// - *apistatus.LockNonRegularObject;
// - *apistatus.SessionTokenNotFound;
// - *apistatus.SessionTokenExpired.
// - [apistatus.ErrContainerNotFound];
// - [apistatus.ErrObjectAccessDenied];
// - [apistatus.ErrObjectLocked];
// - [apistatus.ErrLockNonRegularObject];
// - [apistatus.ErrSessionTokenNotFound];
// - [apistatus.ErrSessionTokenExpired].
func (x *ObjectWriter) Close() (*ResObjectPut, error) {
defer x.cancelCtxStream()
@ -208,15 +206,11 @@ func (x *ObjectWriter) Close() (*ResObjectPut, error) {
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 {
return nil, x.err
}
if !apistatus.IsSuccessful(x.res.st) {
return &x.res, nil
}
const fieldID = "ID"
idV2 := x.respV2.GetBody().GetObjectID()
@ -291,7 +285,7 @@ func (x *objectWriter) InitDataStream(header object.Object) (io.Writer, error) {
return nil, err
}
return nil, apistatus.ErrFromStatus(res.Status())
return nil, apistatus.ErrFromStatus(res)
}
type payloadWriter struct {
@ -312,7 +306,7 @@ func (x *payloadWriter) Close() error {
return err
}
return apistatus.ErrFromStatus(res.Status())
return apistatus.ErrFromStatus(res)
}
// 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"
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
"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"
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
"github.com/nspcc-dev/neofs-sdk-go/object"
@ -88,11 +87,6 @@ func (x *PrmObjectSearch) SetFilters(filters object.SearchFilters) {
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.
//
// Must be initialized using Client.ObjectSearch, any other usage is unsafe.
@ -100,7 +94,6 @@ type ObjectListReader struct {
client *Client
cancelCtxStream context.CancelFunc
err error
res ResObjectSearch
stream interface {
Read(resp *v2object.SearchResponse) error
}
@ -132,8 +125,8 @@ func (x *ObjectListReader) Read(buf []oid.ID) (int, bool) {
return read, false
}
x.res.st, x.err = x.client.processResponse(&resp)
if x.err != nil || !apistatus.IsSuccessful(x.res.st) {
_, x.err = x.client.processResponse(&resp)
if x.err != nil {
return read, false
}
@ -176,11 +169,7 @@ func (x *ObjectListReader) Iterate(f func(oid.ID) bool) error {
// so false means nothing was read.
_, ok := x.Read(buf)
if !ok {
res, err := x.Close()
if err != nil {
return err
}
return apistatus.ErrFromStatus(res.Status())
return x.Close()
}
if f(buf[0]) {
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
// 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.
// If Client is tuned to resolve NeoFS API statuses, then NeoFS failures
// codes are returned as error.
//
// Return statuses:
// Return errors:
// - global (see Client docs);
// - *apistatus.ContainerNotFound;
// - *apistatus.ObjectAccessDenied;
// - *apistatus.SessionTokenExpired.
func (x *ObjectListReader) Close() (*ResObjectSearch, error) {
// - [apistatus.ErrContainerNotFound];
// - [apistatus.ErrObjectAccessDenied];
// - [apistatus.ErrSessionTokenExpired].
func (x *ObjectListReader) Close() error {
defer x.cancelCtxStream()
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.

View file

@ -32,25 +32,14 @@ func (x *PrmAnnounceLocalTrust) SetValues(trusts []reputation.Trust) {
x.trusts = trusts
}
// ResAnnounceLocalTrust groups results of AnnounceLocalTrust operation.
type ResAnnounceLocalTrust struct {
statusRes
}
// 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 client's internal or transport errors are returned as `error`.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// Any errors (local or remote, including returned status codes) are returned as Go errors,
// see [apistatus] package for NeoFS-specific error types.
//
// Immediately panics if parameters are set incorrectly (see PrmAnnounceLocalTrust docs).
// Context is required and must not be nil. It is used for network communication.
//
// Return statuses:
// - global (see Client docs).
func (c *Client) AnnounceLocalTrust(ctx context.Context, prm PrmAnnounceLocalTrust) (*ResAnnounceLocalTrust, error) {
func (c *Client) AnnounceLocalTrust(ctx context.Context, prm PrmAnnounceLocalTrust) error {
// check parameters
switch {
case ctx == nil:
@ -81,24 +70,22 @@ func (c *Client) AnnounceLocalTrust(ctx context.Context, prm PrmAnnounceLocalTru
// init call context
var (
cc contextCall
res ResAnnounceLocalTrust
cc contextCall
)
c.initCallContext(&cc)
cc.meta = prm.prmCommonMeta
cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) {
return rpcapi.AnnounceLocalTrust(&c.c, &req, client.WithContext(ctx))
}
// process call
if !cc.processCall() {
return nil, cc.err
return cc.err
}
return &res, nil
return nil
}
// PrmAnnounceIntermediateTrust groups parameters of AnnounceIntermediateTrust operation.
@ -132,26 +119,15 @@ func (x *PrmAnnounceIntermediateTrust) SetCurrentValue(trust reputation.PeerToPe
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
// 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 client's internal or transport errors are returned as `error`.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// Any errors (local or remote, including returned status codes) are returned as Go errors,
// see [apistatus] package for NeoFS-specific error types.
//
// Immediately panics if parameters are set incorrectly (see PrmAnnounceIntermediateTrust docs).
// Context is required and must not be nil. It is used for network communication.
//
// Return statuses:
// - global (see Client docs).
func (c *Client) AnnounceIntermediateTrust(ctx context.Context, prm PrmAnnounceIntermediateTrust) (*ResAnnounceIntermediateTrust, error) {
func (c *Client) AnnounceIntermediateTrust(ctx context.Context, prm PrmAnnounceIntermediateTrust) error {
// check parameters
switch {
case ctx == nil:
@ -179,22 +155,20 @@ func (c *Client) AnnounceIntermediateTrust(ctx context.Context, prm PrmAnnounceI
// init call context
var (
cc contextCall
res ResAnnounceIntermediateTrust
cc contextCall
)
c.initCallContext(&cc)
cc.meta = prm.prmCommonMeta
cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) {
return rpcapi.AnnounceIntermediateResult(&c.c, &req, client.WithContext(ctx))
}
// process call
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.
type ResSessionCreate struct {
statusRes
id []byte
sessionKey []byte
@ -63,17 +61,11 @@ func (x ResSessionCreate) PublicKey() []byte {
// The session lifetime coincides with the server lifetime. Results can be written
// 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 client's internal or transport errors are returned as `error`.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
// Any errors (local or remote, including returned status codes) are returned as Go errors,
// see [apistatus] package for NeoFS-specific error types.
//
// Immediately panics if parameters are set incorrectly (see PrmSessionCreate docs).
// 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) {
// check context
if ctx == nil {
@ -113,7 +105,6 @@ func (c *Client) SessionCreate(ctx context.Context, prm PrmSessionCreate) (*ResS
cc.meta = prm.prmCommonMeta
cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) {
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
prmInit.SetDefaultSigner(signer)
prmInit.ResolveNeoFSFailures()
var c Client
c.Init(prmInit)

View file

@ -2,10 +2,30 @@ package apistatus
import (
"encoding/binary"
"errors"
"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.
// 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.
func (x *ServerInternal) fromStatusV2(st *status.Status) {
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.
func (x *WrongMagicNumber) fromStatusV2(st *status.Status) {
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.
func (x *SignatureVerification) fromStatusV2(st *status.Status) {
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) {
x.v2 = *st
}

View file

@ -1,6 +1,8 @@
package apistatus
import (
"errors"
"github.com/nspcc-dev/neofs-api-go/v2/container"
"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 {
switch target.(type) {
default:
return false
return errors.Is(Error, target)
case ContainerNotFound, *ContainerNotFound:
return true
}
@ -86,7 +88,7 @@ func (x EACLNotFound) Error() string {
func (x EACLNotFound) Is(target error) bool {
switch target.(type) {
default:
return false
return errors.Is(Error, target)
case EACLNotFound, *EACLNotFound:
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
import (
"errors"
"github.com/nspcc-dev/neofs-api-go/v2/object"
"github.com/nspcc-dev/neofs-api-go/v2/status"
)
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]
// and MUST NOT be changed.
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]
// and MUST NOT be changed.
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.
@ -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.
func (x *ObjectLocked) fromStatusV2(st *status.Status) {
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.
func (x *LockNonRegularObject) fromStatusV2(st *status.Status) {
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.
func (x *ObjectAccessDenied) fromStatusV2(st *status.Status) {
x.v2 = *st
@ -160,7 +204,7 @@ func (x ObjectNotFound) Error() string {
func (x ObjectNotFound) Is(target error) bool {
switch target.(type) {
default:
return false
return errors.Is(Error, target)
case ObjectNotFound, *ObjectNotFound:
return true
}
@ -207,7 +251,7 @@ func (x ObjectAlreadyRemoved) Error() string {
func (x ObjectAlreadyRemoved) Is(target error) bool {
switch target.(type) {
default:
return false
return errors.Is(Error, target)
case ObjectAlreadyRemoved, *ObjectAlreadyRemoved:
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.
func (x *ObjectOutOfRange) fromStatusV2(st *status.Status) {
x.v2 = *st

View file

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

View file

@ -19,7 +19,7 @@ func TestErrors(t *testing.T) {
res := apistatus.ErrFromStatus(st)
require.Equal(t, err, res)
require.ErrorIs(t, res, err)
})
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"
)
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
}
func (x unrecognizedStatusV2) Error() string {
func (x UnrecognizedStatusV2) Error() string {
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.
func (x *unrecognizedStatusV2) fromStatusV2(st *status.Status) {
func (x *UnrecognizedStatusV2) fromStatusV2(st *status.Status) {
x.v2 = *st
}

View file

@ -28,16 +28,29 @@ type StatusV2 interface {
// Note: notice if the return type is a pointer.
//
// Successes:
// - status.OK: *SuccessDefaultV2 (this also includes nil argument).
// - [status.OK]: *[SuccessDefaultV2] (this also includes nil argument).
//
// Common failures:
// - status.Internal: *ServerInternal;
// - status.SignatureVerificationFail: *SignatureVerification.
// - [status.Internal]: *[ServerInternal];
// - [status.SignatureVerificationFail]: *[SignatureVerification].
// - [status.WrongMagicNumber]: *[WrongMagicNumber].
// - [status.NodeUnderMaintenance]: *[NodeUnderMaintenance].
//
// Object failures:
// - object.StatusLocked: *ObjectLocked;
// - object.StatusLockNonRegularObject: *LockNonRegularObject.
// - object.StatusAccessDenied: *ObjectAccessDenied.
// - [object.StatusLocked]: *[ObjectLocked];
// - [object.StatusLockNonRegularObject]: *[LockNonRegularObject].
// - [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 {
var decoder interface {
fromStatusV2(*status.Status)
@ -95,7 +108,7 @@ func FromStatusV2(st *status.Status) Status {
}
if decoder == nil {
decoder = new(unrecognizedStatusV2)
decoder = new(UnrecognizedStatusV2)
}
decoder.fromStatusV2(st)

View file

@ -8,166 +8,15 @@ import (
"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) {
type statusConstructor func() apistatus.Status
for _, testItem := range [...]struct {
status any // Status or statusConstructor
codeV2 uint64
messageV2 string
status any // Status or statusConstructor
codeV2 uint64
messageV2 string
compatibleErrs []error
checkAsErr func(error) bool
}{
{
status: errors.New("some error"),
@ -183,7 +32,7 @@ func TestFromStatusV2(t *testing.T) {
codeV2: 0,
},
{
status: true,
status: false,
codeV2: 0,
},
{
@ -196,93 +45,155 @@ func TestFromStatusV2(t *testing.T) {
},
{
status: (statusConstructor)(func() apistatus.Status {
var st apistatus.ServerInternal
st := new(apistatus.ServerInternal)
st.SetMessage("internal error message")
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 {
var st apistatus.WrongMagicNumber
st := new(apistatus.WrongMagicNumber)
st.WriteCorrectMagic(322)
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 {
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 {
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 {
var st apistatus.ObjectAccessDenied
st := new(apistatus.ObjectAccessDenied)
st.WriteReason("any reason")
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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
@ -311,5 +222,17 @@ func TestFromStatusV2(t *testing.T) {
// must generate the same status.Status message
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
for _, testCase := range testCases {
require.NoError(t, p.DecodeString(testCase))
var b strings.Builder
require.NoError(t, p.WriteStringTo(&b))
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
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)
@ -118,7 +120,9 @@ func (m *mockClient) networkInfo(context.Context, prmNetworkInfo) (netmap.Networ
var ni netmap.NetworkInfo
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
@ -139,8 +143,9 @@ func (m *mockClient) objectGet(context.Context, PrmObjectGet) (ResGetObject, err
return res, nil
}
status := apistatus.ErrFromStatus(m.stOnGetObject)
return res, m.handleError(status, nil)
err := apistatus.ErrFromStatus(m.stOnGetObject)
m.updateErrorRate(err)
return res, err
}
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) {
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)

View file

@ -374,11 +374,8 @@ func (c *clientWrapper) balanceGet(ctx context.Context, prm PrmBalanceGet) (acco
start := time.Now()
res, err := cl.BalanceGet(ctx, cliPrm)
c.incRequests(time.Since(start), methodBalanceGet)
var st apistatus.Status
if res != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
c.updateErrorRate(err)
if err != nil {
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()
res, err := cl.ContainerPut(ctx, prm.prmClient)
c.incRequests(time.Since(start), methodContainerPut)
var st apistatus.Status
if res != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
c.updateErrorRate(err)
if err != nil {
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()
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)
}
@ -431,11 +426,8 @@ func (c *clientWrapper) containerGet(ctx context.Context, prm PrmContainerGet) (
start := time.Now()
res, err := cl.ContainerGet(ctx, cliPrm)
c.incRequests(time.Since(start), methodContainerGet)
var st apistatus.Status
if res != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
c.updateErrorRate(err)
if err != nil {
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()
res, err := cl.ContainerList(ctx, cliPrm)
c.incRequests(time.Since(start), methodContainerList)
var st apistatus.Status
if res != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
c.updateErrorRate(err)
if err != nil {
return nil, fmt.Errorf("container list on client: %w", err)
}
return res.Containers(), nil
@ -480,13 +469,10 @@ func (c *clientWrapper) containerDelete(ctx context.Context, prm PrmContainerDel
}
start := time.Now()
res, err := cl.ContainerDelete(ctx, cliPrm)
err = cl.ContainerDelete(ctx, cliPrm)
c.incRequests(time.Since(start), methodContainerDelete)
var st apistatus.Status
if res != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
c.updateErrorRate(err)
if err != nil {
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()
res, err := cl.ContainerEACL(ctx, cliPrm)
c.incRequests(time.Since(start), methodContainerEACL)
var st apistatus.Status
if res != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
c.updateErrorRate(err)
if err != nil {
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()
res, err := cl.ContainerSetEACL(ctx, cliPrm)
err = cl.ContainerSetEACL(ctx, cliPrm)
c.incRequests(time.Since(start), methodContainerSetEACL)
var st apistatus.Status
if res != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
c.updateErrorRate(err)
if err != nil {
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)
if err = c.handleError(nil, err); err != nil {
c.updateErrorRate(err)
if err != nil {
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()
res, err := cl.EndpointInfo(ctx, sdkClient.PrmEndpointInfo{})
c.incRequests(time.Since(start), methodEndpointInfo)
var st apistatus.Status
if res != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
c.updateErrorRate(err)
if err != nil {
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()
res, err := cl.NetworkInfo(ctx, sdkClient.PrmNetworkInfo{})
c.incRequests(time.Since(start), methodNetworkInfo)
var st apistatus.Status
if res != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
c.updateErrorRate(err)
if err != nil {
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()
wObj, err := cl.ObjectPutInit(ctx, cliPrm)
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)
}
@ -672,17 +648,15 @@ func (c *clientWrapper) objectPut(ctx context.Context, prm PrmObjectPut) (oid.ID
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()
var st apistatus.Status
if res != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil { // here err already carries both status and client errors
c.updateErrorRate(err)
if err != nil { // here err already carries both status and client errors
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()
res, err := cl.ObjectDelete(ctx, cliPrm)
_, err = cl.ObjectDelete(ctx, cliPrm)
c.incRequests(time.Since(start), methodObjectDelete)
var st apistatus.Status
if res != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
c.updateErrorRate(err)
if err != nil {
return fmt.Errorf("delete object on client: %w", err)
}
return nil
@ -749,7 +720,8 @@ func (c *clientWrapper) objectGet(ctx context.Context, prm PrmObjectGet) (ResGet
var res ResGetObject
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)
}
@ -757,12 +729,8 @@ func (c *clientWrapper) objectGet(ctx context.Context, prm PrmObjectGet) (ResGet
successReadHeader := rObj.ReadHeader(&res.Header)
c.incRequests(time.Since(start), methodObjectGet)
if !successReadHeader {
rObjRes, err := rObj.Close()
var st apistatus.Status
if rObjRes != nil {
st = rObjRes.Status()
}
err = c.handleError(st, err)
err = rObj.Close()
c.updateErrorRate(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()
res, err := cl.ObjectHead(ctx, cliPrm)
c.incRequests(time.Since(start), methodObjectHead)
var st apistatus.Status
if res != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
c.updateErrorRate(err)
if err != nil {
return obj, fmt.Errorf("read object header via client: %w", err)
}
if !res.ReadHeader(&obj) {
@ -846,7 +811,8 @@ func (c *clientWrapper) objectRange(ctx context.Context, prm PrmObjectRange) (Re
start := time.Now()
res, err := cl.ObjectRangeInit(ctx, cliPrm)
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)
}
@ -883,7 +849,8 @@ func (c *clientWrapper) objectSearch(ctx context.Context, prm PrmObjectSearch) (
}
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)
}
@ -904,11 +871,8 @@ func (c *clientWrapper) sessionCreate(ctx context.Context, prm prmCreateSession)
start := time.Now()
res, err := cl.SessionCreate(ctx, cliPrm)
c.incRequests(time.Since(start), methodSessionCreate)
var st apistatus.Status
if res != nil {
st = res.Status()
}
if err = c.handleError(st, err); err != nil {
c.updateErrorRate(err)
if err != nil {
return resCreateSession{}, fmt.Errorf("session creation on client: %w", err)
}
@ -978,29 +942,32 @@ func (c *clientWrapper) incRequests(elapsed time.Duration, method MethodIndex) {
}
}
func (c *clientStatusMonitor) handleError(st apistatus.Status, err error) error {
if err != nil {
// non-status logic error that could be returned
// from the SDK client; should not be considered
// as a connection error
var siErr *object.SplitInfoError
if !errors.As(err, &siErr) {
c.incErrorRate()
}
return err
func (c *clientStatusMonitor) updateErrorRate(err error) {
if err == nil {
return
}
err = apistatus.ErrFromStatus(st)
switch err.(type) {
case apistatus.ServerInternal, *apistatus.ServerInternal,
apistatus.WrongMagicNumber, *apistatus.WrongMagicNumber,
apistatus.SignatureVerification, *apistatus.SignatureVerification,
apistatus.NodeUnderMaintenance, *apistatus.NodeUnderMaintenance:
// 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
// from the SDK client; should not be considered
// as a connection error
var siErr *object.SplitInfoError
if !errors.As(err, &siErr) {
c.incErrorRate()
}
return err
}
// 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.
func (x *objectReadCloser) Close() error {
_, err := x.reader.Close()
return err
return x.reader.Close()
}
// 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
// along with the final results. Must be called after using the ResObjectRange.
func (x *ResObjectRange) Close() error {
_, err := x.payload.Close()
return err
return x.payload.Close()
}
// 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) {
n, ok := x.r.Read(buf)
if !ok {
_, err := x.r.Close()
err := x.r.Close()
if err == nil {
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
// along with the final results. Must be called after using the ResObjectSearch.
func (x *ResObjectSearch) Close() {
_, _ = x.r.Close()
_ = x.r.Close()
}
// 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)
for i, tc := range []struct {
status apistatus.Status
err error
expectedError bool
countError bool
}{
{
status: nil,
err: nil,
expectedError: false,
countError: false,
},
{
status: apistatus.SuccessDefaultV2{},
err: nil,
expectedError: false,
countError: false,
},
{
status: apistatus.SuccessDefaultV2{},
err: errors.New("error"),
expectedError: true,
countError: true,
},
{
status: nil,
err: errors.New("error"),
expectedError: true,
countError: true,
},
{
status: apistatus.ObjectNotFound{},
err: nil,
err: apistatus.ObjectNotFound{},
expectedError: true,
countError: false,
},
{
status: apistatus.ServerInternal{},
err: nil,
err: apistatus.ServerInternal{},
expectedError: true,
countError: true,
},
{
status: apistatus.WrongMagicNumber{},
err: nil,
err: apistatus.WrongMagicNumber{},
expectedError: true,
countError: true,
},
{
status: apistatus.SignatureVerification{},
err: nil,
err: apistatus.SignatureVerification{},
expectedError: true,
countError: true,
},
{
status: &apistatus.SignatureVerification{},
err: nil,
err: apistatus.SignatureVerification{},
expectedError: true,
countError: true,
},
{
status: apistatus.NodeUnderMaintenance{},
err: nil,
err: apistatus.NodeUnderMaintenance{},
expectedError: true,
countError: true,
},
} {
t.Run(strconv.Itoa(i), func(t *testing.T) {
errCount := monitor.currentErrorRate()
err := monitor.handleError(tc.status, tc.err)
monitor.updateErrorRate(tc.err)
if tc.expectedError {
require.Error(t, err)
require.Error(t, tc.err)
} else {
require.NoError(t, err)
require.NoError(t, tc.err)
}
if tc.countError {
errCount++

View file

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