Skip to content

Commit 6c487a4

Browse files
authored
🤖 Merge PR DefinitelyTyped#74161 [express-serve-static-core] Fix req.params type by @krzysdz
Co-authored-by: krzysdz <krzysdz@users.noreply.github.com>
1 parent 591fde5 commit 6c487a4

7 files changed

Lines changed: 51 additions & 108 deletions

File tree

‎types/express-serve-static-core/express-serve-static-core-tests.ts‎

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,16 @@ app.route("/:foo").get(req => {
4545
req.is(1);
4646
});
4747

48-
// Params can used as an array
49-
app.get<express.ParamsArray>("/*", req => {
50-
req.params[0]; // $ExpectType string
51-
req.params.length; // $ExpectType number
48+
// Wildcard parameters return string arrays
49+
app.get("/*foo", req => {
50+
req.params.foo; // $ExpectType string[]
51+
req.params.foo.length; // $ExpectType number
5252
});
5353

54-
// Params can used as an array - under route
55-
app.route("/*").get<express.ParamsArray>(req => {
56-
req.params[0]; // $ExpectType string
57-
req.params.length; // $ExpectType number
54+
// Wildcard parameters return string arrays - under route
55+
app.route("/*foo").get(req => {
56+
req.params.foo; // $ExpectType string[]
57+
req.params.foo.length; // $ExpectType number
5858
});
5959

6060
// Params can be a custom type
@@ -74,10 +74,16 @@ app.route("/:foo/:bar").get<{ foo: string; bar: number }>(req => {
7474
req.params.baz;
7575
});
7676

77-
// Optional params
78-
app.get("/:foo/:bar?", req => {
77+
// Regular expressions have parameters that always are strings, never arrays of strings
78+
app.get(/^\/(.*?)\|(?<b>\d+)/, req => {
79+
req.params[0]; // $ExpectType string
80+
req.params.foo; // $ExpectType string
81+
});
82+
83+
// Regular expressions have parameters that always are strings, never arrays of strings - under route
84+
app.route(/^\/(.*?)\|(?<b>\d+)/).get(req => {
85+
req.params[0]; // $ExpectType string
7986
req.params.foo; // $ExpectType string
80-
req.params.bar; // $ExpectType string | undefined
8187
});
8288

8389
// Express 5.0: Optional params
@@ -107,14 +113,6 @@ app.get("/:foo/:bar-:baz/:qux", req => {
107113
req.params.quxx;
108114
});
109115

110-
// regex parameters - not supported
111-
app.get("/:foo/:bar(\\d:+)/:baz", req => {
112-
req.params.foo; // $ExpectType string
113-
req.params.bar; // $ExpectType string
114-
req.params.qux; // $ExpectType string
115-
req.params.quxx; // $ExpectType string
116-
});
117-
118116
// long path parameters - https://github.com/DefinitelyTyped/DefinitelyTyped/pull/53513#issuecomment-870550063
119117
app.get("/website-api/jobalarm/:jobalarmId/:subscriptionId/search", req => {
120118
req.params.jobalarmId; // $ExpectType string

‎types/express-serve-static-core/index.d.ts‎

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,13 @@ export interface Dictionary<T> {
4242
}
4343

4444
export interface ParamsDictionary {
45-
[key: string]: string;
45+
[key: string]: string | string[];
46+
[key: number]: string;
4647
}
47-
export type ParamsArray = string[];
48-
export type Params = ParamsDictionary | ParamsArray;
48+
export interface ParamsFlatDictionary {
49+
[key: string | number]: string;
50+
}
51+
export type Params = ParamsDictionary | ParamsFlatDictionary;
4952

5053
export interface Locals extends Express.Locals {}
5154

@@ -96,20 +99,26 @@ type GetRouteParameter<S extends string> = RemoveTail<
9699
`.${string}`
97100
>;
98101

99-
// prettier-ignore
100-
export type RouteParameters<Route extends string> = Route extends `${infer Required}{${infer Optional}}${infer Next}`
101-
? ParseRouteParameters<Required> & Partial<ParseRouteParameters<Optional>> & RouteParameters<Next>
102-
: ParseRouteParameters<Route>;
102+
// dprint-ignore
103+
export type RouteParameters<Route extends string | RegExp> = Route extends string
104+
? Route extends `${infer Required}{${infer Optional}}${infer Next}`
105+
? ParseRouteParameters<Required> & Partial<ParseRouteParameters<Optional>> & RouteParameters<Next>
106+
: ParseRouteParameters<Route>
107+
: ParamsFlatDictionary;
103108

104109
type ParseRouteParameters<Route extends string> = string extends Route ? ParamsDictionary
105-
: Route extends `${string}(${string}` ? ParamsDictionary // TODO: handling for regex parameters
106110
: Route extends `${string}:${infer Rest}` ?
107111
& (
108112
GetRouteParameter<Rest> extends never ? ParamsDictionary
109-
: GetRouteParameter<Rest> extends `${infer ParamName}?` ? { [P in ParamName]?: string } // TODO: Remove old `?` handling when Express 5 is promoted to "latest"
110113
: { [P in GetRouteParameter<Rest>]: string }
111114
)
112115
& (Rest extends `${GetRouteParameter<Rest>}${infer Next}` ? RouteParameters<Next> : unknown)
116+
: Route extends `${string}*${infer Rest}` ?
117+
& (
118+
GetRouteParameter<Rest> extends never ? ParamsDictionary
119+
: { [P in GetRouteParameter<Rest>]: string[] }
120+
)
121+
& (Rest extends `${GetRouteParameter<Rest>}${infer Next}` ? RouteParameters<Next> : unknown)
113122
: {};
114123

115124
/* eslint-disable @definitelytyped/no-unnecessary-generics */
@@ -118,7 +127,7 @@ export interface IRouterMatcher<
118127
Method extends "all" | "get" | "post" | "put" | "delete" | "patch" | "options" | "head" = any,
119128
> {
120129
<
121-
Route extends string,
130+
Route extends string | RegExp,
122131
P = RouteParameters<Route>,
123132
ResBody = any,
124133
ReqBody = any,
@@ -131,7 +140,7 @@ export interface IRouterMatcher<
131140
...handlers: Array<RequestHandler<P, ResBody, ReqBody, ReqQuery, LocalsObj>>
132141
): T;
133142
<
134-
Path extends string,
143+
Path extends string | RegExp,
135144
P = RouteParameters<Path>,
136145
ResBody = any,
137146
ReqBody = any,
@@ -168,7 +177,7 @@ export interface IRouterMatcher<
168177
(path: PathParams, subApplication: Application): T;
169178
}
170179

171-
export interface IRouterHandler<T, Route extends string = string> {
180+
export interface IRouterHandler<T, Route extends string | RegExp = string> {
172181
(...handlers: Array<RequestHandler<RouteParameters<Route>>>): T;
173182
(...handlers: Array<RequestHandlerParams<RouteParameters<Route>>>): T;
174183
<
@@ -284,7 +293,7 @@ export interface IRouter extends RequestHandler {
284293

285294
use: IRouterHandler<this> & IRouterMatcher<this>;
286295

287-
route<T extends string>(prefix: T): IRoute<T>;
296+
route<T extends string | RegExp>(prefix: T): IRoute<T>;
288297
route(prefix: PathParams): IRoute;
289298
/**
290299
* Stack of configured routes
@@ -303,7 +312,7 @@ export interface ILayer {
303312
handle: (req: Request, res: Response, next: NextFunction) => any;
304313
}
305314

306-
export interface IRoute<Route extends string = string> {
315+
export interface IRoute<Route extends string | RegExp = string> {
307316
path: string;
308317
stack: ILayer[];
309318
all: IRouterHandler<this, Route>;
@@ -381,16 +390,14 @@ export type Errback = (err: Error) => void;
381390

382391
/**
383392
* @param P For most requests, this should be `ParamsDictionary`, but if you're
384-
* using this in a route handler for a route that uses a `RegExp` or a wildcard
385-
* `string` path (e.g. `'/user/*'`), then `req.params` will be an array, in
386-
* which case you should use `ParamsArray` instead.
387-
*
388-
* @see https://expressjs.com/en/api.html#req.params
393+
* using this in a route handler for a route that uses a `RegExp`, then `req.params`
394+
* will only contains strings, in which case you should use `ParamsFlatDictionary` instead.
389395
*
390396
* @example
391-
* app.get('/user/:id', (req, res) => res.send(req.params.id)); // implicitly `ParamsDictionary`
392-
* app.get<ParamsArray>(/user\/(.*)/, (req, res) => res.send(req.params[0]));
393-
* app.get<ParamsArray>('/user/*', (req, res) => res.send(req.params[0]));
397+
* app.get('/user/:id', (req, res) => res.send(req.params.id)); // implicitly `ParamsDictionary`, parameter is string
398+
* app.get('/user/*id', (req, res) => res.send(req.params.id)); // implicitly `ParamsDictionary`, parameter is string[]
399+
* app.get(/user\/(?<id>.*)/, (req, res) => res.send(req.params.id)); // implicitly `ParamsFlatDictionary`, parameter is string
400+
* app.get(/user\/(.*)/, (req, res) => res.send(req.params[0])); // implicitly `ParamsFlatDictionary`, parameter is string
394401
*/
395402
export interface Request<
396403
P = ParamsDictionary,

‎types/express-serve-static-core/v4/express-serve-static-core-tests.ts‎

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,6 @@ app.route("/:foo").get(req => {
4242
req.is(1);
4343
});
4444

45-
// Params can used as an array
46-
app.get<express.ParamsArray>("/*", req => {
47-
req.params[0]; // $ExpectType string
48-
req.params.length; // $ExpectType number
49-
});
50-
51-
// Params can used as an array - under route
52-
app.route("/*").get<express.ParamsArray>(req => {
53-
req.params[0]; // $ExpectType string
54-
req.params.length; // $ExpectType number
55-
});
56-
5745
// Params can be a custom type
5846
// NB. out-of-the-box all params are strings, however, other types are allowed to accommodate request validation/coercion middleware
5947
app.get<{ foo: string; bar: number }>("/:foo/:bar", req => {

‎types/express-serve-static-core/v4/index.d.ts‎

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,9 @@ export interface Dictionary<T> {
4242
}
4343

4444
export interface ParamsDictionary {
45-
[key: string]: string;
45+
[key: string | number]: string;
4646
}
47-
export type ParamsArray = string[];
48-
export type Params = ParamsDictionary | ParamsArray;
47+
export type Params = ParamsDictionary;
4948

5049
export interface Locals extends Express.Locals {}
5150

@@ -382,19 +381,6 @@ export interface RequestRanges extends RangeParserRanges {}
382381

383382
export type Errback = (err: Error) => void;
384383

385-
/**
386-
* @param P For most requests, this should be `ParamsDictionary`, but if you're
387-
* using this in a route handler for a route that uses a `RegExp` or a wildcard
388-
* `string` path (e.g. `'/user/*'`), then `req.params` will be an array, in
389-
* which case you should use `ParamsArray` instead.
390-
*
391-
* @see https://expressjs.com/en/api.html#req.params
392-
*
393-
* @example
394-
* app.get('/user/:id', (req, res) => res.send(req.params.id)); // implicitly `ParamsDictionary`
395-
* app.get<ParamsArray>(/user\/(.*)/, (req, res) => res.send(req.params[0]));
396-
* app.get<ParamsArray>('/user/*', (req, res) => res.send(req.params[0]));
397-
*/
398384
export interface Request<
399385
P = ParamsDictionary,
400386
ResBody = any,

‎types/express/express-tests.ts‎

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import express = require("express");
2-
import { ParamsArray, Request, RequestRanges } from "express-serve-static-core";
2+
import { Request, RequestRanges } from "express-serve-static-core";
33
import * as http from "http";
44

55
namespace express_tests {
@@ -139,24 +139,6 @@ namespace express_tests {
139139
req.params[0];
140140
});
141141

142-
// Params can used as an array
143-
router.get<ParamsArray>("/*", req => {
144-
req.params[0]; // $ExpectType string
145-
req.params.length; // $ExpectType number
146-
});
147-
148-
// Params can used as an array and can be specified via an explicit param type (express-serve-static-core)
149-
router.get("/*", (req: Request<ParamsArray>) => {
150-
req.params[0]; // $ExpectType string
151-
req.params.length; // $ExpectType number
152-
});
153-
154-
// Params can used as an array and can be specified via an explicit param type (express)
155-
router.get("/*", (req: express.Request<ParamsArray>) => {
156-
req.params[0]; // $ExpectType string
157-
req.params.length; // $ExpectType number
158-
});
159-
160142
// Params can be a custom type
161143
// NB. out-of-the-box all params are strings, however, other types are allowed to accomadate request validation/coersion middleware
162144
router.get<{ foo: string; bar: number }>("/:foo/:bar", req => {

‎types/express/v4/express-tests.ts‎

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import express = require("express");
2-
import { ParamsArray, Request, RequestRanges } from "express-serve-static-core";
2+
import { Request, RequestRanges } from "express-serve-static-core";
33
import * as http from "http";
44

55
namespace express_tests {
@@ -146,24 +146,6 @@ namespace express_tests {
146146
req.params[0];
147147
});
148148

149-
// Params can used as an array
150-
router.get<ParamsArray>("/*", req => {
151-
req.params[0]; // $ExpectType string
152-
req.params.length; // $ExpectType number
153-
});
154-
155-
// Params can used as an array and can be specified via an explicit param type (express-serve-static-core)
156-
router.get("/*", (req: Request<ParamsArray>) => {
157-
req.params[0]; // $ExpectType string
158-
req.params.length; // $ExpectType number
159-
});
160-
161-
// Params can used as an array and can be specified via an explicit param type (express)
162-
router.get("/*", (req: express.Request<ParamsArray>) => {
163-
req.params[0]; // $ExpectType string
164-
req.params.length; // $ExpectType number
165-
});
166-
167149
// Params can be a custom type
168150
// NB. out-of-the-box all params are strings, however, other types are allowed to accomadate request validation/coersion middleware
169151
router.get<{ foo: string; bar: number }>("/:foo/:bar", req => {

‎types/i18n/i18n-tests.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ app.get("/ar", (_req: Express.Request, res: i18n.Response) => {
248248
i18n.setLocale(res, "ar");
249249
i18n.setLocale(res.locals, "ar");
250250

251-
i18n.setLocale([req, res.locals], req.params.lang);
251+
i18n.setLocale([req, res.locals], "ar");
252252
i18n.setLocale(res, "ar", true);
253253
});
254254

0 commit comments

Comments
 (0)