Skip to content

Commit

Permalink
Backend | InternalCommandSender exception handling
Browse files Browse the repository at this point in the history
  • Loading branch information
MateuszNaKodach authored May 15, 2020
1 parent 30b277d commit def8863
Show file tree
Hide file tree
Showing 19 changed files with 124 additions and 14 deletions.
15 changes: 15 additions & 0 deletions backend/libs/typescript-sdk/src/exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export class Exception extends Error {
constructor(message: string, private readonly causation: Error | undefined = undefined) {
super(message);
this.stack = causation ? Exception.errorCausedBy(this, causation).stack : this.stack;
}

causedBy(causation: Error): Exception {
return new Exception(Exception.errorCausedBy(this, causation).message, causation);
}

private static errorCausedBy(error: Error, causation: Error): Error {
error.stack += '\nCaused by: \n' + causation.message + '\n' + causation.stack;
return error;
}
}
1 change: 1 addition & 0 deletions backend/libs/typescript-sdk/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './exception';
9 changes: 9 additions & 0 deletions backend/libs/typescript-sdk/tsconfig.lib.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"declaration": true,
"outDir": "../../dist/libs/typescript-sdk"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
}
9 changes: 9 additions & 0 deletions backend/nest-cli.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@
"compilerOptions": {
"tsConfigPath": "libs/eventstore-projections/tsconfig.lib.json"
}
},
"typescript-sdk": {
"type": "library",
"root": "libs/typescript-sdk",
"entryFile": "index",
"sourceRoot": "libs/typescript-sdk/src",
"compilerOptions": {
"tsConfigPath": "libs/typescript-sdk/tsconfig.lib.json"
}
}
},
"compilerOptions": {
Expand Down
5 changes: 5 additions & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"class-transformer": "^0.2.3",
"class-validator": "^0.12.2",
"moment": "^2.25.3",
"monet": "^0.9.1",
"pg": "^8.2.0",
"postgres": "^1.0.2",
"reflect-metadata": "^0.1.13",
Expand Down Expand Up @@ -115,10 +116,12 @@
"@coders-board-library/axios-utils/(.*)": "<rootDir>/libs/axios-utils/src/$1",
"@coders-board-library/axios-utils": "<rootDir>/libs/axios-utils/src",
"@coders-board-library/eventstore-projections/(.*)": "<rootDir>/libs/eventstore-projections/src/$1",
"@coders-board-library/eventstore-projections": "<rootDir>/libs/eventstore-projections/src"
"@coders-board-library/eventstore-projections": "<rootDir>/libs/eventstore-projections/src",
"@coders-board-library/typescript-sdk/(.*)": "<rootDir>/libs/typescript-sdk/src/$1",
"@coders-board-library/typescript-sdk": "<rootDir>/libs/typescript-sdk/src"
}
},
"eslintIgnore": [
"**/*.projection.*"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ApiCreatedResponse, ApiNoContentResponse, ApiTags } from '@nestjs/swagg
import { InviteApplicant } from '../../../application/internal-command/invite-applicant.internal-command';
import { CancelApplicantInvitation } from '../../../application/internal-command/cancel-applicant-invitation.internal-command';
import { v4 as uuid } from 'uuid';
import { CancelApplicantInvitationRequestParams } from './request-params/cancel-applicant-invitation.request-params';

