Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions packages/utils/src/ratelimit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export function updateRateLimits(
* rate limit headers are of the form
* <header>,<header>,..
* where each <header> is of the form
* <retry_after>: <categories>: <scope>: <reason_code>
* <retry_after>: <categories>: <scope>: <reason_code>: <namespaces>
* where
* <retry_after> is a delay in seconds
* <categories> is the event type(s) (error, transaction, etc) being rate limited and is of the form
Expand All @@ -78,14 +78,17 @@ export function updateRateLimits(
* Only present if rate limit applies to the metric_bucket data category.
*/
for (const limit of rateLimitHeader.trim().split(',')) {
const [retryAfter, categories] = limit.split(':', 2);
const [retryAfter, categories, , , namespaces] = limit.split(':', 5);
const headerDelay = parseInt(retryAfter, 10);
const delay = (!isNaN(headerDelay) ? headerDelay : 60) * 1000; // 60sec default
if (!categories) {
updatedRateLimits.all = now + delay;
} else {
for (const category of categories.split(';')) {
updatedRateLimits[category] = now + delay;
// namespaces will be present when category === 'metric_bucket'
if (category !== 'metric_bucket' || !namespaces || namespaces.split(';').includes('custom')) {
updatedRateLimits[category] = now + delay;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// namespaces will be present when category === 'metric_bucket'
if (category !== 'metric_bucket' || !namespaces || namespaces.split(';').includes('custom')) {
updatedRateLimits[category] = now + delay;
}
if (category === 'metric_bucket') {
// namespaces will be present when category === 'metric_bucket'
if (!namespaces || namespaces.split(';').includes('custom')) {
updatedRateLimits[category] = now + delay;
}
} else {
updatedRateLimits[category] = now + delay;
}

This is giga nitting but I would do it like this. Regardless of bundlesize (hoping terser is smart enough). Makes it easier to understand this admittedly convoluted logic.

}
}
}
Expand Down
32 changes: 32 additions & 0 deletions packages/utils/test/ratelimit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,35 @@ describe('updateRateLimits()', () => {
expect(updatedRateLimits.all).toEqual(60_000);
});
});

describe('data category "metric_bucket"', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add another test case for 2700:metric_bucket:organization:quota_exceeded:

test('should add limit for `metric_bucket` category when namespaces contain "custom"', () => {
const rateLimits: RateLimits = {};
const headers = {
'retry-after': null,
'x-sentry-rate-limits': '42:metric_bucket:::custom',
};
const updatedRateLimits = updateRateLimits(rateLimits, { headers }, 0);
expect(updatedRateLimits.metric_bucket).toEqual(42 * 1000);
});

test('should not add limit for `metric_bucket` category when namespaces do not contain "custom"', () => {
const rateLimits: RateLimits = {};
const headers = {
'retry-after': null,
'x-sentry-rate-limits': '42:metric_bucket:::namespace1;namespace2',
};
const updatedRateLimits = updateRateLimits(rateLimits, { headers }, 0);
expect(updatedRateLimits.metric_bucket).toBeUndefined();
});

test('should add limit for `metric_bucket` category when namespaces are empty', () => {
const rateLimits: RateLimits = {};
const headers = {
'retry-after': null,
'x-sentry-rate-limits': '42:metric_bucket',
};
const updatedRateLimits = updateRateLimits(rateLimits, { headers }, 0);
expect(updatedRateLimits.metric_bucket).toEqual(42 * 1000);
});
});