diff --git a/client/accounting.go b/client/accounting.go index f05b314..a86a0ac 100644 --- a/client/accounting.go +++ b/client/accounting.go @@ -2,8 +2,6 @@ package client import ( "context" - "errors" - "fmt" v2accounting "github.com/nspcc-dev/neofs-api-go/v2/accounting" "github.com/nspcc-dev/neofs-api-go/v2/refs" @@ -32,17 +30,11 @@ func (x *PrmBalanceGet) SetAccount(id user.ID) { type ResBalanceGet struct { statusRes - amount *accounting.Decimal -} - -func (x *ResBalanceGet) setAmount(v *accounting.Decimal) { - x.amount = v + amount accounting.Decimal } // Amount returns current amount of funds on the NeoFS account as decimal number. -// -// Client doesn't retain value so modification is safe. -func (x ResBalanceGet) Amount() *accounting.Decimal { +func (x ResBalanceGet) Amount() accounting.Decimal { return x.amount } @@ -96,21 +88,18 @@ func (c *Client) BalanceGet(ctx context.Context, prm PrmBalanceGet) (*ResBalance cc.result = func(r responseV2) { resp := r.(*v2accounting.BalanceResponse) + const fieldBalance = "balance" + bal := resp.GetBody().GetBalance() if bal == nil { - cc.err = errors.New("missing balance field") + cc.err = newErrMissingResponseField(fieldBalance) return } - var d accounting.Decimal - - cc.err = d.ReadFromV2(*bal) + cc.err = res.amount.ReadFromV2(*bal) if cc.err != nil { - cc.err = fmt.Errorf("invalid balance: %w", cc.err) - return + cc.err = newErrInvalidResponseField(fieldBalance, cc.err) } - - res.setAmount(&d) } // process call diff --git a/client/common.go b/client/common.go index 821b88e..4ec59f8 100644 --- a/client/common.go +++ b/client/common.go @@ -251,7 +251,7 @@ func (x *contextCall) close() bool { x.result(x.resp) } - return true + return x.err == nil } // goes through all stages of sending a request and processing a response. Returns true if successful. diff --git a/client/container.go b/client/container.go index 2cbfab5..8ce53ee 100644 --- a/client/container.go +++ b/client/container.go @@ -54,22 +54,16 @@ func (x *PrmContainerPut) WithinSession(s session.Container) { type ResContainerPut struct { statusRes - id *cid.ID + id cid.ID } // ID returns identifier of the container declared to be stored in the system. // Used as a link to information about the container (in particular, you can // asynchronously check if the save was successful). -// -// Client doesn't retain value so modification is safe. -func (x ResContainerPut) ID() *cid.ID { +func (x ResContainerPut) ID() cid.ID { return x.id } -func (x *ResContainerPut) setID(id *cid.ID) { - x.id = id -} - // ContainerPut sends request to save container in NeoFS. // // Exactly one return value is non-nil. By default, server status is returned in res structure. @@ -150,17 +144,19 @@ func (c *Client) ContainerPut(ctx context.Context, prm PrmContainerPut) (*ResCon } cc.result = func(r responseV2) { resp := r.(*v2container.PutResponse) - var cID *cid.ID + + const fieldCnrID = "container ID" cidV2 := resp.GetBody().GetContainerID() - if cidV2 != nil { - var c cid.ID - _ = c.ReadFromV2(*cidV2) - - cID = &c + if cidV2 == nil { + cc.err = newErrMissingResponseField(fieldCnrID) + return } - res.setID(cID) + cc.err = res.id.ReadFromV2(*cidV2) + if cc.err != nil { + cc.err = newErrInvalidResponseField(fieldCnrID, cc.err) + } } // process call @@ -200,10 +196,6 @@ func (x ResContainerGet) Container() container.Container { return x.cnr } -func (x *ResContainerGet) setContainer(cnr container.Container) { - x.cnr = cnr -} - // ContainerGet reads NeoFS container by ID. // // Exactly one return value is non-nil. By default, server status is returned in res structure. @@ -261,15 +253,10 @@ func (c *Client) ContainerGet(ctx context.Context, prm PrmContainerGet) (*ResCon return } - var cnr container.Container - - err := cnr.ReadFromV2(*cnrV2) - if err != nil { - cc.err = fmt.Errorf("invalid container in response: %w", err) - return + cc.err = res.cnr.ReadFromV2(*cnrV2) + if cc.err != nil { + cc.err = fmt.Errorf("invalid container in response: %w", cc.err) } - - res.setContainer(cnr) } // process call @@ -309,10 +296,6 @@ func (x ResContainerList) Containers() []cid.ID { return x.ids } -func (x *ResContainerList) setContainers(ids []cid.ID) { - x.ids = ids -} - // ContainerList requests identifiers of the account-owned containers. // // Exactly one return value is non-nil. By default, server status is returned in res structure. @@ -364,13 +347,15 @@ func (c *Client) ContainerList(ctx context.Context, prm PrmContainerList) (*ResC cc.result = func(r responseV2) { resp := r.(*v2container.ListResponse) - ids := make([]cid.ID, len(resp.GetBody().GetContainerIDs())) + res.ids = make([]cid.ID, len(resp.GetBody().GetContainerIDs())) for i, cidV2 := range resp.GetBody().GetContainerIDs() { - _ = ids[i].ReadFromV2(cidV2) + cc.err = res.ids[i].ReadFromV2(cidV2) + if cc.err != nil { + cc.err = fmt.Errorf("invalid ID in the response: %w", cc.err) + return + } } - - res.setContainers(ids) } // process call @@ -527,20 +512,14 @@ func (x *PrmContainerEACL) SetContainer(id cid.ID) { type ResContainerEACL struct { statusRes - table *eacl.Table + table eacl.Table } // Table returns eACL table of the requested container. -// -// Client doesn't retain value so modification is safe. -func (x ResContainerEACL) Table() *eacl.Table { +func (x ResContainerEACL) Table() eacl.Table { return x.table } -func (x *ResContainerEACL) setTable(table *eacl.Table) { - x.table = 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. @@ -594,11 +573,13 @@ func (c *Client) ContainerEACL(ctx context.Context, prm PrmContainerEACL) (*ResC cc.result = func(r responseV2) { resp := r.(*v2container.GetExtendedACLResponse) - body := resp.GetBody() + eACL := resp.GetBody().GetEACL() + if eACL == nil { + cc.err = newErrMissingResponseField("eACL") + return + } - table := eacl.NewTableFromV2(body.GetEACL()) - - res.setTable(table) + res.table = *eacl.NewTableFromV2(eACL) } // process call @@ -833,7 +814,7 @@ func SyncContainerWithNetwork(ctx context.Context, cnr *container.Container, c * return fmt.Errorf("network info call: %w", err) } - container.ApplyNetworkConfig(cnr, *res.Info()) + container.ApplyNetworkConfig(cnr, res.Info()) return nil } diff --git a/client/errors.go b/client/errors.go index fd3279c..70fe57c 100644 --- a/client/errors.go +++ b/client/errors.go @@ -2,6 +2,7 @@ package client import ( "errors" + "fmt" apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" ) @@ -92,3 +93,14 @@ func IsErrSessionNotFound(err error) bool { return true } } + +// returns error describing missing field with the given name. +func newErrMissingResponseField(name string) error { + return fmt.Errorf("missing %s field in the response", name) +} + +// returns error describing invalid field (according to the NeoFS protocol) +// with the given name and format violation err. +func newErrInvalidResponseField(name string, err error) error { + return fmt.Errorf("invalid %s field in the response: %w", name, err) +} diff --git a/client/netmap.go b/client/netmap.go index 02f9023..13ef045 100644 --- a/client/netmap.go +++ b/client/netmap.go @@ -2,8 +2,6 @@ package client import ( "context" - "errors" - "fmt" v2netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap" rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc" @@ -21,33 +19,21 @@ type PrmEndpointInfo struct { type ResEndpointInfo struct { statusRes - version *version.Version + version version.Version - ni *netmap.NodeInfo + ni netmap.NodeInfo } // LatestVersion returns latest NeoFS API protocol's version in use. -// -// Client doesn't retain value so modification is safe. -func (x ResEndpointInfo) LatestVersion() *version.Version { +func (x ResEndpointInfo) LatestVersion() version.Version { return x.version } -func (x *ResEndpointInfo) setLatestVersion(ver *version.Version) { - x.version = ver -} - // NodeInfo returns information about the NeoFS node served on the remote endpoint. -// -// Client doesn't retain value so modification is safe. -func (x ResEndpointInfo) NodeInfo() *netmap.NodeInfo { +func (x ResEndpointInfo) NodeInfo() netmap.NodeInfo { return x.ni } -func (x *ResEndpointInfo) setNodeInfo(info *netmap.NodeInfo) { - x.ni = info -} - // EndpointInfo requests information about the storage node served on the remote endpoint. // // Method can be used as a health check to see if node is alive and responds to requests. @@ -93,31 +79,33 @@ func (c *Client) EndpointInfo(ctx context.Context, prm PrmEndpointInfo) (*ResEnd body := resp.GetBody() - var ver version.Version - if v2ver := body.GetVersion(); v2ver != nil { - cc.err = ver.ReadFromV2(*v2ver) - if cc.err != nil { - cc.err = fmt.Errorf("invalid version: %w", cc.err) - return - } - } - res.setLatestVersion(&ver) + const fieldVersion = "version" - nodeV2 := body.GetNodeInfo() - if nodeV2 == nil { - cc.err = errors.New("missing node info in response body") + verV2 := body.GetVersion() + if verV2 == nil { + cc.err = newErrMissingResponseField(fieldVersion) return } - var node netmap.NodeInfo - - cc.err = node.ReadFromV2(*nodeV2) + cc.err = res.version.ReadFromV2(*verV2) if cc.err != nil { - cc.err = fmt.Errorf("invalid node info: %w", cc.err) + cc.err = newErrInvalidResponseField(fieldVersion, cc.err) return } - res.setNodeInfo(&node) + const fieldNodeInfo = "node info" + + nodeInfoV2 := body.GetNodeInfo() + if nodeInfoV2 == nil { + cc.err = newErrMissingResponseField(fieldNodeInfo) + return + } + + cc.err = res.ni.ReadFromV2(*nodeInfoV2) + if cc.err != nil { + cc.err = newErrInvalidResponseField(fieldNodeInfo, cc.err) + return + } } // process call @@ -137,20 +125,14 @@ type PrmNetworkInfo struct { type ResNetworkInfo struct { statusRes - info *netmap.NetworkInfo + info netmap.NetworkInfo } // Info returns structured information about the NeoFS network. -// -// Client doesn't retain value so modification is safe. -func (x ResNetworkInfo) Info() *netmap.NetworkInfo { +func (x ResNetworkInfo) Info() netmap.NetworkInfo { return x.info } -func (x *ResNetworkInfo) setInfo(info *netmap.NetworkInfo) { - x.info = info -} - // 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`. @@ -192,21 +174,19 @@ func (c *Client) NetworkInfo(ctx context.Context, prm PrmNetworkInfo) (*ResNetwo cc.result = func(r responseV2) { resp := r.(*v2netmap.NetworkInfoResponse) + const fieldNetInfo = "network info" + netInfoV2 := resp.GetBody().GetNetworkInfo() if netInfoV2 == nil { - cc.err = errors.New("missing network info in response body") + cc.err = newErrMissingResponseField(fieldNetInfo) return } - var netInfo netmap.NetworkInfo - - cc.err = netInfo.ReadFromV2(*netInfoV2) + cc.err = res.info.ReadFromV2(*netInfoV2) if cc.err != nil { - cc.err = fmt.Errorf("invalid network info: %w", cc.err) + cc.err = newErrInvalidResponseField(fieldNetInfo, cc.err) return } - - res.setInfo(&netInfo) } // process call diff --git a/client/object_delete.go b/client/object_delete.go index 3598e02..c669d0c 100644 --- a/client/object_delete.go +++ b/client/object_delete.go @@ -93,18 +93,12 @@ func (x *PrmObjectDelete) WithXHeaders(hs ...string) { type ResObjectDelete struct { statusRes - idTomb *v2refs.ObjectID + tomb oid.ID } -// ReadTombstoneID reads identifier of the created tombstone object. -// Returns false if ID is missing (not read). -func (x ResObjectDelete) ReadTombstoneID(dst *oid.ID) bool { - if x.idTomb != nil { - _ = dst.ReadFromV2(*x.idTomb) - return true - } - - return false +// Tombstone returns identifier of the created tombstone object. +func (x ResObjectDelete) Tombstone() oid.ID { + return x.tomb } // ObjectDelete marks an object for deletion from the container using NeoFS API protocol. @@ -167,7 +161,18 @@ func (c *Client) ObjectDelete(ctx context.Context, prm PrmObjectDelete) (*ResObj return rpcapi.DeleteObject(&c.c, &req, client.WithContext(ctx)) } cc.result = func(r responseV2) { - res.idTomb = r.(*v2object.DeleteResponse).GetBody().GetTombstone().GetObjectID() + const fieldTombstone = "tombstone" + + idTombV2 := r.(*v2object.DeleteResponse).GetBody().GetTombstone().GetObjectID() + if idTombV2 == nil { + cc.err = newErrMissingResponseField(fieldTombstone) + return + } + + cc.err = res.tomb.ReadFromV2(*idTombV2) + if cc.err != nil { + cc.err = newErrInvalidResponseField(fieldTombstone, cc.err) + } } // process call diff --git a/client/object_hash.go b/client/object_hash.go index 48bfcbc..9d0ae19 100644 --- a/client/object_hash.go +++ b/client/object_hash.go @@ -195,6 +195,9 @@ func (c *Client) ObjectHash(ctx context.Context, prm PrmObjectHash) (*ResObjectH } cc.result = func(r responseV2) { res.checksums = r.(*v2object.GetRangeHashResponse).GetBody().GetHashList() + if len(res.checksums) == 0 { + cc.err = newErrMissingResponseField("hash list") + } } // process call diff --git a/client/object_put.go b/client/object_put.go index b7e5124..8c0ca1b 100644 --- a/client/object_put.go +++ b/client/object_put.go @@ -32,20 +32,12 @@ func (x *PrmObjectPutInit) SetCopiesNumber(copiesNumber uint32) { type ResObjectPut struct { statusRes - resp v2object.PutResponse + obj oid.ID } -// ReadStoredObjectID reads identifier of the saved object. -// Returns false if ID is missing (not read). -func (x *ResObjectPut) ReadStoredObjectID(id *oid.ID) bool { - idv2 := x.resp.GetBody().GetObjectID() - if idv2 == nil { - return false - } - - _ = id.ReadFromV2(*idv2) - - return true +// StoredObjectID returns identifier of the saved object. +func (x ResObjectPut) StoredObjectID() oid.ID { + return x.obj } // ObjectWriter is designed to write one object to NeoFS system. @@ -216,13 +208,15 @@ func (c *Client) ObjectPutInit(ctx context.Context, prm PrmObjectPutInit) (*Obje var ( res ResObjectPut w ObjectWriter + + resp v2object.PutResponse ) ctx, w.cancelCtxStream = context.WithCancel(ctx) w.partInit.SetCopiesNumber(prm.copyNum) - stream, err := rpcapi.PutObject(&c.c, &res.resp, client.WithContext(ctx)) + stream, err := rpcapi.PutObject(&c.c, &resp, client.WithContext(ctx)) if err != nil { return nil, fmt.Errorf("open stream: %w", err) } @@ -242,11 +236,25 @@ func (c *Client) ObjectPutInit(ctx context.Context, prm PrmObjectPutInit) (*Obje c.initCallContext(&w.ctxCall) w.ctxCall.req = &req w.ctxCall.statusRes = &res - w.ctxCall.resp = &res.resp + w.ctxCall.resp = &resp w.ctxCall.wReq = func() error { return stream.Write(&req) } w.ctxCall.closer = stream.Close + w.ctxCall.result = func(r responseV2) { + const fieldID = "ID" + + idV2 := r.(*v2object.PutResponse).GetBody().GetObjectID() + if idV2 == nil { + w.ctxCall.err = newErrMissingResponseField(fieldID) + return + } + + w.ctxCall.err = res.obj.ReadFromV2(*idV2) + if w.ctxCall.err != nil { + w.ctxCall.err = newErrInvalidResponseField(fieldID, w.ctxCall.err) + } + } return &w, nil } diff --git a/pool/pool.go b/pool/pool.go index 04ccd42..fba3e1b 100644 --- a/pool/pool.go +++ b/pool/pool.go @@ -289,7 +289,9 @@ func (c *clientWrapper) balanceGet(ctx context.Context, prm PrmBalanceGet) (*acc return nil, fmt.Errorf("balance get on client: %w", err) } - return res.Amount(), nil + bal := res.Amount() + + return &bal, nil } // containerPut invokes sdkClient.ContainerPut parse response status to error and return result as is. @@ -310,12 +312,14 @@ func (c *clientWrapper) containerPut(ctx context.Context, prm PrmContainerPut) ( prm.waitParams.setDefaults() } - err = waitForContainerPresence(ctx, c, res.ID(), &prm.waitParams) + idCnr := res.ID() + + err = waitForContainerPresence(ctx, c, idCnr, &prm.waitParams) if err = c.handleError(nil, err); err != nil { return nil, fmt.Errorf("wait container presence on client: %w", err) } - return res.ID(), nil + return &idCnr, nil } // containerGet invokes sdkClient.ContainerGet parse response status to error and return result as is. @@ -400,7 +404,10 @@ func (c *clientWrapper) containerEACL(ctx context.Context, prm PrmContainerEACL) if err = c.handleError(st, err); err != nil { return nil, fmt.Errorf("get eacl on client: %w", err) } - return res.Table(), nil + + eACL := res.Table() + + return &eACL, nil } // containerSetEACL invokes sdkClient.ContainerSetEACL parse response status to error. @@ -454,7 +461,10 @@ func (c *clientWrapper) endpointInfo(ctx context.Context, _ prmEndpointInfo) (*n if err = c.handleError(st, err); err != nil { return nil, fmt.Errorf("endpoint info on client: %w", err) } - return res.NodeInfo(), nil + + nodeInfo := res.NodeInfo() + + return &nodeInfo, nil } // networkInfo invokes sdkClient.NetworkInfo parse response status to error and return result as is. @@ -470,7 +480,10 @@ func (c *clientWrapper) networkInfo(ctx context.Context, _ prmNetworkInfo) (*net if err = c.handleError(st, err); err != nil { return nil, fmt.Errorf("network info on client: %w", err) } - return res.Info(), nil + + netInfo := res.Info() + + return &netInfo, nil } // objectPut writes object to NeoFS. @@ -551,11 +564,7 @@ func (c *clientWrapper) objectPut(ctx context.Context, prm PrmObjectPut) (*oid.I return nil, fmt.Errorf("client failure: %w", err) } - var id oid.ID - - if !res.ReadStoredObjectID(&id) { - return nil, errors.New("missing ID of the stored object") - } + id := res.StoredObjectID() return &id, nil } @@ -2167,11 +2176,9 @@ func (p Pool) Statistic() Statistic { } // waitForContainerPresence waits until the container is found on the NeoFS network. -func waitForContainerPresence(ctx context.Context, cli client, cnrID *cid.ID, waitParams *WaitParams) error { +func waitForContainerPresence(ctx context.Context, cli client, cnrID cid.ID, waitParams *WaitParams) error { var prm PrmContainerGet - if cnrID != nil { - prm.SetContainerID(*cnrID) - } + prm.SetContainerID(cnrID) return waitFor(ctx, waitParams, func(ctx context.Context) bool { _, err := cli.containerGet(ctx, prm) diff --git a/pool/pool_test.go b/pool/pool_test.go index 3370675..245724c 100644 --- a/pool/pool_test.go +++ b/pool/pool_test.go @@ -11,6 +11,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/keys" apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" + cid "github.com/nspcc-dev/neofs-sdk-go/container/id" neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" "github.com/nspcc-dev/neofs-sdk-go/object" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" @@ -479,7 +480,9 @@ func TestWaitPresence(t *testing.T) { cancel() }() - err := waitForContainerPresence(ctx, mockCli, nil, &WaitParams{ + var idCnr cid.ID + + err := waitForContainerPresence(ctx, mockCli, idCnr, &WaitParams{ timeout: 120 * time.Second, pollInterval: 5 * time.Second, }) @@ -489,7 +492,8 @@ func TestWaitPresence(t *testing.T) { t.Run("context deadline exceeded", func(t *testing.T) { ctx := context.Background() - err := waitForContainerPresence(ctx, mockCli, nil, &WaitParams{ + var idCnr cid.ID + err := waitForContainerPresence(ctx, mockCli, idCnr, &WaitParams{ timeout: 500 * time.Millisecond, pollInterval: 5 * time.Second, }) @@ -499,7 +503,8 @@ func TestWaitPresence(t *testing.T) { t.Run("ok", func(t *testing.T) { ctx := context.Background() - err := waitForContainerPresence(ctx, mockCli, nil, &WaitParams{ + var idCnr cid.ID + err := waitForContainerPresence(ctx, mockCli, idCnr, &WaitParams{ timeout: 10 * time.Second, pollInterval: 500 * time.Millisecond, })