feat!: improve afterError hook to accept array of functions, change to object args (#8389)
Changes the `afterError` hook structure, adds tests / more docs.
Ensures that the `req.responseHeaders` property is respected in the
error handler.
**Breaking**
`afterError` now accepts an array of functions instead of a single
function:
```diff
- afterError: () => {...}
+ afterError: [() => {...}]
```
The args are changed to accept an object with the following properties:
| Argument | Description |
| ------------------- |
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
| **`error`** | The error that occurred. |
| **`context`** | Custom context passed between Hooks. [More
details](./context). |
| **`graphqlResult`** | The GraphQL result object, available if the hook
is executed within a GraphQL context. |
| **`req`** | The
[Request](https://developer.mozilla.org/en-US/docs/Web/API/Request)
object containing the currently authenticated `user` |
| **`collection`** | The [Collection](../configuration/collections) in
which this Hook is running against. This will be `undefined` if the hook
is executed from a non-collection endpoint or GraphQL. |
| **`result`** | The formatted error result object, available if the
hook is executed from a REST context. |
This commit is contained in:
@@ -186,7 +186,6 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
} | null);
|
||||
editedAt?: string | null;
|
||||
globalSlug?: string | null;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
|
||||
@@ -2,7 +2,7 @@ import { fileURLToPath } from 'node:url'
|
||||
import path from 'path'
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import { APIError, type CollectionConfig } from 'payload'
|
||||
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||
import { devUser } from '../credentials.js'
|
||||
@@ -288,6 +288,14 @@ export default buildConfigWithDefaults({
|
||||
method: 'get',
|
||||
path: '/internal-error-here',
|
||||
},
|
||||
{
|
||||
handler: () => {
|
||||
// Throwing an internal error with potentially sensitive data
|
||||
throw new APIError('Connected to the Pentagon. Secret data: ******')
|
||||
},
|
||||
method: 'get',
|
||||
path: '/api-error-here',
|
||||
},
|
||||
],
|
||||
onInit: async (payload) => {
|
||||
await payload.create({
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { Payload, SanitizedCollectionConfig } from 'payload'
|
||||
|
||||
import { randomBytes, randomUUID } from 'crypto'
|
||||
import path from 'path'
|
||||
import { NotFound, type Payload } from 'payload'
|
||||
import { APIError, NotFound } from 'payload'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
|
||||
@@ -1533,6 +1535,75 @@ describe('collections-rest', () => {
|
||||
expect(Array.isArray(result.errors)).toEqual(true)
|
||||
expect(result.errors[0].message).toStrictEqual('Something went wrong.')
|
||||
})
|
||||
|
||||
it('should execute afterError hook on root level and modify result/status', async () => {
|
||||
let err: unknown
|
||||
let errResult: any
|
||||
|
||||
payload.config.hooks.afterError = [
|
||||
({ error, result }) => {
|
||||
err = error
|
||||
errResult = result
|
||||
|
||||
return { status: 400, response: { modified: true } }
|
||||
},
|
||||
]
|
||||
|
||||
const response = await restClient.GET(`/api-error-here`)
|
||||
expect(response.status).toBe(400)
|
||||
|
||||
expect(err).toBeInstanceOf(APIError)
|
||||
expect(errResult).toStrictEqual({
|
||||
errors: [
|
||||
{
|
||||
message: 'Something went wrong.',
|
||||
},
|
||||
],
|
||||
})
|
||||
const result = await response.json()
|
||||
|
||||
expect(result.modified).toBe(true)
|
||||
|
||||
payload.config.hooks.afterError = []
|
||||
})
|
||||
|
||||
it('should execute afterError hook on collection level and modify result', async () => {
|
||||
let err: unknown
|
||||
let errResult: any
|
||||
let collection: SanitizedCollectionConfig
|
||||
|
||||
payload.collections.posts.config.hooks.afterError = [
|
||||
({ error, result, collection: incomingCollection }) => {
|
||||
err = error
|
||||
errResult = result
|
||||
collection = incomingCollection
|
||||
|
||||
return { response: { modified: true } }
|
||||
},
|
||||
]
|
||||
|
||||
const post = await createPost({})
|
||||
|
||||
const response = await restClient.GET(
|
||||
`/${slug}/${typeof post.id === 'number' ? 1000 : randomUUID()}`,
|
||||
)
|
||||
expect(response.status).toBe(404)
|
||||
|
||||
expect(collection.slug).toBe(slug)
|
||||
expect(err).toBeInstanceOf(NotFound)
|
||||
expect(errResult).toStrictEqual({
|
||||
errors: [
|
||||
{
|
||||
message: 'Not Found',
|
||||
},
|
||||
],
|
||||
})
|
||||
const result = await response.json()
|
||||
|
||||
expect(result.modified).toBe(true)
|
||||
|
||||
payload.collections.posts.config.hooks.afterError = []
|
||||
})
|
||||
})
|
||||
|
||||
describe('Local', () => {
|
||||
|
||||
@@ -45,14 +45,14 @@ export const HooksConfig: Promise<SanitizedConfig> = buildConfigWithDefaults({
|
||||
},
|
||||
],
|
||||
hooks: {
|
||||
afterError: () => console.log('Running afterError hook'),
|
||||
afterError: [() => console.log('Running afterError hook')],
|
||||
},
|
||||
onInit: async (payload) => {
|
||||
await seedHooksUsers(payload)
|
||||
await payload.create({
|
||||
collection: hooksSlug,
|
||||
data: {
|
||||
check: 'update',
|
||||
check: true,
|
||||
fieldBeforeValidate: false,
|
||||
collectionBeforeValidate: false,
|
||||
fieldBeforeChange: false,
|
||||
|
||||
Reference in New Issue
Block a user