feat(@nestjs/core) integration custom decorators with pipes

This commit is contained in:
Kamil Myśliwiec
2017-11-25 20:04:40 +01:00
parent 832eb517df
commit bffe159a59
20 changed files with 104 additions and 63 deletions

View File

@@ -1,4 +1,9 @@
# 4.4.0 @soon
## 4.4.1
- **common**: `ValidationPipe` improvement
- **common**: custom route params decorators accepts pipes now
- **core**: bugfix #268
## 4.4.0
- **core**: possibility to create the `NestApplicationContext` using `NestFactory.createApplicationContext()` (Nest application without HTTP server / microservice in the background)
- **core**: create custom params decorators feature (`createRouteParamDecorator()`)

View File

@@ -1 +1 @@
export declare type Paramtype = 'body' | 'query' | 'param';
export declare type Paramtype = 'body' | 'query' | 'param' | 'custom';

View File

@@ -17,6 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
const class_validator_1 = require("class-validator");
const class_transformer_1 = require("class-transformer");
const index_1 = require("../index");
const shared_utils_1 = require("../utils/shared.utils");
let ValidationPipe = class ValidationPipe {
transform(value, metadata) {
return __awaiter(this, void 0, void 0, function* () {
@@ -34,7 +35,7 @@ let ValidationPipe = class ValidationPipe {
}
toValidate(metatype) {
const types = [String, Boolean, Number, Array, Object];
return !types.find(type => metatype === type);
return !types.find(type => metatype === type) && !shared_utils_1.isNil(metatype);
}
};
ValidationPipe = __decorate([

View File

@@ -1,7 +1,7 @@
import { CustomParamFactory } from '../../interfaces/custom-route-param-factory.interface';
import { ParamData } from './route-params.decorator';
import { PipeTransform } from '../../index';
/**
* Create route params custom decorator
* @param factory
*/
export declare const createRouteParamDecorator: (factory: CustomParamFactory) => (data?: ParamData) => ParameterDecorator;
export declare function createRouteParamDecorator(factory: CustomParamFactory): (data?: any, ...pipes: PipeTransform<any>[]) => ParameterDecorator;

View File

@@ -1,20 +1,24 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const constants_1 = require("../../constants");
const assignCustomMetadata = (args, paramtype, index, factory, data) => (Object.assign({}, args, { [`${index}:${paramtype}${constants_1.CUSTOM_ROUTE_AGRS_METADATA}:${index}`]: {
const assignCustomMetadata = (args, paramtype, index, factory, data, ...pipes) => (Object.assign({}, args, { [`${paramtype}${constants_1.CUSTOM_ROUTE_AGRS_METADATA}:${index}`]: {
index,
factory,
data,
pipes,
} }));
const randomString = () => Math.random().toString(36).substring(2, 15);
const randomString = () => Math.random()
.toString(36)
.substring(2, 15);
/**
* Create route params custom decorator
* @param factory
*/
exports.createRouteParamDecorator = (factory) => {
function createRouteParamDecorator(factory) {
const paramtype = randomString() + randomString();
return (data) => (target, key, index) => {
return (data, ...pipes) => (target, key, index) => {
const args = Reflect.getMetadata(constants_1.ROUTE_ARGS_METADATA, target, key) || {};
Reflect.defineMetadata(constants_1.ROUTE_ARGS_METADATA, assignCustomMetadata(args, paramtype, index, factory, data), target, key);
Reflect.defineMetadata(constants_1.ROUTE_ARGS_METADATA, assignCustomMetadata(args, paramtype, index, factory, data, ...pipes), target, key);
};
};
}
exports.createRouteParamDecorator = createRouteParamDecorator;

View File

@@ -7,7 +7,7 @@ class ParamsTokenFactory {
case route_paramtypes_enum_1.RouteParamtypes.BODY: return 'body';
case route_paramtypes_enum_1.RouteParamtypes.PARAM: return 'param';
case route_paramtypes_enum_1.RouteParamtypes.QUERY: return 'query';
default: return null;
default: return 'custom';
}
}
}

View File

@@ -1,6 +1,6 @@
import { RouteParamtypes } from '@nestjs/common/enums/route-paramtypes.enum';
export interface IRouteParamsFactory {
exchangeKeyForValue(key: RouteParamtypes, data: any, {req, res, next}: {
exchangeKeyForValue(key: RouteParamtypes | string, data: any, {req, res, next}: {
req: any;
res: any;
next: any;

View File

@@ -1,7 +1,7 @@
import { RouteParamtypes } from '@nestjs/common/enums/route-paramtypes.enum';
import { IRouteParamsFactory } from './interfaces/route-params-factory.interface';
export declare class RouteParamsFactory implements IRouteParamsFactory {
exchangeKeyForValue(key: RouteParamtypes, data: any, {req, res, next}: {
exchangeKeyForValue(key: RouteParamtypes | string, data: any, {req, res, next}: {
req: any;
res: any;
next: any;

View File

@@ -12,7 +12,7 @@ import { InterceptorsContextCreator } from '../interceptors/interceptors-context
import { InterceptorsConsumer } from '../interceptors/interceptors-consumer';
export interface ParamProperties {
index: number;
type: RouteParamtypes;
type: RouteParamtypes | string;
data: ParamData;
pipes: PipeTransform<any>[];
extractValue: (req, res, next) => any;
@@ -28,7 +28,7 @@ export declare class RouterExecutionContext {
private readonly responseController;
constructor(paramsFactory: IRouteParamsFactory, pipesContextCreator: PipesContextCreator, pipesConsumer: PipesConsumer, guardsContextCreator: GuardsContextCreator, guardsConsumer: GuardsConsumer, interceptorsContextCreator: InterceptorsContextCreator, interceptorsConsumer: InterceptorsConsumer);
create(instance: Controller, callback: (...args) => any, methodName: string, module: string, requestMethod: RequestMethod): (req: any, res: any, next: any) => Promise<any>;
mapParamType(key: string): RouteParamtypes | number;
mapParamType(key: string): string;
reflectCallbackMetadata(instance: Controller, methodName: string): RouteParamsMetadata;
reflectCallbackParamtypes(instance: Controller, methodName: string): any[];
reflectHttpStatusCode(callback: (...args) => any): number;

View File

@@ -58,7 +58,7 @@ class RouterExecutionContext {
}
mapParamType(key) {
const keyPair = key.split(':');
return Number(keyPair[0]);
return keyPair[0];
}
reflectCallbackMetadata(instance, methodName) {
return Reflect.getMetadata(constants_1.ROUTE_ARGS_METADATA, instance, methodName);
@@ -84,8 +84,9 @@ class RouterExecutionContext {
const customExtractValue = this.getCustomFactory(factory, data);
return { index, extractValue: customExtractValue, type, data, pipes };
}
const extractValue = (req, res, next) => this.paramsFactory.exchangeKeyForValue(type, data, { req, res, next });
return { index, extractValue, type, data, pipes };
const nType = Number(type);
const extractValue = (req, res, next) => this.paramsFactory.exchangeKeyForValue(nType, data, { req, res, next });
return { index, extractValue, type: nType, data, pipes };
});
}
getCustomFactory(factory, data) {
@@ -103,7 +104,8 @@ class RouterExecutionContext {
return __awaiter(this, void 0, void 0, function* () {
if (type === route_paramtypes_enum_1.RouteParamtypes.BODY
|| type === route_paramtypes_enum_1.RouteParamtypes.QUERY
|| type === route_paramtypes_enum_1.RouteParamtypes.PARAM) {
|| type === route_paramtypes_enum_1.RouteParamtypes.PARAM
|| shared_utils_1.isString(type)) {
return yield this.pipesConsumer.apply(value, { metatype, type, data }, transforms);
}
return Promise.resolve(value);

View File

@@ -1,6 +1,6 @@
{
"name": "nestjs",
"version": "4.4.0",
"version": "4.4.1",
"description": "Modern, fast, powerful node.js web framework",
"main": "index.js",
"scripts": {

View File

@@ -1 +1 @@
export type Paramtype = 'body' | 'query' | 'param';
export type Paramtype = 'body' | 'query' | 'param' | 'custom';

View File

@@ -2,6 +2,7 @@ import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
import { PipeTransform } from '../interfaces/pipe-transform.interface';
import { Pipe, ArgumentMetadata, BadRequestException } from '../index';
import { isNil } from '../utils/shared.utils';
@Pipe()
export class ValidationPipe implements PipeTransform<any> {
@@ -20,6 +21,6 @@ export class ValidationPipe implements PipeTransform<any> {
private toValidate(metatype): boolean {
const types = [String, Boolean, Number, Array, Object];
return !types.find(type => metatype === type);
return !types.find(type => metatype === type) && !isNil(metatype);
}
}

View File

@@ -1,37 +1,63 @@
import { ROUTE_ARGS_METADATA, CUSTOM_ROUTE_AGRS_METADATA } from '../../constants';
import {
ROUTE_ARGS_METADATA,
CUSTOM_ROUTE_AGRS_METADATA,
} from '../../constants';
import { CustomParamFactory } from '../../interfaces/custom-route-param-factory.interface';
import { RouteParamsMetadata, ParamData } from './route-params.decorator';
import { PipeTransform } from '../../index';
import { isNil, isString } from '../shared.utils';
const assignCustomMetadata = (
args: RouteParamsMetadata,
paramtype: number|string,
index: number,
factory: CustomParamFactory,
data?: ParamData,
args: RouteParamsMetadata,
paramtype: number | string,
index: number,
factory: CustomParamFactory,
data?: ParamData,
...pipes: PipeTransform<any>[]
) => ({
...args,
[`${index}:${paramtype}${CUSTOM_ROUTE_AGRS_METADATA}:${index}`]: {
index,
factory,
data,
},
...args,
[`${paramtype}${CUSTOM_ROUTE_AGRS_METADATA}:${index}`]: {
index,
factory,
data,
pipes,
},
});
const randomString = () => Math.random().toString(36).substring(2, 15);
const randomString = () =>
Math.random()
.toString(36)
.substring(2, 15);
/**
* Create route params custom decorator
* @param factory
* @param factory
*/
export const createRouteParamDecorator = (factory: CustomParamFactory) => {
const paramtype = randomString() + randomString();
return (data?: ParamData): ParameterDecorator => (target, key, index) => {
const args = Reflect.getMetadata(ROUTE_ARGS_METADATA, target, key) || {};
Reflect.defineMetadata(
ROUTE_ARGS_METADATA,
assignCustomMetadata(args, paramtype, index, factory, data),
target,
key,
);
};
};
export function createRouteParamDecorator(
factory: CustomParamFactory,
): (
data?: any,
...pipes: PipeTransform<any>[],
) => ParameterDecorator {
const paramtype = randomString() + randomString();
return (data?, ...pipes: PipeTransform<any>[]): ParameterDecorator => (
target,
key,
index,
) => {
const args = Reflect.getMetadata(ROUTE_ARGS_METADATA, target, key) || {};
Reflect.defineMetadata(
ROUTE_ARGS_METADATA,
assignCustomMetadata(
args,
paramtype,
index,
factory,
data,
...pipes,
),
target,
key,
);
};
}

View File

@@ -17,7 +17,7 @@ const assignMetadata = (
paramtype: RouteParamtypes,
index: number,
data?: ParamData,
...pipes: PipeTransform<any>[]
...pipes: PipeTransform<any>[],
) => ({
...args,
[`${paramtype}:${index}`]: {
@@ -41,7 +41,7 @@ const createRouteParamDecorator = (paramtype: RouteParamtypes) => {
const createPipesRouteParamDecorator = (paramtype: RouteParamtypes) => (
data?,
...pipes: PipeTransform<any>[]
...pipes: PipeTransform<any>[],
): ParameterDecorator => (target, key, index) => {
const args = Reflect.getMetadata(ROUTE_ARGS_METADATA, target, key) || {};
const hasParamData = isNil(data) || isString(data);

View File

@@ -7,7 +7,7 @@ export class ParamsTokenFactory {
case RouteParamtypes.BODY: return 'body';
case RouteParamtypes.PARAM: return 'param';
case RouteParamtypes.QUERY: return 'query';
default: return null;
default: return 'custom';
}
}
}

View File

@@ -1,5 +1,5 @@
import { RouteParamtypes } from '@nestjs/common/enums/route-paramtypes.enum';
export interface IRouteParamsFactory {
exchangeKeyForValue(key: RouteParamtypes, data, { req, res, next });
exchangeKeyForValue(key: RouteParamtypes | string, data, { req, res, next });
}

View File

@@ -2,7 +2,7 @@ import { RouteParamtypes } from '@nestjs/common/enums/route-paramtypes.enum';
import { IRouteParamsFactory } from './interfaces/route-params-factory.interface';
export class RouteParamsFactory implements IRouteParamsFactory {
public exchangeKeyForValue(key: RouteParamtypes, data, { req, res, next }) {
public exchangeKeyForValue(key: RouteParamtypes | string, data, { req, res, next }) {
switch (key) {
case RouteParamtypes.NEXT: return next;
case RouteParamtypes.REQUEST: return req;

View File

@@ -1,6 +1,6 @@
import 'reflect-metadata';
import { ROUTE_ARGS_METADATA, PARAMTYPES_METADATA, HTTP_CODE_METADATA, CUSTOM_ROUTE_AGRS_METADATA } from '@nestjs/common/constants';
import { isUndefined, isFunction } from '@nestjs/common/utils/shared.utils';
import { isUndefined, isFunction, isString } from '@nestjs/common/utils/shared.utils';
import { RouteParamtypes } from '@nestjs/common/enums/route-paramtypes.enum';
import { Controller, Transform } from '@nestjs/common/interfaces';
import { RouteParamsMetadata } from '@nestjs/common/utils';
@@ -17,7 +17,7 @@ import { InterceptorsConsumer } from '../interceptors/interceptors-consumer';
export interface ParamProperties {
index: number;
type: RouteParamtypes;
type: RouteParamtypes | string;
data: ParamData;
pipes: PipeTransform<any>[];
extractValue: (req, res, next) => any;
@@ -73,9 +73,9 @@ export class RouterExecutionContext {
};
}
public mapParamType(key: string): RouteParamtypes | number {
public mapParamType(key: string): string {
const keyPair = key.split(':');
return Number(keyPair[0]);
return keyPair[0];
}
public reflectCallbackMetadata(instance: Controller, methodName: string): RouteParamsMetadata {
@@ -108,8 +108,9 @@ export class RouterExecutionContext {
const customExtractValue = this.getCustomFactory(factory, data);
return { index, extractValue: customExtractValue, type, data, pipes };
}
const extractValue = (req, res, next) => this.paramsFactory.exchangeKeyForValue(type, data, { req, res, next });
return { index, extractValue, type, data, pipes };
const nType = Number(type);
const extractValue = (req, res, next) => this.paramsFactory.exchangeKeyForValue(nType, data, { req, res, next });
return { index, extractValue, type: nType, data, pipes };
});
}
@@ -136,7 +137,8 @@ export class RouterExecutionContext {
if (type === RouteParamtypes.BODY
|| type === RouteParamtypes.QUERY
|| type === RouteParamtypes.PARAM) {
|| type === RouteParamtypes.PARAM
|| isString(type)) {
return await this.pipesConsumer.apply(value, { metatype, type, data }, transforms);
}

View File

@@ -25,8 +25,8 @@ describe('ParamsTokenFactory', () => {
});
});
describe('not available', () => {
it('should returns null', () => {
expect(factory.exchangeEnumForString(-1)).to.be.eql(null);
it('should returns "custom"', () => {
expect(factory.exchangeEnumForString(-1)).to.be.eql('custom');
});
});
});