Skip to content

Commit

Permalink
Merge 4ec8e34 into ead8d55
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilmysliwiec committed Dec 10, 2018
2 parents ead8d55 + 4ec8e34 commit db74f4c
Show file tree
Hide file tree
Showing 61 changed files with 504 additions and 168 deletions.
9 changes: 9 additions & 0 deletions integration/microservices/e2e/sum-nats.spec.ts
Expand Up @@ -73,6 +73,15 @@ describe('NATS transport', () => {
.expect(200, 'true');
});

it(`/GET (exception)`, () => {
return request(server)
.get('/exception')
.expect(200, {
message: 'test',
status: 'error',
});
});

afterEach(async () => {
await app.close();
});
Expand Down
19 changes: 16 additions & 3 deletions integration/microservices/src/nats/nats.controller.ts
@@ -1,12 +1,13 @@
import { Body, Controller, HttpCode, Post, Query } from '@nestjs/common';
import { Body, Controller, Get, HttpCode, Post, Query } from '@nestjs/common';
import {
Client,
ClientProxy,
MessagePattern,
RpcException,
Transport,
} from '@nestjs/microservices';
import { from, Observable, of } from 'rxjs';
import { scan } from 'rxjs/operators';
import { from, Observable, of, throwError } from 'rxjs';
import { catchError, scan } from 'rxjs/operators';

@Controller()
export class NatsController {
Expand Down Expand Up @@ -71,4 +72,16 @@ export class NatsController {
streaming(data: number[]): Observable<number> {
return from(data);
}

@Get('exception')
async getError() {
return await this.client
.send<number>('exception', {})
.pipe(catchError(err => of(err)));
}

@MessagePattern('exception')
throwError(): Observable<number> {
return throwError(new RpcException('test'));
}
}
35 changes: 35 additions & 0 deletions integration/websockets/e2e/error-gateway.spec.ts
@@ -0,0 +1,35 @@
import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import * as io from 'socket.io-client';
import { ErrorGateway } from '../src/error.gateway';

describe('ErrorGateway', () => {
let app: INestApplication;

beforeEach(async () => {
const testingModule = await Test.createTestingModule({
providers: [ErrorGateway],
}).compile();
app = await testingModule.createNestApplication();
await app.listenAsync(3000);
});

it(`should handle error`, async () => {
const ws = io.connect('http://localhost:8080');
ws.emit('push', {
test: 'test',
});
await new Promise(resolve =>
ws.on('exception', data => {
expect(data).to.be.eql({
status: 'error',
message: 'test',
});
resolve();
}),
);
});

afterEach(() => app.close());
});
14 changes: 14 additions & 0 deletions integration/websockets/src/error.gateway.ts
@@ -0,0 +1,14 @@
import {
SubscribeMessage,
WebSocketGateway,
WsException,
} from '@nestjs/websockets';
import { throwError } from 'rxjs';

@WebSocketGateway(8080)
export class ErrorGateway {
@SubscribeMessage('push')
onPush(client, data) {
return throwError(new WsException('test'));
}
}
2 changes: 1 addition & 1 deletion lerna.json
Expand Up @@ -3,5 +3,5 @@
"packages": [
"packages/*"
],
"version": "5.4.1"
"version": "5.5.0"
}
8 changes: 6 additions & 2 deletions packages/common/cache/cache.module.ts
Expand Up @@ -24,7 +24,10 @@ export class CacheModule {
return {
module: CacheModule,
imports: options.imports,
providers: this.createAsyncProviders(options),
providers: [
...this.createAsyncProviders(options),
...(options.extraProviders || []),
],
};
}

Expand Down Expand Up @@ -55,7 +58,8 @@ export class CacheModule {
}
return {
provide: CACHE_MODULE_OPTIONS,
useFactory: async (optionsFactory: CacheOptionsFactory) => optionsFactory.createCacheOptions(),
useFactory: async (optionsFactory: CacheOptionsFactory) =>
optionsFactory.createCacheOptions(),
inject: [options.useExisting || options.useClass],
};
}
Expand Down
3 changes: 2 additions & 1 deletion packages/common/cache/interfaces/cache-module.interface.ts
@@ -1,4 +1,4 @@
import { ModuleMetadata, Type } from '../../interfaces';
import { ModuleMetadata, Provider, Type } from '../../interfaces';
import { CacheManagerOptions } from './cache-manager.interface';

