Skip to content

Commit 7d6d00f

Browse files
streamline errors
1 parent 8fa1963 commit 7d6d00f

15 files changed

Lines changed: 77 additions & 126 deletions

File tree

src/errors.ts

Lines changed: 13 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,56 @@
11
export enum ErrorCodes {
22
MONARCH_ERROR = "MONARCH_ERROR",
3-
PARSE_ERROR = "PARSE_ERROR",
43
VALIDATION_ERROR = "VALIDATION_ERROR",
54
}
65

76
export class MonarchError extends Error {
8-
public code: (typeof ErrorCodes)[keyof typeof ErrorCodes];
9-
public originalError?: Error;
10-
117
constructor(
128
message: string,
13-
code: (typeof ErrorCodes)[keyof typeof ErrorCodes] = ErrorCodes.MONARCH_ERROR,
14-
originalError?: Error,
9+
public code: ErrorCodes = ErrorCodes.MONARCH_ERROR,
10+
public cause?: Error,
1511
) {
1612
super(message);
1713
this.name = this.constructor.name;
1814
this.code = code;
19-
this.originalError = originalError;
15+
this.cause = cause;
2016

21-
if (!!originalError && originalError.stack) {
22-
this.stack = `${this.stack}\nCaused by: ${originalError.stack}`;
17+
if (!!cause && cause.stack) {
18+
this.stack = `${this.stack}\nCaused by: ${cause.stack}`;
2319
} else if (Error.captureStackTrace) {
2420
Error.captureStackTrace(this, this.constructor);
2521
}
2622
}
2723
}
2824

29-
export class MonarchParseError extends MonarchError {
30-
public fieldPath?: (string | number)[];
25+
export class FieldError extends Error {
3126
constructor(
3227
message: string,
33-
fieldPath?: (string | number)[],
34-
originalError?: Error,
28+
public fieldPath?: (string | number)[],
3529
) {
36-
super(message, ErrorCodes.PARSE_ERROR, originalError);
37-
this.fieldPath = normalizeFieldPath(fieldPath);
30+
super(message);
31+
this.fieldPath = fieldPath ?? [];
3832
}
3933
}
4034

4135
export class MonarchValidationError extends MonarchError {
4236
constructor(
4337
message: string,
4438
public fieldPath: string,
45-
originalError?: Error,
39+
cause?: Error,
4640
) {
4741
super(
48-
formatValidationMessage(fieldPath, message),
42+
`Validation error: '${fieldPath}' ${message}`,
4943
ErrorCodes.VALIDATION_ERROR,
50-
originalError,
44+
cause,
5145
);
5246
}
5347
}
5448

55-
export function normalizeFieldPath(
56-
fieldPath?: string | (string | number)[],
57-
): (string | number)[] {
58-
if (typeof fieldPath === "string") {
59-
return fieldPath.split(".");
60-
}
61-
if (Array.isArray(fieldPath)) {
62-
return fieldPath;
63-
}
64-
return [];
65-
}
66-
67-
export function formatErrorPath(
68-
path: string | number | (string | number)[],
69-
): string {
70-
return Array.isArray(path) ? path.join(".") : String(path);
71-
}
72-
7349
export function formatValidationPath(pathSegment: {
7450
schema: string;
7551
field: string;
7652
path?: (string | number)[];
7753
}): string {
7854
const { schema, field, path = [] } = pathSegment;
79-
return formatErrorPath([schema, field, ...path]);
80-
}
81-
82-
export function formatValidationMessage(
83-
fieldPath: string,
84-
message: string,
85-
): string {
86-
return `Validation error: '${fieldPath}' ${message}`;
55+
return [schema, field, ...path].join(".");
8756
}

src/schema/schema.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Projection } from "../collection/types/query-options";
22
import { detectProjection } from "../collection/utils/projection";
33
import {
4-
MonarchParseError,
4+
FieldError,
55
MonarchValidationError,
66
formatValidationPath,
77
} from "../errors";
@@ -63,7 +63,7 @@ export class Schema<
6363
if (parsed === undefined) continue;
6464
data[key as keyof typeof data] = parsed;
6565
} catch (error) {
66-
if (error instanceof MonarchParseError) {
66+
if (error instanceof FieldError) {
6767
throw new MonarchValidationError(
6868
error.message,
6969
formatValidationPath({

src/types/array.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MonarchParseError, normalizeFieldPath } from "../errors";
1+
import { FieldError } from "../errors";
22
import { type AnyMonarchType, MonarchType } from "./type";
33
import type { InferTypeInput, InferTypeOutput } from "./type-helpers";
44

@@ -18,20 +18,18 @@ export class MonarchArray<T extends AnyMonarchType> extends MonarchType<
1818
const parser = MonarchType.parser(type);
1919
parsed[index] = parser(value);
2020
} catch (error) {
21-
if (error instanceof MonarchParseError) {
22-
throw new MonarchParseError(error.message, [
21+
if (error instanceof FieldError) {
22+
throw new FieldError(error.message, [
2323
index,
24-
...normalizeFieldPath(error.fieldPath),
24+
...(error.fieldPath ?? []),
2525
]);
2626
}
2727
throw error;
2828
}
2929
}
3030
return parsed;
3131
}
32-
throw new MonarchParseError(
33-
`expected 'array' received '${typeof input}'`,
34-
);
32+
throw new FieldError(`expected 'array' received '${typeof input}'`);
3533
});
3634
}
3735
}

src/types/boolean.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MonarchParseError } from "../errors";
1+
import { FieldError } from "../errors";
22
import { MonarchType } from "./type";
33

