strong-tie/inbound-calls
0
1'use strict'2 3const statusCodes = require('node:http').STATUS_CODES4const wrapThenable = require('./wrapThenable')5const {6 kReplyHeaders,7 kReplyNextErrorHandler,8 kReplyIsRunningOnErrorHook,9 kReplyHasStatusCode,10 kRouteContext,11 kDisableRequestLogging12} = require('./symbols.js')13 14const {15 FST_ERR_REP_INVALID_PAYLOAD_TYPE,16 FST_ERR_FAILED_ERROR_SERIALIZATION17} = 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] = false32 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.message43 )44 }45 reply.raw.writeHead(reply.raw.statusCode)46 }47 reply.raw.end(payload)48 })49 return50 }51 const errorHandler = reply[kReplyNextErrorHandler] || context.errorHandler52 53 // In case the error handler throws, we set the next errorHandler so we can error again54 reply[kReplyNextErrorHandler] = Object.getPrototypeOf(errorHandler)55 56 // we need to remove content-type to allow content-type guessing for serialization57 delete reply[kReplyHeaders]['content-type']58 delete reply[kReplyHeaders]['content-length']59 60 const func = errorHandler.func61 62 if (!func) {63 reply[kReplyNextErrorHandler] = false64 fallbackErrorHandler(error, reply, cb)65 return66 }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.status86 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.message93 )94 }95 } else {96 if (!reply.log[kDisableRequestLogging]) {97 reply.log.error(98 { req: request, res: reply, err: error },99 error && error.message100 )101 }102 }103 reply.send(error)104}105 106function fallbackErrorHandler (error, reply, cb) {107 const res = reply.raw108 const statusCode = reply.statusCode109 reply[kReplyHeaders]['content-type'] = reply[kReplyHeaders]['content-type'] ?? 'application/json; charset=utf-8'110 let payload111 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 statusCode119 })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/compileSchemasForSerialization128 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 parent146 }147 148 const errorHandler = Object.create(parent)149 errorHandler.func = func150 return errorHandler151}152 153function setErrorHeaders (error, reply) {154 const res = reply.raw155 let statusCode = res.statusCode156 statusCode = (statusCode >= 400) ? statusCode : 500157 // treat undefined and null as same158 if (error != null) {159 if (error.headers !== undefined) {160 reply.headers(error.headers)161 }162 if (error.status >= 400) {163 statusCode = error.status164 } else if (error.statusCode >= 400) {165 statusCode = error.statusCode166 }167 }168 res.statusCode = statusCode169}170 171module.exports = {172 buildErrorHandler,173 handleError174}175 