wip: migrate to mono-repo. SPN has already been moved to spn/
This commit is contained in:
221
spn/terminal/errors.go
Normal file
221
spn/terminal/errors.go
Normal file
@@ -0,0 +1,221 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/safing/portbase/formats/varint"
|
||||
)
|
||||
|
||||
// Error is a terminal error.
|
||||
type Error struct {
|
||||
// id holds the internal error ID.
|
||||
id uint8
|
||||
// external signifies if the error was received from the outside.
|
||||
external bool
|
||||
// err holds the wrapped error or the default error message.
|
||||
err error
|
||||
}
|
||||
|
||||
// ID returns the internal ID of the error.
|
||||
func (e *Error) ID() uint8 {
|
||||
return e.id
|
||||
}
|
||||
|
||||
// Error returns the human readable format of the error.
|
||||
func (e *Error) Error() string {
|
||||
if e.external {
|
||||
return "[ext] " + e.err.Error()
|
||||
}
|
||||
return e.err.Error()
|
||||
}
|
||||
|
||||
// IsExternal returns whether the error occurred externally.
|
||||
func (e *Error) IsExternal() bool {
|
||||
if e == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return e.external
|
||||
}
|
||||
|
||||
// Is returns whether the given error is of the same type.
|
||||
func (e *Error) Is(target error) bool {
|
||||
if e == nil || target == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
t, ok := target.(*Error) //nolint:errorlint // Error implementation, not usage.
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return e.id == t.id
|
||||
}
|
||||
|
||||
// Unwrap returns the wrapped error.
|
||||
func (e *Error) Unwrap() error {
|
||||
if e == nil || e.err == nil {
|
||||
return nil
|
||||
}
|
||||
return e.err
|
||||
}
|
||||
|
||||
// With adds context and details where the error occurred. The provided
|
||||
// message is appended to the error.
|
||||
// A new error with the same ID is returned and must be compared with
|
||||
// errors.Is().
|
||||
func (e *Error) With(format string, a ...interface{}) *Error {
|
||||
// Return nil if error is nil.
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Error{
|
||||
id: e.id,
|
||||
err: fmt.Errorf(e.Error()+": "+format, a...),
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap adds context higher up in the call chain. The provided message is
|
||||
// prepended to the error.
|
||||
// A new error with the same ID is returned and must be compared with
|
||||
// errors.Is().
|
||||
func (e *Error) Wrap(format string, a ...interface{}) *Error {
|
||||
// Return nil if error is nil.
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Error{
|
||||
id: e.id,
|
||||
err: fmt.Errorf(format+": "+e.Error(), a...),
|
||||
}
|
||||
}
|
||||
|
||||
// AsExternal creates and returns an external version of the error.
|
||||
func (e *Error) AsExternal() *Error {
|
||||
// Return nil if error is nil.
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Error{
|
||||
id: e.id,
|
||||
err: e.err,
|
||||
external: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Pack returns the serialized internal error ID. The additional message is
|
||||
// lost and is replaced with the default message upon parsing.
|
||||
func (e *Error) Pack() []byte {
|
||||
// Return nil slice if error is nil.
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return varint.Pack8(e.id)
|
||||
}
|
||||
|
||||
// ParseExternalError parses an external error.
|
||||
func ParseExternalError(id []byte) (*Error, error) {
|
||||
// Return nil for an empty error.
|
||||
if len(id) == 0 {
|
||||
return ErrStopping.AsExternal(), nil
|
||||
}
|
||||
|
||||
parsedID, _, err := varint.Unpack8(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unpack error ID: %w", err)
|
||||
}
|
||||
|
||||
return NewExternalError(parsedID), nil
|
||||
}
|
||||
|
||||
// NewExternalError creates an external error based on the given ID.
|
||||
func NewExternalError(id uint8) *Error {
|
||||
err, ok := errorRegistry[id]
|
||||
if ok {
|
||||
return err.AsExternal()
|
||||
}
|
||||
|
||||
return ErrUnknownError.AsExternal()
|
||||
}
|
||||
|
||||
var errorRegistry = make(map[uint8]*Error)
|
||||
|
||||
func registerError(id uint8, err error) *Error {
|
||||
// Check for duplicate.
|
||||
_, ok := errorRegistry[id]
|
||||
if ok {
|
||||
panic(fmt.Sprintf("error with id %d already registered", id))
|
||||
}
|
||||
|
||||
newErr := &Error{
|
||||
id: id,
|
||||
err: err,
|
||||
}
|
||||
|
||||
errorRegistry[id] = newErr
|
||||
return newErr
|
||||
}
|
||||
|
||||
// func (e *Error) IsSpecial() bool {
|
||||
// if e == nil {
|
||||
// return false
|
||||
// }
|
||||
// return e.id > 0 && e.id < 8
|
||||
// }
|
||||
|
||||
// IsOK returns if the error represents a "OK" or success status.
|
||||
func (e *Error) IsOK() bool {
|
||||
return !e.IsError()
|
||||
}
|
||||
|
||||
// IsError returns if the error represents an erronous condition.
|
||||
func (e *Error) IsError() bool {
|
||||
if e == nil || e.err == nil {
|
||||
return false
|
||||
}
|
||||
if e.id == 0 || e.id >= 8 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Terminal Errors.
|
||||
var (
|
||||
// ErrUnknownError is the default error.
|
||||
ErrUnknownError = registerError(0, errors.New("unknown error"))
|
||||
|
||||
// Error IDs 1-7 are reserved for special "OK" values.
|
||||
|
||||
ErrStopping = registerError(2, errors.New("stopping"))
|
||||
ErrExplicitAck = registerError(3, errors.New("explicit ack"))
|
||||
ErrNoActivity = registerError(4, errors.New("no activity"))
|
||||
|
||||
// Errors IDs 8 and up are for regular errors.
|
||||
|
||||
ErrInternalError = registerError(8, errors.New("internal error"))
|
||||
ErrMalformedData = registerError(9, errors.New("malformed data"))
|
||||
ErrUnexpectedMsgType = registerError(10, errors.New("unexpected message type"))
|
||||
ErrUnknownOperationType = registerError(11, errors.New("unknown operation type"))
|
||||
ErrUnknownOperationID = registerError(12, errors.New("unknown operation id"))
|
||||
ErrPermissionDenied = registerError(13, errors.New("permission denied"))
|
||||
ErrIntegrity = registerError(14, errors.New("integrity violated"))
|
||||
ErrInvalidOptions = registerError(15, errors.New("invalid options"))
|
||||
ErrHubNotReady = registerError(16, errors.New("hub not ready"))
|
||||
ErrRateLimited = registerError(24, errors.New("rate limited"))
|
||||
ErrIncorrectUsage = registerError(22, errors.New("incorrect usage"))
|
||||
ErrTimeout = registerError(62, errors.New("timed out"))
|
||||
ErrUnsupportedVersion = registerError(93, errors.New("unsupported version"))
|
||||
ErrHubUnavailable = registerError(101, errors.New("hub unavailable"))
|
||||
ErrAbandonedTerminal = registerError(102, errors.New("terminal is being abandoned"))
|
||||
ErrShipSunk = registerError(108, errors.New("ship sunk"))
|
||||
ErrDestinationUnavailable = registerError(113, errors.New("destination unavailable"))
|
||||
ErrTryAgainLater = registerError(114, errors.New("try again later"))
|
||||
ErrConnectionError = registerError(121, errors.New("connection error"))
|
||||
ErrQueueOverflow = registerError(122, errors.New("queue overflowed"))
|
||||
ErrCanceled = registerError(125, context.Canceled)
|
||||
)
|
||||
Reference in New Issue
Block a user