-
Notifications
You must be signed in to change notification settings - Fork 8
Improve error model and serialization #69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
20a8501
01082b0
63a8027
91a85ec
e5df0cd
87ce2d2
4807ba0
0e9f41d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,8 +4,6 @@ | |
| package nexus | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "errors" | ||
| "fmt" | ||
| "mime" | ||
| "net/url" | ||
|
|
@@ -26,88 +24,105 @@ const ( | |
|
|
||
| const StatusUpstreamTimeout = 520 | ||
|
|
||
| // A Failure represents failed handler invocations as well as `failed` or `canceled` operation results. Failures | ||
| // shouldn't typically be constructed directly. The SDK APIs take a [FailureConverter] instance that can translate | ||
| // language errors to and from [Failure] instances. | ||
| type Failure struct { | ||
| // A simple text message. | ||
| Message string `json:"message"` | ||
| // A key-value mapping for additional context. Useful for decoding the 'details' field, if needed. | ||
| Metadata map[string]string `json:"metadata,omitempty"` | ||
| // Additional JSON serializable structured data. | ||
| Details json.RawMessage `json:"details,omitempty"` | ||
| } | ||
|
|
||
| // An error that directly represents a wire representation of [Failure]. | ||
| // The SDK will convert to this error by default unless the [FailureConverter] instance is customized. | ||
| type FailureError struct { | ||
| // The underlying Failure object this error represents. | ||
| Failure Failure | ||
| } | ||
|
|
||
| // Error implements the error interface. | ||
| func (e *FailureError) Error() string { | ||
| return e.Failure.Message | ||
| } | ||
|
|
||
| // OperationError represents "failed" and "canceled" operation results. | ||
| type OperationError struct { | ||
| // Error message. | ||
| Message string | ||
| // Stack trace which may be set if this error was generated by a language that supports it. | ||
| StackTrace string | ||
| // State of the operation. Only [OperationStateFailed] and [OperationStateCanceled] are valid. | ||
| State OperationState | ||
| // The underlying cause for this error. | ||
| Cause error | ||
| // Set if this error is constructed from a failure object. | ||
| OriginalFailure *Failure | ||
| } | ||
|
|
||
| // NewOperationFailedError is shorthand for constructing an [OperationError] with state set to | ||
| // [OperationStateFailed] and the given error message as the cause. | ||
| // [OperationStateFailed] and the given error message. | ||
| func NewOperationFailedError(message string) *OperationError { | ||
| return &OperationError{ | ||
| State: OperationStateFailed, | ||
| Cause: errors.New(message), | ||
| State: OperationStateFailed, | ||
| Message: message, | ||
| // Also setting Cause as a temporary workaround for compatibility with older servers. | ||
| Cause: &FailureError{ | ||
| Failure: Failure{ | ||
| Message: message, | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| // OperationFailedErrorf creates an [OperationError] with state set to [OperationStateFailed], using [fmt.Errorf] to | ||
| // construct the cause. | ||
| // OperationFailedErrorf creates an [OperationError] with state set to [OperationStateFailed], using [fmt.Sprintf] to | ||
| // construct the message. | ||
| func OperationFailedErrorf(format string, args ...any) *OperationError { | ||
|
||
| return &OperationError{ | ||
| State: OperationStateFailed, | ||
| Cause: fmt.Errorf(format, args...), | ||
| State: OperationStateFailed, | ||
| Message: fmt.Sprintf(format, args...), | ||
| // Also setting Cause as a temporary workaround for compatibility with older servers. | ||
| Cause: &FailureError{ | ||
| Failure: Failure{ | ||
| Message: fmt.Sprintf(format, args...), | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| // NewOperationCanceledError is shorthand for constructing an [OperationError] with state set to | ||
| // [OperationStateCanceled] and the given error message as the cause. | ||
| // [OperationStateCanceled] and the given error message. | ||
| func NewOperationCanceledError(message string) *OperationError { | ||
| return &OperationError{ | ||
| State: OperationStateCanceled, | ||
| Cause: errors.New(message), | ||
| State: OperationStateCanceled, | ||
| Message: message, | ||
| // Also setting Cause as a temporary workaround for compatibility with older servers. | ||
| Cause: &FailureError{ | ||
| Failure: Failure{ | ||
| Message: message, | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| // OperationCanceledErrorf creates an [OperationError] with state set to [OperationStateCanceled], using [fmt.Errorf] to | ||
| // construct the cause. | ||
| // OperationCanceledErrorf creates an [OperationError] with state set to [OperationStateCanceled], using [fmt.Sprintf] to | ||
| // construct the message. | ||
| func OperationCanceledErrorf(format string, args ...any) *OperationError { | ||
| return &OperationError{ | ||
| State: OperationStateCanceled, | ||
| Cause: fmt.Errorf(format, args...), | ||
| State: OperationStateCanceled, | ||
| Message: fmt.Sprintf(format, args...), | ||
| // Also setting Cause as a temporary workaround for compatibility with older servers. | ||
| Cause: &FailureError{ | ||
| Failure: Failure{ | ||
| Message: fmt.Sprintf(format, args...), | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| // OperationErrorf creates an [OperationError] with the given state, using [fmt.Errorf] to construct the cause. | ||
| // OperationErrorf creates an [OperationError] with the given state, using [fmt.Sprintf] to construct the message. | ||
| func OperationErrorf(state OperationState, format string, args ...any) *OperationError { | ||
| return &OperationError{ | ||
| State: state, | ||
| Cause: fmt.Errorf(format, args...), | ||
| State: state, | ||
| Message: fmt.Sprintf(format, args...), | ||
| // Also setting Cause as a temporary workaround for compatibility with older servers. | ||
| Cause: &FailureError{ | ||
| Failure: Failure{ | ||
| Message: fmt.Sprintf(format, args...), | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| // Error implements the error interface. | ||
| func (e *OperationError) Error() string { | ||
| if e.Cause == nil { | ||
| return fmt.Sprintf("operation %s", e.State) | ||
| message := fmt.Sprintf("operation %s", e.State) | ||
| if len(e.Message) > 0 { | ||
| message += ": " + e.Message | ||
| } else if e.Cause != nil { | ||
| // Only append the cause if message is unset for compatibility with older SDKs which did not have a | ||
| // Message attribute. | ||
| message += ": " + e.Cause.Error() | ||
| } | ||
| return fmt.Sprintf("operation %s: %s", e.State, e.Cause.Error()) | ||
| return message | ||
| } | ||
|
|
||
| // Unwrap returns the cause for use with utilities in the errors package. | ||
|
|
@@ -176,18 +191,42 @@ const ( | |
| type HandlerError struct { | ||
| // Error Type. Defaults to HandlerErrorTypeInternal. | ||
| Type HandlerErrorType | ||
| // Error message. | ||
| Message string | ||
| // Stack trace which may be set if this error was generated by a language that supports it. | ||
| StackTrace string | ||
| // The underlying cause for this error. | ||
| Cause error | ||
| // RetryBehavior of this error. If not specified, retry behavior is determined from the error type. | ||
| RetryBehavior HandlerErrorRetryBehavior | ||
| // Set if this error is constructed from a failure object. | ||
| OriginalFailure *Failure | ||
| } | ||
|
|
||
| // HandlerErrorf creates a [HandlerError] with the given type, using [fmt.Errorf] to construct the cause. | ||
| // HandlerErrorf creates a [HandlerError] with the given type, using [fmt.Sprintf] to construct the message. | ||
| func HandlerErrorf(typ HandlerErrorType, format string, args ...any) *HandlerError { | ||
| return &HandlerError{ | ||
| Type: typ, | ||
| Cause: fmt.Errorf(format, args...), | ||
| Type: typ, | ||
| Message: fmt.Sprintf(format, args...), | ||
| // Also setting Cause as a temporary workaround for compatibility with older servers. | ||
| Cause: &FailureError{ | ||
| Failure: Failure{ | ||
| Message: fmt.Sprintf(format, args...), | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| func (e *HandlerError) retryBehaviorAsOptionalBool() *bool { | ||
| switch e.RetryBehavior { | ||
| case HandlerErrorRetryBehaviorRetryable: | ||
| ret := true | ||
| return &ret | ||
| case HandlerErrorRetryBehaviorNonRetryable: | ||
| ret := false | ||
| return &ret | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // Retryable returns a boolean indicating whether or not this error is retryable based on the error's RetryBehavior and | ||
|
|
@@ -224,10 +263,15 @@ func (e *HandlerError) Error() string { | |
| if len(typ) == 0 { | ||
| typ = HandlerErrorTypeInternal | ||
| } | ||
| if e.Cause == nil { | ||
| return fmt.Sprintf("handler error (%s)", typ) | ||
| message := fmt.Sprintf("handler error (%s)", typ) | ||
| if len(e.Message) > 0 { | ||
| message += ": " + e.Message | ||
| } else if e.Cause != nil { | ||
| // Only append the cause if message is unset for compatibility with older SDKs which did not have a | ||
| // Message attribute. | ||
| message += ": " + e.Cause.Error() | ||
| } | ||
| return fmt.Sprintf("handler error (%s): %s", typ, e.Cause.Error()) | ||
| return message | ||
| } | ||
|
|
||
| // Unwrap returns the cause for use with utilities in the errors package. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought we didn't need this anymore since since we are using application failures?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still required for the server to rehydrate the original failure.