Skip to content

Commit 0239249

Browse files
committed
Improve error messages by including the path
Without the path users need to search all their code for a malformed path or debug to find the issue Closes: pillarjs#336
1 parent f4f51b7 commit 0239249

File tree

2 files changed

+45
-26
lines changed

2 files changed

+45
-26
lines changed

src/index.spec.ts

+14-10
Original file line numberDiff line numberDiff line change
@@ -15,38 +15,38 @@ describe("path-to-regexp", () => {
1515
it("should throw on unbalanced group", () => {
1616
expect(() => parse("/{:foo,")).toThrow(
1717
new TypeError(
18-
"Unexpected END at 7, expected }: https://git.new/pathToRegexpError",
18+
"'/{:foo,': Unexpected END at 7, expected }: https://git.new/pathToRegexpError",
1919
),
2020
);
2121
});
2222
it("should throw on nested unbalanced group", () => {
2323
expect(() => parse("/{:foo/{x,y}")).toThrow(
2424
new TypeError(
25-
"Unexpected END at 12, expected }: https://git.new/pathToRegexpError",
25+
"'/{:foo/{x,y}': Unexpected END at 12, expected }: https://git.new/pathToRegexpError",
2626
),
2727
);
2828
});
2929

3030
it("should throw on missing param name", () => {
3131
expect(() => parse("/:/")).toThrow(
3232
new TypeError(
33-
"Missing parameter name at 2: https://git.new/pathToRegexpError",
33+
"'/:/': Missing parameter name at 2: https://git.new/pathToRegexpError",
3434
),
3535
);
3636
});
3737

3838
it("should throw on missing wildcard name", () => {
3939
expect(() => parse("/*/")).toThrow(
4040
new TypeError(
41-
"Missing parameter name at 2: https://git.new/pathToRegexpError",
41+
"'/*/': Missing parameter name at 2: https://git.new/pathToRegexpError",
4242
),
4343
);
4444
});
4545

4646
it("should throw on unterminated quote", () => {
4747
expect(() => parse('/:"foo')).toThrow(
4848
new TypeError(
49-
"Unterminated quote at 2: https://git.new/pathToRegexpError",
49+
`'/:"foo': Unterminated quote at 2: https://git.new/pathToRegexpError`,
5050
),
5151
);
5252
});
@@ -58,39 +58,43 @@ describe("path-to-regexp", () => {
5858

5959
expect(() => {
6060
toPath();
61-
}).toThrow(new TypeError("Missing parameters: b"));
61+
}).toThrow(new TypeError("'/a/:b/c': Missing parameters: b"));
6262
});
6363

6464
it("should throw when expecting a repeated value", () => {
6565
const toPath = compile("/*foo");
6666

6767
expect(() => {
6868
toPath({ foo: [] });
69-
}).toThrow(new TypeError('Expected "foo" to be a non-empty array'));
69+
}).toThrow(
70+
new TypeError(`'/*foo': Expected "foo" to be a non-empty array`),
71+
);
7072
});
7173

7274
it("should throw when param gets an array", () => {
7375
const toPath = compile("/:foo");
7476

7577
expect(() => {
7678
toPath({ foo: [] });
77-
}).toThrow(new TypeError('Expected "foo" to be a string'));
79+
}).toThrow(new TypeError(`'/:foo': Expected "foo" to be a string`));
7880
});
7981

8082
it("should throw when a wildcard is not an array", () => {
8183
const toPath = compile("/*foo");
8284

8385
expect(() => {
8486
toPath({ foo: "a" });
85-
}).toThrow(new TypeError('Expected "foo" to be a non-empty array'));
87+
}).toThrow(
88+
new TypeError(`'/*foo': Expected "foo" to be a non-empty array`),
89+
);
8690
});
8791

8892
it("should throw when a wildcard array value is not a string", () => {
8993
const toPath = compile("/*foo");
9094

9195
expect(() => {
9296
toPath({ foo: [1, "a"] as any });
93-
}).toThrow(new TypeError('Expected "foo/0" to be a string'));
97+
}).toThrow(new TypeError(`'/*foo': Expected "foo/0" to be a string`));
9498
});
9599
});
96100

src/index.ts

+31-16
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,16 @@ function* lexer(str: string): Generator<LexToken, LexToken> {
145145
}
146146

147147
if (pos) {
148-
throw new TypeError(`Unterminated quote at ${pos}: ${DEBUG_URL}`);
148+
throw new TypeError(
149+
`'${str}': Unterminated quote at ${pos}: ${DEBUG_URL}`,
150+
);
149151
}
150152
}
151153

