-
Notifications
You must be signed in to change notification settings - Fork 1
Open
Description
You could add support to typescript, rewriting withRest or adding compatibility through index.d.ts:
import type { NextApiRequest, NextApiResponse } from 'next'
import Boom from '@hapi/boom'
import Joi from 'joi'
const defaultLogError = (err: Boom.Boom) => {
// Only log internal server errors
if (!err.isServer) {
return
}
// Log original error if passed
if (err.data && err.data.originalError) {
err = err.data.originalError
}
console.error(err.stack)
}
const defaultSendError = (res: NextApiResponse, err: Boom.Boom) => {
const { output } = err
const { headers, statusCode, payload } = output
Object.entries(headers).forEach(([key, value]) =>
res.setHeader(key, value || '')
)
res.status(statusCode).json(payload)
}
/**
* Wraps a HTTP request handler with validation against Joi schemas.
*
* @param {object} schemas - An object with `query`, `body` or `headers` keys and their associated Joi schemas.
* Each of these schemas will be matched against the incoming request.
*
* @returns {Function} The HTTP handler that validates the request.
*
* @example
*
* const getSchema = {
* query: Joi.object({
* id: Joi.string().required(),
* }),
* };
*
* export default withRest({
* GET: withValidation(getSchema)(async req, res) => {
* // Do something with `req.query.id`
*
* return { foo: 'bar' };
* },
* });
*/
export const withValidation =
<T = any, R = any>(schemas: Joi.PartialSchemaMap<T> | undefined) =>
(fn: (arg0: NextApiRequest, arg1: NextApiResponse<R>) => Promise<any>) =>
async (req: NextApiRequest, res: NextApiResponse) => {
const joiSchema = Joi.object(schemas).unknown(true)
let validated: { [x: string]: any }
try {
validated = await joiSchema.validateAsync(req)
} catch (err: any) {
throw Boom.badRequest(err.message, { originalError: err as Error })
}
// Joi normalizes values, so we must copy things back to req
;['headers', 'body', 'query'].forEach((key: string) => {
;(req as any)[key] = validated[key]
})
return fn(req, res)
}
/**
* @typedef {Function} SendError
*
* @param {object} res - Node.js response object.
* @param {Error} err - The Boom error object.
*/
/**
* @typedef {Function} LogError
*
* @param {Error} err - The Boom error object.
*/
/**
* Matches handlers defined in `methods` against the HTTP method, like `GET` or `POST`.
*
* @param {object.<string, Function>} methods - An object mapping HTTP methods to their handlers.
* @param {object} options - The options.
* @param {SendError} options.sendError - A function responsible to send Boom errors back to the client.
* @param {LogError} options.logError - A function that logs errors.
*
* @returns {Function} The composed HTTP handler.
*
* @example
*
* export default withRest({
* GET: async (req, res) => {
* // Do something...
*
* return { foo: 'bar' };
* },
* });
*/
const withRest = (
methods: {
[x: string]: any
},
opts: {
logError?: typeof defaultLogError
sendError?: typeof defaultSendError
} = {
logError: defaultLogError,
sendError: defaultSendError,
}
) => {
const options = {
logError: defaultLogError,
sendError: defaultSendError,
...opts,
}
return async (req: NextApiRequest, res: NextApiResponse) => {
try {
const method = methods && methods[req.method || 'unknown']
if (!method) {
throw Boom.methodNotAllowed(
`Method ${req.method} is not supported for this endpoint`
)
}
const json = await method(req, res)
// Do nothing if the request is already sent (e.g.: a redirect was issued)
if (res.headersSent) {
if (json !== undefined) {
options.logError(
Boom.internal(
'You have sent the response inside your handler but still returned something. This error was not sent to the client, however you should probably not return a value in the handler.'
) // eslint-disable-line max-len
)
}
return
}
// Next.js doesn't support nulls as `RFC7159` dictates, but we do
if (json == null) {
res.setHeader('Content-Type', 'application/json')
res.setHeader('Content-Length', '4')
res.end('null')
} else {
res.json(json)
}
} catch (err: Error | Boom.Boom | any) {
// Not an ApiError? Then wrap it into an ApiError and log it.
let apiError = err
if (!err.isBoom) {
apiError = Boom.internal(undefined, { originalError: err })
}
options.logError(apiError)
options.sendError(res, apiError)
}
}
}
export default withRest
DavideCarvalho
Metadata
Metadata
Assignees
Labels
No labels