Skip to content

Commit

Permalink
- Replaced the decorators metadata with reflect-metadata.
Browse files Browse the repository at this point in the history
  • Loading branch information
etiennenoel committed Jan 13, 2024
1 parent d04adc2 commit ff4941c
Show file tree
Hide file tree
Showing 11 changed files with 85 additions and 65 deletions.
4 changes: 2 additions & 2 deletions packages/common/src/utils/metadata.util.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@ describe("Metadata Util", () =>{
expect(routeContextAtTargetLevel["keyname"]).toBe("value")


MetadataUtil.setToRouteContext("keyname", "value", ClassWithEmptyMethod, "emptyMethod");
MetadataUtil.setToRouteContext("keyname1", "value1", ClassWithEmptyMethod, "emptyMethod");

const routeContextAtPropertyLevel = MetadataUtil.getRouteContext(ClassWithEmptyMethod, "emptyMethod");

expect(typeof routeContextAtPropertyLevel).toBe("object");
expect(Object.keys(routeContextAtPropertyLevel).length).toBe(1)
expect(routeContextAtPropertyLevel["keyname"]).toBe("value")
expect(routeContextAtPropertyLevel["keyname1"]).toBe("value1")
})

it("should append to the Target metadata", () => {
Expand Down
5 changes: 3 additions & 2 deletions packages/common/src/utils/metadata.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class MetadataUtil {
let routeContext;

if(propertyKey === undefined) {
routeContext = Reflect.getMetadata(routeContextMetadataKeynameConstant, target);
routeContext = Reflect.getMetadata(routeContextMetadataKeynameConstant, target.prototype);
} else {
routeContext = Reflect.getMetadata(routeContextMetadataKeynameConstant, target, propertyKey);
}
Expand All @@ -72,7 +72,8 @@ export class MetadataUtil {
routeContext[metadataKeyname] = metadata;

if(propertyKey === undefined) {
Reflect.defineMetadata(routeContextMetadataKeynameConstant, routeContext, target)
// When there are no properties, the metadata is defined on the prototype.
Reflect.defineMetadata(routeContextMetadataKeynameConstant, routeContext, target.prototype)
} else {
Reflect.defineMetadata(routeContextMetadataKeynameConstant, routeContext, target, propertyKey);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/networking/src/decorators/controller.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const controller = (basePath: string) => {
*/
constructor: Function
) => {
Reflect.defineMetadata(basePathMetadataKeyname, basePath, constructor);
Reflect.defineMetadata(basePathMetadataKeyname, basePath, constructor.prototype);

// Push the class prototype in the controllerRegistry that is used to instantiate all the controllers for the router.
controllerRegistry.push(constructor.prototype)
Expand Down
4 changes: 4 additions & 0 deletions packages/networking/src/decorators/route.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export const route = (httpMethod: HttpMethod | string, path: string) => {
}

Reflect.defineMetadata(routeMetadataKeyname, route, target, propertyKey);
const a = Reflect.hasMetadata(routeMetadataKeyname, target, propertyKey);
const b = routeMetadataKeyname;
const c = target;
const d = propertyKey;

MetadataUtil.appendToTargetMetadata(target, routesControllerMetadataKeyname, propertyKey);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {NetworkingModuleKeyname} from "../networking.module.keyname";
import {MethodRouterNode} from "../nodes/method-router.node";
import {RequestInterceptorInterface} from "../interfaces/request-interceptor.interface";
import {injectable} from "tsyringe";
import {responseHeaderMetadataKeyname} from "../decorators/response-header.decorator";
/**
* The Response Interceptor intercepts the response of the router by adding the response headers specified by the response header decorator.
* It is tagged as a RequestInterceptor so it can be automatically injected with the all the other RequestInterceptor.
Expand All @@ -19,8 +20,8 @@ export class ResponseHeadersInterceptor implements RequestInterceptorInterface {
* @param methodNode The methode node.
*/
async interceptResponse(response: Response, request: Request, methodNode?: MethodRouterNode): Promise<Response> {
if(methodNode && methodNode.route.context && methodNode.route.context.hasOwnProperty("responseHeaders")){
response.setHeaders({...response.headers, ...methodNode.route.context.responseHeaders});
if(methodNode && methodNode.route.context && methodNode.route.context.hasOwnProperty(responseHeaderMetadataKeyname)){
response.setHeaders({...response.headers, ...methodNode.route.context[responseHeaderMetadataKeyname]});
}

return response;
Expand Down
93 changes: 49 additions & 44 deletions packages/networking/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import {Route} from "./models/route";
import {MethodRouterNode} from "./nodes/method-router.node";
import {ForbiddenHttpError} from "./errors/forbidden.http-error";
import {ControllerMethodParameterDecoratorResolver} from "./resolvers/controller-method-parameter-decorator.resolver";
import { URL } from 'url';
import {URL} from 'url';
import {
tag,
HttpMethod,
IdentityInterface,
ServiceDefinitionTagEnum,
Request,
Response,
MetadataUtil
MetadataUtil, routeContextMetadataKeynameConstant
} from "@pristine-ts/common";
import {AuthenticationManagerInterface, AuthorizerManagerInterface} from "@pristine-ts/security";
import {LogHandlerInterface} from "@pristine-ts/logging";
Expand Down Expand Up @@ -53,11 +53,11 @@ export class Router implements RouterInterface {
* @param authorizerManager The authorizer manager to validate authorization.
* @param authenticationManager The authentication manager to validate authentication.
*/
public constructor( @inject("LogHandlerInterface") private readonly loghandler: LogHandlerInterface,
private readonly controllerMethodParameterDecoratorResolver: ControllerMethodParameterDecoratorResolver,
@inject("AuthorizerManagerInterface") private readonly authorizerManager: AuthorizerManagerInterface,
@inject("AuthenticationManagerInterface") private readonly authenticationManager: AuthenticationManagerInterface,
private readonly cache: RouterCache) {
public constructor(@inject("LogHandlerInterface") private readonly loghandler: LogHandlerInterface,
private readonly controllerMethodParameterDecoratorResolver: ControllerMethodParameterDecoratorResolver,
@inject("AuthorizerManagerInterface") private readonly authorizerManager: AuthorizerManagerInterface,
@inject("AuthenticationManagerInterface") private readonly authenticationManager: AuthenticationManagerInterface,
private readonly cache: RouterCache) {
}

/**
Expand All @@ -77,17 +77,17 @@ export class Router implements RouterInterface {
* @param level The level at which we are at.
*/
private getRouteTreeLevel(node: RouterNode, message: string, level: number): string {
for(const child of node.children) {
for(let i = 0; i < level; i++){
message+="\t";
for (const child of node.children) {
for (let i = 0; i < level; i++) {
message += "\t";
}
if(child instanceof PathRouterNode){
message+="--" + child.path + "\n";
if (child instanceof PathRouterNode) {
message += "--" + child.path + "\n";
}
if(child instanceof MethodRouterNode){
message+="--" + child.method + "\n";
if (child instanceof MethodRouterNode) {
message += "--" + child.method + "\n";
}
message = this.getRouteTreeLevel(child, message, level+1);
message = this.getRouteTreeLevel(child, message, level + 1);
}
return message;
}
Expand All @@ -112,39 +112,35 @@ export class Router implements RouterInterface {
// * @private
// */
public setup() {
if(this.setupCompleted) {
if (this.setupCompleted) {
return;
}

// Loop over all the controllers and control the Route Tree
controllerRegistry.forEach(controller => {
if(Reflect.hasMetadata(basePathMetadataKeyname, controller) === false) {
return;
}

let basePath: string = Reflect.getMetadata(basePathMetadataKeyname, controller);
let basePath: string = Reflect.getOwnMetadata(basePathMetadataKeyname, controller) ?? "";

// Clean the base path by removing trailing slashes.
if (basePath.endsWith("/")) {
basePath = basePath.slice(0, basePath.length - 1);
}

const routePropertyKeys = Reflect.getMetadata(routesControllerMetadataKeyname, controller);
const routePropertyKeys: string[] = Reflect.getMetadata(routesControllerMetadataKeyname, controller);

for (const methodPropertyKey in routePropertyKeys) {
if(Reflect.hasMetadata(routeMetadataKeyname, controller, methodPropertyKey) === false) {
continue;
routePropertyKeys.forEach(methodPropertyKey => {
if (Reflect.hasMetadata(routeMetadataKeyname, controller, methodPropertyKey) === false) {
return;
}


// Retrieve the "RouteMethodDecorator" object assigned by the @route decorator at .route
const routeMethodDecorator: RouteMethodDecorator = Reflect.getMetadata(routeMetadataKeyname, controller, methodPropertyKey);

// Build the Route object that will be used by the router to dispatch a request to
// the appropriate controller method
const route = new Route(controller.constructor, routeMethodDecorator.methodKeyname);
route.methodArguments = MetadataUtil.getMethodParametersMetadata(controller, methodPropertyKey);
route.context = mergeWith({}, MetadataUtil.getRouteContext(controller), MetadataUtil.getRouteContext(controller, methodPropertyKey));
const context = mergeWith({}, MetadataUtil.getRouteContext(controller.constructor), MetadataUtil.getRouteContext(controller, methodPropertyKey));
route.context = context;

// Build the proper path
let path = routeMethodDecorator.path;
Expand All @@ -163,7 +159,9 @@ export class Router implements RouterInterface {

// Register the route
this.register(routePath, routeMethodDecorator.httpMethod, route);
}

})

})

this.setupCompleted = true;
Expand Down Expand Up @@ -204,7 +202,7 @@ export class Router implements RouterInterface {

let methodNode: MethodRouterNode | undefined = cachedRouterRoute?.methodNode;

if(methodNode === undefined) {
if (methodNode === undefined) {
const methodNodeSpan = tracingManager.startSpan(SpanKeynameEnum.RouterFindMethodRouterNode, SpanKeynameEnum.RouterRequestExecution);
// Retrieve the node to have information about the controller
methodNode = this.root.find(splitPath, request.httpMethod) as MethodRouterNode;
Expand All @@ -222,7 +220,7 @@ export class Router implements RouterInterface {
}, NetworkingModuleKeyname);

// If node doesn't exist, throw a 404 error
if(methodNode === null) {
if (methodNode === null) {
this.loghandler.error("Cannot find the path", {
rootNode: this.root,
request,
Expand All @@ -236,7 +234,7 @@ export class Router implements RouterInterface {
// Get the route parameters
const routeParameters = cachedRouterRoute?.routeParameters ?? (methodNode.parent as PathRouterNode).getRouteParameters(splitPath.reverse());

if(cachedRouterRoute !== undefined && cachedRouterRoute.routeParameters === undefined) {
if (cachedRouterRoute !== undefined && cachedRouterRoute.routeParameters === undefined) {
cachedRouterRoute.routeParameters = routeParameters;
}

Expand Down Expand Up @@ -270,7 +268,7 @@ export class Router implements RouterInterface {
}, NetworkingModuleKeyname);

// Todo: check if the error is an UnauthorizedHttpError, else create one.
if(error instanceof ForbiddenHttpError === false){
if (error instanceof ForbiddenHttpError === false) {
error = new ForbiddenHttpError("You are not allowed to access this.");
}

Expand All @@ -282,7 +280,7 @@ export class Router implements RouterInterface {
try {

// Verify that the identity making the request is authorized to make such a request
if(await this.authorizerManager.isAuthorized(request, methodNode.route.context, container, identity) === false) {
if (await this.authorizerManager.isAuthorized(request, methodNode.route.context, container, identity) === false) {
this.loghandler.error("User not authorized to access this url.", {
request,
context: methodNode.route.context,
Expand All @@ -308,7 +306,7 @@ export class Router implements RouterInterface {
let resolvedMethodArguments: any[] | undefined = this.cache.getCachedControllerMethodArguments(cacheKeyname, interceptedRequest);

// If the cache did not contain the cached controller method arguments
if(resolvedMethodArguments === undefined) {
if (resolvedMethodArguments === undefined) {
this.loghandler.debug("Resolved method arguments were not cached, currently resolving", {
request,
interceptedRequest,
Expand Down Expand Up @@ -346,7 +344,7 @@ export class Router implements RouterInterface {

let returnedResponse: Response;
// If the response is already a Response object, return the response
if(response instanceof Response) {
if (response instanceof Response) {
this.loghandler.debug("Router - Response returned by the controller is a Response object", {
response,
}, NetworkingModuleKeyname)
Expand Down Expand Up @@ -375,8 +373,7 @@ export class Router implements RouterInterface {

routerRequestExecutionSpan.end();
return resolve(interceptedResponse);
}
catch (error) {
} catch (error) {
this.loghandler.error("Router - There was an error trying to execute the request in the router", {
error,
}, NetworkingModuleKeyname)
Expand All @@ -399,7 +396,7 @@ export class Router implements RouterInterface {
* @param methodNode
* @private
*/
private async executeRequestInterceptors( request: Request, container: DependencyContainer, methodNode: MethodRouterNode): Promise<Request> {
private async executeRequestInterceptors(request: Request, container: DependencyContainer, methodNode: MethodRouterNode): Promise<Request> {
this.loghandler.debug("Router - Request Interceptors - Start", {
request,
methodNode,
Expand All @@ -417,7 +414,10 @@ export class Router implements RouterInterface {
// So, we have to verify that the method exists, and if it doesn't we throw
if (typeof interceptor.interceptRequest === "undefined") {
// Simply log a message for now that the interceptors doesn't implement the 'interceptRequest' method.
this.loghandler.debug("The Request Interceptor doesn't implement the interceptRequest method.", {name: interceptor.constructor.name, interceptor});
this.loghandler.debug("The Request Interceptor doesn't implement the interceptRequest method.", {
name: interceptor.constructor.name,
interceptor
});
continue;
}

Expand Down Expand Up @@ -469,7 +469,10 @@ export class Router implements RouterInterface {
// So, we have to verify that the method exists, and if it doesn't we throw
if (typeof interceptor.interceptResponse === "undefined") {
// Simply log a message for now that the interceptors doesn't implement the 'interceptResponse' method.
this.loghandler.debug("Router - The Request Interceptor doesn't implement the interceptResponse method.", {name: interceptor.constructor.name, interceptor}, NetworkingModuleKeyname);
this.loghandler.debug("Router - The Request Interceptor doesn't implement the interceptResponse method.", {
name: interceptor.constructor.name,
interceptor
}, NetworkingModuleKeyname);
continue;
}

Expand Down Expand Up @@ -511,7 +514,7 @@ export class Router implements RouterInterface {

// Execute all the request interceptors
let interceptedResponse = new Response();
if(error instanceof HttpError) {
if (error instanceof HttpError) {
interceptedResponse.status = error.httpStatus;
interceptedResponse.body = {
name: error.name,
Expand All @@ -520,8 +523,7 @@ export class Router implements RouterInterface {
errors: error.errors,
extra: error.extra,
}
}
else {
} else {
interceptedResponse.status = 500;
interceptedResponse.body = {name: error.name, message: error.message, stack: error.stack};
}
Expand All @@ -537,7 +539,10 @@ export class Router implements RouterInterface {
// So, we have to verify that the method exists, and if it doesn't we throw
if (typeof interceptor.interceptError === "undefined") {
// Simply log a message for now that the interceptors doesn't implement the 'interceptError' method.
this.loghandler.info("The Request Interceptor doesn't implement the interceptError method.", {name: interceptor.constructor.name, interceptor});
this.loghandler.info("The Request Interceptor doesn't implement the interceptError method.", {
name: interceptor.constructor.name,
interceptor
});
continue;
}

Expand Down
6 changes: 4 additions & 2 deletions packages/security/src/managers/authentication.manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {AuthenticatorFactory} from "../factories/authenticator.factory";
import {SecurityModuleKeyname} from "../security.module.keyname";
import {IdentityProviderInterface} from "../interfaces/identity-provider.interface";
import {Request} from "@pristine-ts/common";
import {authenticatorMetadataKeyname} from "../decorators/authenticator.decorator";

/**
* The authentication manager provides authentication by returning the identity executing the action.
Expand Down Expand Up @@ -37,13 +38,14 @@ export class AuthenticationManager implements AuthenticationManagerInterface {
* @param container The dependency container from which to resolve the authenticator.
*/
public async authenticate(request: Request, routeContext: any, container: DependencyContainer): Promise<IdentityInterface | undefined> {
if(!routeContext || routeContext.authenticator === undefined) {
const authenticator = routeContext[authenticatorMetadataKeyname];
if(!routeContext || authenticator === undefined) {
return undefined;
}

let identity: IdentityInterface | undefined;

const authenticatorContext: AuthenticatorContextInterface = routeContext.authenticator;
const authenticatorContext: AuthenticatorContextInterface = authenticator;

try {
const instantiatedAuthenticator: AuthenticatorInterface = this.authenticatorFactory.fromContext(authenticatorContext, container);
Expand Down
Loading

0 comments on commit ff4941c

Please sign in to comment.