Skip to content

Commit

Permalink
Add @httpOptions decorator to handle OPTIONS HTTP method. (#313)
Browse files Browse the repository at this point in the history
* Add @httpOptions decorator to handle OPTIONS HTTP method.

* Improve unit tests.

* fixed build system

* remove unnecesary paranthesis caused by casting

* remove unused sort package

---------

Co-authored-by: Podaru Dragos <[email protected]>
  • Loading branch information
evandroabukamel and PodaruDragos authored Nov 25, 2023
1 parent 6014305 commit 91ae97a
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 13 deletions.
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ Registers the decorated controller method as a request handler for a particular

### `@SHORTCUT(path, [middleware, ...])`

Shortcut decorators which are simply wrappers for `@httpMethod`. Right now these include `@httpGet`, `@httpPost`, `@httpPut`, `@httpPatch`, `@httpHead`, `@httpDelete`, and `@All`. For anything more obscure, use `@httpMethod` (Or make a PR :smile:).
Shortcut decorators which are simply wrappers for `@httpMethod`. Right now these include `@httpGet`, `@httpPost`, `@httpPut`, `@httpPatch`, `@httpHead`, `@httpDelete`, `@httpOptions`, and `@All`. For anything more obscure, use `@httpMethod` (Or make a PR :smile:).

### `@request()`

Expand Down Expand Up @@ -636,6 +636,25 @@ class Service {
The `BaseMiddleware.bind()` method will bind the `TYPES.TraceIdValue` if it hasn't been bound yet or re-bind if it has
already been bound.
### Dealing with CORS
If you access a route from a browser and experience a CORS problem, in other words, your browser stops at the
OPTIONS request, you need to add a route for that method too. You need to write a method in your controller class to
handle the same route but for OPTIONS method, it can have empty body and no parameters though.
```ts
@controller("/api/example")
class ExampleController extends BaseHttpController {
@httpGet("/:id")
public get(req: Request, res: Response) {
return {};
}

@httpOptions("/:id")
public options() { }
}
```
## Route Map
If we have some controllers like for example:
Expand Down
25 changes: 16 additions & 9 deletions src/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { DecoratorTarget, Middleware, ControllerMetadata, HandlerDecorator,

export const injectHttpContext = inject(TYPE.HttpContext);

export function controller(path: string, ...middleware: Array<Middleware>) {
export function controller(path: string, ...middleware: Middleware[]) {
return (target: NewableFunction): void => {
const currentMetadata: ControllerMetadata = {
middleware,
Expand Down Expand Up @@ -39,57 +39,64 @@ export function controller(path: string, ...middleware: Array<Middleware>) {

export function all(
path: string,
...middleware: Array<Middleware>
...middleware: Middleware[]
): HandlerDecorator {
return httpMethod('all', path, ...middleware);
}

export function httpGet(
path: string,
...middleware: Array<Middleware>
...middleware: Middleware[]
): HandlerDecorator {
return httpMethod('get', path, ...middleware);
}

export function httpPost(
path: string,
...middleware: Array<Middleware>
...middleware: Middleware[]
): HandlerDecorator {
return httpMethod('post', path, ...middleware);
}

export function httpPut(
path: string,
...middleware: Array<Middleware>
...middleware: Middleware[]
): HandlerDecorator {
return httpMethod('put', path, ...middleware);
}

export function httpPatch(
path: string,
...middleware: Array<Middleware>
...middleware: Middleware[]
): HandlerDecorator {
return httpMethod('patch', path, ...middleware);
}

export function httpHead(
path: string,
...middleware: Array<Middleware>
...middleware: Middleware[]
): HandlerDecorator {
return httpMethod('head', path, ...middleware);
}

export function httpDelete(
path: string,
...middleware: Array<Middleware>
...middleware: Middleware[]
): HandlerDecorator {
return httpMethod('delete', path, ...middleware);
}

export function httpOptions(
path: string,
...middleware: Middleware[]
): HandlerDecorator {
return httpMethod('options', path, ...middleware);
}

export function httpMethod(
method: keyof typeof HTTP_VERBS_ENUM,
path: string,
...middleware: Array<Middleware>
...middleware: Middleware[]
): HandlerDecorator {
return (target: DecoratorTarget, key: string): void => {
const metadata: ControllerMethodMetadata = {
Expand Down
4 changes: 2 additions & 2 deletions src/tsconfig-es6.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"outDir": "../es6",
"target": "ES6"
"outDir": "../es",
"target": "ES2015"
},
"extends": "../tsconfig.json"
}
25 changes: 24 additions & 1 deletion test/features/controller_inheritance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Container, injectable } from 'inversify';
import supertest from 'supertest';
import { json, urlencoded } from 'express';
import { InversifyExpressServer } from '../../src/server';
import { controller, httpGet, requestParam, httpDelete, httpPost, httpPut, requestBody, } from '../../src/decorators';
import { controller, httpGet, requestParam, httpDelete, httpPost, httpPut, requestBody, httpOptions, } from '../../src/decorators';
import { cleanUpMetadata } from '../../src/utils';

type ResponseBody = { args: string, status: number };
Expand Down Expand Up @@ -55,6 +55,14 @@ function getDemoServer() {
) {
return { status: `BASE DELETE! ${id}` };
}

@httpOptions('/:id')
public options(
@requestParam('id') id: string
) {
return { status: `BASE OPTIONS! ${id}` };
}

}

@controller('/api/v1/movies')
Expand Down Expand Up @@ -188,6 +196,21 @@ describe('Derived controller', () => {
});
});

it('Can access methods decorated with @httpOptions from parent', (done) => {

const server = getDemoServer();
const id = 5;

void supertest(server).options(`/api/v1/movies/${id}`)
.expect(200)
.then(res => {
const r = res.body as ResponseBody;
expect(r.status).toEqual(`BASE OPTIONS! ${id}`);
done();
});

});

it('Derived controller can have its own methods', done => {
const server = getDemoServer();
const movieId = 5;
Expand Down

0 comments on commit 91ae97a

Please sign in to comment.