Skip to content

Commit 2a7f2a4

Browse files
authored
Add custom error class (#398)
1 parent 265a2a7 commit 2a7f2a4

File tree

2 files changed

+60
-52
lines changed

2 files changed

+60
-52
lines changed

src/index.spec.ts

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
stringify,
77
pathToRegexp,
88
TokenData,
9+
PathError,
910
} from "./index.js";
1011
import {
1112
PARSER_TESTS,
@@ -18,44 +19,62 @@ import {
1819
* Dynamically generate the entire test suite.
1920
*/
2021
describe("path-to-regexp", () => {
22+
describe("ParseError", () => {
23+
it("should contain original path and debug url", () => {
24+
const error = new PathError(
25+
"Unexpected END at index 7, expected }",
26+
"/{:foo,",
27+
);
28+
29+
expect(error).toBeInstanceOf(TypeError);
30+
expect(error.message).toBe(
31+
"Unexpected END at index 7, expected }: /{:foo,; visit https://git.new/pathToRegexpError for info",
32+
);
33+
expect(error.originalPath).toBe("/{:foo,");
34+
});
35+
36+
it("should omit original url when undefined", () => {
37+
const error = new PathError(
38+
"Unexpected END at index 7, expected }",
39+
undefined,
40+
);
41+
42+
expect(error).toBeInstanceOf(TypeError);
43+
expect(error.message).toBe(
44+
"Unexpected END at index 7, expected }; visit https://git.new/pathToRegexpError for info",
45+
);
46+
expect(error.originalPath).toBeUndefined();
47+
});
48+
});
49+
2150
describe("parse errors", () => {
2251
it("should throw on unbalanced group", () => {
2352
expect(() => parse("/{:foo,")).toThrow(
24-
new TypeError(
25-
"Unexpected END at index 7, expected }: /{:foo,; visit https://git.new/pathToRegexpError for info",
26-
),
53+
new PathError("Unexpected END at index 7, expected }", "/{:foo,"),
2754
);
2855
});
2956

3057
it("should throw on nested unbalanced group", () => {
3158
expect(() => parse("/{:foo/{x,y}")).toThrow(
32-
new TypeError(
33-
"Unexpected END at index 12, expected }: /{:foo/{x,y}; visit https://git.new/pathToRegexpError for info",
34-
),
59+
new PathError("Unexpected END at index 12, expected }", "/{:foo/{x,y}"),
3560
);
3661
});
3762

3863
it("should throw on missing param name", () => {
3964
expect(() => parse("/:/")).toThrow(
40-
new TypeError(
41-
"Missing parameter name at index 2: /:/; visit https://git.new/pathToRegexpError for info",
42-
),
65+
new PathError("Missing parameter name at index 2", "/:/"),
4366
);
4467
});
4568

4669
it("should throw on missing wildcard name", () => {
4770
expect(() => parse("/*/")).toThrow(
48-
new TypeError(
49-
"Missing parameter name at index 2: /*/; visit https://git.new/pathToRegexpError for info",
50-
),
71+
new PathError("Missing parameter name at index 2", "/*/"),
5172
);
5273
});
5374

5475
it("should throw on unterminated quote", () => {
5576
expect(() => parse('/:"foo')).toThrow(
56-
new TypeError(
57-
'Unterminated quote at index 2: /:"foo; visit https://git.new/pathToRegexpError for info',
58-
),
77+
new PathError("Unterminated quote at index 2", '/:"foo'),
5978
);
6079
});
6180
});
@@ -105,9 +124,7 @@ describe("path-to-regexp", () => {
105124
describe("pathToRegexp errors", () => {
106125
it("should throw when missing text between params", () => {
107126
expect(() => pathToRegexp("/:foo:bar")).toThrow(
108-
new TypeError(
109-
'Missing text before "bar": /:foo:bar; visit https://git.new/pathToRegexpError for info',
110-
),
127+
new PathError('Missing text before "bar" param', "/:foo:bar"),
111128
);
112129
});
113130

@@ -119,11 +136,7 @@ describe("path-to-regexp", () => {
119136
{ type: "param", name: "b" },
120137
]),
121138
),
122-
).toThrow(
123-
new TypeError(
124-
'Missing text before "b"; visit https://git.new/pathToRegexpError for info',
125-
),
126-
);
139+
).toThrow(new PathError('Missing text before "b" param', undefined));
127140
});
128141

129142
it("should throw with `originalPath` when missing text between params using TokenData", () => {
@@ -137,11 +150,7 @@ describe("path-to-regexp", () => {
137150
"/[a][b]",
138151
),
139152
),
140-
).toThrow(
141-
new TypeError(
142-
'Missing text before "b": /[a][b]; visit https://git.new/pathToRegexpError for info',
143-
),
144-
);
153+
).toThrow(new PathError('Missing text before "b" param', "/[a][b]"));
145154
});
146155

147156
it("should contain the error line", () => {

src/index.ts

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ const DEFAULT_DELIMITER = "/";
22
const NOOP_VALUE = (value: string) => value;
33
const ID_START = /^[$_\p{ID_Start}]$/u;
44
const ID_CONTINUE = /^[$\u200c\u200d\p{ID_Continue}]$/u;
5-
const DEBUG_URL = "https://git.new/pathToRegexpError";
65

76
/**
87
* Encode a string into another string.
@@ -112,16 +111,6 @@ function escape(str: string) {
112111
return str.replace(/[.+*?^${}()[\]|/\\]/g, "\\$&");
113112
}
114113

115-
/**
116-
* Format error so it's easier to debug.
117-
*/
118-
function errorMessage(text: string, originalPath: string | undefined) {
119-
let message = text;
120-
if (originalPath !== undefined) message += `: ${originalPath}`;
121-
message += `; visit ${DEBUG_URL} for info`;
122-
return message;
123-
}
124-
125114
/**
126115
* Plain text.
127116
*/
@@ -179,6 +168,21 @@ export class TokenData {
179168
) {}
180169
}
181170

171+
/**
172+
* ParseError is thrown when there is an error processing the path.
173+
*/
174+
export class PathError extends TypeError {
175+
constructor(
176+
message: string,
177+
public readonly originalPath: string | undefined,
178+
) {
179+
let text = message;
180+
if (originalPath) text += `: ${originalPath}`;
181+
text += `; visit https://git.new/pathToRegexpError for info`;
182+
super(text);
183+
}
184+
}
185+
182186
/**
183187
* Parse a string for the raw tokens.
184188
*/
@@ -215,16 +219,12 @@ export function parse(str: string, options: ParseOptions = {}): TokenData {
215219
}
216220

217221
if (pos) {
218-
throw new TypeError(
219-
errorMessage(`Unterminated quote at index ${pos}`, str),
220-
);
222+
throw new PathError(`Unterminated quote at index ${pos}`, str);
221223
}
222224
}
223225

224226
if (!value) {
225-
throw new TypeError(
226-
errorMessage(`Missing parameter name at index ${index}`, str),
227-
);
227+
throw new PathError(`Missing parameter name at index ${index}`, str);
228228
}
229229

230230
return value;
@@ -292,11 +292,9 @@ export function parse(str: string, options: ParseOptions = {}): TokenData {
292292
continue;
293293
}
294294

295-
throw new TypeError(
296-
errorMessage(
297-
`Unexpected ${type} at index ${index}, expected ${endType}`,
298-
str,
299-
),
295+
throw new PathError(
296+
`Unexpected ${type} at index ${index}, expected ${endType}`,
297+
str,
300298
);
301299
}
302300

@@ -564,8 +562,9 @@ function toRegExpSource(
564562

565563
if (token.type === "param" || token.type === "wildcard") {
566564
if (!isSafeSegmentParam && !backtrack) {
567-
throw new TypeError(
568-
errorMessage(`Missing text before "${token.name}"`, originalPath),
565+
throw new PathError(
566+
`Missing text before "${token.name}" ${token.type}`,
567+
originalPath,
569568
);
570569
}
571570

0 commit comments

Comments
 (0)