Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.7.0 #119

Merged
merged 33 commits into from
May 3, 2017
Merged

0.7.0 #119

Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
5b13b5c
updated dependencies
Apr 5, 2017
b57ed78
refactored decorators moved each into its own file
Apr 5, 2017
59b821b
moved deprecated decorators into their own directory
Apr 5, 2017
d8303d3
added deprecations in RoutingControllersOptions
Apr 5, 2017
08df531
refactored all decorators usage
Apr 6, 2017
f8f559f
refactored parameter options
Apr 6, 2017
76aba03
refactored decorators
Apr 6, 2017
c67a6ac
fixed wrong test
Apr 6, 2017
8c659ab
added new decorators
Apr 6, 2017
9840157
removed @EmptyResultCode decorator
Apr 6, 2017
36dbc51
remove interceptor functionality
Apr 6, 2017
b226073
removed deprecated decorators
Apr 7, 2017
41d575d
moved json controller back to non-deprecated decorators :(
Apr 7, 2017
9b279da
remove string casting to fix issues with buffer returning
Apr 7, 2017
4ac2766
fixed some decorators plus some refactoring
Apr 7, 2017
8e4e617
refactored RoutingControllers class
Apr 7, 2017
b7591b2
refactored action options object
Apr 7, 2017
0e84913
refactored action parameters stuff
Apr 7, 2017
460b8f2
refactored ActionParameterHandler
Apr 20, 2017
45e6c7f
refactored action metadata + beatifying all other classes
Apr 21, 2017
906fe8d
decorators and drivers refactoring
Apr 24, 2017
15c45d6
refactored named and non-named based parameters hydration
Apr 24, 2017
eaf9b39
implemented #75 - support for custom parameters decorators
Apr 24, 2017
308ade7
added custom authorized and current user decorators support
Apr 24, 2017
8552369
added context decorator for koa
Apr 25, 2017
9bb2008
small fixes; added more docs
Apr 26, 2017
f3b931d
exported metadata args storage; version bump
Apr 26, 2017
17cf0e8
added extra release note
Apr 26, 2017
c9e28df
added role checker support; fixed bug with validation and class trans…
Apr 28, 2017
c263928
implemented global metadata args storage
Apr 28, 2017
f5c5642
Update auto-validation Readme paragraph + typo
MichalLytek Apr 30, 2017
7831c64
added authorized support for controllers, added api for model control…
May 3, 2017
38de552
Merge pull request #126 from 19majkel94/patch-3
pleerock May 3, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 9 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,47 +69,37 @@ You can use routing-controllers with [express.js][1] or [koa.js][2].

`npm install reflect-metadata --save`

and make sure to import it in a global place, like app.ts:
and make sure to import it before you use routing-controllers:

```typescript
import "reflect-metadata";
```

3. ES6 features are used, if you are using old version of node.js you may need to install
[es6-shim](https://github.com/paulmillr/es6-shim):

`npm install es6-shim --save`

and import it in a global place like app.ts:

```typescript
import "es6-shim";
```

4. Install framework:
3. Install framework:

**a. If you want to use routing-controllers with *express.js*, then install it and all required dependencies:**

`npm install express body-parser multer --save`

Optionally you can also install its [typings](https://github.com/typings/typings):
Optionally you can also install their typings:

`typings install dt~express dt~serve-static --save --global`
`npm install @types/express @types/body-parser @types/multer --save`

**b. If you want to use routing-controllers with *koa 2*, then install it and all required dependencies:**

`npm install koa@next koa-router@next koa-bodyparser@next koa-multer --save`
`npm install koa koa-router koa-bodyparser koa-multer --save`

Optionally you can also install its [typings](https://github.com/typings/typings):
Optionally you can also install their typings:

`typings install dt~koa --save --global`
`typings install @types/koa --save --global`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

npm install 😆

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😆


5. Its important to set these options in `tsconfig.json` file of your project:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have skipped 4. - use 1. as guthub markdown will transform it to auto enumerated list.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes I didnt worked on readme yet, will completely update it tomorrow and let you know once again


```json
{
"emitDecoratorMetadata": true,
"experimentalDecorators": true
"experimentalDecorators": true,
"lib": ["es6"]
}
```

Expand Down Expand Up @@ -156,7 +146,6 @@ You can use routing-controllers with [express.js][1] or [koa.js][2].
2. Create a file `app.ts`

```typescript
import "es6-shim"; // this shim is optional if you are using old version of node
import "reflect-metadata"; // this shim is required
import {createExpressServer} from "routing-controllers";
import "./UserController"; // we need to "load" our controller before call createServer. this is required
Expand Down
7 changes: 5 additions & 2 deletions gulpfile.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Gulpclass, Task, SequenceTask, MergedTask} from "gulpclass";
import {Gulpclass, MergedTask, SequenceTask, Task} from "gulpclass";

const gulp = require("gulp");
const del = require("del");
Expand Down Expand Up @@ -171,7 +171,10 @@ export class Gulpfile {
// chai.use(require("sinon-chai"));
// chai.use(require("chai-as-promised"));

return gulp.src(["./build/es5/test/functional/**/*.js"])
return gulp.src([
"./build/es5/test/functional/**/*.js",
"./build/es5/test/issues/**/*.js",
])
.pipe(mocha())
.pipe(istanbul.writeReports());
}
Expand Down
145 changes: 80 additions & 65 deletions src/ActionParameterHandler.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import {plainToClass} from "class-transformer";
import {validateOrReject as validate, ValidationError} from "class-validator";
import {ActionProperties} from "./ActionProperties";
import {BodyRequiredError} from "./error/BodyRequiredError";
import {BadRequestError} from "./http-error/BadRequestError";
import {Driver} from "./driver/Driver";
import {ParameterParseJsonError} from "./error/ParameterParseJsonError";
import {ParameterRequiredError} from "./error/ParameterRequiredError";
import {ParamMetadata} from "./metadata/ParamMetadata";
import {NotFoundError} from "./http-error/NotFoundError";
import {ParamRequiredError} from "./error/ParamRequiredError";

/**
* Helps to handle parameters.
* Handles action parameter.
*/
export class ActionParameterHandler {

Expand All @@ -25,59 +23,54 @@ export class ActionParameterHandler {
// Public Methods
// -------------------------------------------------------------------------

handleParam(actionProperties: ActionProperties, param: ParamMetadata): Promise<any>|any {
/**
* Handles action parameter.
*/
handle(actionProperties: ActionProperties, param: ParamMetadata): Promise<any>|any {

if (param.type === "request")
return actionProperties.request;

if (param.type === "response")
return actionProperties.response;

let value: any, originalValue: any;
value = originalValue = this.driver.getParamFromRequest(actionProperties, param);

const isValueEmpty = value === null || value === undefined || value === "";
const isValueEmptyObject = value instanceof Object && Object.keys(value).length === 0;

if (!isValueEmpty)
value = this.handleParamFormat(value, param);

// check cases when parameter is required but its empty and throw errors in such cases

// get parameter value from request and normalize it
const value = this.normalizeParamValue(this.driver.getParamFromRequest(actionProperties, param), param);

// check cases when parameter is required but its empty and throw errors in this case
if (param.required) {
if (param.type === "body" && !param.name && (isValueEmpty || isValueEmptyObject)) { // body has a special check
return Promise.reject(new BodyRequiredError(actionProperties));
const isValueEmpty = value === null || value === undefined || value === "";
const isValueEmptyObject = value instanceof Object && Object.keys(value).length === 0;

if (param.type === "body" && !param.name && (isValueEmpty || isValueEmptyObject)) { // body has a special check and error message
return Promise.reject(new ParamRequiredError(actionProperties, param));

} else if (param.name && isValueEmpty) { // regular check for all other parameters
return Promise.reject(new ParameterRequiredError(param, actionProperties));
} else if (param.name && isValueEmpty) { // regular check for all other parameters // todo: figure out something with param.name usage and multiple things params (query params, upload files etc.)
return Promise.reject(new ParamRequiredError(actionProperties, param));
}
}

// if transform function is given for this param then apply it
if (param.transform)
value = param.transform(value, actionProperties.request, actionProperties.response);

const promiseValue = value instanceof Promise ? value : Promise.resolve(value);
return promiseValue.then((value: any) => {

if (param.required && originalValue !== null && originalValue !== undefined && isValueEmpty) {
const contentType = param.reflectedType && param.reflectedType.name ? param.reflectedType.name : "content";
const message = param.name ? ` with ${param.name}='${originalValue}'` : ``;
return Promise.reject(new NotFoundError(`Requested ${contentType + message} was not found`));
}
return param.transform(value, actionProperties.request, actionProperties.response);

return value;
});
return value;
}

// -------------------------------------------------------------------------
// Private Methods
// Protected Methods
// -------------------------------------------------------------------------

protected handleParamFormat(value: any, param: ParamMetadata): any {
const format = param.format;
const formatName = format instanceof Function && format.name ? format.name : format instanceof String ? format : "";
switch (formatName.toLowerCase()) {
/**
* Normalizes parameter value.
*/
protected normalizeParamValue(value: any, param: ParamMetadata): any {
if (value === null || value === undefined)
return value;

switch (param.targetName) {
case "number":
if (value === "") return undefined;
return +value;

case "string":
Expand All @@ -86,48 +79,70 @@ export class ActionParameterHandler {
case "boolean":
if (value === "true") {
return true;

} else if (value === "false") {
return false;
}

return !!value;

default:
const isObjectFormat = format instanceof Function || formatName.toLowerCase() === "object";
if (value && (param.parse || isObjectFormat))
if (value && (param.parse || param.isTargetObject)) {
value = this.parseValue(value, param);
value = this.transformValue(value, param);
value = this.validateValue(value, param);
}
}
return value;
}

protected async parseValue(value: any, paramMetadata: ParamMetadata) {
try {
const valueObject = typeof value === "string" ? JSON.parse(value) : value;

let parsedValue: any;
// If value is already by instance of target class, then skip the plain to class step.
if (!(value instanceof paramMetadata.format) && paramMetadata.format !== Object && paramMetadata.format && this.driver.useClassTransformer) {
const options = paramMetadata.classTransformOptions || this.driver.plainToClassTransformOptions;
parsedValue = plainToClass(paramMetadata.format, valueObject, options);
} else {
parsedValue = valueObject;
/**
* Parses string value into a JSON object.
*/
protected parseValue(value: any, paramMetadata: ParamMetadata): any {
if (typeof value === "string") {
try {
return JSON.parse(value);
} catch (error) {
throw new ParameterParseJsonError(paramMetadata.name, value);
}
}

if (paramMetadata.validate || this.driver.enableValidation) {
const options = paramMetadata.validateOptions || this.driver.validationOptions;
return validate(parsedValue, options)
.then(() => parsedValue)
.catch((validationErrors: ValidationError[]) => {
const error: any = new BadRequestError(`Invalid ${paramMetadata.type}, check 'details' property for more info.`);
error.details = validationErrors;
throw error;
});
} else {
return parsedValue;
}
} catch (er) {
throw new ParameterParseJsonError(paramMetadata.name, value);
return value;
}

/**
* Perform class-transformation if enabled.
*/
protected transformValue(value: any, paramMetadata: ParamMetadata): any {
if (this.driver.useClassTransformer &&
paramMetadata.targetType &&
paramMetadata.targetType !== Object &&
!(value instanceof paramMetadata.targetType)) {

const options = paramMetadata.classTransform || this.driver.plainToClassTransformOptions;
value = plainToClass(paramMetadata.targetType, value, options);
}

return value;
}

/**
* Perform class-validation if enabled.
*/
protected validateValue(value: any, paramMetadata: ParamMetadata): Promise<any>|any {
if (paramMetadata.validate || (this.driver.enableValidation && paramMetadata.validate !== false)) {
const options = paramMetadata.validate instanceof Object ? paramMetadata.validate : this.driver.validationOptions;
return validate(value, options)
.then(() => value)
.catch((validationErrors: ValidationError[]) => {
const error: any = new BadRequestError(`Invalid ${paramMetadata.type}, check 'errors' property for more info.`);
error.errors = validationErrors;
throw error;
});
}

return value;
}

}
6 changes: 3 additions & 3 deletions src/RoutingControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class RoutingControllers {
/**
* Used to check and handle controller action parameters.
*/
private paramHandler: ActionParameterHandler;
private parameterHandler: ActionParameterHandler;

/**
* Used to build metadata objects for controllers and middlewares.
Expand All @@ -28,7 +28,7 @@ export class RoutingControllers {
// -------------------------------------------------------------------------

constructor(private driver: Driver) {
this.paramHandler = new ActionParameterHandler(driver);
this.parameterHandler = new ActionParameterHandler(driver);
this.metadataBuilder = new MetadataBuilder();
}

Expand Down Expand Up @@ -87,7 +87,7 @@ export class RoutingControllers {
// compute all parameters
const paramsPromises = action.params
.sort((param1, param2) => param1.index - param2.index)
.map(param => this.paramHandler.handleParam(actionProperties, param));
.map(param => this.parameterHandler.handle(actionProperties, param));

// after all parameters are computed
Promise.all(paramsPromises).then(params => {
Expand Down
8 changes: 3 additions & 5 deletions src/decorator/Body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,11 @@ export function Body(options?: BodyOptions): Function {
method: methodName,
index: index,
type: "body",
reflectedType: format,
format: format,
targetType: format,
parse: false,
required: options ? options.required : false,
classTransformOptions: options ? options.transform : undefined,
validate: options && options.validate ? true : false,
validateOptions: options && options.validate instanceof Object ? options.validate : undefined
classTransform: options ? options.transform : undefined,
validate: options ? options.validate : undefined,
};
defaultMetadataArgsStorage().params.push(metadata);
};
Expand Down
8 changes: 3 additions & 5 deletions src/decorator/BodyParam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,12 @@ export function BodyParam(name: string, options?: ParamOptions): Function {
method: methodName,
index: index,
type: "body",
reflectedType: format,
name: name,
format: format,
targetType: format,
parse: options ? options.parse : false,
required: options ? options.required : false,
classTransformOptions: options ? options.transform : undefined,
validate: options && options.validate ? true : false,
validateOptions: options && options.validate instanceof Object ? options.validate : undefined
classTransform: options ? options.transform : undefined,
validate: options ? options.validate : undefined,
};
defaultMetadataArgsStorage().params.push(metadata);
};
Expand Down
8 changes: 3 additions & 5 deletions src/decorator/CookieParam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,12 @@ export function CookieParam(name: string, options?: ParamOptions) {
method: methodName,
index: index,
type: "cookie",
reflectedType: format,
name: name,
format: format,
targetType: format,
parse: options ? options.parse : false,
required: options ? options.required : false,
classTransformOptions: options ? options.transform : undefined,
validate: options && options.validate ? true : false,
validateOptions: options && options.validate instanceof Object ? options.validate : undefined
classTransform: options ? options.transform : undefined,
validate: options ? options.validate : undefined,
};
defaultMetadataArgsStorage().params.push(metadata);
};
Expand Down
3 changes: 1 addition & 2 deletions src/decorator/CookieParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ export function CookieParams() {
method: methodName,
index: index,
type: "cookie",
reflectedType: format,
format: format,
targetType: format,
parse: false,
required: false,
};
Expand Down
8 changes: 3 additions & 5 deletions src/decorator/HeaderParam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,12 @@ export function HeaderParam(name: string, options?: ParamOptions): Function {
method: methodName,
index: index,
type: "header",
reflectedType: format,
name: name,
format: format,
targetType: format,
parse: options ? options.parse : false,
required: options ? options.required : false,
classTransformOptions: options ? options.transform : undefined,
validate: options && options.validate ? true : false,
validateOptions: options && options.validate instanceof Object ? options.validate : undefined
classTransform: options ? options.transform : undefined,
validate: options ? options.validate : undefined,
};
defaultMetadataArgsStorage().params.push(metadata);
};
Expand Down