Skip to content

Commit

Permalink
fix: add message wrapper to errors and improve resource exhausted err…
Browse files Browse the repository at this point in the history
…or message by using metadata
  • Loading branch information
anitarua committed Oct 29, 2024
1 parent 2126ca8 commit cf98244
Show file tree
Hide file tree
Showing 58 changed files with 670 additions and 261 deletions.
206 changes: 179 additions & 27 deletions internal/momentoerrors/scs_errors.go
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"
Expand All @@ -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 (
Expand All @@ -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"
Expand All @@ -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)
Expand Down
15 changes: 10 additions & 5 deletions internal/momentoerrors/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@ package momentoerrors
import "fmt"

type momentoSvcError struct {
code string
message string
originalErr error
code string
message string
originalErr error
messageWrapper string
}

func newMomentoSvcErr(code string, message string, originalErr error) *momentoSvcError {
func newMomentoSvcErr(code string, message string, originalErr error, messageWrapper string) *momentoSvcError {
return &momentoSvcError{
code,
message,
originalErr,
messageWrapper,
}
}

Expand All @@ -21,7 +23,10 @@ func (err momentoSvcError) Code() string {
}

func (err momentoSvcError) Message() string {
return err.message
if err.messageWrapper == "" {
return err.message
}
return err.messageWrapper + ": " + err.message
}

func (err momentoSvcError) OriginalErr() error {
Expand Down
Loading

0 comments on commit cf98244

Please sign in to comment.