Skip to content
Closed
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
10 changes: 10 additions & 0 deletions packages/errors/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

[Unreleased]: https://github.com/MetaMask/ocap-kernel/
7 changes: 7 additions & 0 deletions packages/errors/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# `errors`

Ocap Kernel errors.

## Contributing

This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/ocap-kernel#readme).
17 changes: 17 additions & 0 deletions packages/errors/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// @ts-check

import baseConfig from '../../eslint.config.mjs';

/** @type {import('eslint').Linter.Config[]} */
const config = [
...baseConfig,
{
languageOptions: {
parserOptions: {
tsconfigRootDir: new URL('.', import.meta.url).pathname,
},
},
},
];

export default config;
81 changes: 81 additions & 0 deletions packages/errors/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
{
"name": "@ocap/errors",
"version": "0.0.0",
"private": true,
"description": "Ocap Kernel errors",
"repository": {
"type": "git",
"url": "https://github.com/MetaMask/ocap-kernel.git"
},
"type": "module",
"exports": {
".": {
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
},
"./package.json": "./package.json"
},
"files": [
"dist/"
],
"scripts": {
"build": "ts-bridge --project tsconfig.build.json --clean",
"build:docs": "typedoc",
"changelog:validate": "../../scripts/validate-changelog.sh utils",
"clean": "rimraf --glob ./dist './*.tsbuildinfo'",
"lint": "yarn lint:eslint && yarn lint:misc --check && yarn constraints && yarn lint:dependencies",
"lint:dependencies": "depcheck",
"lint:eslint": "eslint . --cache",
"lint:fix": "yarn constraints --fix && yarn lint:eslint --fix && yarn lint:misc --write",
"lint:misc": "prettier --no-error-on-unmatched-pattern '**/*.json' '**/*.md' '**/*.html' '!**/CHANGELOG.old.md' '**/*.yml' '!.yarnrc.yml' '!merged-packages/**' --ignore-path ../../.gitignore",
"publish:preview": "yarn npm publish --tag preview",
"test": "vitest run --config vitest.config.ts",
"test:clean": "yarn test --no-cache --coverage.clean",
"test:dev": "yarn test --coverage false",
"test:verbose": "yarn test --reporter verbose",
"test:watch": "vitest --config vitest.config.ts"
},
"dependencies": {
"@metamask/utils": "^9.3.0"
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.16.4",
"@metamask/auto-changelog": "^3.4.4",
"@metamask/eslint-config": "^14.0.0",
"@metamask/eslint-config-nodejs": "^14.0.0",
"@metamask/eslint-config-typescript": "^14.0.0",
"@ts-bridge/cli": "^0.5.1",
"@ts-bridge/shims": "^0.1.1",
"@typescript-eslint/eslint-plugin": "^8.8.1",
"@typescript-eslint/parser": "^8.8.1",
"@typescript-eslint/utils": "^8.8.1",
"@vitest/eslint-plugin": "^1.1.7",
"depcheck": "^1.4.7",
"eslint": "^9.12.0",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.3",
"eslint-plugin-import-x": "^4.3.1",
"eslint-plugin-jsdoc": "^50.3.1",
"eslint-plugin-n": "^17.11.1",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-promise": "^7.1.0",
"jsdom": "^24.1.1",
"prettier": "^3.3.3",
"rimraf": "^6.0.1",
"ses": "^1.9.0",
"typedoc": "^0.26.8",
"typescript": "~5.5.4",
"typescript-eslint": "^8.8.1",
"vite": "^5.3.5",
"vitest": "^2.1.2"
},
"engines": {
"node": "^18.18 || >=20"
}
}
49 changes: 49 additions & 0 deletions packages/errors/src/BaseError.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { describe, it, expect } from 'vitest';

import { BaseError } from './BaseError.js';
import { ErrorCode } from './constants.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', () => {
const error = new BaseError(mockCode, mockMessage);
expect(error).toBeInstanceOf(BaseError);
expect(error).toBeInstanceOf(Error);
expect(error.name).toBe('BaseError');
expect(error.message).toBe(mockMessage);
expect(error.code).toBe(mockCode);
expect(error.data).toBeUndefined();
expect(error.cause).toBeUndefined();
});

it('should create a BaseError with all properties', () => {
const error = new BaseError(mockCode, mockMessage, mockData, mockCause);
expect(error.name).toBe('BaseError');
expect(error.message).toBe(mockMessage);
expect(error.code).toBe(mockCode);
expect(error.data).toStrictEqual(mockData);
expect(error.cause).toBe(mockCause);
});

it('should inherit 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', () => {
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', () => {
const error = new BaseError(mockCode, mockMessage, mockData);
expect(error.data).toStrictEqual(mockData);
expect(error.cause).toBeUndefined();
});
});
20 changes: 20 additions & 0 deletions packages/errors/src/BaseError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Json } from '@metamask/utils';

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

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

public data: Json | undefined;

public cause: unknown;

constructor(code: ErrorCode, message: string, data?: Json, cause?: unknown) {
super(message, { cause });

this.name = this.constructor.name;
this.code = code;
this.data = data;
this.cause = cause;
}
}
9 changes: 9 additions & 0 deletions packages/errors/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export enum ErrorCode {
SupervisorReadError = 'SUPERVISOR_READ_ERROR',
VatAlreadyExists = 'VAT_ALREADY_EXISTS',
VatCapTpConnectionExists = 'VAT_CAPTP_CONNECTION_EXISTS',
VatCapTpConnectionNotFound = 'VAT_CAPTP_CONNECTION_NOT_FOUND',
VatDeleted = 'VAT_DELETED',
VatNotFound = 'VAT_NOT_FOUND',
VatReadError = 'VAT_READ_ERROR',
}
98 changes: 98 additions & 0 deletions packages/errors/src/errors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { describe, it, expect } from 'vitest';

import { ErrorCode } from './constants.js';
import {
VatAlreadyExistsError,
VatNotFoundError,
SupervisorReadError,
VatReadError,
VatCapTpConnectionExistsError,
VatCapTpConnectionNotFoundError,
VatDeletedError,
} from './errors.js';