export interface CacheModuleOptions extends CacheManagerOptions {
Expand All @@ -17,4 +17,5 @@ export interface CacheModuleAsyncOptions
...args: any[]
) => Promise<CacheModuleOptions> | CacheModuleOptions;
inject?: any[];
extraProviders?: Provider[];
}
8 changes: 4 additions & 4 deletions packages/common/decorators/core/inject.decorator.ts
Expand Up @@ -15,17 +15,17 @@ export function Inject<T = any>(token?: T) {
token && isFunction(token) ? ((token as any) as Function).name : token;

if (!isUndefined(index)) {
const dependencies =
let dependencies =
Reflect.getMetadata(SELF_DECLARED_DEPS_METADATA, target) || [];

dependencies.push({ index, param: type });
dependencies = [...dependencies, { index, param: type }];
Reflect.defineMetadata(SELF_DECLARED_DEPS_METADATA, dependencies, target);
return;
}
const properties =
let properties =
Reflect.getMetadata(PROPERTY_DEPS_METADATA, target.constructor) || [];

properties.push({ key, type });
properties = [...properties, { key, type }];
Reflect.defineMetadata(
PROPERTY_DEPS_METADATA,
properties,
Expand Down
1 change: 1 addition & 0 deletions packages/common/http/http.constants.ts
@@ -1,2 +1,3 @@
export const AXIOS_INSTANCE_TOKEN = 'AXIOS_INSTANCE_TOKEN';
export const HTTP_MODULE_ID = 'HTTP_MODULE_ID';
export const HTTP_MODULE_OPTIONS = 'HTTP_MODULE_OPTIONS';
65 changes: 61 additions & 4 deletions packages/common/http/http.module.ts
@@ -1,9 +1,18 @@
import Axios, { AxiosRequestConfig } from 'axios';
import Axios from 'axios';
import { Module } from '../decorators/modules/module.decorator';
import { DynamicModule } from '../interfaces';
import { DynamicModule, Provider } from '../interfaces';
import { randomStringGenerator } from '../utils/random-string-generator.util';
import { AXIOS_INSTANCE_TOKEN, HTTP_MODULE_ID } from './http.constants';
import {
AXIOS_INSTANCE_TOKEN,
HTTP_MODULE_ID,
HTTP_MODULE_OPTIONS,
} from './http.constants';
import { HttpService } from './http.service';
import {
HttpModuleAsyncOptions,
HttpModuleOptions,
HttpModuleOptionsFactory,
} from './interfaces';

@Module({
providers: [
Expand All @@ -16,7 +25,7 @@ import { HttpService } from './http.service';
exports: [HttpService],
})
export class HttpModule {
static register(config: AxiosRequestConfig): DynamicModule {
static register(config: HttpModuleOptions): DynamicModule {
return {
module: HttpModule,
providers: [
Expand All @@ -31,4 +40,52 @@ export class HttpModule {
],
};
}

static registerAsync(options: HttpModuleAsyncOptions): DynamicModule {
return {
module: HttpModule,
imports: options.imports,
providers: [
...this.createAsyncProviders(options),
{
provide: HTTP_MODULE_ID,
useValue: randomStringGenerator(),
},
...(options.extraProviders || []),
],
};
}

private static createAsyncProviders(
options: HttpModuleAsyncOptions,
): Provider[] {
if (options.useExisting || options.useFactory) {
return [this.createAsyncOptionsProvider(options)];
}
return [
this.createAsyncOptionsProvider(options),
{
provide: options.useClass,
useClass: options.useClass,
},
];
}

private static createAsyncOptionsProvider(
options: HttpModuleAsyncOptions,
): Provider {
if (options.useFactory) {
return {
provide: HTTP_MODULE_OPTIONS,
useFactory: options.useFactory,
inject: options.inject || [],
};
}
return {
provide: HTTP_MODULE_OPTIONS,
useFactory: async (optionsFactory: HttpModuleOptionsFactory) =>
optionsFactory.createHttpOptions(),
inject: [options.useExisting || options.useClass],
};
}
}
1 change: 1 addition & 0 deletions packages/common/http/index.ts
@@ -1,2 +1,3 @@
export * from './http.module';
export * from './http.service';
export * from './interfaces';
19 changes: 19 additions & 0 deletions packages/common/http/interfaces/http-module.interface.ts
@@ -0,0 +1,19 @@
import { AxiosRequestConfig } from 'axios';
import { ModuleMetadata, Provider, Type } from '../../interfaces';

export interface HttpModuleOptions extends AxiosRequestConfig {}

export interface HttpModuleOptionsFactory {
createHttpOptions(): Promise<HttpModuleOptions> | HttpModuleOptions;
}

export interface HttpModuleAsyncOptions
extends Pick<ModuleMetadata, 'imports'> {
useExisting?: Type<HttpModuleOptions>;
useClass?: Type<HttpModuleOptions>;
useFactory?: (
...args: any[]
) => Promise<HttpModuleOptions> | HttpModuleOptions;
inject?: any[];
extraProviders?: Provider[];
}
1 change: 1 addition & 0 deletions packages/common/http/interfaces/index.ts
@@ -0,0 +1 @@
export * from './http-module.interface';
1 change: 1 addition & 0 deletions packages/common/index.ts
Expand Up @@ -39,6 +39,7 @@ export {
Provider,
RpcExceptionFilter,
Type,
ValidationError,
WebSocketAdapter,
WsExceptionFilter,
} from './interfaces';
Expand Down
28 changes: 28 additions & 0 deletions packages/common/interfaces/external/validation-error.interface.ts
@@ -0,0 +1,28 @@
/**
* Validation error description.
* @see https://github.com/typestack/class-validator
*/
export interface ValidationError {
/**
* Object that was validated.
*/
target: Object;
/**
* Object's property that haven't pass validation.
*/
property: string;
/**
* Value that haven't pass a validation.
*/
value: any;
/**
* Constraints that failed validation with error messages.
*/
constraints: {
[type: string]: string;
};
/**
* Contains all nested validation errors of the property.
*/
children: ValidationError[];
}
1 change: 1 addition & 0 deletions packages/common/interfaces/index.ts
Expand Up @@ -4,6 +4,7 @@ export * from './controllers/controller.interface';
export * from './exceptions/exception-filter.interface';
export * from './exceptions/rpc-exception-filter.interface';
export * from './exceptions/ws-exception-filter.interface';
export * from './external/validation-error.interface';
export * from './features/arguments-host.interface';
export * from './features/can-activate.interface';
export * from './features/custom-route-param-factory.interface';
Expand Down
Expand Up @@ -75,6 +75,7 @@ export interface NatsOptions {
reconnectTimeWait?: number;
servers?: string[];
tls?: any;
queue?: string;
};
}

Expand Down
2 changes: 1 addition & 1 deletion packages/common/package.json
@@ -1,6 +1,6 @@
{
"name": "@nestjs/common",
"version": "5.4.1",
"version": "5.5.0",
"description": "Nest - modern, fast, powerful node.js web framework (@common)",
"author": "Kamil Mysliwiec",
"repository": {
Expand Down
18 changes: 14 additions & 4 deletions packages/common/pipes/validation.pipe.ts
@@ -1,6 +1,10 @@
import { Optional } from '../decorators';
import { Injectable } from '../decorators/core';
import { ArgumentMetadata, BadRequestException } from '../index';
import {
ArgumentMetadata,
BadRequestException,
ValidationError,
} from '../index';
import { ValidatorOptions } from '../interfaces/external/validator-options.interface';
import { PipeTransform } from '../interfaces/features/pipe-transform.interface';
import { loadPackage } from '../utils/load-package.util';
Expand All @@ -9,6 +13,7 @@ import { isNil } from '../utils/shared.utils';
export interface ValidationPipeOptions extends ValidatorOptions {
transform?: boolean;
disableErrorMessages?: boolean;
exceptionFactory?: (errors: ValidationError[]) => any;
}

let classValidator: any = {};
Expand All @@ -19,13 +24,20 @@ export class ValidationPipe implements PipeTransform<any> {
protected isTransformEnabled: boolean;
protected isDetailedOutputDisabled?: boolean;
protected validatorOptions: ValidatorOptions;
protected exceptionFactory: (errors: ValidationError[]) => any;

constructor(@Optional() options?: ValidationPipeOptions) {
options = options || {};
const { transform, disableErrorMessages, ...validatorOptions } = options;
this.isTransformEnabled = !!transform;
this.validatorOptions = validatorOptions;
this.isDetailedOutputDisabled = disableErrorMessages;
this.exceptionFactory =
options.exceptionFactory ||
(errors =>
new BadRequestException(
this.isDetailedOutputDisabled ? undefined : errors,
));

const loadPkg = (pkg: any) => loadPackage(pkg, 'ValidationPipe');
classValidator = loadPkg('class-validator');
Expand All @@ -43,9 +55,7 @@ export class ValidationPipe implements PipeTransform<any> {
);
const errors = await classValidator.validate(entity, this.validatorOptions);
if (errors.length > 0) {
throw new BadRequestException(
this.isDetailedOutputDisabled ? undefined : errors,
);
throw this.exceptionFactory(errors);
}
return this.isTransformEnabled
? entity
Expand Down

0 comments on commit db74f4c

Please sign in to comment.