Skip to content

Commit

Permalink
feat: json-rpc 예외 핸들러 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
HyungJu committed May 6, 2024
1 parent e38ce4f commit bf681b1
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 29 deletions.
2 changes: 2 additions & 0 deletions apps/server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { VaccinationModule } from './vaccination/vaccination.module';
import { NipModule } from './nip/nip.module';
import { PasswordResetModule } from './password-reset/password-reset.module';
import { JsonRpcModule } from './json-rpc/json-rpc.module';
import { DomainExceptionFilter } from './exception-filter';

@Module({
imports: [
Expand All @@ -22,6 +23,7 @@ import { JsonRpcModule } from './json-rpc/json-rpc.module';
PasswordResetModule,
JsonRpcModule.forRoot(),
],
providers: [DomainExceptionFilter],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
Expand Down
24 changes: 11 additions & 13 deletions apps/server/src/exception-filter.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';
import { DomainException } from './exception/domain-exception';
import { Request, Response } from 'express';
import { ErrorResponse } from './common/error-response';
import { JsonRpcExceptionFilter } from './json-rpc/json-rpc.decorator';
import { ExceptionFilter } from './json-rpc/exceptions/exception-filter';

@Catch(DomainException)
export class DomainExceptionFilter implements ExceptionFilter {
catch(exception: DomainException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();

response
.status(400)
.json(ErrorResponse.of(exception.errorData, exception.errorData.message));
@JsonRpcExceptionFilter(DomainException)
export class DomainExceptionFilter implements ExceptionFilter<DomainException> {
catch(exception: DomainException, callback: any) {
callback({
code: -100,
message: exception.errorData.message,
errorData: exception.errorData,
data: exception.data,
});
}
}
15 changes: 15 additions & 0 deletions apps/server/src/json-rpc/default-exception-filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { JsonRpcExceptionFilter } from './json-rpc.decorator';
import { JsonRpcError } from './error';
import { ExceptionFilter } from './exceptions/exception-filter';
import { JSONRPCCallbackType } from 'jayson';

@JsonRpcExceptionFilter(Error)
export class JsonRpcDefaultExceptionFilter implements ExceptionFilter<Error> {
catch(exception: Error, callback: JSONRPCCallbackType): void {
if (exception instanceof JsonRpcError) {
callback(exception);
} else {
throw exception;
}
}
}
8 changes: 8 additions & 0 deletions apps/server/src/json-rpc/exceptions/exception-filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as jayson from 'jayson';

export interface ExceptionFilter<T extends Error> {
catch(
exception: T,
callback: jayson.JSONRPCCallbackType
): Promise<void> | void;
}
33 changes: 33 additions & 0 deletions apps/server/src/json-rpc/exceptions/exception-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Injectable } from '@nestjs/common';
import { ExceptionFilter } from './exception-filter';
import { JSONRPCCallbackType } from 'jayson';

export type JsonRpcExceptionHandlerMap<T extends Error> = {
[exceptionType: string]: ExceptionFilter<T>;
};

@Injectable()
export class JsonRpcExceptionHandler<T extends Error> {
private exceptionFilters: JsonRpcExceptionHandlerMap<any> = {};

constructor() {}

registerFilter(exception: string, filter: ExceptionFilter<T>): void {
this.exceptionFilters[exception] = filter;
}

handle(exception: T, callback: JSONRPCCallbackType): void {
const exceptionType = Object.getPrototypeOf(exception).constructor.name;
const filter = this.exceptionFilters[exceptionType];

if (!filter) {
throw exception;
}

try {
filter.catch(exception, callback);
} catch (e) {
callback(e, e);
}
}
}
10 changes: 9 additions & 1 deletion apps/server/src/json-rpc/json-rpc.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { SetMetadata } from '@nestjs/common';
import { applyDecorators, Injectable, SetMetadata } from '@nestjs/common';

export const JSON_RPC_CONTROLLER = Symbol('JSON_RPC_CONTROLLER');
export const JSON_RPC_METHOD = Symbol('JSON_RPC_METHOD');
export const JSON_RPC_PARAMS = Symbol('JSON_RPC_PARAMS');
export const JSON_RPC_EXCEPTION_FILTER = Symbol('JSON_RPC_EXCEPTION_FILTER');

export function JsonRpcController(controllerName: string): ClassDecorator {
return SetMetadata(JSON_RPC_CONTROLLER, controllerName);
Expand All @@ -12,6 +13,13 @@ export function JsonRpcMethod(methodName: string) {
return SetMetadata(JSON_RPC_METHOD, methodName);
}

export function JsonRpcExceptionFilter<T>(exception: T): ClassDecorator {
return applyDecorators(
SetMetadata(JSON_RPC_EXCEPTION_FILTER, exception),
Injectable
);
}

export type RequestContext = {
req: Request;
body: any;
Expand Down
16 changes: 14 additions & 2 deletions apps/server/src/json-rpc/json-rpc.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,29 @@ import {
import { DiscoveryModule } from '@nestjs/core';
import { JsonRpcService } from './json-rpc.service';
import { LoggerMiddleware } from './middleware';
import { JsonRpcDefaultExceptionFilter } from './default-exception-filter';
import { JsonRpcExceptionHandler } from './exceptions/exception-handler';

@Module({
imports: [DiscoveryModule],
providers: [JsonRpcService, Logger],
providers: [
JsonRpcService,
Logger,
JsonRpcDefaultExceptionFilter,
JsonRpcExceptionHandler,
],
})
export class JsonRpcModule implements NestModule {
static forRoot(): DynamicModule {
return {
global: true,
module: JsonRpcModule,
providers: [JsonRpcService, Logger],
providers: [
JsonRpcService,
Logger,
JsonRpcDefaultExceptionFilter,
JsonRpcExceptionHandler,
],
};
}

Expand Down
52 changes: 41 additions & 11 deletions apps/server/src/json-rpc/json-rpc.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ import { DiscoveryService, MetadataScanner, Reflector } from '@nestjs/core';
import * as jayson from 'jayson';
import {
JSON_RPC_CONTROLLER,
JSON_RPC_EXCEPTION_FILTER,
JSON_RPC_METHOD,
JSON_RPC_PARAMS,
JsonRpcParam,
JsonRpcParamsMetadata,
RequestContext,
} from './json-rpc.decorator';
import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';
import { ExceptionFilter } from './exceptions/exception-filter';
import { JsonRpcExceptionHandler } from './exceptions/exception-handler';
import { JsonRpcError } from './error';
import { DomainException } from '../exception/domain-exception';

@Injectable()
export class JsonRpcService implements OnModuleInit {
Expand All @@ -20,6 +22,7 @@ export class JsonRpcService implements OnModuleInit {
constructor(
private metadataScanner: MetadataScanner,
private discoveryService: DiscoveryService,
private exceptionHandler: JsonRpcExceptionHandler<any>,
private reflector: Reflector,
private logger: Logger
) {}
Expand Down Expand Up @@ -60,7 +63,11 @@ export class JsonRpcService implements OnModuleInit {

this.server.method(
handlerName,
async (params: any | any[], context: any, callback: any) => {
async (
params: any,
context: RequestContext,
callback: jayson.JSONRPCCallbackType
) => {
try {
const result = await methodRef.apply(
instance,
Expand All @@ -69,23 +76,45 @@ export class JsonRpcService implements OnModuleInit {
body: params,
})
);

callback(null, result);
} catch (error) {
if (error instanceof JsonRpcError) {
return callback(error);
}
if (error instanceof DomainException) {
return callback(new JsonRpcError(-32001, error.message, error));
try {
this.exceptionHandler.handle(error, callback);
} catch (e) {
callback(new JsonRpcError(-32603, e.message, e.data));
}

console.log(error);

return callback(new JsonRpcError(-32000, 'Internal server error'));
}
}
);
}

registerExceptionFilters() {
this.discoveryService
.getProviders()
.filter((wrapper) => wrapper.isDependencyTreeStatic())
.filter((wrapper) => wrapper.instance)
.forEach((provider: InstanceWrapper) => {
const { instance } = provider;

const exception: Error | undefined = Reflect.getOwnMetadata(
JSON_RPC_EXCEPTION_FILTER,
instance.constructor
);

if (!exception) {
return;
}

const exceptionFilter = instance as ExceptionFilter<any>;
this.exceptionHandler.registerFilter(exception.name, exceptionFilter);
this.logger.log(
`Registered exception filter ${exception.name}`,
JsonRpcService.name
);
});
}

registerMethods() {
this.discoveryService
.getProviders()
Expand Down Expand Up @@ -126,5 +155,6 @@ export class JsonRpcService implements OnModuleInit {

onModuleInit(): any {
this.registerMethods();
this.registerExceptionFilters();
}
}
2 changes: 0 additions & 2 deletions apps/server/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { DomainExceptionFilter } from './exception-filter';

async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new DomainExceptionFilter());
await app.listen(3000);
}

Expand Down

0 comments on commit bf681b1

Please sign in to comment.