Attempted to add proper error handling within the Page decorator such that errors are handled consistently both with and without the Page decorator
All checks were successful
Publish to Private NPM Registry / publish (push) Successful in 55s
All checks were successful
Publish to Private NPM Registry / publish (push) Successful in 55s
This commit is contained in:
parent
98faf2462d
commit
a308d69766
1 changed files with 100 additions and 39 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { Request, Response } from 'express';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
|
||||
// Response type guard
|
||||
function isResponse(obj) {
|
||||
|
|
@ -36,50 +36,111 @@ function isResponse(obj) {
|
|||
* @param otherParams Any other parameters to pass to the page
|
||||
*/
|
||||
export function Page(title: string, page: string, extraScripts: (string | { script: string, defer: boolean })[] = [], extraStyles: string[] = [], ...otherParams: any[]) {
|
||||
const doRender = (propertyKey: string, output: any, ...args: any[]) => {
|
||||
if (!args.some(arg => isResponse(arg)) || args.filter(arg => isResponse(arg)).length > 1) {
|
||||
console.warn(`Page decorator: Incorrect number of response objects found in arguments for ${propertyKey}. Should ONLY be one response object.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const renderParams: { [key: string]: any } = {
|
||||
title: title,
|
||||
page: page,
|
||||
extraStyles: extraStyles,
|
||||
extraScripts: extraScripts,
|
||||
...otherParams
|
||||
};
|
||||
|
||||
// If the decorated method's output is an object, we want to merge it with the renderParams
|
||||
if(typeof output === 'object') {
|
||||
Object.entries(output).forEach((entry) => {
|
||||
renderParams[entry[0]] = entry[1];
|
||||
});
|
||||
}
|
||||
|
||||
args.find(arg => isResponse(arg)).render('base', renderParams);
|
||||
}
|
||||
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
const original = descriptor.value;
|
||||
|
||||
descriptor.value = async function (...args: any[]) {
|
||||
if (!args.some(arg => isResponse(arg)) || args.filter(arg => isResponse(arg)).length > 1) {
|
||||
console.warn(`Page decorator: Incorrect number of response objects found in arguments for ${propertyKey}. Should ONLY be one response object.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// We run the original here so that if the decorated method has specific checks it needs to make (ex. if the ID of whatever actually exists) it can make them before rendering the page
|
||||
//
|
||||
// !!!IMPORTANT!!!
|
||||
// This currently is kind of a hacky solution, because we bind to the target, which is the prototype rather than the instance.
|
||||
// This means instance data is not properly available/accessible within the decorated method.
|
||||
// However, you can use the `this` keyword to access other methods on the class, etc...
|
||||
//
|
||||
// The above (this approach) is necessary at time of writing because the `this` (which should be bound to for the instance) seems to be undefined within the context of the decorator function here for some reason
|
||||
// Ideally, would dive into this and do it properly but this seems to be good enough for now.
|
||||
const output = await original.bind(target)(...args);
|
||||
|
||||
// If the output is false, we don't want to render the page
|
||||
//
|
||||
// This allows the decorated method to handle the response itself
|
||||
// Ex. Logging in, if the user is already logged in we want to redirect them, not render the login page again
|
||||
if(typeof output === 'boolean' && !output) {
|
||||
return;
|
||||
}
|
||||
// If the original function has more than 3 parameters, we can assume it is an error handler
|
||||
// An error handler starts with the error object, then the request, response and next functions.
|
||||
if(original.length > 3) {
|
||||
descriptor.value = async function (err: Error, req: Request, res: Response, next: NextFunction, ...args: any[]) {
|
||||
try {
|
||||
// We run the original here so that if the decorated method has specific checks it needs to make (ex. if the ID of whatever actually exists) it can make them before rendering the page
|
||||
//
|
||||
// !!!IMPORTANT!!!
|
||||
// This currently is kind of a hacky solution, because we bind to the target, which is the prototype rather than the instance.
|
||||
// This means instance data is not properly available/accessible within the decorated method.
|
||||
// However, you can use the `this` keyword to access other methods on the class, etc...
|
||||
//
|
||||
// The above (this approach) is necessary at time of writing because the `this` (which should be bound to for the instance) seems to be undefined within the context of the decorator function here for some reason
|
||||
// Ideally, would dive into this and do it properly but this seems to be good enough for now.
|
||||
const output = await original.bind(target)(err, req, res, next, ...args);
|
||||
|
||||
// If the output is false, we don't want to render the page
|
||||
//
|
||||
// This allows the decorated method to handle the response itself
|
||||
// Ex. Logging in, if the user is already logged in we want to redirect them, not render the login page again
|
||||
if(typeof output === 'boolean' && !output) {
|
||||
return;
|
||||
}
|
||||
|
||||
const renderParams: { [key: string]: any } = {
|
||||
title: title,
|
||||
page: page,
|
||||
extraStyles: extraStyles,
|
||||
extraScripts: extraScripts,
|
||||
...otherParams
|
||||
};
|
||||
|
||||
// If the decorated method's output is an object, we want to merge it with the renderParams
|
||||
if(typeof output === 'object') {
|
||||
Object.entries(output).forEach((entry) => {
|
||||
renderParams[entry[0]] = entry[1];
|
||||
});
|
||||
doRender(propertyKey, output, err, req, res, next, ...args);
|
||||
}
|
||||
catch(error) {
|
||||
next(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
descriptor.value = async function (req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
// We run the original here so that if the decorated method has specific checks it needs to make (ex. if the ID of whatever actually exists) it can make them before rendering the page
|
||||
//
|
||||
// !!!IMPORTANT!!!
|
||||
// This currently is kind of a hacky solution, because we bind to the target, which is the prototype rather than the instance.
|
||||
// This means instance data is not properly available/accessible within the decorated method.
|
||||
// However, you can use the `this` keyword to access other methods on the class, etc...
|
||||
//
|
||||
// The above (this approach) is necessary at time of writing because the `this` (which should be bound to for the instance) seems to be undefined within the context of the decorator function here for some reason
|
||||
// Ideally, would dive into this and do it properly but this seems to be good enough for now.
|
||||
const output = await original.bind(target)(req, res, next);
|
||||
|
||||
// If the output is false, we don't want to render the page
|
||||
//
|
||||
// This allows the decorated method to handle the response itself
|
||||
// Ex. Logging in, if the user is already logged in we want to redirect them, not render the login page again
|
||||
if(typeof output === 'boolean' && !output) {
|
||||
return;
|
||||
}
|
||||
|
||||
args.find(arg => isResponse(arg)).render('base', renderParams);
|
||||
doRender(propertyKey, output, req, res, next);
|
||||
|
||||
/*const renderParams: { [key: string]: any } = {
|
||||
title: title,
|
||||
page: page,
|
||||
extraStyles: extraStyles,
|
||||
extraScripts: extraScripts,
|
||||
...otherParams
|
||||
};
|
||||
|
||||
// If the decorated method's output is an object, we want to merge it with the renderParams
|
||||
if(typeof output === 'object') {
|
||||
Object.entries(output).forEach((entry) => {
|
||||
renderParams[entry[0]] = entry[1];
|
||||
});
|
||||
}
|
||||
|
||||
args.find(arg => isResponse(arg)).render('base', renderParams);*/
|
||||
}
|
||||
catch(error) {
|
||||
next(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue