typenexus
Version:
TypeNexus is a good tool for API encapsulation and management. It provides a clean and lightweight way to package TypeORM functionality, helping you build applications faster while reducing template code redundancy and type conversion work.
225 lines (197 loc) • 7.32 kB
text/typescript
import { ActionType } from './types/ActionType.js';
import { ControllerMetadata } from './ControllerMetadata.js';
import { ActionMetadataArgs } from './args/ActionMetadataArgs.js';
import { ResponseHandlerMetadata } from './ResponseHandleMetadata.js';
import { Action } from '../Action.js';
import { ParamMetadata } from './ParamMetadata.js';
import { ParamConstructorMetadata } from './ParamConstructorMetadata.js';
import { UseMetadata } from './UseMetadata.js';
import { TypeNexusOptions } from '../DriverOptions.js';
/**
* Action metadata.
*/
export class ActionMetadata {
/**
* Action's controller.
*/
controllerMetadata: ControllerMetadata;
/**
* Class on which's method this action is attached.
*/
target: Function;
/**
* Object's method that will be executed on this action.
*/
method: string;
/**
* Route to be registered for the action.
*/
route: string | RegExp;
/**
* Indicates if this action uses Body.
*/
isBodyUsed: boolean;
/**
* Indicates if this action uses Uploaded File.
*/
isFileUsed: boolean;
/**
* Indicates if this action uses Uploaded Files.
*/
isFilesUsed: boolean;
/**
* Extra options used by @Body decorator.
*/
bodyExtraOptions: any;
/**
* Action type represents http method used for the registered route. Can be one of the value defined in ActionTypes
* class.
*/
type: ActionType;
/**
* Action's parameters.
*/
params: ParamMetadata[];
/**
* Action's constructor parameters.
*/
paramsConstructor: ParamConstructorMetadata[] = [];
/**
* Full route to this action (includes controller base route).
*/
fullRoute: string | RegExp;
/**
* Indicates if this action uses Authorized decorator.
*/
isAuthorizedUsed: boolean;
/**
* Roles set by @Authorized decorator.
*/
authorizedRoles: any[];
/**
* Indicates if controller of this action is json-typed.
*/
isJsonTyped: boolean;
/**
* Action's use metadatas.
*/
uses: UseMetadata[];
/**
* Http code to be used on undefined action returned content.
*/
undefinedResultCode: number | Function;
/**
* Http code to be used on null action returned content.
*/
nullResultCode: number | Function;
/**
* Http code to be set on successful response.
*/
successHttpCode: number;
/**
* Specifies redirection url for this action.
*/
redirect: string;
/**
* Rendered template to be used for this controller action.
*/
renderedTemplate: string;
/**
* Response headers to be set.
*/
headers: { [name: string]: any };
constructor(
controllerMetadata: ControllerMetadata,
args: ActionMetadataArgs,
private globalOptions: TypeNexusOptions,
) {
this.controllerMetadata = controllerMetadata;
this.route = args.route;
this.target = args.target;
this.method = args.method;
this.type = args.type;
}
/**
* Builds everything action metadata needs.
* Action metadata can be used only after its build.
*/
build(responseHandlers: ResponseHandlerMetadata[]) {
const authorizedHandler = responseHandlers.find((handler) => handler.type === 'authorized');
const contentTypeHandler = responseHandlers.find((handler) => handler.type === 'content-type');
const undefinedResultHandler = responseHandlers.find((handler) => handler.type === 'on-undefined');
const nullResultHandler = responseHandlers.find((handler) => handler.type === 'on-null');
const successCodeHandler = responseHandlers.find((handler) => handler.type === 'success-code');
const redirectHandler = responseHandlers.find((handler) => handler.type === 'redirect');
const renderedTemplateHandler = responseHandlers.find((handler) => handler.type === 'rendered-template');
const bodyParam = this.params.find((param) => param.type === 'body');
if (successCodeHandler) this.successHttpCode = successCodeHandler.value;
if (redirectHandler) this.redirect = redirectHandler.value;
if (renderedTemplateHandler) this.renderedTemplate = renderedTemplateHandler.value;
this.undefinedResultCode = undefinedResultHandler
? undefinedResultHandler.value
: this.globalOptions.defaults && this.globalOptions.defaults.undefinedResultCode;
this.nullResultCode = nullResultHandler
? nullResultHandler.value
: this.globalOptions.defaults && this.globalOptions.defaults.nullResultCode;
this.bodyExtraOptions = bodyParam ? bodyParam.extraOptions : undefined;
this.isBodyUsed = !!this.params.find((param) => param.type === 'body' || param.type === 'body-param');
this.isFilesUsed = !!this.params.find((param) => param.type === 'files');
this.isFileUsed = !!this.params.find((param) => param.type === 'file');
this.isJsonTyped =
contentTypeHandler !== undefined
? /json/.test(contentTypeHandler.value)
: this.controllerMetadata.type === 'json';
this.fullRoute = this.buildFullRoute();
this.headers = this.buildHeaders(responseHandlers);
this.isAuthorizedUsed = this.controllerMetadata.isAuthorizedUsed || !!authorizedHandler;
this.authorizedRoles = (this.controllerMetadata.authorizedRoles || []).concat(
(authorizedHandler && authorizedHandler.value) || [],
);
}
/**
* Appends base route to a given regexp route.
*/
static appendBaseRoute(baseRoute: string, route: RegExp | string) {
const prefix = `/${baseRoute}`.replace(/\/+/g, '/');
if (typeof route === 'string') return `${prefix}/${route}`.replace(/\/+/g, '/');
if (!baseRoute || baseRoute === '') return route;
const fullPath = `^${prefix}${route.toString().substring(1)}?$`;
return new RegExp(fullPath, route.flags);
}
/**
* Builds full action route.
*/
private buildFullRoute(): string | RegExp {
if (this.route instanceof RegExp) {
if (this.controllerMetadata.route) {
return ActionMetadata.appendBaseRoute(this.controllerMetadata.route, this.route);
}
return this.route;
}
let path: string = '';
if (this.controllerMetadata.route) path += this.controllerMetadata.route;
if (this.route && typeof this.route === 'string') path += this.route;
return path;
}
/**
* Calls action method.
* Action method is an action defined in a user controller.
*/
callMethod(params: any[], paramsConstructor: any[], action: Action) {
const controllerInstance = this.controllerMetadata.getInstance(action, paramsConstructor);
return controllerInstance[this.method].apply(controllerInstance, params);
}
/**
* Builds action response headers.
*/
private buildHeaders(responseHandlers: ResponseHandlerMetadata[]) {
const locationHandler = responseHandlers.find((handler) => handler.type === 'location');
const contentTypeHandler = responseHandlers.find((handler) => handler.type === 'content-type');
const headerHandlers = responseHandlers.filter((handler) => handler.type === 'header');
const headers: ActionMetadata['headers'] = {};
if (locationHandler) headers['Location'] = locationHandler.value;
if (contentTypeHandler) headers['Content-type'] = contentTypeHandler.value;
if (headerHandlers) headerHandlers.map((handler) => (headers[handler.value] = handler.secondaryValue));
return headers;
}
}