152154
if (!value) {
153-
throw new TypeError(`Missing parameter name at ${i}: ${DEBUG_URL}`);
155+
throw new TypeError(
156+
`'${str}': Missing parameter name at ${i}: ${DEBUG_URL}`,
157+
);
154158
}
155159

156160
return value;
@@ -198,12 +202,12 @@ class Iter {
198202
return token.value;
199203
}
200204

201-
consume(type: TokenType): string {
205+
consume(type: TokenType, pathString: string): string {
202206
const value = this.tryConsume(type);
203207
if (value !== undefined) return value;
204208
const { type: nextType, index } = this.peek();
205209
throw new TypeError(
206-
`Unexpected ${nextType} at ${index}, expected ${type}: ${DEBUG_URL}`,
210+
`'${pathString}': Unexpected ${nextType} at ${index}, expected ${type}: ${DEBUG_URL}`,
207211
);
208212
}
209213

@@ -312,7 +316,7 @@ export function parse(str: string, options: ParseOptions = {}): TokenData {
312316
continue;
313317
}
314318

315-
it.consume(endType);
319+
it.consume(endType, str);
316320
return tokens;
317321
}
318322
}
@@ -331,14 +335,16 @@ export function compile<P extends ParamData = ParamData>(
331335
const { encode = encodeURIComponent, delimiter = DEFAULT_DELIMITER } =
332336
options;
333337
const data = path instanceof TokenData ? path : parse(path, options);
334-
const fn = tokensToFunction(data.tokens, delimiter, encode);
338+
const fn = tokensToFunction(data.tokens, delimiter, encode, path);
335339

336-
return function path(params: P = {} as P) {
337-
const [path, ...missing] = fn(params);
340+
return function pathFn(params: P = {} as P) {
341+
const [returnPath, ...missing] = fn(params);
338342
if (missing.length) {
339-
throw new TypeError(`Missing parameters: ${missing.join(", ")}`);
343+
throw new TypeError(
344+
`'${stringify(path)}': Missing parameters: ${missing.join(", ")}`,
345+
);
340346
}
341-
return path;
347+
return returnPath;
342348
};
343349
}
344350

@@ -349,9 +355,10 @@ function tokensToFunction(
349355
tokens: Token[],
350356
delimiter: string,
351357
encode: Encode | false,
358+
path: Path,
352359
) {
353360
const encoders = tokens.map((token) =>
354-
tokenToFunction(token, delimiter, encode),
361+
tokenToFunction(token, delimiter, encode, path),
355362
);
356363

357364
return (data: ParamData) => {
@@ -374,11 +381,12 @@ function tokenToFunction(
374381
token: Token,
375382
delimiter: string,
376383
encode: Encode | false,
384+
path: Path,
377385
): (data: ParamData) => string[] {
378386
if (token.type === "text") return () => [token.value];
379387

380388
if (token.type === "group") {
381-
const fn = tokensToFunction(token.tokens, delimiter, encode);
389+
const fn = tokensToFunction(token.tokens, delimiter, encode, path);
382390

383391
return (data) => {
384392
const [value, ...missing] = fn(data);
@@ -395,15 +403,17 @@ function tokenToFunction(
395403
if (value == null) return ["", token.name];
396404

397405
if (!Array.isArray(value) || value.length === 0) {
398-
throw new TypeError(`Expected "${token.name}" to be a non-empty array`);
406+
throw new TypeError(
407+
`'${stringify(path)}': Expected "${token.name}" to be a non-empty array`,
408+
);
399409
}
400410

401411
return [
402412
value
403413
.map((value, index) => {
404414
if (typeof value !== "string") {
405415
throw new TypeError(
406-
`Expected "${token.name}/${index}" to be a string`,
416+
`'${stringify(path)}': Expected "${token.name}/${index}" to be a string`,
407417
);
408418
}
409419

@@ -419,7 +429,9 @@ function tokenToFunction(
419429
if (value == null) return ["", token.name];
420430

421431
if (typeof value !== "string") {
422-
throw new TypeError(`Expected "${token.name}" to be a string`);
432+
throw new TypeError(
433+
`'${stringify(path)}': Expected "${token.name}" to be a string`,
434+
);
423435
}
424436

425437
return [encodeValue(value)];
@@ -611,7 +623,10 @@ function negate(delimiter: string, backtrack: string) {
611623
/**
612624
* Stringify token data into a path string.
613625
*/
614-
export function stringify(data: TokenData) {
626+
export function stringify(data: Path) {
627+
if (typeof data === "string") {
628+
return data;
629+
}
615630
return data.tokens
616631
.map(function stringifyToken(token, index, tokens): string {
617632
if (token.type === "text") return escapeText(token.value);

0 commit comments

Comments
 (0)