Skip to content

Commit

Permalink
Added validationOptions for class-validators
Browse files Browse the repository at this point in the history
  • Loading branch information
iyobo committed Feb 14, 2021
1 parent b3fb08f commit 2efb017
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 54 deletions.
76 changes: 47 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,16 @@ If you would like to contrinute in other ways, Pull requests are also welcome!

## How to Use

First you want to install it:
First you want to install some core dependencies, along with koa-ts-controllers:

`yarn add koa-ts-controllers` or `npm i koa-ts-controllers`
`yarn add koa koa-router koa-bodyparser class-validator koa-ts-controllers`
or
`npm i koa koa-router koa-bodyparser class-validator koa-ts-controllers`

Now have a look at the usage below.

```typescript
--- main.ts
---main.ts

import {bootstrapControllers} from 'koa-ts-controllers';
import Koa from 'koa';
Expand All @@ -59,20 +61,25 @@ await bootstrapControllers(app, {
versions:{
1: 'This version is deprecated and will soon be removed. Consider migrating to version 2 ASAP',
2: true,
dangote: true // great for custom, business client specific endpoint versions
dangote: true // great for custom, business client specific endpoint versions
},
errorHandler: async (err, ctx) { // optional error handler
console.log('err', err);
ctx.body = { error: err}
ctx.status = 500
}
});
errorHandler: async(err, ctx)
{ // optional error handler
console.log('err', err);
ctx.body = {error: err}
ctx.status = 500
}
})
;

app.use(bodyParser());

// ignore this block by setting attachRoutes: true in bootstrapControllers options
app.use(router.routes());
app.use(router.allowedMethods());

...
app.start(3000)
```

It all begins from the `bootstrapControllers` function. This accepts a koa app, and generates endpoints as defines in the controller classes inserted into the `controllers` option.
Expand Down Expand Up @@ -267,26 +274,37 @@ Call this in your main file to initialize your controllers.
`options` is an object of type

