Skip to content
Merged
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
2 changes: 2 additions & 0 deletions packages/errors/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"test:watch": "vitest --config vitest.config.ts"
},
"dependencies": {
"@metamask/superstruct": "^3.1.0",
"@metamask/utils": "^9.3.0"
},
"devDependencies": {
Expand All @@ -50,6 +51,7 @@
"@metamask/eslint-config": "^14.0.0",
"@metamask/eslint-config-nodejs": "^14.0.0",
"@metamask/eslint-config-typescript": "^14.0.0",
"@ocap/test-utils": "workspace:^",
"@ts-bridge/cli": "^0.5.1",
"@ts-bridge/shims": "^0.1.1",
"@typescript-eslint/eslint-plugin": "^8.8.1",
Expand Down
17 changes: 12 additions & 5 deletions packages/errors/src/BaseError.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import { describe, it, expect } from 'vitest';

import { BaseError } from './BaseError.js';
import { ErrorCode } from './constants.js';
import type { MarshaledOcapError } from './types.js';

describe('BaseError', () => {
const mockCode = ErrorCode.VatNotFound;
const mockMessage = 'VAT was not found.';
const mockData = { key: 'value' };
const mockCause = new Error('Root cause error');

it('should create a BaseError with required properties', () => {
it('creates a BaseError with required properties', () => {
const error = new BaseError(mockCode, mockMessage);
expect(error).toBeInstanceOf(BaseError);
expect(error).toBeInstanceOf(Error);
Expand All @@ -20,7 +21,7 @@ describe('BaseError', () => {
expect(error.cause).toBeUndefined();
});

it('should create a BaseError with all properties', () => {
it('creates a BaseError with all properties', () => {
const error = new BaseError(mockCode, mockMessage, mockData, mockCause);
expect(error.name).toBe('BaseError');
expect(error.message).toBe(mockMessage);
Expand All @@ -29,21 +30,27 @@ describe('BaseError', () => {
expect(error.cause).toBe(mockCause);
});

it('should inherit from the Error class and have the correct name', () => {
it('inherits from the Error class and have the correct name', () => {
const error = new BaseError(mockCode, mockMessage);
expect(error).toBeInstanceOf(Error);
expect(error.name).toBe('BaseError');
});

it('should correctly handle a missing data parameter', () => {
it('handles a missing data parameter', () => {
const error = new BaseError(mockCode, mockMessage, undefined, mockCause);
expect(error.data).toBeUndefined();
expect(error.cause).toBe(mockCause);
});

it('should correctly handle a missing cause parameter', () => {
it('handles a missing cause parameter', () => {
const error = new BaseError(mockCode, mockMessage, mockData);
expect(error.data).toStrictEqual(mockData);
expect(error.cause).toBeUndefined();
});

it('throws an error when unmarshal is called', () => {
expect(() => BaseError.unmarshal({} as MarshaledOcapError)).toThrow(
'Unmarshal method not implemented',
);
});
});
18 changes: 14 additions & 4 deletions packages/errors/src/BaseError.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import type { Json } from '@metamask/utils';

import type { ErrorCode } from './constants.js';
import type { MarshaledOcapError, OcapError } from './types.js';

export class BaseError extends Error {
export class BaseError extends Error implements OcapError {
public readonly code: ErrorCode;

public data: Json | undefined;

public cause: unknown;
public readonly data: Json | undefined;

constructor(code: ErrorCode, message: string, data?: Json, cause?: unknown) {
super(message, { cause });
Expand All @@ -16,5 +15,16 @@ export class BaseError extends Error {
this.code = code;
this.data = data;
this.cause = cause;
harden(this);
}

/**
* A placeholder for unmarshal functionality. Should be implemented in subclasses.
*
* @param _marshaledError - The marshaled error to unmarshal.
*/
public static unmarshal(_marshaledError: MarshaledOcapError): BaseError {
throw new Error('Unmarshal method not implemented');
}
}
harden(BaseError);
47 changes: 47 additions & 0 deletions packages/errors/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
import type { Struct } from '@metamask/superstruct';
import { lazy, literal, optional, string, union } from '@metamask/superstruct';
import { JsonStruct, object } from '@metamask/utils';
import type { NonEmptyArray } from '@metamask/utils';

import type { MarshaledError, MarshaledOcapError } from './types.js';

/**
* Enum defining all error codes for Ocap errors.
*/
export enum ErrorCode {
StreamReadError = 'STREAM_READ_ERROR',
VatAlreadyExists = 'VAT_ALREADY_EXISTS',
Expand All @@ -6,3 +16,40 @@ export enum ErrorCode {
VatDeleted = 'VAT_DELETED',
VatNotFound = 'VAT_NOT_FOUND',
}

/**
* A sentinel value used to identify marshaled errors.
*/
export const ErrorSentinel = '@@MARSHALED_ERROR';

const ErrorCodeStruct = union(
Object.values(ErrorCode).map((code) => literal(code)) as NonEmptyArray<
Struct<ErrorCode>
>,
);

export const marshaledErrorSchema = {
[ErrorSentinel]: literal(true),
message: string(),
code: optional(ErrorCodeStruct),
data: optional(JsonStruct),
stack: optional(string()),
};

/**
* Struct to validate marshaled errors.
*/
export const MarshaledErrorStruct = object({
...marshaledErrorSchema,
cause: optional(union([string(), lazy(() => MarshaledErrorStruct)])),
}) as Struct<MarshaledError>;

/**
* Struct to validate marshaled ocap errors.
*/
export const MarshaledOcapErrorStruct = object({
...marshaledErrorSchema,
code: ErrorCodeStruct,
data: JsonStruct,
cause: optional(union([string(), lazy(() => MarshaledErrorStruct)])),
}) as Struct<MarshaledOcapError>;
98 changes: 0 additions & 98 deletions packages/errors/src/errors.test.ts

This file was deleted.

58 changes: 0 additions & 58 deletions packages/errors/src/errors.ts

This file was deleted.

Loading