mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 07:45:18 +00:00
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,方便断言
|
// 把 console.error 转换成 error,方便断言
|
||||||
(() => {
|
(() => {
|
||||||
const spy = jest.spyOn(console, 'error');
|
const spy = jest.spyOn(console, 'error');
|
||||||
beforeAll(() => {
|
|
||||||
spy.mockImplementation((message) => {
|
|
||||||
console.log(message);
|
|
||||||
throw new Error(message);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
spy.mockRestore();
|
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';
|
import * as middlewares from './middlewares';
|
||||||
|
|
||||||
export default class UsersPlugin extends Plugin {
|
export default class UsersPlugin extends Plugin {
|
||||||
|
|
||||||
async beforeLoad() {
|
async beforeLoad() {
|
||||||
this.db.on('users.afterCreateWithAssociations', async (model, options) => {
|
this.db.on('users.afterCreateWithAssociations', async (model, options) => {
|
||||||
const { transaction } = 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 { createCli, createDatabase, createI18n, createResourcer, registerMiddlewares } from './helper';
|
||||||
import { Plugin } from './plugin';
|
import { Plugin } from './plugin';
|
||||||
import { PluginManager, InstallOptions } from './plugin-manager';
|
import { PluginManager, InstallOptions } from './plugin-manager';
|
||||||
|
import { ErrorHandler } from './error-handler';
|
||||||
|
|
||||||
export interface ResourcerOptions {
|
export interface ResourcerOptions {
|
||||||
prefix?: string;
|
prefix?: string;
|
||||||
@ -84,6 +85,8 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
|
|
||||||
public readonly acl: ACL;
|
public readonly acl: ACL;
|
||||||
|
|
||||||
|
public readonly errorHandler: ErrorHandler;
|
||||||
|
|
||||||
protected plugins = new Map<string, Plugin>();
|
protected plugins = new Map<string, Plugin>();
|
||||||
|
|
||||||
public listenServer: Server;
|
public listenServer: Server;
|
||||||
@ -101,6 +104,8 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
app: this,
|
app: this,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.errorHandler = new ErrorHandler(this);
|
||||||
|
|
||||||
registerMiddlewares(this, options);
|
registerMiddlewares(this, options);
|
||||||
if (options.registerActions !== false) {
|
if (options.registerActions !== false) {
|
||||||
registerActions(this);
|
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 { dataWrapping } from './middlewares/data-wrapping';
|
||||||
import { table2resource } from './middlewares/table2resource';
|
import { table2resource } from './middlewares/table2resource';
|
||||||
|
|
||||||
|
import ValidationError from 'sequelize';
|
||||||
|
|
||||||
export function createDatabase(options: ApplicationOptions) {
|
export function createDatabase(options: ApplicationOptions) {
|
||||||
if (options.database instanceof Database) {
|
if (options.database instanceof Database) {
|
||||||
return options.database;
|
return options.database;
|
||||||
@ -93,7 +95,23 @@ export function createCli(app: Application, options: ApplicationOptions): Comman
|
|||||||
return cli;
|
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) {
|
export function registerMiddlewares(app: Application, options: ApplicationOptions) {
|
||||||
|
registerErrorHandler(app);
|
||||||
|
|
||||||
if (options.bodyParser !== false) {
|
if (options.bodyParser !== false) {
|
||||||
app.use(
|
app.use(
|
||||||
bodyParser({
|
bodyParser({
|
||||||
|
Loading…
Reference in New Issue
Block a user