Skip to content

Commit

Permalink
refactor: refine error types
Browse files Browse the repository at this point in the history
  • Loading branch information
robert-bo-davis committed May 9, 2024
1 parent e1696c1 commit 4426e1d
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 61 deletions.
80 changes: 41 additions & 39 deletions core/src/error/errors.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
import type { CodeLlmErrorParams, ErrorCode } from '@/.';
import type { CodeLlmErrorParams, ErrorCodes } from '@/.';

import { CODE_LLM_ERRORS } from './constants.js';
import { getConfig } from '@/config/index.js';

/**
* These functions make up a TS safe error handling system.
*
* The types are a bit messy and overly complicated so that this system can be extended
* and used by other projects that use it (like the Remix frontend).
*
* The double generics extends/defaults are ugly, but it works. The
* base problem is that we want type hinting for the error codes, but we also want to
* allow for custom error codes in the Remix project.
*
* Since the
*/

const isErrorCode = (code: unknown): code is keyof typeof CODE_LLM_ERRORS => {
return (code as string) in CODE_LLM_ERRORS;
};

const getErrorMessage = <TCode extends TCodeBase, TCodeBase = ErrorCode>(
code: TCode,
const getErrorMessage = <TCodes extends ErrorCodes = ErrorCodes>(
code: keyof TCodes,
message?: string,
): string => {
if (message) {
Expand All @@ -22,7 +35,7 @@ const getErrorMessage = <TCode extends TCodeBase, TCodeBase = ErrorCode>(
return code as string;
};

export type CodeLlmErrorType = CodeLlmError<ErrorCode>;
export type CodeLlmErrorType = CodeLlmError<ErrorCodes>;

/**
* A custom error class for the CodeLlm project.
Expand All @@ -35,26 +48,20 @@ export type CodeLlmErrorType = CodeLlmError<ErrorCode>;
* The message will be pulled from the CODE_LLM_ERRORS constant.
*/
export class CodeLlmError<
TCode extends TCodeBase,
TCodeBase = ErrorCode,
TCodes extends ErrorCodes = ErrorCodes,
> extends Error {
code: TCode;
code: keyof TCodes;

override message: string;

override cause: CodeLlmErrorParams<TCode, TCodeBase>['cause'];
override cause: CodeLlmErrorParams<TCodes>['cause'];

meta: CodeLlmErrorParams<TCode, TCodeBase>['meta'];
meta: CodeLlmErrorParams<TCodes>['meta'];

constructor({
cause,
code,
message,
meta,
}: CodeLlmErrorParams<TCode, TCodeBase>) {
constructor({ cause, code, message, meta }: CodeLlmErrorParams<TCodes>) {
super();
this.code = code;
this.message = getErrorMessage<TCode, TCodeBase>(code, message);
this.message = getErrorMessage<TCodes>(code, message);
this.cause = cause;
this.meta = meta || {};
}
Expand All @@ -67,13 +74,13 @@ export class CodeLlmError<
*
* @returns {bool} Whether the target is a CodeLlmError
*/
export const isError = <TCode extends TCodeBase, TCodeBase = ErrorCode>(
export const isError = <TCodes extends ErrorCodes = ErrorCodes>(
target: unknown,
code?: TCode,
): target is CodeLlmError<TCode, TCodeBase> => {
code?: keyof TCodes,
): target is CodeLlmError<TCodes> => {
return (
target instanceof CodeLlmError &&
(!code || (target as CodeLlmError<TCode, TCodeBase>).code === code)
(!code || (target as CodeLlmError<TCodes>).code === code)
);
};

Expand All @@ -86,17 +93,17 @@ export const isError = <TCode extends TCodeBase, TCodeBase = ErrorCode>(
*
* @returns The result of the target or a CodeLlmError with the error in the meta
*/
export const mayFail = <T, TCode extends TCodeBase, TCodeBase = ErrorCode>(
export const mayFail = <T, TCodes extends ErrorCodes = ErrorCodes>(
target: () => T,
code: TCode,
meta: CodeLlmErrorParams<TCode, TCodeBase>['meta'] = {},
code: keyof TCodes,
meta: CodeLlmErrorParams<TCodes>['meta'] = {},
) => {
try {
const targetResp = target();
if (isError(targetResp)) throw targetResp;
return targetResp;
} catch (e) {
return new CodeLlmError<TCode, TCodeBase>({ cause: e, code, meta });
return new CodeLlmError<TCodes>({ cause: e, code, meta });
}
};

Expand All @@ -110,21 +117,17 @@ export const mayFail = <T, TCode extends TCodeBase, TCodeBase = ErrorCode>(
* @returns - The result of the promise or a CodeLlmError with the error in the meta
*
*/
export const promiseMayFail = async <
T,
TCode extends TCodeBase,
TCodeBase = ErrorCode,
>(
export const promiseMayFail = async <T, TCodes extends ErrorCodes = ErrorCodes>(
target: Promise<T>,
code: TCode,
meta: CodeLlmErrorParams<TCode, TCodeBase>['meta'] = {},
code: keyof TCodes,
meta: CodeLlmErrorParams<TCodes>['meta'] = {},
) => {
try {
const targetRes = await target;
if (isError(targetRes)) throw targetRes;
return targetRes;
} catch (e) {
return new CodeLlmError<TCode, TCodeBase>({ cause: e, code, meta });
return new CodeLlmError<TCodes>({ cause: e, code, meta });
}
};

Expand All @@ -138,11 +141,10 @@ export const promiseMayFail = async <
*/
export const promiseMapMayFail = async <
T,
TCode extends TCodeBase,
TCodeBase = ErrorCode,
TCodes extends ErrorCodes = ErrorCodes,
>(
map: Promise<T>[],
code: TCode,
code: keyof TCodes,
) => {
const resolved = await Promise.allSettled(map);
const errors = resolved.filter(
Expand All @@ -151,7 +153,7 @@ export const promiseMapMayFail = async <

const results = resolved.filter((item) => item.status === 'fulfilled');
if (errors.length) {
return new CodeLlmError<TCode, TCodeBase>({
return new CodeLlmError<TCodes>({
code,
meta: { errors, results },
});
Expand All @@ -168,8 +170,8 @@ export const promiseMapMayFail = async <
* @returns The error
* @throws The error if the config is initialized and shouldThrow is true
*/
export const throwOrReturn = <TCode extends TCodeBase, TCodeBase = ErrorCode>(
e: CodeLlmError<TCode, TCodeBase>,
export const throwOrReturn = <TCodes extends ErrorCodes = ErrorCodes>(
e: CodeLlmError<TCodes>,
) => {
const config = getConfig();
if (isError(config, 'config:NotInitialized')) throw e;
Expand All @@ -180,6 +182,6 @@ export const throwOrReturn = <TCode extends TCodeBase, TCodeBase = ErrorCode>(
return e;
};

export const newError = (args: CodeLlmErrorParams<ErrorCode>) => {
export const newError = (args: CodeLlmErrorParams<ErrorCodes>) => {
return new CodeLlmError(args);
};
10 changes: 4 additions & 6 deletions core/src/error/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@ import type { CODE_LLM_ERRORS } from './constants';

import { z } from 'zod';

export type ErrorCode = keyof typeof CODE_LLM_ERRORS;
export type ErrorCodes = typeof CODE_LLM_ERRORS;
export type ErrorCode = keyof ErrorCodes;

export type CodeLlmErrorParams<
TCode extends TCodeBase,
TCodeBase = ErrorCode,
> = {
export type CodeLlmErrorParams<TCodes extends ErrorCodes = ErrorCodes> = {
cause?: unknown;
code: TCode;
code: keyof TCodes;
message?: string;
meta?: Record<string, unknown>;
};
Expand Down
2 changes: 2 additions & 0 deletions remix/app/.server/errors/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { chatModel, userModel } from '@remix/.server/models';
import { authService, chatService, userService } from '@remix/.server/services';
import { CODE_LLM_ERRORS } from '@codellm/core';

export const ERROR_CODES = {
...CODE_LLM_ERRORS,
...chatModel.ERRORS,
...userModel.ERRORS,
...authService.ERRORS,
Expand Down
25 changes: 11 additions & 14 deletions remix/app/.server/errors/errors.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import type {
ErrorCode as BaseErrorCode,
CodeLlmErrorParams,
} from '@codellm/core';
import type { CodeLlmErrorParams } from '@codellm/core';

import { ERROR_CODES } from './constants';

Expand All @@ -12,35 +9,35 @@ import {
promiseMayFail as basePromiseMayFail,
} from '@codellm/core';

export type RemixErrorCodes = keyof typeof ERROR_CODES;
export type ErrorCodes = typeof ERROR_CODES;

export type ErrorCode = BaseErrorCode | RemixErrorCodes;
export type ErrorCode = keyof ErrorCodes;

export type RemixError = CodeLlmError<ErrorCode, ErrorCode>;
export type RemixError = CodeLlmError<ErrorCodes>;

export const isError = (
target: unknown,
code?: ErrorCode,
): target is RemixError => {
return baseIsError<ErrorCode, ErrorCode>(target, code);
return baseIsError<ErrorCodes>(target, code);
};

export const mayFail = <T>(
target: () => T,
code: ErrorCode,
meta: CodeLlmError<ErrorCode, ErrorCode>['meta'] = {},
meta: CodeLlmError<ErrorCodes>['meta'] = {},
) => {
return BaseMayFail<T, ErrorCode, ErrorCode>(target, code, meta);
return BaseMayFail<T, ErrorCodes>(target, code, meta);
};

export const promiseMayFail = async <T>(
target: Promise<T>,
code: ErrorCode,
meta: CodeLlmError<ErrorCode, ErrorCode>['meta'] = {},
meta: CodeLlmError<ErrorCodes>['meta'] = {},
) => {
return basePromiseMayFail<T, ErrorCode, ErrorCode>(target, code, meta);
return basePromiseMayFail<T, ErrorCodes>(target, code, meta);
};

export const newError = (params: CodeLlmErrorParams<ErrorCode, ErrorCode>) => {
return new CodeLlmError<ErrorCode, ErrorCode>(params);
export const newError = (params: CodeLlmErrorParams<ErrorCodes>) => {
return new CodeLlmError<ErrorCodes>(params);
};
4 changes: 2 additions & 2 deletions remix/tests/tools/expectError.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { ErrorCode } from '@remix/.server/errors';
import type { ErrorCode, ErrorCodes } from '@remix/.server/errors';

import { expect } from 'vitest';
import { CodeLlmError } from '@codellm/core';

export const expectError = (resp: unknown, code: ErrorCode) => {
expect(resp).toBeInstanceOf(CodeLlmError);
expect((resp as CodeLlmError<ErrorCode, ErrorCode>).code).toEqual(code);
expect((resp as CodeLlmError<ErrorCodes>).code).toEqual(code);
};

0 comments on commit 4426e1d

Please # to comment.