feat: error handle middleware (#214)
* feat: error handle middleware * feat: application error handler * feat: handle with sequelizeValidationError * fix: test * fix: test
This commit is contained in:
parent
1c6289dd88
commit
86065fa208
@ -7,14 +7,7 @@ jest.setTimeout(300000);
|
||||
// 把 console.error 转换成 error,方便断言
|
||||
(() => {
|
||||
const spy = jest.spyOn(console, 'error');
|
||||
beforeAll(() => {
|
||||
spy.mockImplementation((message) => {
|
||||
console.log(message);
|
||||
throw new Error(message);
|
||||
});
|
||||
});
|
||||
afterAll(() => {
|
||||
spy.mockRestore();
|
||||
});
|
||||
})();
|
||||
|
||||
|
41
packages/actions/src/__tests__/create-with-expection.test.ts
Normal file
41
packages/actions/src/__tests__/create-with-expection.test.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { mockServer } from '@nocobase/test';
|
||||
|
||||
describe('create with exception', () => {
|
||||
it('should handle validationError', async () => {
|
||||
const app = mockServer();
|
||||
|
||||
app.collection({
|
||||
name: 'users',
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
allowNull: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await app.loadAndInstall();
|
||||
|
||||
const response = await app
|
||||
.agent()
|
||||
.resource('users')
|
||||
.create({
|
||||
values: {
|
||||
title: 't1',
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.statusCode).toEqual(400);
|
||||
|
||||
expect(response.body).toEqual({
|
||||
errors: [
|
||||
{
|
||||
message: 'users.name cannot be null',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await app.destroy();
|
||||
});
|
||||
});
|
@ -5,7 +5,6 @@ import * as actions from './actions/users';
|
||||
import * as middlewares from './middlewares';
|
||||
|
||||
export default class UsersPlugin extends Plugin {
|
||||
|
||||
async beforeLoad() {
|
||||
this.db.on('users.afterCreateWithAssociations', async (model, options) => {
|
||||
const { transaction } = options;
|
||||
@ -85,5 +84,4 @@ export default class UsersPlugin extends Plugin {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
48
packages/server/src/__tests__/error-handle.test.ts
Normal file
48
packages/server/src/__tests__/error-handle.test.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { mockServer } from '@nocobase/test';
|
||||
|
||||
describe('error handle', () => {
|
||||
it('should handle error with default handler', async () => {
|
||||
const app = mockServer();
|
||||
|
||||
app.use(async () => {
|
||||
throw new Error('some thing went wrong');
|
||||
});
|
||||
|
||||
const response = await app.agent().post('/');
|
||||
|
||||
expect(response.statusCode).toEqual(500);
|
||||
expect(response.body.errors[0].message).toEqual('some thing went wrong');
|
||||
});
|
||||
|
||||
it('should handle error by custom handler', async () => {
|
||||
class CustomError extends Error {
|
||||
constructor(message, errors) {
|
||||
super(message);
|
||||
this.name = 'CustomError';
|
||||
}
|
||||
}
|
||||
|
||||
const app = mockServer();
|
||||
|
||||
app.errorHandler.register(
|
||||
(err) => {
|
||||
return err.name == 'CustomError';
|
||||
},
|
||||
(err, ctx) => {
|
||||
ctx.body = {
|
||||
message: 'hello',
|
||||
};
|
||||
ctx.status = 422;
|
||||
},
|
||||
);
|
||||
|
||||
app.use(async () => {
|
||||
throw new CustomError('some thing went wrong', []);
|
||||
});
|
||||
|
||||
const response = await app.agent().post('/');
|
||||
|
||||
expect(response.statusCode).toEqual(422);
|
||||
expect(response.body).toEqual({ message: 'hello' });
|
||||
});
|
||||
});
|
@ -12,6 +12,7 @@ import { createACL } from './acl';
|
||||
import { createCli, createDatabase, createI18n, createResourcer, registerMiddlewares } from './helper';
|
||||
import { Plugin } from './plugin';
|
||||
import { PluginManager, InstallOptions } from './plugin-manager';
|
||||
import { ErrorHandler } from './error-handler';
|
||||
|
||||
export interface ResourcerOptions {
|
||||
prefix?: string;
|
||||
@ -84,6 +85,8 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
||||
|
||||
public readonly acl: ACL;
|
||||
|
||||
public readonly errorHandler: ErrorHandler;
|
||||
|
||||
protected plugins = new Map<string, Plugin>();
|
||||
|
||||
public listenServer: Server;
|
||||
@ -101,6 +104,8 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
||||
app: this,
|
||||
});
|
||||
|
||||
this.errorHandler = new ErrorHandler(this);
|
||||
|
||||
registerMiddlewares(this, options);
|
||||
if (options.registerActions !== false) {
|
||||
registerActions(this);
|
||||
|
44
packages/server/src/error-handler.ts
Normal file
44
packages/server/src/error-handler.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import Application from './application';
|
||||
|
||||
export class ErrorHandler {
|
||||
handlers = [];
|
||||
|
||||
constructor(app: Application) {}
|
||||
|
||||
register(guard: (err) => boolean, render: (err, ctx) => void) {
|
||||
this.handlers.push({
|
||||
guard,
|
||||
render,
|
||||
});
|
||||
}
|
||||
|
||||
defaultHandler(err, ctx) {
|
||||
ctx.status = err.statusCode || err.status || 500;
|
||||
ctx.body = {
|
||||
errors: [
|
||||
{
|
||||
message: err.message,
|
||||
code: err.code,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
middleware() {
|
||||
const self = this;
|
||||
|
||||
return async function errorHandler(ctx, next) {
|
||||
try {
|
||||
await next();
|
||||
} catch (err) {
|
||||
for (const handler of self.handlers) {
|
||||
if (handler.guard(err)) {
|
||||
return handler.render(err, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
self.defaultHandler(err, ctx);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -9,6 +9,8 @@ import Application, { ApplicationOptions } from './application';
|
||||
import { dataWrapping } from './middlewares/data-wrapping';
|
||||
import { table2resource } from './middlewares/table2resource';
|
||||
|
||||
import ValidationError from 'sequelize';
|
||||
|
||||
export function createDatabase(options: ApplicationOptions) {
|
||||
if (options.database instanceof Database) {
|
||||
return options.database;
|
||||
@ -93,7 +95,23 @@ export function createCli(app: Application, options: ApplicationOptions): Comman
|
||||
return cli;
|
||||
}
|
||||
|
||||
function registerErrorHandler(app: Application) {
|
||||
app.errorHandler.register(
|
||||
(err) => err.name == 'SequelizeValidationError',
|
||||
(err, ctx) => {
|
||||
ctx.body = {
|
||||
errors: err.errors.map((err) => ({ message: err.message })),
|
||||
};
|
||||
|
||||
ctx.status = 400;
|
||||
},
|
||||
);
|
||||
app.use(app.errorHandler.middleware());
|
||||
}
|
||||
|
||||
export function registerMiddlewares(app: Application, options: ApplicationOptions) {
|
||||
registerErrorHandler(app);
|
||||
|
||||
if (options.bodyParser !== false) {
|
||||
app.use(
|
||||
bodyParser({
|
||||
|
Loading…
Reference in New Issue
Block a user