fix(next): removes reliance on instanceof from api error formatting (#5482)

This commit is contained in:
Jacob Fletcher
2024-03-27 09:06:47 -04:00
committed by GitHub
parent dd37519185
commit a8082c551b
6 changed files with 98 additions and 36 deletions

2
.vscode/launch.json vendored
View File

@@ -10,7 +10,7 @@
"cwd": "${workspaceFolder}" "cwd": "${workspaceFolder}"
}, },
{ {
"command": "pnpm run dev _community -- --no-turbo", "command": "node --no-deprecation test/dev.js fields",
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
"name": "Run Dev Community", "name": "Run Dev Community",
"request": "launch", "request": "launch",

View File

@@ -26,6 +26,7 @@ export const updateOne: UpdateOne = async function updateOne(
}) })
let result let result
try { try {
result = await Model.findOneAndUpdate(query, data, options) result = await Model.findOneAndUpdate(query, data, options)
} catch (error) { } catch (error) {

View File

@@ -6,7 +6,7 @@ import path from 'path'
import { APIError } from 'payload/errors' import { APIError } from 'payload/errors'
import { streamFile } from '../../../next-stream-file/index.js' import { streamFile } from '../../../next-stream-file/index.js'
import { RouteError } from '../RouteError.js' import { routeError } from '../routeError.js'
import { checkFileAccess } from './checkFileAccess.js' import { checkFileAccess } from './checkFileAccess.js'
// /:collectionSlug/file/:filename // /:collectionSlug/file/:filename
@@ -64,7 +64,7 @@ export const getFile = async ({ collection, filename, req }: Args): Promise<Resp
status: httpStatus.OK, status: httpStatus.OK,
}) })
} catch (error) { } catch (error) {
return RouteError({ return routeError({
collection, collection,
err: error, err: error,
req, req,

View File

@@ -12,7 +12,7 @@ import type {
} from './types.js' } from './types.js'
import { createPayloadRequest } from '../../utilities/createPayloadRequest.js' import { createPayloadRequest } from '../../utilities/createPayloadRequest.js'
import { RouteError } from './RouteError.js' import { routeError } from './routeError.js'
import { access } from './auth/access.js' import { access } from './auth/access.js'
import { forgotPassword } from './auth/forgotPassword.js' import { forgotPassword } from './auth/forgotPassword.js'
import { init } from './auth/init.js' import { init } from './auth/init.js'
@@ -281,7 +281,7 @@ export const GET =
return RouteNotFoundResponse(slug) return RouteNotFoundResponse(slug)
} catch (error) { } catch (error) {
return RouteError({ return routeError({
collection, collection,
err: error, err: error,
req, req,
@@ -423,7 +423,7 @@ export const POST =
return RouteNotFoundResponse(slug) return RouteNotFoundResponse(slug)
} catch (error) { } catch (error) {
return RouteError({ return routeError({
collection, collection,
err: error, err: error,
req, req,
@@ -492,7 +492,7 @@ export const DELETE =
return RouteNotFoundResponse(slug) return RouteNotFoundResponse(slug)
} catch (error) { } catch (error) {
return RouteError({ return routeError({
collection, collection,
err: error, err: error,
req, req,
@@ -561,7 +561,7 @@ export const PATCH =
return RouteNotFoundResponse(slug) return RouteNotFoundResponse(slug)
} catch (error) { } catch (error) {
return RouteError({ return routeError({
collection, collection,
err: error, err: error,
req, req,

View File

@@ -1,13 +1,21 @@
import type { Collection, PayloadRequest } from 'payload/types' import type { Collection, PayloadRequest } from 'payload/types'
import httpStatus from 'http-status' import httpStatus from 'http-status'
import { APIError } from 'payload/errors' import { APIError, ValidationError } from 'payload/errors'
export type ErrorResponse = { data?: any; errors: unknown[]; stack?: string } export type ErrorResponse = { data?: any; errors: unknown[]; stack?: string }
const formatErrors = (incoming: { [key: string]: unknown } | APIError | Error): ErrorResponse => { const formatErrors = (incoming: { [key: string]: unknown } | APIError): ErrorResponse => {
if (incoming) { if (incoming) {
if (incoming instanceof APIError && incoming.data) { // Cannot use `instanceof` to check error type: https://github.com/microsoft/TypeScript/issues/13965
// Instead, get the prototype of the incoming error and check its constructor name
const proto = Object.getPrototypeOf(incoming)
// Payload 'ValidationError' and 'APIError'
if (
(proto.constructor.name === 'ValidationError' || proto.constructor.name === 'APIError') &&
incoming.data
) {
return { return {
errors: [ errors: [
{ {
@@ -19,8 +27,8 @@ const formatErrors = (incoming: { [key: string]: unknown } | APIError | Error):
} }
} }
// mongoose // Mongoose 'ValidationError': https://mongoosejs.com/docs/api/error.html#Error.ValidationError
if (!(incoming instanceof APIError || incoming instanceof Error) && incoming.errors) { if (proto.constructor.name === 'ValidationError' && 'errors' in incoming && incoming.errors) {
return { return {
errors: Object.keys(incoming.errors).reduce((acc, key) => { errors: Object.keys(incoming.errors).reduce((acc, key) => {
acc.push({ acc.push({
@@ -58,7 +66,7 @@ const formatErrors = (incoming: { [key: string]: unknown } | APIError | Error):
} }
} }
export const RouteError = async ({ export const routeError = async ({
collection, collection,
err, err,
req, req,
@@ -78,7 +86,9 @@ export const RouteError = async ({
} }
const { config, logger } = req.payload const { config, logger } = req.payload
let response = formatErrors(err) let response = formatErrors(err)
let status = err.status || httpStatus.INTERNAL_SERVER_ERROR let status = err.status || httpStatus.INTERNAL_SERVER_ERROR
logger.error(err.stack) logger.error(err.stack)

View File

@@ -11,7 +11,11 @@
"esModuleInterop": true, "esModuleInterop": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"jsx": "preserve", "jsx": "preserve",
"lib": ["dom", "dom.iterable", "esnext"], "lib": [
"dom",
"dom.iterable",
"esnext"
],
"noEmit": true, "noEmit": true,
"outDir": "./dist", "outDir": "./dist",
"resolveJsonModule": true, "resolveJsonModule": true,
@@ -19,7 +23,11 @@
"skipLibCheck": true, "skipLibCheck": true,
"sourceMap": true, "sourceMap": true,
"strict": false, "strict": false,
"types": ["jest", "node", "@types/jest"], "types": [
"jest",
"node",
"@types/jest"
],
"incremental": true, "incremental": true,
"isolatedModules": true, "isolatedModules": true,
"plugins": [ "plugins": [
@@ -28,26 +36,65 @@
} }
], ],
"paths": { "paths": {
"@payload-config": ["./test/admin/config.ts"], "@payload-config": [
"@payloadcms/live-preview": ["./packages/live-preview/src"], "./test/fields/config.ts"
"@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"], ],
"@payloadcms/ui/assets": ["./packages/ui/src/assets/index.ts"], "@payloadcms/live-preview": [
"@payloadcms/ui/elements/*": ["./packages/ui/src/elements/*/index.tsx"], "./packages/live-preview/src"
"@payloadcms/ui/fields/*": ["./packages/ui/src/fields/*/index.tsx"], ],
"@payloadcms/ui/forms/*": ["./packages/ui/src/forms/*/index.tsx"], "@payloadcms/live-preview-react": [
"@payloadcms/ui/graphics/*": ["./packages/ui/src/graphics/*/index.tsx"], "./packages/live-preview-react/src/index.ts"
"@payloadcms/ui/hooks/*": ["./packages/ui/src/hooks/*.ts"], ],
"@payloadcms/ui/icons/*": ["./packages/ui/src/icons/*/index.tsx"], "@payloadcms/ui/assets": [
"@payloadcms/ui/providers/*": ["./packages/ui/src/providers/*/index.tsx"], "./packages/ui/src/assets/index.ts"
"@payloadcms/ui/templates/*": ["./packages/ui/src/templates/*/index.tsx"], ],
"@payloadcms/ui/utilities/*": ["./packages/ui/src/utilities/*.ts"], "@payloadcms/ui/elements/*": [
"@payloadcms/ui/scss": ["./packages/ui/src/scss.scss"], "./packages/ui/src/elements/*/index.tsx"
"@payloadcms/ui/scss/app.scss": ["./packages/ui/src/scss/app.scss"], ],
"@payloadcms/next/*": ["./packages/next/src/*"], "@payloadcms/ui/fields/*": [
"@payloadcms/next": ["./packages/next/src/exports/*"] "./packages/ui/src/fields/*/index.tsx"
],
"@payloadcms/ui/forms/*": [
"./packages/ui/src/forms/*/index.tsx"
],
"@payloadcms/ui/graphics/*": [
"./packages/ui/src/graphics/*/index.tsx"
],
"@payloadcms/ui/hooks/*": [
"./packages/ui/src/hooks/*.ts"
],
"@payloadcms/ui/icons/*": [
"./packages/ui/src/icons/*/index.tsx"
],
"@payloadcms/ui/providers/*": [
"./packages/ui/src/providers/*/index.tsx"
],
"@payloadcms/ui/templates/*": [
"./packages/ui/src/templates/*/index.tsx"
],
"@payloadcms/ui/utilities/*": [
"./packages/ui/src/utilities/*.ts"
],
"@payloadcms/ui/scss": [
"./packages/ui/src/scss.scss"
],
"@payloadcms/ui/scss/app.scss": [
"./packages/ui/src/scss/app.scss"
],
"@payloadcms/next/*": [
"./packages/next/src/*"
],
"@payloadcms/next": [
"./packages/next/src/exports/*"
]
} }
}, },
"exclude": ["dist", "build", "temp", "node_modules"], "exclude": [
"dist",
"build",
"temp",
"node_modules"
],
"composite": true, "composite": true,
"references": [ "references": [
{ {
@@ -108,5 +155,9 @@
"path": "./packages/ui" "path": "./packages/ui"
} }
], ],
"include": ["next-env.d.ts", ".next/types/**/*.ts", "scripts/**/*.ts"] "include": [
"next-env.d.ts",
".next/types/**/*.ts",
"scripts/**/*.ts"
]
} }