@ApiTags('inviting-applicants')
@Controller('/rest-api/v1/applicant-invitations')
Expand Down Expand Up @@ -36,7 +37,7 @@ export class ApplicantInvitationV1WriteSideController {
})
@HttpCode(204)
@Post(':invitationId/cancellation')
postApplicantInvitationCancellation(@Param('invitationId') invitationId: string) {
return this.internalCommandBus.sendAndWait(new CancelApplicantInvitation(invitationId));
postApplicantInvitationCancellation(@Param() params: CancelApplicantInvitationRequestParams) {
return this.internalCommandBus.sendAndWait(new CancelApplicantInvitation(params.invitationId));
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { IsDefined, IsNotEmpty } from 'class-validator';
import { IsDefined, IsNotEmpty, IsUUID } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';

export class CancelApplicantInvitationRequestBody {
export class CancelApplicantInvitationRequestParams {
@ApiProperty()
@IsUUID()
@IsDefined()
@IsNotEmpty()
readonly invitationId: string;
Expand Down
2 changes: 2 additions & 0 deletions backend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import { InternalCommandInvalidSchemaExceptionFilter } from './shared-kernel/write-side/presentation/rest-api/nestjs-exception-filter/internal-command-invalid-schema.exception-filter';

function setupSwagger(app: INestApplication) {
const options = new DocumentBuilder()
Expand All @@ -21,6 +22,7 @@ async function bootstrap() {
const app = await NestFactory.create(AppModule);
setupValidationPipe(app);
setupSwagger(app);
app.useGlobalFilters(new InternalCommandInvalidSchemaExceptionFilter());
await app.listen(process.env.CODERSBOARD_SERVER_PORT || 4000);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { InternalCommand } from './internal-command';
import { Exception } from '@coders-board-library/typescript-sdk/exception';
import { ValidationError } from 'class-validator';

export class InternalCommandInvalidSchemaException extends Exception {
constructor(command: InternalCommand, readonly validationErrors: ValidationError[]) {
super(
`Internal command ${
command.constructor.name
} rejected! Schema doesn't match. \n Validation errors: ${validationErrors.map(
(it, index) => `\n${index + 1}. ${it}`,
)}`,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { InternalCommand } from './internal-command';
import { Exception } from '@coders-board-library/typescript-sdk/exception';

export class InternalCommandRejectedException extends Exception {
constructor(command: InternalCommand, causation: Error | undefined) {
super(`Internal command ${command.constructor.name} rejected!`, causation);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ export interface InternalCommandSender {
sendAndWait<R = void>(command: InternalCommand): Promise<R>;

//TODO: Add inbox and ack
sendAndForget(command: InternalCommand): void;
sendAndForget(command: InternalCommand): Promise<void>;
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import { InternalCommandSender } from '../../application/internal-command-sender/internal-command-sender';
import { InternalCommand } from '../../application/internal-command-sender/internal-command';
import { validateOrReject } from 'class-validator';
import { InternalCommandRejectedException } from '../../application/internal-command-sender/internal-command-rejected.exception';
import { InternalCommandInvalidSchemaException } from '../../application/internal-command-sender/internal-command-invalid-schema.exception';

export class ClassValidatorInternalCommandSender implements InternalCommandSender {
constructor(private readonly commandSender: InternalCommandSender) {}

sendAndWait<R>(command: InternalCommand): Promise<R> {
return validateOrReject(command).then(() => this.commandSender.sendAndWait<R>(command));
return validateOrReject(command)
.catch(validationError => {
throw new InternalCommandInvalidSchemaException(command, validationError);
})
.then(() => this.commandSender.sendAndWait<R>(command));
}

sendAndForget<T extends InternalCommand>(command: T) {
return validateOrReject(command).then(() => this.commandSender.sendAndForget(command));
sendAndForget<T extends InternalCommand>(command: T): Promise<void> {
return validateOrReject(command)
.catch(validationError => {
throw new InternalCommandInvalidSchemaException(command, validationError);
})
.then(() => this.commandSender.sendAndForget(command));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ export class NestJsInternalCommandSender implements InternalCommandSender {
return this.commandBus.execute(command);
}

sendAndForget<T extends InternalCommand>(command: T) {
sendAndForget<T extends InternalCommand>(command: T): Promise<void> {
this.commandBus.execute(command).then();
return Promise.resolve();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ExceptionFilter, Catch, ArgumentsHost, HttpStatus } from '@nestjs/common';
import { InternalCommandInvalidSchemaException } from '../../../application/internal-command-sender/internal-command-invalid-schema.exception';

@Catch(InternalCommandInvalidSchemaException)
export class InternalCommandInvalidSchemaExceptionFilter implements ExceptionFilter {
catch(exception: InternalCommandInvalidSchemaException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();

response.status(HttpStatus.BAD_REQUEST).json({
statusCode: HttpStatus.BAD_REQUEST,
timestamp: new Date().toISOString(),
path: request.url,
message: exception.validationErrors, //TODO: Flatten from NestJS validation pipe!
});
}
}
4 changes: 3 additions & 1 deletion backend/test-integration/jest-integration.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"@coders-board-library/axios-utils/(.*)": "<rootDir>/../libs/axios-utils/src/$1",
"@coders-board-library/axios-utils": "<rootDir>/../libs/axios-utils/src",
"@coders-board-library/eventstore-projections/(.*)": "<rootDir>/../libs/eventstore-projections/src/$1",
"@coders-board-library/eventstore-projections": "<rootDir>/../libs/eventstore-projections/src"
"@coders-board-library/eventstore-projections": "<rootDir>/../libs/eventstore-projections/src",
"@coders-board-library/typescript-sdk/(.*)": "<rootDir>/../libs/typescript-sdk/src/$1",
"@coders-board-library/typescript-sdk": "<rootDir>/../libs/typescript-sdk/src"
}
}
4 changes: 3 additions & 1 deletion backend/test-module/jest-module.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"@coders-board-library/axios-utils/(.*)": "<rootDir>/../libs/axios-utils/src/$1",
"@coders-board-library/axios-utils": "<rootDir>/../libs/axios-utils/src",
"@coders-board-library/eventstore-projections/(.*)": "<rootDir>/../libs/eventstore-projections/src/$1",
"@coders-board-library/eventstore-projections": "<rootDir>/../libs/eventstore-projections/src"
"@coders-board-library/eventstore-projections": "<rootDir>/../libs/eventstore-projections/src",
"@coders-board-library/typescript-sdk/(.*)": "<rootDir>/../libs/typescript-sdk/src/$1",
"@coders-board-library/typescript-sdk": "<rootDir>/../libs/typescript-sdk/src"
}
}
4 changes: 3 additions & 1 deletion backend/test-unit/jest-unit.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"@coders-board-library/axios-utils/(.*)": "<rootDir>/../libs/axios-utils/src/$1",
"@coders-board-library/axios-utils": "<rootDir>/../libs/axios-utils/src",
"@coders-board-library/eventstore-projections/(.*)": "<rootDir>/../libs/eventstore-projections/src/$1",
"@coders-board-library/eventstore-projections": "<rootDir>/../libs/eventstore-projections/src"
"@coders-board-library/eventstore-projections": "<rootDir>/../libs/eventstore-projections/src",
"@coders-board-library/typescript-sdk/(.*)": "<rootDir>/../libs/typescript-sdk/src/$1",
"@coders-board-library/typescript-sdk": "<rootDir>/../libs/typescript-sdk/src"
}
}
6 changes: 6 additions & 0 deletions backend/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@
],
"@coders-board-library/eventstore-projections/*": [
"libs/eventstore-projections/src/*"
],
"@coders-board-library/typescript-sdk": [
"libs/typescript-sdk/src"
],
"@coders-board-library/typescript-sdk/*": [
"libs/typescript-sdk/src/*"
]
}
},
Expand Down

0 comments on commit def8863

Please sign in to comment.