forked from TrueCloudLab/frostfs-sdk-go
[#92] client: Accept structured parameters in non-object operations
Define `XPrm` type for each `X` client operation which structures parameters. Export setters of each parameterized value. Emphasize that some parameters are required. Make the client panic when the parameters are incorrectly set. Get rid of vadiadic call options and `CallOption` type. Improve documentation of client behavior. Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
parent
596774ce5b
commit
213d20e3fb
13 changed files with 1100 additions and 885 deletions
176
client/common.go
176
client/common.go
|
@ -1,10 +1,14 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
|
||||
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/signature"
|
||||
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/session"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/version"
|
||||
)
|
||||
|
||||
// common interface of resulting structures with API status.
|
||||
|
@ -87,3 +91,175 @@ func (c *Client) processResponseV2(res *processResponseV2Res, prm processRespons
|
|||
|
||||
return unsuccessfulStatus
|
||||
}
|
||||
|
||||
type prmSession struct {
|
||||
tokenSessionSet bool
|
||||
tokenSession session.Token
|
||||
}
|
||||
|
||||
// SetSessionToken sets token of the session within which request should be sent.
|
||||
func (x *prmSession) SetSessionToken(tok session.Token) {
|
||||
x.tokenSession = tok
|
||||
x.tokenSessionSet = true
|
||||
}
|
||||
|
||||
func (x prmSession) writeToMetaHeader(meta *v2session.RequestMetaHeader) {
|
||||
if x.tokenSessionSet {
|
||||
meta.SetSessionToken(x.tokenSession.ToV2())
|
||||
}
|
||||
}
|
||||
|
||||
// panic messages.
|
||||
const (
|
||||
panicMsgMissingContext = "missing context"
|
||||
panicMsgMissingContainer = "missing container"
|
||||
)
|
||||
|
||||
// groups all the details required to send a single request and process a response to it.
|
||||
type contextCall struct {
|
||||
// ==================================================
|
||||
// state vars that do not require explicit initialization
|
||||
|
||||
// final error to be returned from client method
|
||||
err error
|
||||
|
||||
// received response
|
||||
resp responseV2
|
||||
|
||||
// ==================================================
|
||||
// shared parameters which are set uniformly on all calls
|
||||
|
||||
// request signing key
|
||||
key ecdsa.PrivateKey
|
||||
|
||||
// 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
|
||||
|
||||
// ==================================================
|
||||
// custom call parameters
|
||||
|
||||
// structure of the call result
|
||||
statusRes resCommon
|
||||
|
||||
// request to be signed with a key and sent
|
||||
req interface {
|
||||
GetMetaHeader() *v2session.RequestMetaHeader
|
||||
SetMetaHeader(*v2session.RequestMetaHeader)
|
||||
}
|
||||
|
||||
// function to send a request (unary) and receive a response
|
||||
call func() (responseV2, error)
|
||||
|
||||
// function of writing response fields to the resulting structure (optional)
|
||||
result func(v2 responseV2)
|
||||
}
|
||||
|
||||
func (x contextCall) prepareRequest() {
|
||||
meta := x.req.GetMetaHeader()
|
||||
if meta == nil {
|
||||
meta = new(v2session.RequestMetaHeader)
|
||||
x.req.SetMetaHeader(meta)
|
||||
}
|
||||
|
||||
if meta.GetTTL() == 0 {
|
||||
meta.SetTTL(2)
|
||||
}
|
||||
|
||||
if meta.GetVersion() == nil {
|
||||
meta.SetVersion(version.Current().ToV2())
|
||||
}
|
||||
|
||||
meta.SetNetworkMagic(x.netMagic)
|
||||
}
|
||||
|
||||
// performs common actions of response processing and writes any problem as a result status or client error
|
||||
// (in both cases returns false).
|
||||
//
|
||||
// Actions:
|
||||
// * verify signature (internal);
|
||||
// * call response callback (internal);
|
||||
// * unwrap status error (optional).
|
||||
func (x *contextCall) processResponse() bool {
|
||||
// call response callback if set
|
||||
if x.callbackResp != nil {
|
||||
x.err = x.callbackResp(ResponseMetaInfo{
|
||||
key: x.resp.GetVerificationHeader().GetBodySignature().GetKey(),
|
||||
})
|
||||
if x.err != nil {
|
||||
x.err = fmt.Errorf("response callback error: %w", x.err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// note that we call response callback before signature check since it is expected more lightweight
|
||||
// while verification needs marshaling
|
||||
|
||||
// verify response signature
|
||||
x.err = signature.VerifyServiceMessage(x.resp)
|
||||
if x.err != nil {
|
||||
x.err = fmt.Errorf("invalid response signature: %w", x.err)
|
||||
return false
|
||||
}
|
||||
|
||||
// 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 !successfulStatus && x.resolveAPIFailures {
|
||||
x.err = apistatus.ErrFromStatus(st)
|
||||
return false
|
||||
}
|
||||
|
||||
x.statusRes.setStatus(st)
|
||||
|
||||
return successfulStatus
|
||||
}
|
||||
|
||||
// goes through all stages of sending a request and processing a response. Returns true if successful.
|
||||
func (x *contextCall) processCall() bool {
|
||||
// prepare the request
|
||||
x.prepareRequest()
|
||||
|
||||
// sign the request
|
||||
x.err = signature.SignServiceMessage(&x.key, x.req)
|
||||
if x.err != nil {
|
||||
x.err = fmt.Errorf("sign request: %w", x.err)
|
||||
return false
|
||||
}
|
||||
|
||||
// perform RPC
|
||||
x.resp, x.err = x.call()
|
||||
if x.err != nil {
|
||||
x.err = fmt.Errorf("transport error: %w", x.err)
|
||||
return false
|
||||
}
|
||||
|
||||
// process the response
|
||||
ok := x.processResponse()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// write response to resulting structure
|
||||
if x.result != nil {
|
||||
x.result(x.resp)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// initializes static cross-call parameters inherited from client.
|
||||
func (c *Client) initCallContext(ctx *contextCall) {
|
||||
ctx.key = *c.opts.key
|
||||
ctx.resolveAPIFailures = c.opts.parseNeoFSErrors
|
||||
ctx.callbackResp = c.opts.cbRespInfo
|
||||
ctx.netMagic = c.opts.netMagic
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue