Skip to content

Commit

Permalink
chore: clarify limit exceeded in resource exhausted errors (#1448)
Browse files Browse the repository at this point in the history
* chore: clarify limit exceeded in resource exhausted errors

* revise error mapping to pass err metadata value, add enum for limit exceeded message wrapper variations

* add extra null check before calling toString

* add extra test to large-messages.test.ts
  • Loading branch information
anitarua authored Oct 28, 2024
1 parent 7940127 commit 448eb96
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,10 @@ export class CacheServiceErrorMapper
return new TimeoutError(...errParams);
case Status.UNAUTHENTICATED:
return new AuthenticationError(...errParams);
case Status.RESOURCE_EXHAUSTED:
return new LimitExceededError(...errParams);
case Status.RESOURCE_EXHAUSTED: {
const errCause = errParams[2]?.get('err')?.[0]?.toString();
return new LimitExceededError(...errParams, errCause);
}
case Status.ALREADY_EXISTS: {
let errCause = errParams[2]?.get('err')?.[0];
// TODO: Remove this once the error message is standardized on the server side
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,22 @@ describe('CacheClient', () => {
expect(responseValue).toEqual(value);
}, `expected 5mb retrieved string to match 5mb value that was set for key ${key}`);
});

it('should fail with RESOURCE_EXHAUSTED_ERROR when setting a value greater than 5mb', async () => {
const cacheKey = v4();
const cacheValue = 'x'.repeat(5_300_000);
const setResponse = await cacheClient.set(
integrationTestCacheName,
cacheKey,
cacheValue
);
const stringifiedSetResponse = setResponse.toString();
expectWithMessage(() => {
expect(setResponse).toBeInstanceOf(CacheSet.Error);
}, `expected ERROR but got ${stringifiedSetResponse}`);
expect(stringifiedSetResponse).toInclude('RESOURCE_EXHAUSTED');
expect(stringifiedSetResponse).toInclude(
'Request size limit exceeded for this account'
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,11 @@ export class CacheServiceErrorMapper
return new TimeoutError(...errParams);
case StatusCode.UNAUTHENTICATED:
return new AuthenticationError(...errParams);
case StatusCode.RESOURCE_EXHAUSTED:
return new LimitExceededError(...errParams);
case StatusCode.RESOURCE_EXHAUSTED: {
const meta = errParams[2] ?? {};
const errCause = meta['err'];
return new LimitExceededError(...errParams, errCause);
}
case StatusCode.ALREADY_EXISTS: {
let errCause = '';
// TODO: Remove this once the error message is standardized on the server side
Expand Down
72 changes: 69 additions & 3 deletions packages/core/src/errors/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export class MomentoErrorTransportDetails {
export abstract class SdkError extends Error {
protected readonly _errorCode: MomentoErrorCode;
protected readonly _messageWrapper: string;
private readonly _transportDetails: MomentoErrorTransportDetails;
protected readonly _transportDetails: MomentoErrorTransportDetails;
constructor(
message: string,
code = 0,
Expand Down Expand Up @@ -174,13 +174,79 @@ export class InvalidArgumentError extends SdkError {
override _messageWrapper = 'Invalid argument passed to Momento client';
}

enum LimitExceededMessageWrapper {
TOPIC_SUBSCRIPTIONS_LIMIT_EXCEEDED = 'Topic subscriptions limit exceeded for this account',
OPERATIONS_RATE_LIMIT_EXCEEDED = 'Request rate limit exceeded for this account',
THROUGHPUT_RATE_LIMIT_EXCEEDED = 'Bandwidth limit exceeded for this account',
REQUEST_SIZE_LIMIT_EXCEEDED = 'Request size limit exceeded for this account',
ITEM_SIZE_LIMIT_EXCEEDED = 'Item size limit exceeded for this account',
ELEMENT_SIZE_LIMIT_EXCEEDED = 'Element size limit exceeded for this account',
UNKNOWN_LIMIT_EXCEEDED = 'Limit exceeded for this account',
}

/**
* Error when calls are throttled due to request limit rate
*/
export class LimitExceededError extends SdkError {
override _errorCode = MomentoErrorCode.LIMIT_EXCEEDED_ERROR;
override _messageWrapper =
'Request rate, bandwidth, or object size exceeded the limits for this account. To resolve this error, reduce your usage as appropriate or contact us at [email protected] to request a limit increase';
override _messageWrapper = this.determineMessageWrapper();
private errCause: string | undefined;

constructor(
message: string,
code = 0,
metadata: object | undefined = undefined,
stack: string | undefined = undefined,
errCause?: string
) {
super(message, code, metadata, stack);
this.errCause = errCause;
}

private determineMessageWrapper() {
// If provided, we use the `err` metadata value to determine the most
// appropriate error message to return.
if (this.errCause !== undefined) {
switch (this.errCause) {
case 'topic_subscriptions_limit_exceeded':
return LimitExceededMessageWrapper.TOPIC_SUBSCRIPTIONS_LIMIT_EXCEEDED;
case 'operations_rate_limit_exceeded':
return LimitExceededMessageWrapper.OPERATIONS_RATE_LIMIT_EXCEEDED;
case 'throughput_rate_limit_exceeded':
return LimitExceededMessageWrapper.THROUGHPUT_RATE_LIMIT_EXCEEDED;
case 'request_size_limit_exceeded':
return LimitExceededMessageWrapper.REQUEST_SIZE_LIMIT_EXCEEDED;
case 'item_size_limit_exceeded':
return LimitExceededMessageWrapper.ITEM_SIZE_LIMIT_EXCEEDED;
case 'element_size_limit_exceeded':
return LimitExceededMessageWrapper.ELEMENT_SIZE_LIMIT_EXCEEDED;
default:
return LimitExceededMessageWrapper.UNKNOWN_LIMIT_EXCEEDED;
}
}

// If `err` metadata is unavailable, try to use the error details field
// to return the an appropriate error message.
if (this._transportDetails.grpc.details !== undefined) {
const details = this._transportDetails.grpc.details.toLowerCase();
if (details.includes('subscribers')) {
return LimitExceededMessageWrapper.TOPIC_SUBSCRIPTIONS_LIMIT_EXCEEDED;
} else if (details.includes('operations')) {
return LimitExceededMessageWrapper.OPERATIONS_RATE_LIMIT_EXCEEDED;
} else if (details.includes('throughput')) {
return LimitExceededMessageWrapper.THROUGHPUT_RATE_LIMIT_EXCEEDED;
} else if (details.includes('request limit')) {
return LimitExceededMessageWrapper.REQUEST_SIZE_LIMIT_EXCEEDED;
} else if (details.includes('item size')) {
return LimitExceededMessageWrapper.ITEM_SIZE_LIMIT_EXCEEDED;
} else if (details.includes('element size')) {
return LimitExceededMessageWrapper.ELEMENT_SIZE_LIMIT_EXCEEDED;
}
}

// If all else fails, return a generic "limit exceeded" message
return LimitExceededMessageWrapper.UNKNOWN_LIMIT_EXCEEDED;
}
}

/**
Expand Down

0 comments on commit 448eb96

Please sign in to comment.