-
Notifications
You must be signed in to change notification settings - Fork 30.5k
[express-serve-static-core] add RouteParameters type for IRouterMatcher that uses template literal types #51262
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1422df0
70d4a7b
4864a0d
32f904a
e393fd3
87b3976
68f5fdb
7b078d7
a865771
02afe5c
59dc475
bdbc6ae
51f372c
57ab756
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "private": true, | ||
| "types": "index", | ||
| "typesVersions": { | ||
| "<=4.0": { "*": ["ts4.0/*"] } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,168 @@ | ||
| import * as express from 'express-serve-static-core'; | ||
|
|
||
| const app: express.Application = {} as any; | ||
| app.listen(3000); | ||
| app.listen(3000, () => { | ||
| // no-op error callback | ||
| }); | ||
|
|
||
| app.get('/:foo', req => { | ||
| req.params.foo; // $ExpectType string | ||
| req.params[0]; // $ExpectType string | ||
| // $ExpectType string | false | null | ||
| req.is(['application/json', 'application/xml']); | ||
| // $ExpectType string | false | null | ||
| req.is('audio/wav'); | ||
| // $ExpectError | ||
| req.is(1); | ||
| }); | ||
|
|
||
| app.route('/:foo').get(req => { | ||
| req.params.foo; // $ExpectType string | ||
| req.params[0]; // $ExpectType string | ||
| // $ExpectType string | false | null | ||
| req.is(['application/json', 'application/xml']); | ||
| // $ExpectType string | false | null | ||
| req.is('audio/wav'); | ||
| // $ExpectError | ||
| req.is(1); | ||
| }); | ||
|
|
||
| // Params can used as an array | ||
| app.get<express.ParamsArray>('/*', req => { | ||
| req.params[0]; // $ExpectType string | ||
| req.params.length; // $ExpectType number | ||
| }); | ||
|
|
||
| // Params can used as an array - under route | ||
| app.route('/*').get<express.ParamsArray>(req => { | ||
| req.params[0]; // $ExpectType string | ||
| req.params.length; // $ExpectType number | ||
| }); | ||
|
|
||
| // Params can be a custom type | ||
| // NB. out-of-the-box all params are strings, however, other types are allowed to accommodate request validation/coercion middleware | ||
| app.get<{ foo: string; bar: number }>('/:foo/:bar', req => { | ||
| req.params.foo; // $ExpectType string | ||
| req.params.bar; // $ExpectType number | ||
| req.params.baz; // $ExpectError | ||
| }); | ||
|
|
||
| // Params can be a custom type - under route | ||
| app.route('/:foo/:bar').get<{ foo: string; bar: number }>(req => { | ||
| req.params.foo; // $ExpectType string | ||
| req.params.bar; // $ExpectType number | ||
| req.params.baz; // $ExpectError | ||
| }); | ||
|
|
||
| // Optional params currently not supported | ||
| // TODO: support optional params - https://github.com/DefinitelyTyped/DefinitelyTyped/pull/51262#discussion_r638786417 | ||
| app.get('/:foo/:bar?', req => { | ||
| req.params.foo; // $ExpectType string | ||
| req.params['bar?']; // $ExpectType string | ||
| }); | ||
|
|
||
| // Query can be a custom type | ||
| app.get<{}, any, any, { q: string }>('/:foo', req => { | ||
| req.query.q; // $ExpectType string | ||
| req.query.a; // $ExpectError | ||
| }); | ||
|
|
||
| // Query can be a custom type - under route | ||
| app.route('/:foo').get<{}, any, any, { q: string }>(req => { | ||
| req.query.q; // $ExpectType string | ||
| req.query.a; // $ExpectError | ||
| }); | ||
|
|
||
| // Query will be defaulted to Query type | ||
| app.get('/:foo', req => { | ||
| req.query; // $ExpectType ParsedQs | ||
| }); | ||
|
|
||
| // Query will be defaulted to Query type - under route | ||
| app.route('/:foo').get(req => { | ||
| req.query; // $ExpectType ParsedQs | ||
| }); | ||
|
|
||
| // Next can receive a Error parameter to delegate to Error handler | ||
| app.get('/nexterr', (req, res, next) => { | ||
| next(new Error('dummy')); // $ExpectType void | ||
| }); | ||
|
|
||
| // Next can receive a 'router' parameter to fall back to next router | ||
| app.get('/nextrouter', (req, res, next) => { | ||
| next('router'); // $ExpectType void | ||
| }); | ||
|
|
||
| // Default types | ||
| app.post('/', (req, res) => { | ||
| req.params[0]; // $ExpectType string | ||
|
|
||
| req.body; // $ExpectType any | ||
| res.send('ok'); // $ExpectType Response<any, Record<string, any>, number> | ||
| }); | ||
|
|
||
| // Default types - under route | ||
| app.route('/').post((req, res) => { | ||
| req.params[0]; // $ExpectType string | ||
|
|
||
| req.body; // $ExpectType any | ||
| res.send('ok'); // $ExpectType Response<any, Record<string, any>, number> | ||
| }); | ||
|
|
||
| // No params, only response body type | ||
| app.get<never, { foo: string }>('/', (req, res) => { | ||
| req.params.baz; // $ExpectError | ||
|
|
||
| res.send({ foo: 'ok' }); // $ExpectType Response<{ foo: string; }, Record<string, any>, number> | ||
| req.body; // $ExpectType any | ||
| }); | ||
|
|
||
| // No params, only response body type - under route | ||
| app.route('/').get<never, { foo: string }>((req, res) => { | ||
| req.params.baz; // $ExpectError | ||
|
|
||
| res.send({ foo: 'ok' }); // $ExpectType Response<{ foo: string; }, Record<string, any>, number> | ||
| req.body; // $ExpectType any | ||
| }); | ||
|
|
||
| // No params, request body type and response body type | ||
| app.post<never, { foo: string }, { bar: number }>('/', (req, res) => { | ||
| req.params.baz; // $ExpectError | ||
|
|
||
| res.send({ foo: 'ok' }); // $ExpectType Response<{ foo: string; }, Record<string, any>, number> | ||
| req.body.bar; // $ExpectType number | ||
|
|
||
| res.json({ baz: 'fail' }); // $ExpectError | ||
| req.body.baz; // $ExpectError | ||
| }); | ||
|
|
||
| // No params, request body type and response body type - under route | ||
| app.route('/').post<never, { foo: string }, { bar: number }>((req, res) => { | ||
| req.params.baz; // $ExpectError | ||
|
|
||
| res.send({ foo: 'ok' }); // $ExpectType Response<{ foo: string; }, Record<string, any>, number> | ||
| req.body.bar; // $ExpectType number | ||
|
|
||
| res.json({ baz: 'fail' }); // $ExpectError | ||
| req.body.baz; // $ExpectError | ||
| }); | ||
|
|
||
| app.engine('ntl', (_filePath, _options, callback) => { | ||
| callback(new Error('not found.')); | ||
| }); | ||
|
|
||
| // Status test | ||
| { | ||
| type E = express.Response<unknown, any, 'abc'>; // $ExpectError | ||
| type B = express.Response<unknown, any, 123>; | ||
| type C = Parameters<B['status']>[0]; // $ExpectType 123 | ||
| type D = Parameters<B['sendStatus']>[0]; // $ExpectType 123 | ||
| } | ||
|
|
||
| // Locals can be a custom type | ||
| app.get<{}, any, any, {}, { foo: boolean }>('/locals', (req, res, next) => { | ||
| res.locals.foo; // $ExpectType boolean | ||
| res.locals.bar; // $ExpectError | ||
| res.send({ foo: 'ok' }); // $ExpectType Response<any, { foo: boolean; }, number> | ||
| }); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Express docs have a few examples of tricky routes: https://expressjs.com/en/guide/routing.html#route-parameters It would be a good idea to add them to the tests here to make sure they work as expected:
While it's not explicitly mentioned on that page, if you click through to This may be tested elsewhere, but the path can also be a regular expression, and that should continue to work as before (there are no regex literals… yet!).
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh, wasn't aware of this. i'll add support for
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about just adding a test for
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. though i'm keen to rewrite the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like a decent fallback workaround for non standard implementations. Would users still be able to provide custom parameters like in https://github.com/DefinitelyTyped/DefinitelyTyped/pull/51262/files#diff-c5d8f61506d81e402ccb7f5f031730ce5ee044b86777af13924d7edfae4243f1R45
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, this works:
i can add a test for this if you'd like
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great, good to have this workaround on hand for when it might break someones implementation (although it is hard to imagine how it would). This is a great PR!