```typescript
{
router?: KoaRouter; // an instance of koa-router. if not supplied, will create and add its own router to app.
controllers: Array<string>; // glob to load all controllers e.g [__dirname + '/controllers/**/*.ts']
basePath?: string; // prefix for API URI
import validationOptions from 'jest-validate/build/defaultConfig';

// default: {1: true} The active versions of this API. default is {'1': true} meaning all routes will take
the form /base/v1/controller/action.
versions?: Array<number | string> | object;

// default: false. Set to true to prevent your API from enjoying versioning. i.e path: /api/controller/action.
// Not recommended unless you wish to handle versioning manually in each controller's basePath.
disableVersioning?: boolean

// Default: false. set to true to attach a default koa-body middleware to your koa app.
// If you leave this as false, you must ensure you are attaching a body parser to your koa app somewhere before
// bootstrapserver is called.
initBodyParser?: boolean;

// Default: true. Makes your boom errors better received downstream.
boomifyErrors?: boolean;
{
router ? : KoaRouter; // an instance of koa-router. if not supplied, will create and add its own router to app.
controllers: Array<string>; // glob to load all controllers e.g [__dirname + '/controllers/**/*.ts']
basePath ? : string; // prefix for API URI

// default: {1: true} The active versions of this API. default is {'1': true} meaning all routes will take
// the form /base/v1/controller/action.
versions ? : Array<number | string> | object;

// default: false. Set to true to prevent your API from enjoying versioning. i.e path: /api/controller/action.
// Not recommended unless you wish to handle versioning manually in each controller's basePath.
disableVersioning ? : boolean

// Default: false. set to true to attach a default koa-body middleware to your koa app.
// If you leave this as false, you must ensure you are attaching a body parser to your koa app somewhere before
// bootstrapserver is called.
initBodyParser ? : boolean;

// Default: true. Makes your boom errors better received downstream.
boomifyErrors ? : boolean;

// Default: false. If true, will attach the routes to your koa app for you automatically as opposed to doing it manually
// app.use(router.routes());
// app.use(router.allowedMethods());
attachRoutes ? : boolean

//Default: empty. Here you can set validation options for class-validator which is optionally used to validate endpoint arguments.
// To see options, visit: https://github.com/typestack/class-validator#passing-options
validationOptions ?: ValidatorOptions
}
```

Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,15 @@
"dependencies": {
"@hapi/boom": "^9.0.0",
"class-transformer": "^0.2.3",
"class-transformer-validator": "^0.9.1",
"lodash": "^4.17.15",
"reflect-metadata": "^0.1.13"
},
"peerDependencies": {
"class-validator": "^0.x"
"class-validator": "^0.x",
"koa": "^2.11.0",
"koa-bodyparser": "^4.2.1",
"koa-router": "^8.0.8"
},
"devDependencies": {
"@types/boom": "^7.3.0",
Expand Down
21 changes: 21 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {generateRoutes} from './util/generateRoutes';
import {importClassesFromDirectories} from './util/importClasses';
import Boom from '@hapi/boom';
import {ValidatorOptions} from 'class-validator';

export interface KoaControllerOptions {
controllers: Array<string | Function>;
Expand All @@ -9,6 +11,14 @@ export interface KoaControllerOptions {
router: any;
flow?: Array<Function>;
errorHandler?: Function;

// if true, will attach generated routes to the koa app
attachRoutes?: boolean;

// options for class-validator
validatorOptions?: ValidatorOptions

// attempt to convert number strings to numbers
}

export let options: KoaControllerOptions;
Expand Down Expand Up @@ -64,6 +74,7 @@ export const bootstrapControllers = async (
options = params;
options.versions = options.versions || {1: true};
options.flow = options.flow || [];
options.validatorOptions = options.validatorOptions || {};
options.errorHandler = options.errorHandler || defaultErrorHandler;

/**
Expand Down Expand Up @@ -104,6 +115,16 @@ export const bootstrapControllers = async (
}

await generateRoutes(options.router, options, metadata);

if (options.attachRoutes) {
// Combine routes
app.use(options.router.routes());
app.use(options.router.allowedMethods({
methodNotAllowed: () => Boom.notFound(),
notImplemented: () => Boom.notImplemented(),
throw: true,
}));
}
};

export * from 'class-validator';
Expand Down
6 changes: 6 additions & 0 deletions src/tests/util/flow/flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ export const unauthorizedFlow = async () => {
throw Boom.unauthorized('401 for life');
};

/**
* Test middleware that throws an unexpected error
* @param ctx
* @param next
*/
export const badFlow = async (ctx, next) => {
const a: any = {};
// should fail and throw error.
a.hello.world = 'whoo';
await next();
};
Expand Down
19 changes: 16 additions & 3 deletions src/util/generateRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,21 @@ const argumentInjectorTranslations = {
}
};

/**
* Processes an endpoint-function argument and validates it etc
* @param ctx
* @param index
* @param injectSource
* @param injectOptions
* @param type
* @param options
*/
async function _determineArgument(
ctx: Context,
index,
{injectSource, injectOptions},
type
type,
options: KoaControllerOptions
) {
let result;

Expand All @@ -78,7 +88,9 @@ async function _determineArgument(
// validate if this is a class
if (result && isClass(type)) {
result = await plainToClass(type, result);
const errors = await validate(result); // TODO: wrap around this to trap runtime errors

const errors = await validate(result, options.validatorOptions); // TODO: wrap around this to trap runtime errors

if (errors.length > 0) {
throw boom.badData(
'validation error for argument type: ' + injectSource,
Expand Down Expand Up @@ -174,7 +186,8 @@ async function _generateEndPoints(
ctx,
index,
argumentMeta,
action.argumentTypes[index]
action.argumentTypes[index],
options
);
}
}
Expand Down

0 comments on commit 2efb017

Please sign in to comment.