44
export const boolean = () => new MonarchBoolean();
@@ -7,9 +7,7 @@ export class MonarchBoolean extends MonarchType<boolean, boolean> {
77
constructor() {
88
super((input) => {
99
if (typeof input === "boolean") return input;
10-
throw new MonarchParseError(
11-
`expected 'boolean' received '${typeof input}'`,
12-
);
10+
throw new FieldError(`expected 'boolean' received '${typeof input}'`);
1311
});
1412
}
1513
}

src/types/date.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MonarchParseError } from "../errors";
1+
import { FieldError } from "../errors";
22
import { MonarchType } from "./type";
33

44
export const date = () => new MonarchDate();
@@ -7,15 +7,15 @@ export class MonarchDate extends MonarchType<Date, Date> {
77
constructor() {
88
super((input) => {
99
if (input instanceof Date) return input;
10-
throw new MonarchParseError(`expected 'Date' received '${typeof input}'`);
10+
throw new FieldError(`expected 'Date' received '${typeof input}'`);
1111
});
1212
}
1313

1414
public after(afterDate: Date) {
1515
return date().extend(this, {
1616
preParse: (input) => {
1717
if (input > afterDate) return input;
18-
throw new MonarchParseError(`date must be after ${afterDate}`);
18+
throw new FieldError(`date must be after ${afterDate}`);
1919
},
2020
});
2121
}
@@ -24,7 +24,7 @@ export class MonarchDate extends MonarchType<Date, Date> {
2424
return date().extend(this, {
2525
preParse: (input) => {
2626
if (input > targetDate) {
27-
throw new MonarchParseError(
27+
throw new FieldError(
2828
`date must be before ${targetDate.toISOString()}`,
2929
);
3030
}
@@ -51,7 +51,7 @@ export class MonarchDateString extends MonarchType<string, Date> {
5151
if (typeof input === "string" && !Number.isNaN(Date.parse(input))) {
5252
return new Date(input);
5353
}
54-
throw new MonarchParseError(
54+
throw new FieldError(
5555
`expected 'ISO Date string' received '${typeof input}'`,
5656
);
5757
});
@@ -62,7 +62,7 @@ export class MonarchDateString extends MonarchType<string, Date> {
6262
preParse: (input) => {
6363
const date = new Date(input);
6464
if (date > afterDate) return input;
65-
throw new MonarchParseError(`date must be after ${afterDate}`);
65+
throw new FieldError(`date must be after ${afterDate}`);
6666
},
6767
});
6868
}
@@ -72,7 +72,7 @@ export class MonarchDateString extends MonarchType<string, Date> {
7272
preParse: (input) => {
7373
const date = new Date(input);
7474
if (date > targetDate) {
75-
throw new MonarchParseError(
75+
throw new FieldError(
7676
`date must be before ${targetDate.toISOString()}`,
7777
);
7878
}

src/types/literal.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MonarchParseError } from "../errors";
1+
import { FieldError } from "../errors";
22
import { MonarchType } from "./type";
33

44
export const literal = <T extends string | number | boolean>(...values: T[]) =>
@@ -11,7 +11,7 @@ export class MonarchLiteral<
1111
super((input) => {
1212
const _values = new Set(values);
1313
if (_values.has(input)) return input;
14-
throw new MonarchParseError(
14+
throw new FieldError(
1515
`unknown value '${input}', literal may only specify known values`,
1616
);
1717
});

src/types/number.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MonarchParseError } from "../errors";
1+
import { FieldError } from "../errors";
22
import { MonarchType } from "./type";
33

44
export const number = () => new MonarchNumber();
@@ -7,17 +7,15 @@ export class MonarchNumber extends MonarchType<number, number> {
77
constructor() {
88
super((input) => {
99
if (typeof input === "number") return input;
10-
throw new MonarchParseError(
11-
`expected 'number' received '${typeof input}'`,
12-
);
10+
throw new FieldError(`expected 'number' received '${typeof input}'`);
1311
});
1412
}
1513

1614
public min(value: number) {
1715
return number().extend(this, {
1816
preParse: (input) => {
1917
if (input < value) {
20-
throw new MonarchParseError(
18+
throw new FieldError(
2119
`number must be greater than or equal to ${value}`,
2220
);
2321
}
@@ -30,9 +28,7 @@ export class MonarchNumber extends MonarchType<number, number> {
3028
return number().extend(this, {
3129
preParse: (input) => {
3230
if (input > value) {
33-
throw new MonarchParseError(
34-
`number must be less than or equal to ${value}`,
35-
);
31+
throw new FieldError(`number must be less than or equal to ${value}`);
3632
}
3733
return input;
3834
},
@@ -51,7 +47,7 @@ export class MonarchNumber extends MonarchType<number, number> {
5147
return number().extend(this, {
5248
postParse: (input) => {
5349
if (input % value !== 0) {
54-
throw new MonarchParseError(`number must be a multiple of ${value}`);
50+
throw new FieldError(`number must be a multiple of ${value}`);
5551
}
5652
return input;
5753
},

src/types/object.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MonarchParseError, normalizeFieldPath } from "../errors";
1+
import { FieldError } from "../errors";
22
import { type AnyMonarchType, MonarchType } from "./type";
33
import type {
44
InferTypeInput,
@@ -17,7 +17,7 @@ export class MonarchObject<
1717
if (typeof input === "object" && input !== null) {
1818
for (const key of Object.keys(input)) {
1919
if (!(key in types)) {
20-
throw new MonarchParseError(
20+
throw new FieldError(
2121
`unknown field '${key}', object may only specify known fields`,
2222
);
2323
}
@@ -33,20 +33,18 @@ export class MonarchObject<
3333
input[key as keyof typeof input] as InferTypeInput<T[keyof T]>,
3434
);
3535
} catch (error) {
36-
if (error instanceof MonarchParseError) {
37-
throw new MonarchParseError(error.message, [
36+
if (error instanceof FieldError) {
37+
throw new FieldError(error.message, [
3838
key,
39-
...normalizeFieldPath(error.fieldPath),
39+
...(error.fieldPath ?? []),
4040
]);
4141
}
4242
throw error;
4343
}
4444
}
4545
return parsed;
4646
}
47-
throw new MonarchParseError(
48-
`expected 'object' received '${typeof input}'`,
49-
);
47+
throw new FieldError(`expected 'object' received '${typeof input}'`);
5048
});
5149
}
5250
}

src/types/objectId.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ObjectId } from "mongodb";
2-
import { MonarchParseError } from "../errors";
2+
import { FieldError } from "../errors";
33
import { MonarchType } from "./type";
44

55
export const objectId = () => new MonarchObjectId();
@@ -8,7 +8,7 @@ export class MonarchObjectId extends MonarchType<ObjectId | string, ObjectId> {
88
constructor() {
99
super((input) => {
1010
if (ObjectId.isValid(input)) return new ObjectId(input);
11-
throw new MonarchParseError(
11+
throw new FieldError(
1212
`expected valid ObjectId received '${typeof input}' ${input}`,
1313
);
1414
});

src/types/record.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MonarchParseError, normalizeFieldPath } from "../errors";
1+
import { FieldError } from "../errors";
22
import { type AnyMonarchType, MonarchType } from "./type";
33
import type { InferTypeInput, InferTypeOutput } from "./type-helpers";
44

@@ -18,20 +18,18 @@ export class MonarchRecord<T extends AnyMonarchType> extends MonarchType<
1818
const parser = MonarchType.parser(type);
1919
parsed[key] = parser(value);
2020
} catch (error) {
21-
if (error instanceof MonarchParseError) {
22-
throw new MonarchParseError(error.message, [
21+
if (error instanceof FieldError) {
22+
throw new FieldError(error.message, [
2323
key,
24-
...normalizeFieldPath(error.fieldPath),
24+
...(error.fieldPath ?? []),
2525
]);
2626
}
2727
throw error;
2828
}
2929
}
3030
return parsed;
3131
}
32-
throw new MonarchParseError(
33-
`expected 'object' received '${typeof input}'`,
34-
);
32+
throw new FieldError(`expected 'object' received '${typeof input}'`);
3533
});
3634
}
3735
}

0 commit comments

Comments
 (0)