typescript-rest
Version:
A Library to create RESTFul APIs with Typescript
481 lines • 20.1 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
var bodyParser = require("body-parser");
var cookieParser = require("cookie-parser");
var multer = require("multer");
var metadata = require("./metadata");
var Errors = require("./server-errors");
var _ = require("lodash");
var server_types_1 = require("./server-types");
var server_return_1 = require("./server-return");
var InternalServer = (function () {
function InternalServer(router) {
this.router = router;
}
InternalServer.registerServiceClass = function (target) {
InternalServer.pathsResolved = false;
target = InternalServer.serviceFactory.getTargetClass(target);
var name = target['name'] || target.constructor['name'];
if (!InternalServer.serverClasses.has(name)) {
InternalServer.serverClasses.set(name, new metadata.ServiceClass(target));
InternalServer.inheritParentClass(name);
}
var serviceClass = InternalServer.serverClasses.get(name);
return serviceClass;
};
InternalServer.inheritParentClass = function (name) {
var classData = InternalServer.serverClasses.get(name);
var parent = Object.getPrototypeOf(classData.targetClass.prototype).constructor;
var parentClassData = InternalServer.getServiceClass(parent);
if (parentClassData) {
if (parentClassData.methods) {
parentClassData.methods.forEach(function (value, key) {
classData.methods.set(key, _.cloneDeep(value));
});
}
if (parentClassData.properties) {
parentClassData.properties.forEach(function (value, key) {
classData.properties.set(key, _.cloneDeep(value));
});
}
if (parentClassData.languages) {
for (var _i = 0, _a = parentClassData.languages; _i < _a.length; _i++) {
var lang = _a[_i];
classData.languages.push(lang);
}
}
if (parentClassData.accepts) {
for (var _b = 0, _c = parentClassData.accepts; _b < _c.length; _b++) {
var acc = _c[_b];
classData.accepts.push(acc);
}
}
}
};
InternalServer.registerServiceMethod = function (target, methodName) {
if (methodName) {
InternalServer.pathsResolved = false;
var classData = InternalServer.registerServiceClass(target);
if (!classData.methods.has(methodName)) {
classData.methods.set(methodName, new metadata.ServiceMethod());
}
var serviceMethod = classData.methods.get(methodName);
return serviceMethod;
}
return null;
};
InternalServer.prototype.buildServices = function (types) {
var _this = this;
if (types) {
types = types.map(function (type) { return InternalServer.serviceFactory.getTargetClass(type); });
}
InternalServer.serverClasses.forEach(function (classData) {
classData.methods.forEach(function (method) {
if (_this.validateTargetType(classData.targetClass, types)) {
_this.buildService(classData, method);
}
});
});
InternalServer.pathsResolved = true;
this.handleNotAllowedMethods();
};
InternalServer.prototype.buildService = function (serviceClass, serviceMethod) {
var _this = this;
var handler = function (req, res, next) {
_this.callTargetEndPoint(serviceClass, serviceMethod, req, res, next);
};
if (!serviceMethod.resolvedPath) {
InternalServer.resolveProperties(serviceClass, serviceMethod);
}
var middleware = this.buildServiceMiddleware(serviceMethod);
var args = [serviceMethod.resolvedPath];
args = args.concat(middleware);
args.push(handler);
switch (serviceMethod.httpMethod) {
case server_types_1.HttpMethod.GET:
this.router.get.apply(this.router, args);
break;
case server_types_1.HttpMethod.POST:
this.router.post.apply(this.router, args);
break;
case server_types_1.HttpMethod.PUT:
this.router.put.apply(this.router, args);
break;
case server_types_1.HttpMethod.DELETE:
this.router.delete.apply(this.router, args);
break;
case server_types_1.HttpMethod.HEAD:
this.router.head.apply(this.router, args);
break;
case server_types_1.HttpMethod.OPTIONS:
this.router.options.apply(this.router, args);
break;
case server_types_1.HttpMethod.PATCH:
this.router.patch.apply(this.router, args);
break;
default:
throw Error("Invalid http method for service [" + serviceMethod.resolvedPath + "]");
}
};
InternalServer.getServiceClass = function (target) {
target = InternalServer.serviceFactory.getTargetClass(target);
return InternalServer.serverClasses.get(target['name'] || target.constructor['name']) || null;
};
InternalServer.prototype.validateTargetType = function (targetClass, types) {
if (types && types.length > 0) {
return (types.indexOf(targetClass) > -1);
}
return true;
};
InternalServer.prototype.handleNotAllowedMethods = function () {
var _this = this;
var paths = InternalServer.getPaths();
paths.forEach(function (path) {
var supported = InternalServer.getHttpMethods(path);
var allowedMethods = new Array();
supported.forEach(function (method) {
allowedMethods.push(server_types_1.HttpMethod[method]);
});
var allowed = allowedMethods.join(', ');
_this.router.all(path, function (req, res, next) {
res.set('Allow', allowed);
throw new Errors.MethodNotAllowedError();
});
});
};
InternalServer.prototype.getUploader = function () {
if (!this.upload) {
var options = {};
if (InternalServer.fileDest) {
options.dest = InternalServer.fileDest;
}
if (InternalServer.fileFilter) {
options.fileFilter = InternalServer.fileFilter;
}
if (InternalServer.fileLimits) {
options.limits = InternalServer.fileLimits;
}
if (options.dest) {
this.upload = multer(options);
}
else {
this.upload = multer();
}
}
return this.upload;
};
InternalServer.prototype.buildServiceMiddleware = function (serviceMethod) {
var result = new Array();
if (serviceMethod.mustParseCookies) {
var args = [];
if (InternalServer.cookiesSecret) {
args.push(InternalServer.cookiesSecret);
}
if (InternalServer.cookiesDecoder) {
args.push({ decode: InternalServer.cookiesDecoder });
}
result.push(cookieParser.apply(this, args));
}
if (serviceMethod.mustParseBody) {
if (serviceMethod.bodyParserOptions) {
result.push(bodyParser.json(serviceMethod.bodyParserOptions));
}
else {
result.push(bodyParser.json());
}
// TODO adicionar parser de XML para o body
}
if (serviceMethod.mustParseForms || serviceMethod.acceptMultiTypedParam) {
if (serviceMethod.bodyParserOptions) {
result.push(bodyParser.urlencoded(serviceMethod.bodyParserOptions));
}
else {
result.push(bodyParser.urlencoded({ extended: true }));
}
}
if (serviceMethod.files.length > 0) {
var options_1 = new Array();
serviceMethod.files.forEach(function (fileData) {
if (fileData.singleFile) {
options_1.push({ 'name': fileData.name, 'maxCount': 1 });
}
else {
options_1.push({ 'name': fileData.name });
}
});
result.push(this.getUploader().fields(options_1));
}
return result;
};
InternalServer.prototype.processResponseHeaders = function (serviceMethod, context) {
if (serviceMethod.resolvedLanguages) {
if (serviceMethod.httpMethod === server_types_1.HttpMethod.GET) {
context.response.vary('Accept-Language');
}
context.response.set('Content-Language', context.language);
}
if (serviceMethod.resolvedAccepts) {
context.response.vary('Accept');
}
};
InternalServer.prototype.checkAcceptance = function (serviceMethod, context) {
if (serviceMethod.resolvedLanguages) {
var lang = context.request.acceptsLanguages(serviceMethod.resolvedLanguages);
if (lang) {
context.language = lang;
}
}
else {
var languages = context.request.acceptsLanguages();
if (languages && languages.length > 0) {
context.language = languages[0];
}
}
if (serviceMethod.resolvedAccepts) {
var accept = context.request.accepts(serviceMethod.resolvedAccepts);
if (accept) {
context.accept = accept;
}
else {
throw new Errors.NotAcceptableError('Accept');
}
}
if (!context.language) {
throw new Errors.NotAcceptableError('Accept-Language');
}
};
InternalServer.prototype.createService = function (serviceClass, context) {
var _this = this;
var serviceObject = InternalServer.serviceFactory.create(serviceClass.targetClass);
if (serviceClass.hasProperties()) {
serviceClass.properties.forEach(function (property, key) {
serviceObject[key] = _this.processParameter(property.type, context, property.name, property.propertyType);
});
}
return serviceObject;
};
InternalServer.prototype.callTargetEndPoint = function (serviceClass, serviceMethod, req, res, next) {
var context = new server_types_1.ServiceContext();
context.request = req;
context.response = res;
context.next = next;
this.checkAcceptance(serviceMethod, context);
var serviceObject = this.createService(serviceClass, context);
var args = this.buildArgumentsList(serviceMethod, context);
var toCall = serviceClass.targetClass.prototype[serviceMethod.name] || serviceClass.targetClass[serviceMethod.name];
var result = toCall.apply(serviceObject, args);
this.processResponseHeaders(serviceMethod, context);
this.sendValue(result, res, next);
};
InternalServer.prototype.sendValue = function (value, res, next) {
var _this = this;
switch (typeof value) {
case 'number':
res.send(value.toString());
break;
case 'string':
res.send(value);
break;
case 'boolean':
res.send(value.toString());
break;
case 'undefined':
if (!res.headersSent) {
res.sendStatus(204);
}
break;
default:
if (value.filePath && value instanceof server_return_1.DownloadResource) {
res.download(value.filePath, value.fileName);
}
else if (value instanceof server_return_1.DownloadBinaryData) {
res.writeHead(200, {
'Content-Length': value.content.length,
'Content-Type': value.mimeType,
'Content-disposition': 'attachment;filename=' + value.fileName
});
res.end(value.content);
}
else if (value.location && value instanceof server_types_1.ReferencedResource) {
res.set('Location', value.location);
if (value.body) {
res.status(value.statusCode);
this.sendValue(value.body, res, next);
}
else {
res.sendStatus(value.statusCode);
}
}
else if (value.then) {
Promise.resolve(value)
.then(function (val) {
_this.sendValue(val, res, next);
}).catch(function (err) {
next(err);
});
}
else {
res.json(value);
}
}
};
InternalServer.prototype.buildArgumentsList = function (serviceMethod, context) {
var _this = this;
var result = new Array();
serviceMethod.parameters.forEach(function (param) {
result.push(_this.processParameter(param.paramType, context, param.name, param.type));
});
return result;
};
InternalServer.prototype.processParameter = function (paramType, context, name, type) {
switch (paramType) {
case metadata.ParamType.path:
return this.convertType(context.request.params[name], type);
case metadata.ParamType.query:
return this.convertType(context.request.query[name], type);
case metadata.ParamType.header:
return this.convertType(context.request.header(name), type);
case metadata.ParamType.cookie:
return this.convertType(context.request.cookies[name], type);
case metadata.ParamType.body:
return this.convertType(context.request.body, type);
case metadata.ParamType.file:
var files = context.request.files ? context.request.files[name] : null;
if (files && files.length > 0) {
return files[0];
}
return null;
case metadata.ParamType.files:
return context.request.files[name];
case metadata.ParamType.form:
return this.convertType(context.request.body[name], type);
case metadata.ParamType.param:
var paramValue = context.request.body[name] ||
context.request.query[name];
return this.convertType(paramValue, type);
case metadata.ParamType.context:
return context;
case metadata.ParamType.context_request:
return context.request;
case metadata.ParamType.context_response:
return context.response;
case metadata.ParamType.context_next:
return context.next;
case metadata.ParamType.context_accept:
return context.accept;
case metadata.ParamType.context_accept_language:
return context.language;
default:
throw Error('Invalid parameter type');
}
};
InternalServer.prototype.convertType = function (paramValue, paramType) {
var serializedType = paramType['name'];
switch (serializedType) {
case 'Number':
return paramValue ? parseFloat(paramValue) : 0;
case 'Boolean':
return paramValue === 'true';
default:
return paramValue;
}
};
InternalServer.resolveAllPaths = function () {
if (!InternalServer.pathsResolved) {
InternalServer.paths.clear();
InternalServer.serverClasses.forEach(function (classData) {
classData.methods.forEach(function (method) {
if (!method.resolvedPath) {
InternalServer.resolveProperties(classData, method);
}
});
});
InternalServer.pathsResolved = true;
}
};
InternalServer.getPaths = function () {
InternalServer.resolveAllPaths();
var result = new Set();
InternalServer.paths.forEach(function (value, key) {
result.add(key);
});
return result;
};
InternalServer.getHttpMethods = function (path) {
InternalServer.resolveAllPaths();
var methods = InternalServer.paths.get(path);
return methods || new Set();
};
InternalServer.resolveLanguages = function (serviceClass, serviceMethod) {
var resolvedLanguages = new Array();
if (serviceClass.languages) {
serviceClass.languages.forEach(function (lang) {
resolvedLanguages.push(lang);
});
}
if (serviceMethod.languages) {
serviceMethod.languages.forEach(function (lang) {
resolvedLanguages.push(lang);
});
}
if (resolvedLanguages.length > 0) {
serviceMethod.resolvedLanguages = resolvedLanguages;
}
};
InternalServer.resolveAccepts = function (serviceClass, serviceMethod) {
var resolvedAccepts = new Array();
if (serviceClass.accepts) {
serviceClass.accepts.forEach(function (accept) {
resolvedAccepts.push(accept);
});
}
if (serviceMethod.accepts) {
serviceMethod.accepts.forEach(function (accept) {
resolvedAccepts.push(accept);
});
}
if (resolvedAccepts.length > 0) {
serviceMethod.resolvedAccepts = resolvedAccepts;
}
};
InternalServer.resolveProperties = function (serviceClass, serviceMethod) {
InternalServer.resolveLanguages(serviceClass, serviceMethod);
InternalServer.resolveAccepts(serviceClass, serviceMethod);
InternalServer.resolvePath(serviceClass, serviceMethod);
};
InternalServer.resolvePath = function (serviceClass, serviceMethod) {
var classPath = serviceClass.path ? serviceClass.path.trim() : '';
var resolvedPath = _.startsWith(classPath, '/') ? classPath : '/' + classPath;
if (_.endsWith(resolvedPath, '/')) {
resolvedPath = resolvedPath.slice(0, resolvedPath.length - 1);
}
if (serviceMethod.path) {
var methodPath = serviceMethod.path.trim();
resolvedPath = resolvedPath + (_.startsWith(methodPath, '/') ? methodPath : '/' + methodPath);
}
var declaredHttpMethods = InternalServer.paths.get(resolvedPath);
if (!declaredHttpMethods) {
declaredHttpMethods = new Set();
InternalServer.paths.set(resolvedPath, declaredHttpMethods);
}
if (declaredHttpMethods.has(serviceMethod.httpMethod)) {
throw Error("Duplicated declaration for path [" + resolvedPath + "], method [" + serviceMethod.httpMethod + "].");
}
declaredHttpMethods.add(serviceMethod.httpMethod);
serviceMethod.resolvedPath = resolvedPath;
};
return InternalServer;
}());
InternalServer.serverClasses = new Map();
InternalServer.paths = new Map();
InternalServer.pathsResolved = false;
InternalServer.serviceFactory = {
create: function (serviceClass) {
return new serviceClass();
},
getTargetClass: function (serviceClass) {
return serviceClass;
}
};
exports.InternalServer = InternalServer;
//# sourceMappingURL=server-container.js.map