Skip to content

Commit

Permalink
Return errors instead of panic (#414)
Browse files Browse the repository at this point in the history
close #390
  • Loading branch information
roman-khimov authored May 19, 2023
2 parents cfdd870 + d1bcce5 commit 7002b3b
Show file tree
Hide file tree
Showing 22 changed files with 210 additions and 140 deletions.
8 changes: 4 additions & 4 deletions client/accounting.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ func (x ResBalanceGet) Amount() accounting.Decimal {
// 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 errors:
// - [ErrMissingAccount]
func (c *Client) BalanceGet(ctx context.Context, prm PrmBalanceGet) (*ResBalanceGet, error) {
switch {
case ctx == nil:
panic(panicMsgMissingContext)
case !prm.accountSet:
panic("account not set")
return nil, ErrMissingAccount
}

// form request body
Expand Down
10 changes: 7 additions & 3 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,23 +74,27 @@ func (c *Client) Init(prm PrmInit) {
// One-time method call during application start-up stage (after Init ) is expected.
// Calling multiple times leads to undefined behavior.
//
// Return client errors:
// - [ErrMissingServer]
// - [ErrNonPositiveTimeout]
//
// See also Init / Close.
func (c *Client) Dial(prm PrmDial) error {
if prm.endpoint == "" {
panic("server address is unset or empty")
return ErrMissingServer
}

if prm.timeoutDialSet {
if prm.timeoutDial <= 0 {
panic("non-positive timeout")
return ErrNonPositiveTimeout
}
} else {
prm.timeoutDial = 5 * time.Second
}

if prm.streamTimeoutSet {
if prm.streamTimeout <= 0 {
panic("non-positive timeout")
return ErrNonPositiveTimeout
}
} else {
prm.streamTimeout = 10 * time.Second
Expand Down
8 changes: 0 additions & 8 deletions client/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,6 @@ func writeXHeadersToMeta(xHeaders []string, h *v2session.RequestMetaHeader) {
h.SetXHeaders(hs)
}

// panic messages.
const (
panicMsgMissingContext = "missing context"
panicMsgMissingContainer = "missing container"
panicMsgMissingObject = "missing object"
panicMsgOwnerExtract = "extract owner failed"
)

// groups all the details required to send a single request and process a response to it.
type contextCall struct {
// ==================================================
Expand Down
59 changes: 30 additions & 29 deletions client/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,15 @@ func (c *Client) defaultSigner() neofscrypto.Signer {
//
// Success can be verified by reading by identifier (see ResContainerPut.ID).
//
// 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 errors:
// - [ErrMissingContainer]
func (c *Client) ContainerPut(ctx context.Context, prm PrmContainerPut) (*ResContainerPut, error) {
// check parameters
switch {
case ctx == nil:
panic(panicMsgMissingContext)
case !prm.cnrSet:
panic(panicMsgMissingContainer)
return nil, ErrMissingContainer
}

// TODO: check private signer is set before forming the request
Expand Down Expand Up @@ -206,14 +206,14 @@ func (x ResContainerGet) Container() container.Container {
// 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 errors:
// - [ErrMissingContainer]
func (c *Client) ContainerGet(ctx context.Context, prm PrmContainerGet) (*ResContainerGet, error) {
switch {
case ctx == nil:
panic(panicMsgMissingContext)
case !prm.idSet:
panic(panicMsgMissingContainer)
return nil, ErrMissingContainer
}

var cidV2 refs.ContainerID
Expand Down Expand Up @@ -296,15 +296,15 @@ func (x ResContainerList) Containers() []cid.ID {
// 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 errors:
// - [ErrMissingAccount]
func (c *Client) ContainerList(ctx context.Context, prm PrmContainerList) (*ResContainerList, error) {
// check parameters
switch {
case ctx == nil:
panic(panicMsgMissingContext)
case !prm.ownerSet:
panic("account not set")
return nil, ErrMissingAccount
}

// form request body
Expand Down Expand Up @@ -402,17 +402,17 @@ func (x *PrmContainerDelete) WithinSession(tok session.Container) {
//
// Success can be verified by reading by identifier (see GetContainer).
//
// Immediately panics if parameters are set incorrectly (see PrmContainerDelete docs).
// Context is required and must not be nil. It is used for network communication.
//
// Return errors:
// - [ErrMissingContainer]
//
// Reflects all internal errors in second return value (transport problems, response processing, etc.).
func (c *Client) ContainerDelete(ctx context.Context, prm PrmContainerDelete) error {
// check parameters
switch {
case ctx == nil:
panic(panicMsgMissingContext)
case !prm.idSet:
panic(panicMsgMissingContainer)
return ErrMissingContainer
}

// sign container ID
Expand Down Expand Up @@ -510,15 +510,15 @@ func (x ResContainerEACL) Table() eacl.Table {
// 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 errors:
// - [ErrMissingContainer]
func (c *Client) ContainerEACL(ctx context.Context, prm PrmContainerEACL) (*ResContainerEACL, error) {
// check parameters
switch {
case ctx == nil:
panic(panicMsgMissingContext)
case !prm.idSet:
panic(panicMsgMissingContainer)
return nil, ErrMissingContainer
}

var cidV2 refs.ContainerID
Expand Down Expand Up @@ -619,20 +619,21 @@ func (x *PrmContainerSetEACL) WithinSession(s session.Container) {
//
// Success can be verified by reading by identifier (see EACL).
//
// Immediately panics if parameters are set incorrectly (see PrmContainerSetEACL docs).
// Return errors:
// - [ErrMissingEACL]
// - [ErrMissingEACLContainer]
//
// Context is required and must not be nil. It is used for network communication.
func (c *Client) ContainerSetEACL(ctx context.Context, prm PrmContainerSetEACL) error {
// check parameters
switch {
case ctx == nil:
panic(panicMsgMissingContext)
case !prm.tableSet:
panic("eACL table not set")
return ErrMissingEACL
}

_, isCIDSet := prm.table.CID()
if !isCIDSet {
panic("missing container in eACL table")
return ErrMissingEACLContainer
}

// sign the eACL table
Expand Down Expand Up @@ -720,15 +721,15 @@ func (x *PrmAnnounceSpace) SetValues(vs []container.SizeEstimation) {
//
// At this moment success can not be checked.
//
// 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 errors:
// - [ErrMissingAnnouncements]
func (c *Client) ContainerAnnounceUsedSpace(ctx context.Context, prm PrmAnnounceSpace) error {
// check parameters
switch {
case ctx == nil:
panic(panicMsgMissingContext)
case len(prm.announcements) == 0:
panic("missing announcements")
return ErrMissingAnnouncements
}

// convert list of SDK announcement structures into NeoFS-API v2 list
Expand Down
81 changes: 75 additions & 6 deletions client/errors.go
Original file line number Diff line number Diff line change
@@ -1,37 +1,106 @@
package client

import (
"errors"
"fmt"
)

var (
errMissingResponseField missingResponseFieldErr
// ErrMissingServer is returned when server endpoint is empty in parameters.
ErrMissingServer = errors.New("server address is unset or empty")
// ErrNonPositiveTimeout is returned when any timeout is below zero in parameters.
ErrNonPositiveTimeout = errors.New("non-positive timeout")

// ErrMissingContainer is returned when container is not provided.
ErrMissingContainer = errors.New("missing container")
// ErrMissingObject is returned when object is not provided.
ErrMissingObject = errors.New("missing object")
// ErrMissingAccount is returned when account/owner is not provided.
ErrMissingAccount = errors.New("missing account")
// ErrMissingEACL is returned when eACL table is not provided.
ErrMissingEACL = errors.New("missing eACL table")
// ErrMissingEACLContainer is returned when container info is not provided in eACL table.
ErrMissingEACLContainer = errors.New("missing container in eACL table")
// ErrMissingAnnouncements is returned when announcements are not provided.
ErrMissingAnnouncements = errors.New("missing announcements")
// ErrZeroRangeLength is returned when range parameter has zero length.
ErrZeroRangeLength = errors.New("zero range length")
// ErrMissingRanges is returned when empty ranges list is provided.
ErrMissingRanges = errors.New("missing ranges")
// ErrZeroEpoch is returned when zero epoch is provided.
ErrZeroEpoch = errors.New("zero epoch")
// ErrMissingTrusts is returned when empty slice of trusts is provided.
ErrMissingTrusts = errors.New("missing trusts")
// ErrMissingTrust is returned when empty trust is not provided.
ErrMissingTrust = errors.New("missing trust")

// ErrUnexpectedReadCall is returned when we already got all data but truing to get more.
ErrUnexpectedReadCall = errors.New("unexpected call to `Read`")

// ErrSign is returned when unable to sign service message.
ErrSign SignError

// ErrMissingResponseField is returned when required field is not exists in NeoFS api response.
ErrMissingResponseField MissingResponseFieldErr
)

type missingResponseFieldErr struct {
// MissingResponseFieldErr contains field name which should be in NeoFS API response.
type MissingResponseFieldErr struct {
name string
}

func (e missingResponseFieldErr) Error() string {
// Error implements the error interface.
func (e MissingResponseFieldErr) Error() string {
return fmt.Sprintf("missing %s field in the response", e.name)
}

func (e missingResponseFieldErr) Is(target error) bool {
// Is implements interface for correct checking current error type with [errors.Is].
func (e MissingResponseFieldErr) Is(target error) bool {
switch target.(type) {
default:
return false
case missingResponseFieldErr, *missingResponseFieldErr:
case MissingResponseFieldErr, *MissingResponseFieldErr:
return true
}
}

// returns error describing missing field with the given name.
func newErrMissingResponseField(name string) error {
return missingResponseFieldErr{name: name}
return MissingResponseFieldErr{name: 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)
}

// SignError wraps another error with reason why sign process was failed.
type SignError struct {
err error
}

// NewSignError is a constructor for [SignError].
func NewSignError(err error) SignError {
return SignError{err: err}
}

// Error implements the error interface.
func (e SignError) Error() string {
return fmt.Sprintf("sign: %v", e.err)
}

// Unwrap implements the error interface.
func (e SignError) Unwrap() error {
return e.err
}

// Is implements interface for correct checking current error type with [errors.Is].
func (e SignError) Is(target error) bool {
switch target.(type) {
default:
return false
case SignError, *SignError:
return true
}
}
17 changes: 17 additions & 0 deletions client/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package client_test

import (
"errors"
"testing"

"github.com/nspcc-dev/neofs-sdk-go/client"
"github.com/stretchr/testify/require"
)

func Test_SignError(t *testing.T) {
someErr := errors.New("some error")
signErr := client.NewSignError(someErr)

require.ErrorIs(t, signErr, someErr)
require.ErrorIs(t, signErr, client.ErrSign)
}
17 changes: 0 additions & 17 deletions client/netmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,11 @@ func (x ResEndpointInfo) NodeInfo() netmap.NodeInfo {
// 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.).
func (c *Client) EndpointInfo(ctx context.Context, prm PrmEndpointInfo) (*ResEndpointInfo, error) {
// check context
if ctx == nil {
panic(panicMsgMissingContext)
}

// form request
var req v2netmap.LocalNodeInfoRequest

Expand Down Expand Up @@ -130,17 +124,11 @@ func (x ResNetworkInfo) Info() netmap.NetworkInfo {
// 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.).
func (c *Client) NetworkInfo(ctx context.Context, prm PrmNetworkInfo) (*ResNetworkInfo, error) {
// check context
if ctx == nil {
panic(panicMsgMissingContext)
}

// form request
var req v2netmap.NetworkInfoRequest

Expand Down Expand Up @@ -207,11 +195,6 @@ func (x ResNetMapSnapshot) NetMap() netmap.NetMap {
// 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.).
func (c *Client) NetMapSnapshot(ctx context.Context, _ PrmNetMapSnapshot) (*ResNetMapSnapshot, error) {
// check context
if ctx == nil {
panic(panicMsgMissingContext)
}

// form request body
var body v2netmap.SnapshotRequestBody

Expand Down
Loading

0 comments on commit 7002b3b

Please sign in to comment.