web_plsql
Version:
The Express Middleware for Oracle PL/SQL
297 lines (255 loc) • 7 kB
JavaScript
/*
* Error handling
*/
import util from 'node:util';
import escape from 'escape-html';
import {ProcedureError} from './procedureError.js';
import {RequestError} from './requestError.js';
import {inspectRequest, logToFile} from './trace.js';
import {errorToString} from './error.js';
/**
* @typedef {import('express').Request} Request
* @typedef {import('express').Response} Response
* @typedef {import('./types.js').BindParameterConfig} BindParameterConfig
* @typedef {import('./types.js').environmentType} environmentType
* @typedef {import('./types.js').configPlSqlHandlerType} configPlSqlHandlerType
* @typedef {{html: string; text: string}} outputType
*/
/**
* Convert value to string
* @param {unknown} value - The value to convert.
* @returns {string} - The string representation of the value.
*/
const inspect = (value) => util.inspect(value, {showHidden: false, depth: null, colors: false});
/**
* Convert LF and/or CR to <br>
* @param {string} text - The text to convert.
* @returns {string} - The converted text.
*/
const convertToHtml = (text) => {
let html = escape(text);
html = html.replace(/(?:\r\n|\r|\n)/g, '<br />');
html = html.replace(/\t/g, ' ');
return html;
};
/**
* Get html
* @param {string} text - The text to convert.
* @returns {string} - The converted text.
*/
const getHtml = (text) => `<p>${convertToHtml(text)}</p>`;
/**
* Get text header
* @param {string} text - The text to convert.
* @returns {string} - The converted text.
*/
const getHeaderText = (text) => `\n${text}\n${'='.repeat(text.length)}\n`;
/**
* Get html header
* @param {string} text - The text to convert.
* @returns {string} - The converted text.
*/
const getHeaderHtml = (text) => `<h1>${text}</h1>`;
/**
* Get text
* @param {string} text - The text to convert.
* @returns {string} - The converted text.
*/
const getText = (text) => `${text}\n`;
/**
* Get evnironment
* @param {outputType} output - The output.
* @param {environmentType} environment - The environment.
*/
const getEnvironment = (output, environment) => {
let html = '<table>';
let text = '';
try {
for (const key in environment) {
html += `<tr><td>${key}:</td><td>${environment[key]}</td></tr>`;
text += `${key}=${environment[key]}\n`;
}
} catch (err) {
/* istanbul ignore next */
output.html += errorToString(err);
/* istanbul ignore next */
output.text += errorToString(err);
/* istanbul ignore next */
return;
}
html += '</table>';
output.html += html;
output.text += text;
};
/**
* Show an error page
* @param {Request} req - The req object represents the HTTP request.
* @param {unknown} error - The error.
* @returns {outputType} - The output.
*/
const getError = (req, error) => {
let timestamp = new Date();
let message = '';
/** @type {environmentType | null} */
let environment = null;
/** @type {string | null} */
let sql = null;
/** @type {BindParameterConfig} */
let bind = {};
// what type of Error did we receive
if (error instanceof ProcedureError) {
timestamp = error.timestamp;
/* istanbul ignore next */
message = error.stack ?? '';
environment = error.environment;
sql = error.sql;
bind = error.bind;
} else if (error instanceof RequestError) {
timestamp = error.timestamp;
/* istanbul ignore next */
message = error.stack ?? '';
} else if (error instanceof Error) {
/* istanbul ignore next */
message = errorToString(error);
} else {
if (typeof error === 'string') {
/* istanbul ignore next */
message = `${error}\n`;
}
try {
/* istanbul ignore next */
new Error();
} catch (err) {
/* istanbul ignore next */
message += errorToString(err);
}
}
/** @type {outputType} */
const output = {
html: '',
text: '',
};
// timestamp
let header = 'TIMESTAMP';
output.html += getHeaderHtml(header);
output.html += getHtml(timestamp.toUTCString());
// error
header = 'ERROR';
output.html += getHeaderHtml(header);
output.html += getHtml(message);
output.text += getHeaderText(header);
output.text += getText(message);
// request
header = 'REQUEST';
output.text += getHeaderText(header);
output.text += getText(inspectRequest(req));
// parameters
if (typeof sql === 'string' && bind) {
header = 'PROCEDURE';
output.html += getHeaderHtml(header);
output.text += getHeaderText(header);
getProcedure(output, sql, bind);
}
// environment
if (environment) {
header = 'ENVIRONMENT';
output.html += getHeaderHtml(header);
output.text += getHeaderText(header);
getEnvironment(output, environment);
}
return output;
};
/**
* Get procedure
* @param {outputType} output - The output.
* @param {string} sql - The SQL to execute.
* @param {BindParameterConfig} bind - The bind parameters.
*/
const getProcedure = (output, sql, bind) => {
let html = '<table>';
let text = '';
text += `PROCEDURE: ${sql}\n`;
html += `<tr><td>PROCEDURE:</td><td>${sql}</td></tr>`;
try {
/* istanbul ignore else */
for (const key in bind) {
const value = inspect(bind[key].val);
html += `<tr><td>${key}:</td><td>${value}</td></tr>`;
text += `${key}: ${value}\n`;
}
} catch (err) {
/* istanbul ignore next */
output.html += err instanceof Error ? err.toString() : 'ERROR';
/* istanbul ignore next */
output.text += err instanceof Error ? err.toString() : 'ERROR';
/* istanbul ignore next */
return;
}
html += '</table>';
output.html += html;
output.text += text;
};
/**
* getHtmlPage
* @param {string} body - The body.
* @returns {string} - The html page.
*/
const getHtmlPage = (body) => `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>web_plsql error page</title>
<style type="text/css">
html {
font-family: monospace, sans-serif;
font-size: 12px;
}
h1 {
font-size: 16px;
padding: 2px;
background-color: #cc0000;
}
</style>
</head>
<body>
${body}
</body>
</html>
`;
/**
* Show an error page
*
* @param {Request} req - The req object represents the HTTP request.
* @param {Response} res - The res object represents the HTTP response that an Express app sends when it gets an HTTP request.
* @param {configPlSqlHandlerType} options - The configuration options.
* @param {unknown} error - The error.
*/
export const errorPage = (req, res, options, error) => {
let output = {
html: '',
text: '',
};
// get the error description
try {
output = getError(req, error);
} catch (err) {
/* istanbul ignore next */
const header = 'ERROR';
/* istanbul ignore next */
const message = errorToString(err);
/* istanbul ignore next */
output.html += getHeaderHtml(header) + getHtml(message);
/* istanbul ignore next */
output.text += getHeaderHtml(header) + getHtml(message);
}
// trace to file
logToFile(output.text);
// console
console.error(output.text);
// show page
if (options.errorStyle === 'basic') {
res.status(404).send('Page not found');
} else {
res.status(404).send(getHtmlPage(output.html));
}
};