[express]: adding generics for query type (#43434)

* add-generics-to-query-type: adding generics for query type in express core

* add-generics-to-query-type: fixing test case + lint

* add-generics-to-query-type: updating the default query type for express

* add-generics-to-query-type: fixing failed test cases

* add-generics-to-query-type: fixing express-paginate-tests

Co-authored-by: Puneet Arora <parora@atlassian.com>
This commit is contained in:
Puneet Arora 2020-04-08 09:29:55 -07:00 committed by GitHub
parent df14f81746
commit bb2673643a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 43 additions and 17 deletions

View File

@ -49,7 +49,7 @@ app.get('/hasrole', function (req, res, next) {
});
app.post('/setrole', function (req, res, next) {
req.session.setRole(req.query.role);
req.session.setRole(req.query.role as string);
res.send(200);
});
@ -59,4 +59,4 @@ app.get('/getrole', function (req, res, next) {
app.use(easySession.isLoggedIn());
app.use(easySession.isFresh());
app.use(easySession.checkRole('user'));
app.use(easySession.checkRole('user'));

View File

@ -12,7 +12,7 @@ app.get('/users', async (req, res, next) => {
return findAndCountAll({limit: req.query.limit, offset: req.skip})
.then(results => {
const itemCount = results.count;
const pageCount = Math.ceil(results.count / req.query.limit);
const pageCount = Math.ceil(results.count / parseInt(req.query.limit as string, 10));
res.render('users/all_users', {
users: results.rows,
pageCount,
@ -20,7 +20,7 @@ app.get('/users', async (req, res, next) => {
currentPageHref: paginate.href(req)(false, req.params),
// Instead of exposing this to the html template, we'll test this here and pass a static number
hasNextPages: paginate.hasNextPages(req)(pageCount),
pages: paginate.getArrayPages(req)(3, pageCount, req.query.page)
pages: paginate.getArrayPages(req)(3, pageCount, parseInt(req.query.page as string, 10))
});
}).catch(next);
});

View File

@ -32,6 +32,17 @@ app.get<{ foo: string }>('/:foo', req => {
// Params cannot be a custom type that does not conform to constraint
app.get<{ foo: number }>('/:foo', () => {}); // $ExpectError
// Query can be a custom type
app.get<{}, any, any, {q: string}>('/:foo', req => {
req.query.q; // $ExpectType string
req.query.a; // $ExpectError
});
// Query will be defaulted to Query type
app.get('/:foo', req => {
req.query; // $ExpectType Query
});
// Default types
app.post("/", (req, res) => {
req.params[0]; // $ExpectType string

View File

@ -40,26 +40,30 @@ export interface ParamsDictionary { [key: string]: string; }
export type ParamsArray = string[];
export type Params = ParamsDictionary | ParamsArray;
export interface RequestHandler<P extends Params = ParamsDictionary, ResBody = any, ReqBody = any> {
// Return type of qs.parse, the default query parser (https://expressjs.com/en/api.html#app-settings-property).
export interface Query { [key: string]: string | string[] | Query | Query[]; }
export interface RequestHandler<P extends Params = ParamsDictionary, ResBody = any, ReqBody = any, ReqQuery = Query> {
// tslint:disable-next-line callable-types (This is extended from and can't extend from a type alias in ts<2.2
(req: Request<P, ResBody, ReqBody>, res: Response<ResBody>, next: NextFunction): any;
(req: Request<P, ResBody, ReqBody, ReqQuery>, res: Response<ResBody>, next: NextFunction): any;
}
export type ErrorRequestHandler<P extends Params = ParamsDictionary, ResBody = any, ReqBody = any> = (err: any, req: Request<P, ResBody, ReqBody>, res: Response<ResBody>, next: NextFunction) => any;
export type ErrorRequestHandler<P extends Params = ParamsDictionary, ResBody = any, ReqBody = any, ReqQuery = Query> =
(err: any, req: Request<P, ResBody, ReqBody, ReqQuery>, res: Response<ResBody>, next: NextFunction) => any;
export type PathParams = string | RegExp | Array<string | RegExp>;
export type RequestHandlerParams<P extends Params = ParamsDictionary, ResBody = any, ReqBody = any>
= RequestHandler<P, ResBody, ReqBody>
| ErrorRequestHandler<P, ResBody, ReqBody>
export type RequestHandlerParams<P extends Params = ParamsDictionary, ResBody = any, ReqBody = any, ReqQuery = Query>
= RequestHandler<P, ResBody, ReqBody, ReqQuery>
| ErrorRequestHandler<P, ResBody, ReqBody, ReqQuery>
| Array<RequestHandler<P>
| ErrorRequestHandler<P>>;
export interface IRouterMatcher<T, Method extends 'all' | 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options' | 'head' = any> {
// tslint:disable-next-line no-unnecessary-generics (This generic is meant to be passed explicitly.)
<P extends Params = ParamsDictionary, ResBody = any, ReqBody = any>(path: PathParams, ...handlers: Array<RequestHandler<P, ResBody, ReqBody>>): T;
<P extends Params = ParamsDictionary, ResBody = any, ReqBody = any, ReqQuery = Query>(path: PathParams, ...handlers: Array<RequestHandler<P, ResBody, ReqBody, ReqQuery>>): T;
// tslint:disable-next-line no-unnecessary-generics (This generic is meant to be passed explicitly.)
<P extends Params = ParamsDictionary, ResBody = any, ReqBody = any>(path: PathParams, ...handlers: Array<RequestHandlerParams<P, ResBody, ReqBody>>): T;
<P extends Params = ParamsDictionary, ResBody = any, ReqBody = any, ReqQuery = Query>(path: PathParams, ...handlers: Array<RequestHandlerParams<P, ResBody, ReqBody, ReqQuery>>): T;
(path: PathParams, subApplication: Application): T;
}
@ -209,7 +213,7 @@ export type Errback = (err: Error) => void;
* app.get<ParamsArray>(/user\/(.*)/, (req, res) => res.send(req.params[0]));
* app.get<ParamsArray>('/user/*', (req, res) => res.send(req.params[0]));
*/
export interface Request<P extends Params = ParamsDictionary, ResBody = any, ReqBody = any> extends http.IncomingMessage, Express.Request {
export interface Request<P extends Params = ParamsDictionary, ResBody = any, ReqBody = any, ReqQuery = Query> extends http.IncomingMessage, Express.Request {
/**
* Return request header.
*
@ -463,7 +467,7 @@ export interface Request<P extends Params = ParamsDictionary, ResBody = any, Req
params: P;
query: any;
query: ReqQuery;
route: any;

View File

@ -168,6 +168,17 @@ namespace express_tests {
// Params cannot be a custom type that does not conform to constraint
router.get<{ foo: number }>('/:foo', () => {}); // $ExpectError
// Query can be a custom type
router.get('/:foo', (req: express.Request<{}, any, any , {q: string}>) => {
req.query.q; // $ExpectType string
req.query.a; // $ExpectError
});
// Query will be defaulted to any
router.get('/:foo', (req: express.Request<{}>) => {
req.query; // $ExpectType Query
});
// Response will default to any type
router.get("/", (req: Request, res: express.Response) => {
res.json({});

View File

@ -104,7 +104,7 @@ declare namespace e {
interface IRouterMatcher<T> extends core.IRouterMatcher<T> { }
interface MediaType extends core.MediaType { }
interface NextFunction extends core.NextFunction { }
interface Request<P extends core.Params = core.ParamsDictionary> extends core.Request<P> { }
interface Request<P extends core.Params = core.ParamsDictionary, ResBody = any, ReqBody = any, ReqQuery = core.Query> extends core.Request<P, ResBody, ReqBody, ReqQuery> { }
interface RequestHandler<P extends core.Params = core.ParamsDictionary> extends core.RequestHandler<P> { }
interface RequestParamHandler extends core.RequestParamHandler { }
export interface Response<ResBody = any> extends core.Response<ResBody> { }

View File

@ -41,5 +41,5 @@ storage.getSanitizedFileName('IMAGE.jpg'); // $ExpectType string
storage.exists('tmp/123456.jpg', '/'); // $ExpectType Promise<boolean>
storage.save(image, '/'); // $ExpectType Promise<string>
storage.serve(); // $ExpectType (req: Request<ParamsDictionary>, res: Response<any>, next: NextFunction) => void
storage.serve(); // $ExpectType (req: Request<ParamsDictionary, any, any, Query>, res: Response<any>, next: NextFunction) => void
storage.delete('tmp/123456.jpg', '/'); // $ExpectType Promise<boolean>

View File

@ -33,7 +33,7 @@ opts.jwtFromRequest = ExtractJwt.fromUrlQueryParameter('param_name');
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderWithScheme('param_name');
opts.jwtFromRequest = ExtractJwt.fromExtractors([ExtractJwt.fromHeader('x-api-key'), ExtractJwt.fromBodyField('field_name'), ExtractJwt.fromUrlQueryParameter('param_name')]);
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.jwtFromRequest = (req: Request) => { return req.query.token; };
opts.jwtFromRequest = (req: Request) => { return req.query.token as string; };
opts.secretOrKey = new Buffer('secret');
declare function findUser(condition: {id: string}, callback: (error: any, user :any) => void): void;