Skip to content

Commit

Permalink
feat: add basic html error
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 committed Mar 30, 2022
1 parent 0929bcd commit d090aac
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 27 deletions.
5 changes: 5 additions & 0 deletions playground/api/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { defineEventHandler } from 'h3'

export default defineEventHandler((_event) => {
throw new Error('Booo')
})
37 changes: 20 additions & 17 deletions playground/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,27 @@ import { defineEventHandler } from 'h3'

export default defineEventHandler(() => {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<title>Nitro Playground</title>
</head>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<title>Nitro Playground</title>
</head>
<body>
<h1>Welcome to Nitro playground!</h1>
<ul>
<li>
<a href="/api/test">/api/test</a>
</li>
<li>
<a href="/api/cache">/api/cache</a>
</li>
</ul>
</body>
<body>
<h1>Welcome to Nitro playground!</h1>
<ul>
<li>
<a href="/api/test">/api/test</a>
</li>
<li>
<a href="/api/cache">/api/cache</a>
</li>
<li>
<a href="/api/error">/api/error</a>
</li>
</ul>
</body>
</html>`
})
45 changes: 39 additions & 6 deletions src/runtime/error.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
// import ansiHTML from 'ansi-html'
import type { CompatibilityEvent } from 'h3'
import { normalizeError } from './utils'
import { normalizeError, isJsonRequest } from './utils'

const isDev = process.env.NODE_ENV === 'development'

interface ParsedError {
url: string
statusCode: number
statusMessage: number
message: string
stack: string[]
}

export default function handleError (error: any, event: CompatibilityEvent) {
const { stack, statusCode, statusMessage, message } = normalizeError(error)

const showDetails = isDev && statusCode !== 404

const errorObject = {
url: event.req.url,
statusCode,
statusMessage,
message,
description: isDev && statusCode !== 404
? `<pre>${stack.map(i => `<span class="stack${i.internal ? ' internal' : ''}">${i.text}</span>`).join('\n')}</pre>`
: ''
stack: showDetails ? stack.map(i => i.text) : undefined
}

// Console output
Expand All @@ -24,6 +32,31 @@ export default function handleError (error: any, event: CompatibilityEvent) {

event.res.statusCode = statusCode
event.res.statusMessage = statusMessage
event.res.setHeader('Content-Type', 'application/json')
event.res.end(JSON.stringify(errorObject))

if (isJsonRequest(event)) {
event.res.setHeader('Content-Type', 'application/json')
event.res.end(JSON.stringify(errorObject))
} else {
event.res.setHeader('Content-Type', 'text/html')
event.res.end(renderHTMLError(errorObject))
}
}

function renderHTMLError (error: ParsedError): string {
return `<!DOCTYPE html>
<html lang="en">
<head>
<title>${error.statusMessage} (${error.statusCode})</title>
<link rel="stylesheet" href="https://necolas.github.io/normalize.css/7.0.0/normalize.css">
<style> body { margin: 2em; } </style>
</head>
<body>
<h1>${error.statusMessage} (${error.statusCode})</h1>
<p>Error: ${error.message}</p>
<ul>${'\n' + error.stack.map(i => ` <li>${i}</li>`).join('\n')}
</ul>
<hr>
Generated by Nitro at ${new Date().toISOString()}
</body>
</html>`
}
17 changes: 13 additions & 4 deletions src/runtime/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,19 @@ export async function useRequestBody (request: globalThis.Request): Promise<any>
}
}

const hasReqHeader = (req, header, includes) => req.headers[header] && req.headers[header].toLowerCase().includes(includes)
export function hasReqHeader (req, header, includes) {
return req.headers[header] && req.headers[header].toLowerCase().includes(includes)
}

export const isJsonRequest = (event: CompatibilityEvent) => hasReqHeader(event.req, 'accept', 'application/json') || hasReqHeader(event.req, 'user-agent', 'curl/') || hasReqHeader(event.req, 'user-agent', 'httpie/')
export function isJsonRequest (event: CompatibilityEvent) {
return hasReqHeader(event.req, 'accept', 'application/json') ||
hasReqHeader(event.req, 'user-agent', 'curl/') ||
hasReqHeader(event.req, 'user-agent', 'httpie/') ||
event.req.url.endsWith('.json') ||
event.req.url.includes('/api/')
}

export const normalizeError = (error: any) => {
export function normalizeError (error: any) {
const cwd = process.cwd()
const stack = (error.stack || '')
.split('\n')
Expand All @@ -41,6 +49,7 @@ export const normalizeError = (error: any) => {
const text = line
.replace(cwd + '/', './')
.replace('webpack:/', '')
.replace('file://', '')
.trim()
return {
text,
Expand All @@ -51,7 +60,7 @@ export const normalizeError = (error: any) => {
})

const statusCode = error.statusCode || 500
const statusMessage = error.statusMessage ?? (statusCode === 404 ? 'Page Not Found' : 'Internal Server Error')
const statusMessage = error.statusMessage ?? (statusCode === 404 ? 'Route Not Found' : 'Internal Server Error')
const message = error.message || error.toString()

return {
Expand Down

0 comments on commit d090aac

Please sign in to comment.