UNPKG

graphql-http

Version:

Simple, pluggable, zero-dependency, GraphQL over HTTP spec compliant server, client and audit suite.

150 lines (149 loc) 5.24 kB
/** * 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; }