-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: add message wrapper to errors and improve resource exhausted err…
…or message by using metadata
- Loading branch information
Showing
58 changed files
with
670 additions
and
261 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
package momentoerrors | ||
|
||
import ( | ||
"strings" | ||
|
||
"google.golang.org/grpc/codes" | ||
"google.golang.org/grpc/metadata" | ||
"google.golang.org/grpc/status" | ||
|
@@ -15,7 +17,7 @@ type MomentoSvcErr interface { | |
|
||
// NewMomentoSvcErr returns a new Momento service error. | ||
func NewMomentoSvcErr(code string, message string, originalErr error) MomentoSvcErr { | ||
return newMomentoSvcErr(code, message, originalErr) | ||
return newMomentoSvcErr(code, message, originalErr, code) | ||
} | ||
|
||
const ( | ||
|
@@ -37,23 +39,29 @@ const ( | |
AuthenticationError = "AuthenticationError" | ||
// LimitExceededError occurs when request rate, bandwidth, or object size exceeded the limits for the account. | ||
LimitExceededError = "LimitExceededError" | ||
// NotFoundError occurs when a cache with specified name doesn't exist. | ||
// NotFoundError occurs when a cache with specified name doesn"t exist. | ||
// | ||
// Deprecated: Use more specific CacheNotFoundError, StoreNotFoundError, or ItemNotFoundError instead. | ||
NotFoundError = "NotFoundError" | ||
// CacheNotFoundError occurs when a cache with specified name doesn't exist. | ||
// CacheNotFoundError occurs when a cache with specified name doesn"t exist. | ||
CacheNotFoundError = "NotFoundError" | ||
// StoreNotFoundError occurs when a store with specified name doesn't exist. | ||
// StoreNotFoundError occurs when a store with specified name doesn"t exist. | ||
StoreNotFoundError = "StoreNotFoundError" | ||
// ItemNotFoundError occurs when an item with specified key doesn't exist. | ||
// ItemNotFoundError occurs when an item with specified key doesn"t exist. | ||
ItemNotFoundError = "ItemNotFoundError" | ||
// AlreadyExistsError occurs when a cache with specified name already exists. | ||
// | ||
// Deprecated: Use more specific CacheAlreadyExistsError or StoreAlreadyExistsError instead. | ||
AlreadyExistsError = "AlreadyExistsError" | ||
// CacheAlreadyExistsError occurs when a cache with specified name already exists. | ||
CacheAlreadyExistsError = "AlreadyExistsError" | ||
// StoreAlreadyExistsError occurs when a store with specified name already exists. | ||
StoreAlreadyExistsError = "StoreAlreadyExistsError" | ||
// UnknownServiceError occurs when an unknown error has occurred. | ||
UnknownServiceError = "UnknownServiceError" | ||
// ServerUnavailableError occurs when the server was unable to handle the request. | ||
ServerUnavailableError = "ServerUnavailableError" | ||
// FailedPreconditionError occurs when the system is not in a state required for the operation's execution. | ||
// FailedPreconditionError occurs when the system is not in a state required for the operation"s execution. | ||
FailedPreconditionError = "FailedPreconditionError" | ||
// InternalServerErrorMessage is the message for an unexpected error occurring while trying to fulfill the request. | ||
InternalServerErrorMessage = "CacheService failed with an internal error" | ||
|
@@ -63,60 +71,204 @@ const ( | |
ConnectionError = "ConnectionError" | ||
) | ||
|
||
const ( | ||
TopicSubscriptionsLimitExceeded = "Topic subscriptions limit exceeded for this account" | ||
OperationsRateLimitExceeded = "Request rate limit exceeded for this account" | ||
ThroughputRateLimitExceeded = "Bandwidth limit exceeded for this account" | ||
RequestSizeLimitExceeded = "Request size limit exceeded for this account" | ||
ItemSizeLimitExceeded = "Item size limit exceeded for this account" | ||
ElementSizeLimitExceeded = "Element size limit exceeded for this account" | ||
UnknownLimitExceeded = "Limit exceeded for this account" | ||
) | ||
|
||
// ConvertSvcErr converts gRPC error to MomentoSvcErr. | ||
func ConvertSvcErr(err error, metadata ...metadata.MD) MomentoSvcErr { | ||
if grpcStatus, ok := status.FromError(err); ok { | ||
switch grpcStatus.Code() { | ||
case codes.InvalidArgument: | ||
return NewMomentoSvcErr(InvalidArgumentError, grpcStatus.Message(), err) | ||
return newMomentoSvcErr( | ||
InvalidArgumentError, | ||
grpcStatus.Message(), | ||
err, | ||
"Invalid argument passed to Momento client", | ||
) | ||
case codes.Unimplemented: | ||
return NewMomentoSvcErr(BadRequestError, grpcStatus.Message(), err) | ||
return newMomentoSvcErr( | ||
BadRequestError, | ||
grpcStatus.Message(), | ||
err, | ||
"The request was invalid; please contact us at [email protected]", | ||
) | ||
case codes.OutOfRange: | ||
return NewMomentoSvcErr(BadRequestError, grpcStatus.Message(), err) | ||
return newMomentoSvcErr( | ||
BadRequestError, | ||
grpcStatus.Message(), | ||
err, | ||
"The request was invalid; please contact us at [email protected]", | ||
) | ||
case codes.FailedPrecondition: | ||
return NewMomentoSvcErr(FailedPreconditionError, grpcStatus.Message(), err) | ||
return newMomentoSvcErr( | ||
FailedPreconditionError, | ||
grpcStatus.Message(), | ||
err, | ||
"System is not in a state required for the operation's execution", | ||
) | ||
case codes.Canceled: | ||
return NewMomentoSvcErr(CanceledError, grpcStatus.Message(), err) | ||
return newMomentoSvcErr( | ||
CanceledError, | ||
grpcStatus.Message(), | ||
err, | ||
"The request was cancelled by the server; please contact us at [email protected]", | ||
) | ||
case codes.DeadlineExceeded: | ||
return NewMomentoSvcErr(TimeoutError, grpcStatus.Message(), err) | ||
return newMomentoSvcErr( | ||
TimeoutError, | ||
grpcStatus.Message(), | ||
err, | ||
"The client's configured timeout was exceeded; you may need to use a Configuration with more lenient timeouts", | ||
) | ||
case codes.PermissionDenied: | ||
return NewMomentoSvcErr(PermissionError, grpcStatus.Message(), err) | ||
return newMomentoSvcErr( | ||
PermissionError, | ||
grpcStatus.Message(), | ||
err, | ||
"Insufficient permissions to perform operation", | ||
) | ||
case codes.Unauthenticated: | ||
return NewMomentoSvcErr(AuthenticationError, grpcStatus.Message(), err) | ||
return newMomentoSvcErr( | ||
AuthenticationError, | ||
grpcStatus.Message(), | ||
err, | ||
"Invalid authentication credentials to connect to Momento service", | ||
) | ||
case codes.ResourceExhausted: | ||
return NewMomentoSvcErr(LimitExceededError, grpcStatus.Message(), err) | ||
// By default, use the generic limit exceeded message wrapper. | ||
messageWrapper := UnknownLimitExceeded | ||
|
||
// If available, use metadata to determine cause of resource exhausted error. | ||
if len(metadata) > 1 { | ||
errData, ok := metadata[1]["err"] | ||
if ok && errData[0] != "" { | ||
switch errData[0] { | ||
case "topic_subscriptions_limit_exceeded": | ||
messageWrapper = TopicSubscriptionsLimitExceeded | ||
case "operations_rate_limit_exceeded": | ||
messageWrapper = OperationsRateLimitExceeded | ||
case "throughput_rate_limit_exceeded": | ||
messageWrapper = ThroughputRateLimitExceeded | ||
case "request_size_limit_exceeded": | ||
messageWrapper = RequestSizeLimitExceeded | ||
case "item_size_limit_exceeded": | ||
messageWrapper = ItemSizeLimitExceeded | ||
case "element_size_limit_exceeded": | ||
messageWrapper = ElementSizeLimitExceeded | ||
} | ||
} else { | ||
// If err metadata is not available, try string matching on the | ||
// error details to return the most specific message wrapper. | ||
lowerCasedMessage := strings.ToLower(grpcStatus.Message()) | ||
if strings.Contains(lowerCasedMessage, "subscribers") { | ||
messageWrapper = TopicSubscriptionsLimitExceeded | ||
} else if strings.Contains(lowerCasedMessage, "operations") { | ||
messageWrapper = OperationsRateLimitExceeded | ||
} else if strings.Contains(lowerCasedMessage, "throughput") { | ||
messageWrapper = ThroughputRateLimitExceeded | ||
} else if strings.Contains(lowerCasedMessage, "request limit") { | ||
messageWrapper = RequestSizeLimitExceeded | ||
} else if strings.Contains(lowerCasedMessage, "item size") { | ||
messageWrapper = ItemSizeLimitExceeded | ||
} else if strings.Contains(lowerCasedMessage, "element size") { | ||
messageWrapper = ElementSizeLimitExceeded | ||
} | ||
} | ||
} | ||
return newMomentoSvcErr(LimitExceededError, grpcStatus.Message(), err, messageWrapper) | ||
case codes.NotFound: | ||
cacheMessageWrapper := "A cache with the specified name does not exist. To resolve this error, make sure you have created the cache before attempting to use it" | ||
storeMessageWrapper := "A store with the specified name does not exist. To resolve this error, make sure you have created the store before attempting to use it" | ||
// Use metadata to determine cause of not found error | ||
if len(metadata) > 1 { | ||
errData, ok := metadata[1]["err"] | ||
// In the absence of error metadata, return CacheNotFoundError. | ||
// This is currently re-mapped to a StoreNotFoundError in the PreviewStorageClient's | ||
// This is currently re-mapped to a StoreNotFoundError in the PreviewStorageClient"s | ||
// DeleteStore method. | ||
if !ok { | ||
return NewMomentoSvcErr(CacheNotFoundError, grpcStatus.Message(), err) | ||
return newMomentoSvcErr(CacheNotFoundError, grpcStatus.Message(), err, cacheMessageWrapper) | ||
} | ||
errCause := errData[0] | ||
if errCause == "item_not_found" { | ||
return NewMomentoSvcErr(ItemNotFoundError, grpcStatus.Message(), err) | ||
return newMomentoSvcErr( | ||
ItemNotFoundError, | ||
grpcStatus.Message(), | ||
err, | ||
"An item with the specified key does not exist", | ||
) | ||
} else if errCause == "store_not_found" { | ||
return NewMomentoSvcErr(StoreNotFoundError, grpcStatus.Message(), err) | ||
return newMomentoSvcErr(StoreNotFoundError, grpcStatus.Message(), err, storeMessageWrapper) | ||
} | ||
} | ||
return NewMomentoSvcErr(CacheNotFoundError, grpcStatus.Message(), err) | ||
return newMomentoSvcErr(CacheNotFoundError, grpcStatus.Message(), err, cacheMessageWrapper) | ||
case codes.AlreadyExists: | ||
return NewMomentoSvcErr(AlreadyExistsError, grpcStatus.Message(), err) | ||
cacheMessageWrapper := "A cache with the specified name already exists. To resolve this error, either delete the existing cache and make a new one, or use a different name" | ||
storeMessageWrapper := "A store with the specified name already exists. To resolve this error, either delete the existing store and make a new one, or use a different name" | ||
if len(metadata) > 1 { | ||
errData, ok := metadata[1]["err"] | ||
// In the absence of error metadata, return CacheAlreadyExistsError. | ||
if !ok { | ||
return newMomentoSvcErr(CacheAlreadyExistsError, grpcStatus.Message(), err, cacheMessageWrapper) | ||
} | ||
errCause := errData[0] | ||
switch errCause { | ||
case "store_already_exists": | ||
return newMomentoSvcErr(StoreAlreadyExistsError, grpcStatus.Message(), err, storeMessageWrapper) | ||
default: | ||
return newMomentoSvcErr(CacheAlreadyExistsError, grpcStatus.Message(), err, cacheMessageWrapper) | ||
} | ||
} | ||
// If no metadata is available, return CacheAlreadyExistsError by default. | ||
return newMomentoSvcErr(CacheAlreadyExistsError, grpcStatus.Message(), err, cacheMessageWrapper) | ||
case codes.Unknown: | ||
return NewMomentoSvcErr(UnknownServiceError, grpcStatus.Message(), err) | ||
return newMomentoSvcErr( | ||
UnknownServiceError, | ||
grpcStatus.Message(), | ||
err, | ||
"Service returned an unknown response; please contact us at [email protected]", | ||
) | ||
case codes.Aborted: | ||
return NewMomentoSvcErr(InternalServerError, grpcStatus.Message(), err) | ||
return newMomentoSvcErr( | ||
InternalServerError, | ||
grpcStatus.Message(), | ||
err, | ||
"Unexpected error encountered while trying to fulfill the request; please contact us at [email protected]", | ||
) | ||
case codes.Internal: | ||
return NewMomentoSvcErr(InternalServerError, grpcStatus.Message(), err) | ||
return newMomentoSvcErr( | ||
InternalServerError, | ||
grpcStatus.Message(), | ||
err, | ||
"Unexpected error encountered while trying to fulfill the request; please contact us at [email protected]", | ||
) | ||
case codes.Unavailable: | ||
return NewMomentoSvcErr(ServerUnavailableError, grpcStatus.Message(), err) | ||
return newMomentoSvcErr( | ||
ServerUnavailableError, | ||
grpcStatus.Message(), | ||
err, | ||
"The server was unable to handle the request; consider retrying. If the error persists, please contact us at [email protected]", | ||
) | ||
case codes.DataLoss: | ||
return NewMomentoSvcErr(InternalServerError, grpcStatus.Message(), err) | ||
return newMomentoSvcErr( | ||
InternalServerError, | ||
grpcStatus.Message(), | ||
err, | ||
"Unexpected error encountered while trying to fulfill the request; please contact us at [email protected]", | ||
) | ||
default: | ||
return NewMomentoSvcErr(UnknownServiceError, InternalServerErrorMessage, err) | ||
return newMomentoSvcErr( | ||
UnknownServiceError, | ||
InternalServerErrorMessage, | ||
err, | ||
"Service returned an unknown response; please contact us at [email protected]", | ||
) | ||
} | ||
} | ||
return NewMomentoSvcErr(ClientSdkError, ClientSdkErrorMessage, err) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.