Skip to content

Commit 46bd1a7

Browse files
committed
update capability error codes, API and finalize implementation
1 parent 666e7c5 commit 46bd1a7

File tree

14 files changed

+525
-215
lines changed

14 files changed

+525
-215
lines changed

pkg/capabilities/errors/error.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package errors
2+
3+
import "fmt"
4+
5+
type Origin int
6+
7+
const (
8+
// OriginSystem The error originated from a system issue.
9+
OriginSystem = 0
10+
11+
// OriginUser The error originated from user input or action.
12+
OriginUser = 1
13+
)
14+
15+
func (o Origin) String() string {
16+
switch o {
17+
case OriginSystem:
18+
return "System"
19+
case OriginUser:
20+
return "User"
21+
default:
22+
return "Unknown"
23+
}
24+
}
25+
26+
// FromOriginString converts a string to an Origin value.
27+
func FromOriginString(s string) Origin {
28+
switch s {
29+
case "System":
30+
return OriginSystem
31+
case "User":
32+
return OriginUser
33+
default:
34+
return Origin(-1)
35+
}
36+
}
37+
38+
type Visibility int
39+
40+
const (
41+
// VisibilityPublic The full details of the error can be shared across all nodes in the network.
42+
VisibilityPublic = 0
43+
44+
// VisibilityPrivate The error contains sensitive information that should only be visible to the local node.
45+
VisibilityPrivate = 1
46+
)
47+
48+
// String returns the string representation of the Visibility value.
49+
func (v Visibility) String() string {
50+
switch v {
51+
case VisibilityPublic:
52+
return "Public"
53+
case VisibilityPrivate:
54+
return "Private"
55+
default:
56+
return "Unknown"
57+
}
58+
}
59+
60+
// FromVisibilityString converts a string to a Visibility value.
61+
func FromVisibilityString(s string) Visibility {
62+
switch s {
63+
case "Public":
64+
return VisibilityPublic
65+
case "Private":
66+
return VisibilityPrivate
67+
default:
68+
return Visibility(-1)
69+
}
70+
}
71+
72+
type Error interface {
73+
error
74+
75+
Visibility() Visibility
76+
Origin() Origin
77+
Code() ErrorCode
78+
SerializeToString() string
79+
SerializeToRemoteReportableString() string
80+
Equals(otherErr Error) bool
81+
}
82+
83+
type capabilityError struct {
84+
err error
85+
origin Origin
86+
visibility Visibility
87+
errorCode ErrorCode
88+
}
89+
90+
func NewError(err error, visibility Visibility, origin Origin, errorCode ErrorCode) Error {
91+
return &capabilityError{
92+
err: err,
93+
origin: origin,
94+
visibility: visibility,
95+
errorCode: errorCode,
96+
}
97+
}
98+
99+
// NewPublicSystemError indicates that the wrapped error is due to a system-level issue and does not contain any
100+
// sensitive information that should only be visible to the node on which it occurred, making it safe to share the full error details
101+
// with other nodes in the network.
102+
func NewPublicSystemError(err error, errorCode ErrorCode) Error {
103+
return NewError(err, VisibilityPublic, OriginSystem, errorCode)
104+
}
105+
106+
// NewPublicUserError indicates that the wrapped error is due to a user-level issue and does not contain any
107+
// information that should only be visible to the node on which it occurred, making it safe to share the full error details
108+
// with other nodes in the network.
109+
func NewPublicUserError(err error, errorCode ErrorCode) Error {
110+
return NewError(err, VisibilityPublic, OriginUser, errorCode)
111+
}
112+
113+
// NewPrivateSystemError indicates that the wrapped error is due to a system-level issue and may contain
114+
// sensitive information that should only be visible to the node on which it occurred. The error code will still be
115+
// visible to other nodes in the network.
116+
func NewPrivateSystemError(err error, errorCode ErrorCode) Error {
117+
return NewError(err, VisibilityPrivate, OriginSystem, errorCode)
118+
}
119+
120+
// NewPrivateUserError indicates that the wrapped error is due to a user-level issue and may contain
121+
// sensitive information that should only be visible to the node on which it occurred. The error code will still be
122+
// visible to other nodes in the network.
123+
func NewPrivateUserError(err error, errorCode ErrorCode) Error {
124+
return NewError(err, VisibilityPrivate, OriginSystem, errorCode)
125+
}
126+
127+
func (e capabilityError) Error() string {
128+
return fmt.Sprintf("[%d]%s:", e.errorCode, e.errorCode.String()) + " " + e.err.Error()
129+
}
130+
131+
func (e capabilityError) Origin() Origin {
132+
return e.origin
133+
}
134+
135+
func (e capabilityError) Visibility() Visibility {
136+
return e.visibility
137+
}
138+
139+
func (e capabilityError) Code() ErrorCode {
140+
return e.errorCode
141+
}
142+
143+
func (e capabilityError) Equals(otherErr Error) bool {
144+
return e.errorCode == otherErr.Code() &&
145+
e.origin == otherErr.Origin() &&
146+
e.visibility == otherErr.Visibility() &&
147+
e.Error() == otherErr.Error()
148+
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
package errors
2+
3+
type ErrorCode uint32
4+
5+
// Capability error codes are primarily based on gRPC error codes:
6+
// https://grpc.github.io/grpc/core/md_doc_statuscodes.html
7+
// Custom error codes specific to this project should start from 100 to avoid
8+
// conflicts with future gRPC codes. Note: 0 (OK) is intentionally excluded
9+
// because capability errors must always indicate a failure condition.
10+
const (
11+
// Canceled indicates the operation was canceled (typically by the caller).
12+
Canceled ErrorCode = 1
13+
14+
// Unknown error. An example of where this error may be returned is
15+
// if a Status value received from another address space belongs to
16+
// an error-space that is not known in this address space. Also
17+
// errors raised by APIs that do not return enough error information
18+
// may be converted to this error.
19+
Unknown ErrorCode = 2
20+
21+
// InvalidArgument indicates client specified an invalid argument.
22+
// Note that this differs from FailedPrecondition. It indicates arguments
23+
// that are problematic regardless of the state of the system
24+
// (e.g., a malformed file name).
25+
InvalidArgument ErrorCode = 3
26+
27+
// DeadlineExceeded means operation expired before completion.
28+
// For operations that change the state of the system, this error may be
29+
// returned even if the operation has completed successfully. For
30+
// example, a successful response from a server could have been delayed
31+
// long enough for the deadline to expire.
32+
DeadlineExceeded ErrorCode = 4
33+
34+
// NotFound means some requested entity (e.g., file or directory) was
35+
// not found.
36+
NotFound ErrorCode = 5
37+
38+
// AlreadyExists means an attempt to create an entity failed because one
39+
// already exists.
40+
AlreadyExists ErrorCode = 6
41+
42+
// PermissionDenied indicates the caller does not have permission to
43+
// execute the specified operation. It must not be used for rejections
44+
// caused by exhausting some resource (use ResourceExhausted
45+
// instead for those errors). It must not be
46+
// used if the caller cannot be identified (use Unauthenticated
47+
// instead for those errors).
48+
PermissionDenied ErrorCode = 7
49+
50+
// ResourceExhausted indicates some resource has been exhausted, perhaps
51+
// a per-user quota, or perhaps the entire file system is out of space.
52+
ResourceExhausted ErrorCode = 8
53+
54+
// FailedPrecondition indicates operation was rejected because the
55+
// system is not in a state required for the operation's execution.
56+
// For example, directory to be deleted may be non-empty, an rmdir
57+
// operation is applied to a non-directory, etc.
58+
//
59+
// A litmus test that may help a service implementor in deciding
60+
// between FailedPrecondition, Aborted, and Unavailable:
61+
// (a) Use Unavailable if the client can retry just the failing call.
62+
// (b) Use Aborted if the client should retry at a higher-level
63+
// (e.g., restarting a read-modify-write sequence).
64+
// (c) Use FailedPrecondition if the client should not retry until
65+
// the system state has been explicitly fixed. E.g., if an "rmdir"
66+
// fails because the directory is non-empty, FailedPrecondition
67+
// should be returned since the client should not retry unless
68+
// they have first fixed up the directory by deleting files from it.
69+
// (d) Use FailedPrecondition if the client performs conditional
70+
// REST Get/Update/Delete on a resource and the resource on the
71+
// server does not match the condition. E.g., conflicting
72+
// read-modify-write on the same resource.
73+
FailedPrecondition ErrorCode = 9
74+
75+
// Aborted indicates the operation was aborted, typically due to a
76+
// concurrency issue like sequencer check failures, transaction aborts,
77+
// etc.
78+
//
79+
// See litmus test above for deciding between FailedPrecondition,
80+
// Aborted, and Unavailable.
81+
Aborted ErrorCode = 10
82+
83+
// OutOfRange means operation was attempted past the valid range.
84+
// E.g., seeking or reading past end of file.
85+
//
86+
// Unlike InvalidArgument, this error indicates a problem that may
87+
// be fixed if the system state changes. For example, a 32-bit file
88+
// system will generate InvalidArgument if asked to read at an
89+
// offset that is not in the range [0,2^32-1], but it will generate
90+
// OutOfRange if asked to read from an offset past the current
91+
// file size.
92+
//
93+
// There is a fair bit of overlap between FailedPrecondition and
94+
// OutOfRange. We recommend using OutOfRange (the more specific
95+
// error) when it applies so that callers who are iterating through
96+
// a space can easily look for an OutOfRange error to detect when
97+
// they are done.
98+
OutOfRange ErrorCode = 11
99+
100+
// Unimplemented indicates operation is not implemented or not
101+
// supported/enabled in this service.
102+
Unimplemented ErrorCode = 12
103+
104+
// Internal errors. Means some invariants expected by underlying
105+
// system has been broken. If you see one of these errors,
106+
// something is very broken.
107+
Internal ErrorCode = 13
108+
109+
// Unavailable indicates the service is currently unavailable.
110+
// This is a most likely a transient condition and may be corrected
111+
// by retrying with a backoff. Note that it is not always safe to retry
112+
// non-idempotent operations.
113+
//
114+
// See litmus test above for deciding between FailedPrecondition,
115+
// Aborted, and Unavailable.
116+
Unavailable ErrorCode = 14
117+
118+
// DataLoss indicates unrecoverable data loss or corruption.
119+
DataLoss ErrorCode = 15
120+
121+
// Unauthenticated indicates the request does not have valid
122+
// authentication credentials for the operation.
123+
Unauthenticated ErrorCode = 16
124+
125+
// Custom error codes not defined in the gRPC error space are defined below this point,
126+
// starting at 100 to avoid collision with future gRPC defined codes.
127+
128+
// ConsensusFailed indicates failure to reach consensus
129+
ConsensusFailed ErrorCode = 100
130+
)
131+
132+
// String returns the string representation of the ErrorCode.
133+
func (e ErrorCode) String() string {
134+
if s, ok := errorCodeToString[e]; ok {
135+
return s
136+
}
137+
return "Unknown"
138+
}
139+
140+
var errorCodeToString = map[ErrorCode]string{
141+
Canceled: "Canceled",
142+
Unknown: "Unknown",
143+
InvalidArgument: "InvalidArgument",
144+
DeadlineExceeded: "DeadlineExceeded",
145+
NotFound: "NotFound",
146+
AlreadyExists: "AlreadyExists",
147+
PermissionDenied: "PermissionDenied",
148+
ResourceExhausted: "ResourceExhausted",
149+
FailedPrecondition: "FailedPrecondition",
150+
Aborted: "Aborted",
151+
OutOfRange: "OutOfRange",
152+
Unimplemented: "Unimplemented",
153+
Internal: "Internal",
154+
Unavailable: "Unavailable",
155+
DataLoss: "DataLoss",
156+
Unauthenticated: "Unauthenticated",
157+
ConsensusFailed: "ConsensusFailed",
158+
}
159+
160+
var stringToErrorCode = map[string]ErrorCode{
161+
"Canceled": Canceled,
162+
"Unknown": Unknown,
163+
"InvalidArgument": InvalidArgument,
164+
"DeadlineExceeded": DeadlineExceeded,
165+
"NotFound": NotFound,
166+
"AlreadyExists": AlreadyExists,
167+
"PermissionDenied": PermissionDenied,
168+
"ResourceExhausted": ResourceExhausted,
169+
"FailedPrecondition": FailedPrecondition,
170+
"Aborted": Aborted,
171+
"OutOfRange": OutOfRange,
172+
"Unimplemented": Unimplemented,
173+
"Internal": Internal,
174+
"Unavailable": Unavailable,
175+
"DataLoss": DataLoss,
176+
"Unauthenticated": Unauthenticated,
177+
"ConsensusFailed": ConsensusFailed,
178+
}
179+
180+
func FromErrorCodeString(str string) ErrorCode {
181+
if code, ok := stringToErrorCode[str]; ok {
182+
return code
183+
}
184+
return Unknown
185+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package errors
2+
3+
import (
4+
"errors"
5+
"strings"
6+
)
7+
8+
const errorMessageSeparator = ":"
9+
10+
func PrePendPrivateVisibilityIdentifier(errorMessage string) string {
11+
return Visibility(VisibilityPrivate).String() + errorMessageSeparator + errorMessage
12+
}
13+
14+
func DeserializeErrorFromString(errorMsg string) Error {
15+
parts := strings.SplitN(errorMsg, errorMessageSeparator, 4)
16+
17+
if len(parts) < 4 {
18+
// To maintain backwards compatability with messages from remote nodes on an older code version, create an error
19+
// with the full message and default to private system error with an unknown error code.
20+
return NewError(errors.New(errorMsg), Visibility(VisibilityPrivate), Origin(OriginSystem), Unknown)
21+
}
22+
23+
visibility := FromVisibilityString(parts[0])
24+
origin := FromOriginString(parts[1])
25+
errorCode := FromErrorCodeString(parts[2])
26+
errorMsg = parts[3]
27+
28+
return NewError(errors.New(errorMsg), visibility, origin, errorCode)
29+
}
30+
31+
func (e capabilityError) SerializeToString() string {
32+
return e.serializeToString(e.err.Error())
33+
}
34+
35+
func (e capabilityError) serializeToString(errMsg string) string {
36+
return e.visibility.String() + errorMessageSeparator + e.origin.String() + errorMessageSeparator + e.Code().String() + errorMessageSeparator + errMsg
37+
}
38+
39+
func (e capabilityError) SerializeToRemoteReportableString() string {
40+
if e.Visibility() == VisibilityPublic {
41+
return e.serializeToString(e.err.Error())
42+
}
43+
44+
return e.serializeToString(": error whilst executing capability - the error message is not publicly reportable")
45+
}

0 commit comments

Comments
 (0)