Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions packages/kernel-errors/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const ErrorCode = {
SubclusterNotFound: 'SUBCLUSTER_NOT_FOUND',
SampleGenerationError: 'SAMPLE_GENERATION_ERROR',
InternalError: 'INTERNAL_ERROR',
ResourceLimitError: 'RESOURCE_LIMIT_ERROR',
} as const;

export type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
Expand Down
233 changes: 233 additions & 0 deletions packages/kernel-errors/src/errors/ResourceLimitError.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
import { describe, it, expect } from 'vitest';

import { ResourceLimitError } from './ResourceLimitError.ts';
import { ErrorCode, ErrorSentinel } from '../constants.ts';
import { unmarshalErrorOptions } from '../marshal/unmarshalError.ts';
import type { MarshaledOcapError } from '../types.ts';

describe('ResourceLimitError', () => {
it('creates a ResourceLimitError with the correct properties', () => {
const error = new ResourceLimitError('Connection limit exceeded');
expect(error).toBeInstanceOf(ResourceLimitError);
expect(error.code).toBe(ErrorCode.ResourceLimitError);
expect(error.message).toBe('Connection limit exceeded');
expect(error.data).toBeUndefined();
});

it('creates a ResourceLimitError with connection limit data', () => {
const error = new ResourceLimitError('Connection limit exceeded', {
data: {
limitType: 'connection',
current: 100,
limit: 100,
},
});
expect(error).toBeInstanceOf(ResourceLimitError);
expect(error.code).toBe(ErrorCode.ResourceLimitError);
expect(error.message).toBe('Connection limit exceeded');
expect(error.data).toStrictEqual({
limitType: 'connection',
current: 100,
limit: 100,
});
});

it('creates a ResourceLimitError with message size limit data', () => {
const error = new ResourceLimitError('Message size limit exceeded', {
data: {
limitType: 'messageSize',
current: 1048577,
limit: 1048576,
},
});
expect(error).toBeInstanceOf(ResourceLimitError);
expect(error.code).toBe(ErrorCode.ResourceLimitError);
expect(error.message).toBe('Message size limit exceeded');
expect(error.data).toStrictEqual({
limitType: 'messageSize',
current: 1048577,
limit: 1048576,
});
});

it('creates a ResourceLimitError with partial data', () => {
const error = new ResourceLimitError('Resource limit exceeded', {
data: {
limitType: 'connection',
},
});
expect(error).toBeInstanceOf(ResourceLimitError);
expect(error.data).toStrictEqual({
limitType: 'connection',
});
});

it('creates a ResourceLimitError with a cause', () => {
const cause = new Error('Original error');
const error = new ResourceLimitError('Resource limit exceeded', { cause });
expect(error).toBeInstanceOf(ResourceLimitError);
expect(error.code).toBe(ErrorCode.ResourceLimitError);
expect(error.cause).toBe(cause);
});

it('creates a ResourceLimitError with a custom stack', () => {
const customStack = 'custom stack trace';
const error = new ResourceLimitError('Resource limit exceeded', {
stack: customStack,
});
expect(error).toBeInstanceOf(ResourceLimitError);
expect(error.stack).toBe(customStack);
});

it('unmarshals a valid marshaled ResourceLimitError with connection limit data', () => {
const marshaledError: MarshaledOcapError = {
[ErrorSentinel]: true,
message: 'Connection limit exceeded',
code: ErrorCode.ResourceLimitError,
data: {
limitType: 'connection',
current: 100,
limit: 100,
},
stack: 'stack trace',
};

const unmarshaledError = ResourceLimitError.unmarshal(
marshaledError,
unmarshalErrorOptions,
);
expect(unmarshaledError).toBeInstanceOf(ResourceLimitError);
expect(unmarshaledError.code).toBe(ErrorCode.ResourceLimitError);
expect(unmarshaledError.message).toBe('Connection limit exceeded');
expect(unmarshaledError.stack).toBe('stack trace');
expect(unmarshaledError.data).toStrictEqual({
limitType: 'connection',
current: 100,
limit: 100,
});
});

it('unmarshals a valid marshaled ResourceLimitError with message size limit data', () => {
const marshaledError: MarshaledOcapError = {
[ErrorSentinel]: true,
message: 'Message size limit exceeded',
code: ErrorCode.ResourceLimitError,
data: {
limitType: 'messageSize',
current: 1048577,
limit: 1048576,
},
stack: 'stack trace',
};

const unmarshaledError = ResourceLimitError.unmarshal(
marshaledError,
unmarshalErrorOptions,
);
expect(unmarshaledError).toBeInstanceOf(ResourceLimitError);
expect(unmarshaledError.code).toBe(ErrorCode.ResourceLimitError);
expect(unmarshaledError.message).toBe('Message size limit exceeded');
expect(unmarshaledError.data).toStrictEqual({
limitType: 'messageSize',
current: 1048577,
limit: 1048576,
});
});

it('unmarshals a valid marshaled ResourceLimitError without data', () => {
const marshaledError = {
[ErrorSentinel]: true,
message: 'Resource limit exceeded',
code: ErrorCode.ResourceLimitError,
stack: 'stack trace',
} as unknown as MarshaledOcapError;

const unmarshaledError = ResourceLimitError.unmarshal(
marshaledError,
unmarshalErrorOptions,
);
expect(unmarshaledError).toBeInstanceOf(ResourceLimitError);
expect(unmarshaledError.code).toBe(ErrorCode.ResourceLimitError);
expect(unmarshaledError.message).toBe('Resource limit exceeded');
expect(unmarshaledError.data).toBeUndefined();
});

it.each([
{
name: 'invalid limitType value',
marshaledError: {
[ErrorSentinel]: true,
message: 'Resource limit exceeded',
code: ErrorCode.ResourceLimitError,
data: {
limitType: 'invalid',
current: 100,
limit: 100,
},
stack: 'stack trace',
} as unknown as MarshaledOcapError,
expectedError:
'At path: data.limitType -- Expected the value to satisfy a union of `literal | literal`, but received: "invalid"',
},
{
name: 'invalid current type',
marshaledError: {
[ErrorSentinel]: true,
message: 'Resource limit exceeded',
code: ErrorCode.ResourceLimitError,
data: {
limitType: 'connection',
current: 'not a number',
limit: 100,
},
stack: 'stack trace',
} as unknown as MarshaledOcapError,
expectedError:
'At path: data.current -- Expected a number, but received: "not a number"',
},
{
name: 'invalid limit type',
marshaledError: {
[ErrorSentinel]: true,
message: 'Resource limit exceeded',
code: ErrorCode.ResourceLimitError,
data: {
limitType: 'connection',
current: 100,
limit: 'not a number',
},
stack: 'stack trace',
} as unknown as MarshaledOcapError,
expectedError:
'At path: data.limit -- Expected a number, but received: "not a number"',
},
{
name: 'wrong error code',
marshaledError: {
[ErrorSentinel]: true,
message: 'Resource limit exceeded',
code: 'WRONG_ERROR_CODE' as ErrorCode,
stack: 'stack trace',
} as unknown as MarshaledOcapError,
expectedError:
'At path: code -- Expected the literal `"RESOURCE_LIMIT_ERROR"`, but received: "WRONG_ERROR_CODE"',
},
{
name: 'missing required fields',
marshaledError: {
[ErrorSentinel]: true,
message: 'Resource limit exceeded',
// Missing code field
} as unknown as MarshaledOcapError,
expectedError:
'At path: code -- Expected the literal `"RESOURCE_LIMIT_ERROR"`, but received: undefined',
},
])(
'throws an error when unmarshaling with $name',
({ marshaledError, expectedError }) => {
expect(() =>
ResourceLimitError.unmarshal(marshaledError, unmarshalErrorOptions),
).toThrow(expectedError);
},
);
});
76 changes: 76 additions & 0 deletions packages/kernel-errors/src/errors/ResourceLimitError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {
assert,
literal,
number,
object,
optional,
union,
} from '@metamask/superstruct';

