graphql-http
Version:
Simple, pluggable, zero-dependency, GraphQL over HTTP spec compliant server, client and audit suite.
150 lines (149 loc) • 5.24 kB
JavaScript
/**
* Renders the provided audit results to well-formatted and valid HTML.
*
* Do note that the rendered result is not an HTML document, it's rather
* just a component with results.
*/
export async function renderAuditResultsToHTML(results) {
const grouped = {
total: 0,
ok: [],
notice: [],
warn: [],
error: [],
};
for (const result of results) {
grouped.total++;
if (result.status === 'ok') {
grouped[result.status].push(result);
}
else {
grouped[result.status].push(result);
}
}
let report = '<i>* This report was auto-generated by graphql-http</i>\n';
report += '\n';
report += '<h1>GraphQL over HTTP audit report</h1>\n';
report += '\n';
report += '<ul>\n';
report += `<li><b>${grouped.total}</b> audits in total</li>\n`;
// font-family: monospace helps render native emojis in HTML
if (grouped.ok.length) {
report += `<li><span style="font-family: monospace">✅</span> <b>${grouped.ok.length}</b> pass</li>\n`;
}
if (grouped.notice.length) {
report += `<li><span style="font-family: monospace">💡</span> <b>${grouped.notice.length}</b> notices (suggestions)</li>\n`;
}
if (grouped.warn.length) {
report += `<li><span style="font-family: monospace">❗️</span> <b>${grouped.warn.length}</b> warnings (optional)</li>\n`;
}
if (grouped.error.length) {
report += `<li><span style="font-family: monospace">❌</span> <b>${grouped.error.length}</b> errors (required)</li>\n`;
}
report += '</ul>\n';
report += '\n';
if (grouped.ok.length) {
report += '<h2>Passing</h2>\n';
report += '<ol>\n';
for (const [, result] of grouped.ok.entries()) {
report += `<li><code>${result.id}</code> ${result.name}</li>\n`;
}
report += '</ol>\n';
report += '\n';
}
if (grouped.notice.length) {
report += `<h2>Notices</h2>\n`;
report +=
'The server <i>MAY</i> support these, but are truly optional. These are suggestions following recommended conventions.\n';
report += '<ol>\n';
for (const [, result] of grouped.notice.entries()) {
report += await printAuditFail(result);
}
report += '</ol>\n';
report += '\n';
}
if (grouped.warn.length) {
report += `<h2>Warnings</h2>\n`;
report += 'The server <i>SHOULD</i> support these, but is not required.\n';
report += '<ol>\n';
for (const [, result] of grouped.warn.entries()) {
report += await printAuditFail(result);
}
report += '</ol>\n';
report += '\n';
}
if (grouped.error.length) {
report += `<h2>Errors</h2>\n`;
report += 'The server <b>MUST</b> support these.\n';
report += '<ol>\n';
for (const [, result] of grouped.error.entries()) {
report += await printAuditFail(result);
}
report += '</ol>\n';
}
return report;
}
async function printAuditFail(result) {
var _a;
let report = '';
report += `<li><code>${result.id}</code> ${result.name}\n`;
report += '<details>\n';
report += `<summary>${truncate(result.reason)}</summary>\n`;
report += '<pre><code class="lang-json">'; // no "\n" because they count in HTML pre tags
const res = result.response;
const headers = {};
for (const [key, val] of res.headers.entries()) {
// some headers change on each run, dont report it
if (key === 'date') {
headers[key] = '<timestamp>';
}
else if (['cf-ray', 'server-timing', 'set-cookie'].includes(key)) {
headers[key] = '<omitted>';
}
else {
headers[key] = val;
}
}
let text = '', json;
try {
text = await res.text();
json = JSON.parse(text);
// is json, there shouldnt be nothing to sanitize (hopefully)
}
catch (_b) {
// is not json, avoid rendering html (rest is allowed)
if ((_a = res.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.includes('text/html')) {
text = '<html omitted>';
}
}
const stringified = JSON.stringify({
status: res.status,
statusText: res.statusText,
headers,
body: json || ((text === null || text === void 0 ? void 0 : text.length) > 5120 ? '<body is too long>' : text) || null,
}, (_k, v) => {
if (v != null && typeof v === 'object' && !Array.isArray(v)) {
// sort object fields for stable stringify
const acc = {};
return Object.keys(v)
.sort()
.reverse() // body on bottom
.reduce((acc, k) => {
acc[k] = v[k];
return acc;
}, acc);
}
return v;
}, 2);
report += stringified + '\n';
report += '</code></pre>\n';
report += '</details>\n';
report += '</li>\n';
return report;
}
function truncate(str, len = 1024) {
if (str.length > len) {
return str.substring(0, len) + '...';
}
return str;
}