all files / modules/middleware/ router.js

85.71% Statements 54/63
78.13% Branches 25/32
80% Functions 4/5
85.71% Lines 54/63
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189                                                                                                                              16× 16×   16× 16× 34×     34× 34× 13×   13×       13×     13×                                                                         11×   11×                                                    
/* jshint -W084 */
const d = require("describe-property");
const objectAssign = require("object-assign");
const compileRoute = require("../utils/compileRoute");
const isRegExp = require("../utils/isRegExp");
const makeParams = require("../utils/makeParams");
const RoutingProperties = require("../utils/RoutingProperties");
 
const LEADING_HTTP_METHOD_MATCHER = /^(DELETE|GET|HEAD|OPTIONS|POST|PUT|TRACE)\s+(.+)$/;
const {is, not, isEmpty, compose} = require("ramda"),
    isnt = compose(not, is);
/**
 * A middleware that provides pattern-based routing for URLs, with optional
 * support for restricting matches to a specific request method. Named segments
 * of the URL are added to conn.params and take precedence over all others.
 *
 *   app.use(mach.router, {
 *
 *     'GET /login': function (conn) {
 *       // conn.method == 'GET'
 *       // conn.pathname == '/login'
 *     },
 *
 *     'POST /login': function (conn) {
 *       // conn.method == 'POST'
 *       // conn.pathname == '/login'
 *     },
 *
 *     'DELETE /users/:id': function (conn) {
 *       // conn.method == 'DELETE'
 *       // conn.pathname == '/users/5'
 *       // conn.params == { id: 5 }
 *     }
 *
 *   });
 *
 * This function may also be used outside the context of a middleware stack
 * to create a standalone app. Routes may be given one at a time:
 *
 *   let app = mach.router();
 *
 *   app.get('/login', function (conn) {
 *     // ...
 *   });
 *
 *   app.delete('/users/:id', function (conn) {
 *     // ...
 *   });
 *
 * Or all at once:
 *
 *   let app = mach.router({
 *
 *     'GET /login': function (conn) {
 *       // ...
 *     },
 *
 *     'DELETE /users/:id': function (conn) {
 *       // ...
 *     }
 *
 *   });
 *
 * Note: Routes are always tried in the order they were defined.
 */
function createRouter(app, map) {
  // Allow mach.router(map)
    Iif (typeof app === "object") {
        map = app;
        app = null;
    }
 
    const routes = {};
 
    function router(conn) {
        const method = conn.method;
        const routesToTry = (routes[method] || []).concat(routes.ANY || []);
 
        let route, match;
        for (let i = 0, len = routesToTry.length; i < len; ++i) {
            route = routesToTry[i];
 
      // Try to match the route.
            match = route.pattern.exec(conn.pathname);
            if (match) {
                const params = makeParams(route.keys, Array.prototype.slice.call(match, 1));
 
                Iif (conn.params) {
          // Route params take precedence above all others.
                    objectAssign(conn.params, params);
                } else {
                    conn.params = params;
                }
 
                return conn.call(route.app);
            }
        }
 
        return conn.call(app);
    }
 
    Object.defineProperties(router, {
 
    /**
     * Adds a new route that runs the given app when the pattern matches the
     * path used in the request. If the pattern is a string, it is automatically
     * compiled. The following signatures are supported:
     *
     *   route('/users/:id', app)
     *   route('/users/:id', 'PUT', app)
     *   route('/users/:id', [ 'GET', 'PUT' ], app)
     *   route('GET /users/:id', app)
     */
        route: d(function (pattern, methods, app) {
            if (is(Function, methods)) {
                app = methods;
                methods = null;
            }
 
            Iif (isnt(Function, app)) {
                throw new Error("Route needs an app");
            }
 
            if (is(String, methods)) {
                methods = [methods];
            } else if (!Array.isArray(methods)) {
                methods = [];
            }
 
            const keys = [];
 
            if (is(String, pattern)) {
                let match;
 
                match = pattern.match(LEADING_HTTP_METHOD_MATCHER);
                if (match) {
                    methods.push(match[1]);
                    pattern = match[2];
                }
 
                pattern = compileRoute(pattern, keys);
            }
 
            Iif (!isRegExp(pattern)) {
                throw new Error("Route pattern must be a RegExp");
            }
 
            const route = {pattern, keys, app};
 
            if (isEmpty(methods)) {
                methods.push("ANY");
            }
 
            methods.forEach(function (method) {
                const upperMethod = method.toUpperCase();
 
                if (routes[upperMethod]) {
                    routes[upperMethod].push(route);
                } else {
                    routes[upperMethod] = [route];
                }
            });
        }),
 
    /**
     * Sets the given app as the default for this router.
     */
        run: d(function (downstreamApp) {
            app = downstreamApp;
        })
 
    });
 
  // Allow app.use(mach.router, map)
    Iif (is(Object, map)) {
        for (const route in map) {
            if (map.hasOwnProperty(route)) {
                router.route(route, map[route]);
            }
        }
    }
 
    Object.defineProperties(router, RoutingProperties);
 
    return router;
}
 
module.exports = createRouter;