UNPKG

http-hash

Version:

HTTP router based on a strict path tree structure

176 lines (147 loc) 4.61 kB
'use strict'; module.exports = HttpHash; function HttpHash() { if (!(this instanceof HttpHash)) { return new HttpHash(); } this._hash = new RouteNode(); } HttpHash.prototype.get = get; HttpHash.prototype.set = set; function get(pathname) { var pathSegments = pathname.split('/'); var hash = this._hash; var splat = null; var params = {}; var variablePaths; for (var i = 0; i < pathSegments.length; i++) { var segment = pathSegments[i]; if (!segment && !hash.isSplat) { continue; } else if ( segment === '__proto__' && hash.hasOwnProperty('proto') ) { hash = hash.proto; } else if (hash.staticPaths.hasOwnProperty(segment)) { hash = hash.staticPaths[segment]; } else if ((variablePaths = hash.variablePaths)) { if (variablePaths.isSplat) { splat = pathSegments.slice(i).join('/'); hash = variablePaths; break; } else { params[variablePaths.segment] = segment; hash = variablePaths; } } else { hash = null; break; } } // Match the empty splat if (hash && hash.handler === null && hash.variablePaths && hash.variablePaths.isSplat ) { splat = ''; hash = hash.variablePaths; } return new RouteResult(hash, params, splat); } function set(pathname, handler) { var pathSegments = pathname.split('/'); var hash = this._hash; var lastIndex = pathSegments.length - 1; var splatIndex = pathname.indexOf('*'); var hasSplat = splatIndex >= 0; if (hasSplat && splatIndex !== pathname.length - 1) { throw SplatError(pathname); } for (var i = 0; i < pathSegments.length; i++) { var segment = pathSegments[i]; if (!segment) { continue; } if (hasSplat && i === lastIndex) { hash = ( hash.variablePaths || (hash.variablePaths = new RouteNode(hash, segment, true)) ); if (!hash.isSplat) { throw RouteConflictError(pathname, hash); } } else if (segment.indexOf(':') === 0) { segment = segment.slice(1); hash = ( hash.variablePaths || (hash.variablePaths = new RouteNode(hash, segment)) ); if (hash.segment !== segment || hash.isSplat) { throw RouteConflictError(pathname, hash); } } else if (segment === '__proto__') { hash = ( ( hash.hasOwnProperty('proto') && hash.proto ) || (hash.proto = new RouteNode(hash, segment)) ); } else { hash = ( ( hash.staticPaths.hasOwnProperty(segment) && hash.staticPaths[segment] ) || (hash.staticPaths[segment] = new RouteNode(hash, segment)) ); } } if (hash.handler === null) { hash.src = pathname; hash.handler = handler; } else { throwRouteConflictError(pathname, hash); } } function RouteNode(parent, segment, isSplat) { this.parent = parent || null; this.segment = segment || null; this.handler = null; this.staticPaths = {}; this.variablePaths = null; this.isSplat = !!isSplat; this.src = null; } function RouteResult(node, params, splat) { this.handler = node && node.handler || null; this.splat = splat; this.params = params; this.src = node && node.src || null; } function SplatError(pathname) { var err = new Error('The splat * must be the last segment of the path'); err.pathname = pathname; return err; } function RouteConflictError(pathname, hash) { var conflictPath = hash.isSplat ? '' : '/'; while (hash && hash.parent) { var prefix = ( !hash.isSplat && hash === hash.parent.variablePaths ) ? ':' : ''; conflictPath = '/' + prefix + hash.segment + conflictPath; hash = hash.parent; } var err = new Error('Route conflict'); err.attemptedPath = pathname; err.conflictPath = conflictPath; return err; } // Break this out to prevent deoptimization of path.set function throwRouteConflictError(pathname, hash) { throw RouteConflictError(pathname, hash); }