describe('Errors classes', () => {
const mockVatId = 'mockVatId';
const mockSupervisorId = 'mockSupervisorId';
const mockOriginalError = new Error('Original error');

describe('VatAlreadyExistsError', () => {
it('should create a VatAlreadyExistsError with the correct properties', () => {
const error = new VatAlreadyExistsError(mockVatId);
expect(error).toBeInstanceOf(VatAlreadyExistsError);
expect(error.code).toBe(ErrorCode.VatAlreadyExists);
expect(error.message).toBe('Vat already exists.');
expect(error.data).toStrictEqual({ vatId: mockVatId });
expect(error.cause).toBeUndefined();
});
});

describe('VatNotFoundError', () => {
it('should create a VatNotFoundError with the correct properties', () => {
const error = new VatNotFoundError(mockVatId);
expect(error).toBeInstanceOf(VatNotFoundError);
expect(error.code).toBe(ErrorCode.VatNotFound);
expect(error.message).toBe('Vat does not exist.');
expect(error.data).toStrictEqual({ vatId: mockVatId });
expect(error.cause).toBeUndefined();
});
});

describe('SupervisorReadError', () => {
it('should create a SupervisorReadError with the correct properties', () => {
const error = new SupervisorReadError(
mockSupervisorId,
mockOriginalError,
);
expect(error).toBeInstanceOf(SupervisorReadError);
expect(error.code).toBe(ErrorCode.SupervisorReadError);
expect(error.message).toBe('Unexpected read error from Supervisor.');
expect(error.data).toStrictEqual({ supervisorId: mockSupervisorId });
expect(error.cause).toBe(mockOriginalError);
});
});

describe('VatReadError', () => {
it('should create a VatReadError with the correct properties', () => {
const error = new VatReadError(mockVatId, mockOriginalError);
expect(error).toBeInstanceOf(VatReadError);
expect(error.code).toBe(ErrorCode.VatReadError);
expect(error.message).toBe('Unexpected read error from Vat.');
expect(error.data).toStrictEqual({ vatId: mockVatId });
expect(error.cause).toBe(mockOriginalError);
});
});

describe('VatCapTpConnectionExistsError', () => {
it('should create a VatCapTpConnectionExistsError with the correct properties', () => {
const error = new VatCapTpConnectionExistsError(mockVatId);
expect(error).toBeInstanceOf(VatCapTpConnectionExistsError);
expect(error.code).toBe(ErrorCode.VatCapTpConnectionExists);
expect(error.message).toBe('Vat already has a CapTP connection.');
expect(error.data).toStrictEqual({ vatId: mockVatId });
expect(error.cause).toBeUndefined();
});
});

describe('VatCapTpConnectionNotFoundError', () => {
it('should create a VatCapTpConnectionNotFoundError with the correct properties', () => {
const error = new VatCapTpConnectionNotFoundError(mockVatId);
expect(error).toBeInstanceOf(VatCapTpConnectionNotFoundError);
expect(error.code).toBe(ErrorCode.VatCapTpConnectionNotFound);
expect(error.message).toBe('Vat does not have a CapTP connection.');
expect(error.data).toStrictEqual({ vatId: mockVatId });
expect(error.cause).toBeUndefined();
});
});

describe('VatDeletedError', () => {
it('should create a VatDeletedError with the correct properties', () => {
const error = new VatDeletedError(mockVatId);
expect(error).toBeInstanceOf(VatDeletedError);
expect(error.code).toBe(ErrorCode.VatDeleted);
expect(error.message).toBe('Vat was deleted.');
expect(error.data).toStrictEqual({ vatId: mockVatId });
expect(error.cause).toBeUndefined();
});
});
});
70 changes: 70 additions & 0 deletions packages/errors/src/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { BaseError } from './BaseError.js';
import { ErrorCode } from './constants.js';

export class VatAlreadyExistsError extends BaseError {
constructor(vatId: string) {
super(ErrorCode.VatAlreadyExists, 'Vat already exists.', {
vatId,
});
}
}

export class VatNotFoundError extends BaseError {
constructor(vatId: string) {
super(ErrorCode.VatNotFound, 'Vat does not exist.', { vatId });
}
}

export class SupervisorReadError extends BaseError {
constructor(supervisorId: string, originalError: Error) {
super(
ErrorCode.SupervisorReadError,
'Unexpected read error from Supervisor.',
{
supervisorId,
},
originalError,
);
}
}

export class VatReadError extends BaseError {
constructor(vatId: string, originalError: Error) {
super(
ErrorCode.VatReadError,
'Unexpected read error from Vat.',
{
vatId,
},
originalError,
);
}
}

export class VatCapTpConnectionExistsError extends BaseError {
constructor(vatId: string) {
super(
ErrorCode.VatCapTpConnectionExists,
'Vat already has a CapTP connection.',
{
vatId,
},
);
}
}

export class VatCapTpConnectionNotFoundError extends BaseError {
constructor(vatId: string) {
super(
ErrorCode.VatCapTpConnectionNotFound,
'Vat does not have a CapTP connection.',
{ vatId },
);
}
}

export class VatDeletedError extends BaseError {
constructor(vatId: string) {
super(ErrorCode.VatDeleted, 'Vat was deleted.', { vatId });
}
}
19 changes: 19 additions & 0 deletions packages/errors/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { describe, it, expect } from 'vitest';

import * as indexModule from './index.js';

describe('index', () => {
it('has the expected exports', () => {
expect(Object.keys(indexModule).sort()).toStrictEqual([
'ErrorCode',
'SupervisorReadError',
'VatAlreadyExistsError',
'VatCapTpConnectionExistsError',
'VatCapTpConnectionNotFoundError',
'VatDeletedError',
'VatNotFoundError',
'VatReadError',
'toError',
]);
});
});
Loading