import { BaseError } from '../BaseError.ts';
import { marshaledErrorSchema, ErrorCode } from '../constants.ts';
import type { ErrorOptionsWithStack, MarshaledOcapError } from '../types.ts';

export class ResourceLimitError extends BaseError {
constructor(
message: string,
options?: ErrorOptionsWithStack & {
data?: {
limitType?: 'connection' | 'messageSize';
current?: number;
limit?: number;
};
},
) {
super(ErrorCode.ResourceLimitError, message, {
...options,
});
harden(this);
}

/**
* A superstruct struct for validating marshaled {@link ResourceLimitError} instances.
*/
public static struct = object({
...marshaledErrorSchema,
code: literal(ErrorCode.ResourceLimitError),
data: optional(
object({
limitType: optional(
union([literal('connection'), literal('messageSize')]),
),
current: optional(number()),
limit: optional(number()),
}),
),
});

/**
* Unmarshals a {@link MarshaledError} into a {@link ResourceLimitError}.
*
* @param marshaledError - The marshaled error to unmarshal.
* @param unmarshalErrorOptions - The function to unmarshal the error options.
* @returns The unmarshaled error.
*/
public static unmarshal(
marshaledError: MarshaledOcapError,
unmarshalErrorOptions: (
marshaledError: MarshaledOcapError,
) => ErrorOptionsWithStack,
): ResourceLimitError {
assert(marshaledError, this.struct);
const options = unmarshalErrorOptions(marshaledError);
const data = marshaledError.data as
| {
limitType?: 'connection' | 'messageSize';
current?: number;
limit?: number;
}
| undefined;
return new ResourceLimitError(marshaledError.message, {
...options,
...(data !== undefined && { data }),
});
}
}
harden(ResourceLimitError);
2 changes: 2 additions & 0 deletions packages/kernel-errors/src/errors/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AbortError } from './AbortError.ts';
import { DuplicateEndowmentError } from './DuplicateEndowmentError.ts';
import { EvaluatorError } from './EvaluatorError.ts';
import { ResourceLimitError } from './ResourceLimitError.ts';
import { SampleGenerationError } from './SampleGenerationError.ts';
import { StreamReadError } from './StreamReadError.ts';
import { VatAlreadyExistsError } from './VatAlreadyExistsError.ts';
Expand All @@ -19,4 +20,5 @@ export const errorClasses = {
[ErrorCode.SubclusterNotFound]: SubclusterNotFoundError,
[ErrorCode.SampleGenerationError]: SampleGenerationError,
[ErrorCode.InternalError]: EvaluatorError,
[ErrorCode.ResourceLimitError]: ResourceLimitError,
} as const;
1 change: 1 addition & 0 deletions packages/kernel-errors/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('index', () => {
'EvaluatorError',
'MarshaledErrorStruct',
'MarshaledOcapErrorStruct',
'ResourceLimitError',
'SampleGenerationError',
'StreamReadError',
'SubclusterNotFoundError',
Expand Down
1 change: 1 addition & 0 deletions packages/kernel-errors/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export { VatNotFoundError } from './errors/VatNotFoundError.ts';
export { StreamReadError } from './errors/StreamReadError.ts';
export { SubclusterNotFoundError } from './errors/SubclusterNotFoundError.ts';
export { AbortError } from './errors/AbortError.ts';
export { ResourceLimitError } from './errors/ResourceLimitError.ts';
export {
ErrorCode,
ErrorSentinel,
Expand Down
Loading
Loading