Skip to content

Commit

Permalink
feat(common): Add UseBeforeEach decorator.
Browse files Browse the repository at this point in the history
  • Loading branch information
Romakita committed Jun 14, 2019
1 parent 6b31211 commit 18be9cb
Show file tree
Hide file tree
Showing 15 changed files with 391 additions and 87 deletions.
8 changes: 3 additions & 5 deletions docs/docs/snippets/interceptors/interceptor-example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,14 @@ export class MyInterceptor implements IInterceptor {
*
* opts: Static params that can be provided when the interceptor is attached to a specific method
*/
intercept(context: IInterceptorContext<any>, next: IInterceptorNextHandler) {
async intercept(context: IInterceptorContext<any>, next: IInterceptorNextHandler) {
console.log(`the method ${context.propertyKey} will be executed with args ${context.args} and static data ${context.options}`);
// let the original method proceed
const result = context.next();
// let the original method by calling next function
const result = await next();

console.log(`the method was executed, and returned ${result}`);

// must return the returned value back to the caller
// the retValue might be a promise in which case you can use .then to chain other code logic
// or you can use async aroundInvoke and await ctx.proceed() to execute the code in linear fashion
return result;
}
}
3 changes: 1 addition & 2 deletions docs/docs/snippets/interceptors/interceptor-usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import {MyInterceptor} from "../interceptors/MyInterceptor";

export class MyService {
// MyInterceptor is going to be used to intercept this method whenever called
// 'simple data' is static data that will be passed as second arg the the interceptor aroundInvoke
// this can be any data, you can pass array or object for that matter
// 'simple data' is static data that will be passed as second arg to the interceptor.intercept method
@Intercept(MyInterceptor, "simple data")
mySimpleMethod() {
console.log("the simple method is executed");
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/mvc/decorators/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Method
export * from "./method/use";
export * from "./method/useBefore";
export * from "./method/useBeforeEach";
export * from "./method/useAfter";
export * from "./method/useAuth";
export * from "./method/route";
Expand Down
20 changes: 11 additions & 9 deletions packages/common/src/mvc/decorators/method/useAfter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {getDecoratorType, Store, Type} from "@tsed/core";
import {DecoratorParameters, getDecoratorType, StoreMerge, UnsupportedDecoratorType} from "@tsed/core";
import {EndpointRegistry} from "../../registries/EndpointRegistry";

/**
Expand All @@ -22,15 +22,17 @@ import {EndpointRegistry} from "../../registries/EndpointRegistry";
* @endpoint
*/
export function UseAfter(...args: any[]): Function {
return <T>(target: Type<any>, targetKey?: string, descriptor?: TypedPropertyDescriptor<T>): TypedPropertyDescriptor<T> | void => {
if (getDecoratorType([target, targetKey, descriptor]) === "method") {
EndpointRegistry.useAfter(target, targetKey!, args);
return <T>(...decoratorArgs: DecoratorParameters): TypedPropertyDescriptor<T> | void => {
switch (getDecoratorType(decoratorArgs, true)) {
case "method":
EndpointRegistry.useAfter(decoratorArgs[0], decoratorArgs[1]!, args);

return descriptor;
return decoratorArgs[2] as any;
case "class":
StoreMerge("middlewares", {useAfter: args})(...decoratorArgs);
break;
default:
throw new UnsupportedDecoratorType(UseAfter, decoratorArgs);
}

Store.from(target).merge("middlewares", {
useAfter: args
});
};
}
22 changes: 12 additions & 10 deletions packages/common/src/mvc/decorators/method/useBefore.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {getDecoratorType, Store, Type} from "@tsed/core";
import {DecoratorParameters, getDecoratorType, StoreMerge, UnsupportedDecoratorType} from "@tsed/core";
import {EndpointRegistry} from "../../registries/EndpointRegistry";

/**
Expand All @@ -7,7 +7,7 @@ import {EndpointRegistry} from "../../registries/EndpointRegistry";
*
* ```typescript
* @Controller('/')
* @UseBefore(Middleware1)
* @UseBefore(Middleware1) // called only one time before all endpoint
* export class Ctrl {
*
* @Get('/')
Expand All @@ -22,15 +22,17 @@ import {EndpointRegistry} from "../../registries/EndpointRegistry";
* @endpoint
*/
export function UseBefore(...args: any[]): Function {
return <T>(target: Type<any>, targetKey?: string, descriptor?: TypedPropertyDescriptor<T>): TypedPropertyDescriptor<T> | void => {
if (getDecoratorType([target, targetKey, descriptor]) === "method") {
EndpointRegistry.useBefore(target, targetKey!, args);
return <T>(...decoratorArgs: DecoratorParameters): TypedPropertyDescriptor<T> | void => {
switch (getDecoratorType(decoratorArgs, true)) {
case "method":
EndpointRegistry.useBefore(decoratorArgs[0], decoratorArgs[1]!, args);

return descriptor;
return decoratorArgs[2] as any;
case "class":
StoreMerge("middlewares", {useBefore: args})(...decoratorArgs);
break;
default:
throw new UnsupportedDecoratorType(UseBefore, decoratorArgs);
}

Store.from(target).merge("middlewares", {
useBefore: args
});
};
}
38 changes: 38 additions & 0 deletions packages/common/src/mvc/decorators/method/useBeforeEach.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {decorateMethodsOf, DecoratorParameters, getDecoratorType, UnsupportedDecoratorType} from "@tsed/core";
import {UseBefore} from "./UseBefore";

/**
* Mounts the specified middleware function or functions at the specified path: the middleware function is executed when
* the base of the requested path matches `path.
*
* ```typescript
* @Controller('/')
* @UseBeforeEach(Middleware1) // Called before each endpoint
* export class Ctrl {
*
* @Get('/')
* get() { }
* }
*
* ```
*
* @returns {Function}
* @param args
* @decorator
* @endpoint
*/
export function UseBeforeEach(...args: any[]): Function {
return <T>(...decoratorArgs: DecoratorParameters): TypedPropertyDescriptor<T> | void => {
switch (getDecoratorType(decoratorArgs, true)) {
case "method":
return UseBefore(...args)(...decoratorArgs);

case "class":
decorateMethodsOf(decoratorArgs[0], UseBefore(...args));
break;

default:
throw new UnsupportedDecoratorType(UseBeforeEach, decoratorArgs);
}
};
}
93 changes: 72 additions & 21 deletions packages/common/test/mvc/decorators/method/useAfter.spec.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,100 @@
import {decoratorArgs, descriptorOf, Store} from "@tsed/core";
import {expect} from "chai";
import {prototypeOf, Store, UnsupportedDecoratorType} from "@tsed/core";
import * as Sinon from "sinon";
import {EndpointRegistry, UseAfter} from "../../../../src/mvc";

class Test {
test() {
class CustomMiddleware {
use() {
}
}

describe("UseAfter()", () => {
describe("when the decorator is use on a method", () => {
before(() => {
this.endpointRegistryStub = Sinon.stub(EndpointRegistry, "useAfter");
describe("when the decorator is use on a class", () => {
class Test {
test() {
}
}

this.returns = UseAfter(() => {
})(...decoratorArgs(Test, "test"));
before(() => {
Sinon.stub(EndpointRegistry, "useAfter");
});

after(() => {
this.endpointRegistryStub.restore();
// @ts-ignore
EndpointRegistry.useAfter.restore();
});

afterEach(() => {
// @ts-ignore
EndpointRegistry.useAfter.resetHistory();
});

it("should add the middleware on the use stack", () => {
this.endpointRegistryStub.should.be.calledWithExactly(Test, "test", [Sinon.match.func]);
// WHEN
UseAfter(CustomMiddleware)(Test);

// THEN
Store.from(Test).get("middlewares").should.deep.eq({useAfter: [CustomMiddleware]});
});
});
describe("when the decorator is use on a method", () => {
before(() => {
Sinon.stub(EndpointRegistry, "useAfter");
});

after(() => {
// @ts-ignore
EndpointRegistry.useAfter.restore();
});

afterEach(() => {
// @ts-ignore
EndpointRegistry.useAfter.resetHistory();
});

it("should return a descriptor", () => {
this.returns.should.be.deep.eq(descriptorOf(Test, "test"));
it("should add the middleware on the use stack", () => {
// WHEN
class Test {
@UseAfter(CustomMiddleware)
test() {
}
}

// THEN
EndpointRegistry.useAfter.should.be.calledWithExactly(prototypeOf(Test), "test", [CustomMiddleware]);
});
});
describe("when the decorator is use in another way", () => {
class Test {
test() {
}
}

describe("when the decorator is use on a class", () => {
before(() => {
this.returns = UseAfter(() => {
})(Test);
Sinon.stub(EndpointRegistry, "useAfter");
});

after(() => {
// @ts-ignore
EndpointRegistry.useAfter.restore();
});

this.store = Store.from(Test).get("middlewares");
afterEach(() => {
// @ts-ignore
EndpointRegistry.useAfter.resetHistory();
});

it("should add the middleware on the use stack", () => {
expect(this.store.useAfter[0]).to.be.a("function");
});
// WHEN
let actualError;
try {
UseAfter(CustomMiddleware)(Test, "property");
} catch (er) {
actualError = er;
}

it("should return nothing", () => {
expect(this.returns).to.eq(undefined);
// THEN
actualError.should.instanceOf(UnsupportedDecoratorType);
actualError.message.should.eq("UseAfter cannot used as property.static at Test.property");
});
});
});
95 changes: 74 additions & 21 deletions packages/common/test/mvc/decorators/method/useBefore.spec.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,100 @@
import {decoratorArgs, descriptorOf, Store} from "@tsed/core";
import {expect} from "chai";
import {prototypeOf, Store, UnsupportedDecoratorType} from "@tsed/core";
import * as Sinon from "sinon";
import {EndpointRegistry, UseBefore} from "../../../../src/mvc";

class Test {
test() {
class CustomMiddleware {
use() {
}
}

describe("UseBefore()", () => {
describe("when the decorator is use on a method", () => {
before(() => {
this.endpointRegistryStub = Sinon.stub(EndpointRegistry, "useBefore");
describe("when the decorator is use on a class", () => {
class Test {
test() {
}
}

this.returns = UseBefore(() => {
})(...decoratorArgs(Test, "test"));
before(() => {
Sinon.stub(EndpointRegistry, "useBefore");
});

after(() => {
this.endpointRegistryStub.restore();
// @ts-ignore
EndpointRegistry.useBefore.restore();
});

afterEach(() => {
// @ts-ignore
EndpointRegistry.useBefore.resetHistory();
});

it("should add the middleware on the use stack", () => {
this.endpointRegistryStub.should.be.calledWithExactly(Test, "test", [Sinon.match.func]);
// WHEN
UseBefore(CustomMiddleware)(Test);

// THEN
Store.from(Test).get("middlewares").should.deep.eq({useBefore: [CustomMiddleware]});
});
});
describe("when the decorator is use on a method", () => {
before(() => {
Sinon.stub(EndpointRegistry, "useBefore");
});

after(() => {
// @ts-ignore
EndpointRegistry.useBefore.restore();
});

it("should return a descriptor", () => {
this.returns.should.be.deep.eq(descriptorOf(Test, "test"));
afterEach(() => {
// @ts-ignore
EndpointRegistry.useBefore.resetHistory();
});

it("should add the middleware on the use stack", () => {
// WHEN
class Test {
@UseBefore(CustomMiddleware)
test() {
}
}

// THEN
EndpointRegistry.useBefore.should.be.calledWithExactly(prototypeOf(Test), "test", [CustomMiddleware]);
});
});
describe("when the decorator is use in another way", () => {
class Test {
test() {
}
}

describe("when the decorator is use on a class", () => {
before(() => {
this.returns = UseBefore(() => {
})(Test);
Sinon.stub(EndpointRegistry, "useBefore");
});

this.store = Store.from(Test).get("middlewares");
after(() => {
// @ts-ignore
EndpointRegistry.useBefore.restore();
});
it("should add the middleware on the use stack", () => {
expect(this.store.useBefore[0]).to.be.a("function");

afterEach(() => {
// @ts-ignore
EndpointRegistry.useBefore.resetHistory();
});
it("should return nothing", () => {
expect(this.returns).to.eq(undefined);

it("should add the middleware on the use stack", () => {
// WHEN
let actualError;
try {
UseBefore(CustomMiddleware)(Test, "property");
} catch (er) {
actualError = er;
}

// THEN
actualError.should.instanceOf(UnsupportedDecoratorType);
actualError.message.should.eq("UseBefore cannot used as property.static at Test.property");
});
});
});
Loading

0 comments on commit 18be9cb

Please sign in to comment.