UNPKG

4.96 kBJavaScriptView Raw
1'use strict'
2
3const statusCodes = require('node:http').STATUS_CODES
4const wrapThenable = require('./wrapThenable')
5const {
6 kReplyHeaders,
7 kReplyNextErrorHandler,
8 kReplyIsRunningOnErrorHook,
9 kReplyHasStatusCode,
10 kRouteContext,
11 kDisableRequestLogging
12} = require('./symbols.js')
13
14const {
15 FST_ERR_REP_INVALID_PAYLOAD_TYPE,
16 FST_ERR_FAILED_ERROR_SERIALIZATION
17} = require('./errors')
18
19const { getSchemaSerializer } = require('./schemas')
20
21const serializeError = require('./error-serializer')
22
23const rootErrorHandler = {
24 func: defaultErrorHandler,
25 toJSON () {
26 return this.func.name.toString() + '()'
27 }
28}
29
30function handleError (reply, error, cb) {
31 reply[kReplyIsRunningOnErrorHook] = false
32
33 const context = reply[kRouteContext]
34 if (reply[kReplyNextErrorHandler] === false) {
35 fallbackErrorHandler(error, reply, function (reply, payload) {
36 try {
37 reply.raw.writeHead(reply.raw.statusCode, reply[kReplyHeaders])
38 } catch (error) {
39 if (!reply.log[kDisableRequestLogging]) {
40 reply.log.warn(
41 { req: reply.request, res: reply, err: error },
42 error && error.message
43 )
44 }
45 reply.raw.writeHead(reply.raw.statusCode)
46 }
47 reply.raw.end(payload)
48 })
49 return
50 }
51 const errorHandler = reply[kReplyNextErrorHandler] || context.errorHandler
52
53 // In case the error handler throws, we set the next errorHandler so we can error again
54 reply[kReplyNextErrorHandler] = Object.getPrototypeOf(errorHandler)
55
56 // we need to remove content-type to allow content-type guessing for serialization
57 delete reply[kReplyHeaders]['content-type']
58 delete reply[kReplyHeaders]['content-length']
59
60 const func = errorHandler.func
61
62 if (!func) {
63 reply[kReplyNextErrorHandler] = false
64 fallbackErrorHandler(error, reply, cb)
65 return
66 }
67
68 try {
69 const result = func(error, reply.request, reply)
70 if (result !== undefined) {
71 if (result !== null && typeof result.then === 'function') {
72 wrapThenable(result, reply)
73 } else {
74 reply.send(result)
75 }
76 }
77 } catch (err) {
78 reply.send(err)
79 }
80}
81
82function defaultErrorHandler (error, request, reply) {
83 setErrorHeaders(error, reply)
84 if (!reply[kReplyHasStatusCode] || reply.statusCode === 200) {
85 const statusCode = error.statusCode || error.status
86 reply.code(statusCode >= 400 ? statusCode : 500)
87 }
88 if (reply.statusCode < 500) {
89 if (!reply.log[kDisableRequestLogging]) {
90 reply.log.info(
91 { res: reply, err: error },
92 error && error.message
93 )
94 }
95 } else {
96 if (!reply.log[kDisableRequestLogging]) {
97 reply.log.error(
98 { req: request, res: reply, err: error },
99 error && error.message
100 )
101 }
102 }
103 reply.send(error)
104}
105
106function fallbackErrorHandler (error, reply, cb) {
107 const res = reply.raw
108 const statusCode = reply.statusCode
109 reply[kReplyHeaders]['content-type'] = reply[kReplyHeaders]['content-type'] ?? 'application/json; charset=utf-8'
110 let payload
111 try {
112 const serializerFn = getSchemaSerializer(reply[kRouteContext], statusCode, reply[kReplyHeaders]['content-type'])
113 payload = (serializerFn === false)
114 ? serializeError({
115 error: statusCodes[statusCode + ''],
116 code: error.code,
117 message: error.message,
118 statusCode
119 })
120 : serializerFn(Object.create(error, {
121 error: { value: statusCodes[statusCode + ''] },
122 message: { value: error.message },
123 statusCode: { value: statusCode }
124 }))
125 } catch (err) {
126 if (!reply.log[kDisableRequestLogging]) {
127 // error is always FST_ERR_SCH_SERIALIZATION_BUILD because this is called from route/compileSchemasForSerialization
128 reply.log.error({ err, statusCode: res.statusCode }, 'The serializer for the given status code failed')
129 }
130 reply.code(500)
131 payload = serializeError(new FST_ERR_FAILED_ERROR_SERIALIZATION(err.message, error.message))
132 }
133
134 if (typeof payload !== 'string' && !Buffer.isBuffer(payload)) {
135 payload = serializeError(new FST_ERR_REP_INVALID_PAYLOAD_TYPE(typeof payload))
136 }
137
138 reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
139
140 cb(reply, payload)
141}
142
143function buildErrorHandler (parent = rootErrorHandler, func) {
144 if (!func) {
145 return parent
146 }
147
148 const errorHandler = Object.create(parent)
149 errorHandler.func = func
150 return errorHandler
151}
152
153function setErrorHeaders (error, reply) {
154 const res = reply.raw
155 let statusCode = res.statusCode
156 statusCode = (statusCode >= 400) ? statusCode : 500
157 // treat undefined and null as same
158 if (error != null) {
159 if (error.headers !== undefined) {
160 reply.headers(error.headers)
161 }
162 if (error.status >= 400) {
163 statusCode = error.status
164 } else if (error.statusCode >= 400) {
165 statusCode = error.statusCode
166 }
167 }
168 res.statusCode = statusCode
169}
170
171module.exports = {
172 buildErrorHandler,
173 handleError
174}