@dominion-framework/dominion
Version:
Declarative Promise based Node.js framework for REST API with zero dependencies.
121 lines (99 loc) • 4.93 kB
JavaScript
const Config = require("./../../config");
const Errors = require("./../errors");
const Messages = require("./../messages");
const makeRoute = require("./makeRoute");
const registeredRoutes = new Map();
class Router {
static makeRoute() {
return makeRoute.apply(this, arguments);
}
static addRoutes(routes) {
if (!routes) {
throw new Errors.Fatal("Routes definitions is missing");
}
routes.forEach((route) => {
let handlersMap;
if (!registeredRoutes.has(route.method)) {
registeredRoutes.set(route.method, new Map());
}
handlersMap = registeredRoutes.get(route.method);
if (handlersMap.has(route.pattern)) {
throw new Errors.Fatal(`Route for ${route.method} ${route.pattern} is already defined`);
}
handlersMap.set(route.pattern, route);
});
return routes;
};
static handle(req, res) {
const message = new Messages(req, res);
const [args, handler] = findRoute(message.request.method, message.request.path);
message.request.handler = handler;
const handlers = [
...Messages.request.getInterceptors(),
handler.handler.bind(message, ...args),
...Messages.response.getInterceptors()
];
let messagePromise = Promise.resolve();
handlers.forEach(function (interceptorFunction) {
messagePromise = messagePromise.then(interceptorFunction.bind(message));
});
messagePromise
.then(body => {
if (body
&& handler.factory
&& (Array.isArray(body) ? body.some(model => !(model instanceof handler.factory.__model__))
: !(body instanceof handler.factory.__model__))) {
throw new Errors.Fatal(`Response has to be an instance or instances array of '${handler.factory.__model__.name}' factory. But given: ${body}`);
}
return body;
})
.catch(function (error) {
if (error instanceof Errors.NotFound
|| (error instanceof Errors.Database && error.originalError.errno === 1452 /* ER_NO_REFERENCED_ROW_2 */)) {
this.response.status = this.response.STATUSES._404_NotFound;
} else if (error instanceof Errors.BadRequest) {
this.response.status = this.response.STATUSES._400_BadRequest;
} else if (error instanceof Errors.Validation) {
this.response.status = this.response.STATUSES._400_BadRequest;
} else if (error instanceof Errors.Unauthorized) {
this.response.headers["WWW-Authenticate"] = "Bearer";
this.response.status = this.response.STATUSES._401_Unauthorized;
} else if (error instanceof Errors.Forbidden) {
this.response.status = this.response.STATUSES._403_Forbidden;
} else if (error instanceof Errors.Database && (
error.originalError.errno === 1062 /* ER_DUP_ENTRY */
|| error.originalError.errno === 1451 /* ER_ROW_IS_REFERENCED_2 */)
) {
this.response.status = this.response.STATUSES._409_Conflict;
} else if (error instanceof Errors.NotImplemented) {
this.response.status = this.response.STATUSES._501_NotImplemented;
} else {
this.response.status = this.response.STATUSES._500_InternalServerError;
}
return Config.env.production ? (console.error(error), "") : error.toJSON ? error : error.toString();
}.bind(message))
.then(function (body) {
this.response.body = body;
this.response.send();
return this;
}.bind(message));
return messagePromise;
};
}
const findRoute = function (method, path) {
const handlersMap = registeredRoutes.get(method);
if (handlersMap) {
const handlersIterator = handlersMap.keys();
let regexpPattern, args;
while (regexpPattern = handlersIterator.next().value) {
if (args = regexpPattern.exec(path)) {
args = args.map(arg => typeof arg == "undefined" ? arg : decodeURIComponent(arg));
return [args.slice(1), handlersMap.get(regexpPattern)];
}
}
}
return [[], makeRoute(method, function () {
throw new Errors.NotImplemented(`Route '/${path}" does not exist.`)
})];
};
module.exports = Router;