spirit-router
Version:
fast router for spirit
192 lines (170 loc) • 5.68 kB
JavaScript
;
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
/*
* For Routes, HTTP method shortcuts
*/
var path_regexp = require("path-to-regexp");
var spirit = require("spirit");
var render = require("./render").render;
/**
* converts a route definition to something more friendly
* to deal with. As well as `path` is converted to a regexp
*
* returns a Route, which is just a map
*
* The `body` can be of any type or a function that returns
* just about any type as long as `render` knows
* how to render it, or it is a valid response map.
*
* Type checks are done where types do not make sense.
*
* `args` and `body` are both optional. If only one of them is
* provided, then `body` is assumed as there would be not point
* to specify `args` with no `body.`
*
* @param {string} method - http verb
* @param {string} path - URL path of route
* @param {string[]} args - arguments used to destructure the request, and to be passed into `body`
* @param {function} body - a function that accepts args
* @return {Route}
*/
var compile = function compile(method, path, args, body) {
var route_err = " [Method: " + method + ", Path: " + path + "]";
// guard
if (typeof method !== "string" || typeof path !== "string" && path instanceof RegExp === false || !Array.isArray(args) && typeof args !== "undefined") {
throw new TypeError("Cannot compile route, invalid argument types passed to compile; Expecting (verb:string, path:string|regexp, arguments:array, body:*)");
}
if (body === null || body === "" || (typeof body === "undefined" ? "undefined" : _typeof(body)) === "object" && Object.keys(body).length === 0) {
throw new TypeError("The body of a route cannot be an empty value." + route_err);
}
// guard
if (!method || !path) {
throw new Error("Cannot compile route, empty string passed." + route_err);
}
if (args === undefined) {
args = [];
}
if (args.length && typeof body !== "function") {
throw new Error("Cannot compile route with arguments but an body that is undefined. The arguments would never be used." + route_err);
}
var keys = [];
var re = path_regexp(path, keys);
return {
method: method.toUpperCase(),
path: { re: re, keys: keys, path: path },
handler: make_handler(body, args)
};
};
/**
* Destructures against `obj` the keys of `args`
* Prioritizes `obj`.params then `obj` when look up
*
* Returns an array of values ordered the same way as `args`
*
* @param {string[]} args - a string of keys to destructure in `obj`
* @param {object} obj - a map
* @return {*[]}
*/
var _destructure = function _destructure(args, obj) {
function lookup(k, o) {
if (typeof o === "undefined") {
return undefined;
}
return o[k];
}
return args.map(function (arg) {
var v = void 0;
// if arg is an array, do not assume / search
// return it directly
if (Array.isArray(arg)) {
if (arg[0] === "request") return obj;
if (arg[0] === "req" && typeof obj["req"] === "function") return obj.req();
v = lookup(arg[0], obj);
if (arg.length === 1) {
return v;
}
return v[arg[1]];
}
// otherwise make assumptions of where to look
// prioritizing certain keys
var priority = ["params", ""]; // "" means root of obj
// return the first thing that's not undefined
priority.some(function (p, i) {
var o = obj[p];
if (p === "") {
// root obj
if (arg === "request") {
v = obj;
return true; // if root obj & "request" exit loop
}
o = obj;
}
v = lookup(arg, o);
return typeof v !== "undefined";
});
if (arg === "req" && typeof v === "function") {
return v();
}
return v;
});
};
var make_handler = function make_handler(body, args) {
return function (request) {
return spirit.node.utils.callp_response(body, _destructure(args, request)).then(function (resp) {
if (typeof resp !== "undefined") {
return render(request, resp);
}
return resp;
});
};
};
/**
* converts a Route to a map of it's keys and values as matched
* by Route.path.re to it's Route.path.keys
*
* @param {Route} route - a Route
* @param {RegExp} regexp_params - result of Route's matched regexp
* @return {object|undefined}
*/
var decompile = function decompile(route, regex_params) {
// regex_params is probably null because
// Route.path.re.exec didn't match anything
if (regex_params === null || regex_params.length < 2) {
return undefined;
}
var params = {};
regex_params.forEach(function (param, idx) {
if (idx) {
// ignore 0 index
var key = route.path.keys[idx - 1];
params[key.name] = param;
}
});
return params;
};
var verb = function verb(method, path, args, body) {
if (body === undefined) {
body = args;
args = undefined;
}
return [method, path, args, body];
};
/*
* The following are just "helper" functions to convert
* a set of arguments into a array that can then be used
* for compiling
*/
var verbs = {};
var http_methods = ["get", "put", "post", "delete", "head", "options", "patch"];
http_methods.forEach(function (method) {
verbs[method] = verb.bind(null, method);
});
verbs.any = verb.bind(null, "*");
module.exports = {
verb: verb,
verbs: verbs,
compile: compile,
decompile: decompile,
make_handler: make_handler,
_destructure: _destructure
};