Commit 7c5cd4aa authored by nanahira's avatar nanahira

add exception handler

parent c65b9be3
import { Test, TestingModule } from '@nestjs/testing';
import { KoishiExceptionHandlerService } from './koishi-exception-handler.service';
describe('KoishiExceptionHandlerService', () => {
let service: KoishiExceptionHandlerService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [KoishiExceptionHandlerService],
}).compile();
service = module.get<KoishiExceptionHandlerService>(KoishiExceptionHandlerService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
import {
ConsoleLogger,
HttpException,
Inject,
Injectable,
} from '@nestjs/common';
import { WsException } from '@nestjs/websockets';
import { KOISHI_MODULE_OPTIONS } from '../utility/koishi.constants';
import { KoishiModuleOptions } from '../utility/koishi.interfaces';
@Injectable()
export class KoishiExceptionHandlerService extends ConsoleLogger {
constructor(
@Inject(KOISHI_MODULE_OPTIONS)
private readonly koishiModuleOptions: KoishiModuleOptions,
) {
super('KoishiExceptionHandler');
}
handleActionException(e: Error) {
if (e instanceof HttpException || e instanceof WsException) {
return e.message;
} else {
this.error(e.message, e.stack);
return (
this.koishiModuleOptions.actionErrorMessage || 'Internal Server Error'
);
}
}
}
......@@ -2,10 +2,14 @@ import { Injectable } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { KoishiCommandInterceptorRegistration } from '../utility/koishi.interfaces';
import { Command } from 'koishi';
import { KoishiExceptionHandlerService } from '../koishi-exception-handler/koishi-exception-handler.service';
@Injectable()
export class KoishiInterceptorManagerService {
constructor(private readonly moduleRef: ModuleRef) {}
constructor(
private readonly moduleRef: ModuleRef,
private readonly exceptionHandler: KoishiExceptionHandlerService,
) {}
getInterceptor(interceptorDef: KoishiCommandInterceptorRegistration) {
if (typeof interceptorDef !== 'object') {
return this.moduleRef.get(interceptorDef, { strict: false });
......@@ -18,7 +22,13 @@ export class KoishiInterceptorManagerService {
interceptorDef: KoishiCommandInterceptorRegistration,
) {
const interceptor = this.getInterceptor(interceptorDef);
command.before((...params) => interceptor.intercept(...params));
command.before(async (...params) => {
try {
return await interceptor.intercept(...params);
} catch (e) {
return this.exceptionHandler.handleActionException(e);
}
});
}
addInterceptors(
......
......@@ -28,6 +28,7 @@ import { KoishiHttpDiscoveryService } from './koishi-http-discovery/koishi-http-
import { KoishiWebsocketGateway } from './providers/koishi-websocket.gateway';
import { KoishiMetadataFetcherService } from './koishi-metadata-fetcher/koishi-metadata-fetcher.service';
import { KoishiInterceptorManagerService } from './koishi-interceptor-manager/koishi-interceptor-manager.service';
import { KoishiExceptionHandlerService } from './koishi-exception-handler/koishi-exception-handler.service';
const koishiContextProvider: Provider<Context> = {
provide: KOISHI_CONTEXT,
......@@ -49,6 +50,7 @@ const koishiContextProvider: Provider<Context> = {
KoishiHttpDiscoveryService,
KoishiMetadataFetcherService,
KoishiInterceptorManagerService,
KoishiExceptionHandlerService,
],
exports: [KoishiService, koishiContextProvider],
})
......
......@@ -18,6 +18,8 @@ import { Module } from '@nestjs/core/injector/module';
import { KoishiMetadataFetcherService } from '../koishi-metadata-fetcher/koishi-metadata-fetcher.service';
import { KoishiInterceptorManagerService } from '../koishi-interceptor-manager/koishi-interceptor-manager.service';
import { Registrar } from 'koishi-decorators';
import { handleActionException } from '../utility/wrap-action';
import { KoishiExceptionHandlerService } from '../koishi-exception-handler/koishi-exception-handler.service';
@Injectable()
export class KoishiMetascanService {
......@@ -30,6 +32,7 @@ export class KoishiMetascanService {
@Inject(KOISHI_MODULE_OPTIONS)
private readonly koishiModuleOptions: KoishiModuleOptions,
private readonly intercepterManager: KoishiInterceptorManagerService,
private readonly exceptionHandler: KoishiExceptionHandlerService,
) {}
addInterceptors(
......@@ -57,6 +60,13 @@ export class KoishiMetascanService {
),
);
this.addInterceptors(command, interceptorDefs);
command.action(async (argv) => {
try {
return await argv.next();
} catch (e) {
return this.exceptionHandler.handleActionException(e);
}
}, true);
}
}
......
......@@ -20,6 +20,7 @@ export interface KoishiModuleOptions
loggerColor?: number;
moduleSelection?: KoishiModuleSelection[];
globalInterceptors?: KoishiCommandInterceptorRegistration[];
actionErrorMessage?: string;
}
export interface KoishiModuleOptionsFactory {
......
import { HttpException } from '@nestjs/common';
import { WsException } from '@nestjs/websockets';
export function handleActionException(e: Error) {
if (e instanceof HttpException || e instanceof WsException) {
return e.message;
} else {
throw e;
}
}
import { Test } from '@nestjs/testing';
import { KoishiModule } from '../src/koishi.module';
import { KoishiService } from '../src/koishi.service';
import { INestApplication, Injectable } from '@nestjs/common';
import {
INestApplication,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { KoishiWsAdapter } from '../src/koishi.ws-adapter';
import http from 'http';
import request from 'supertest';
......@@ -23,6 +27,16 @@ class KoishiTestService {
async onEcho(@PutOption('content', '-c <content:string>') content: string) {
return `bot: ${content}`;
}
@UseCommand('boo')
async onBoo(@PutOption('content', '-c <content:string>') content: string) {
throw new NotFoundException(`boo: ${content}`);
}
@UseCommand('bow')
async onBow() {
throw new Error('bow!');
}
}
describe('HttpServer', () => {
......@@ -94,4 +108,18 @@ describe('HttpServer', () => {
expect(methodCtx.filter(wrongSession1)).toBe(false);
expect(methodCtx.filter(wrongSession2)).toBe(false);
});
it('should handle command error', () => {
const command = koishiApp.command('boo');
expect(command.execute({ options: { content: 'bow!' } })).resolves.toBe(
'boo: bow!',
);
});
it('should handle unknown error', () => {
const command = koishiApp.command('bow');
expect(command.execute({ options: { content: 'bow!' } })).resolves.toBe(
'Internal Server Error',
);
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment