@akala/core
Version:
226 lines (193 loc) • 4.89 kB
text/typescript
/*!
* router
* Copyright(c) 2013 Roman Shtylman
* Copyright(c) 2014 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Module dependencies.
* @private
*/
import * as pathRegexp from 'path-to-regexp';
import debug from 'debug';
import { NextFunction } from '../eachAsync';
import { Route } from './route';
import pathToRegexp = pathRegexp.pathToRegexp;
var log = debug('router:layer');
/**
* Module variables.
* @private
*/
var hasOwnProperty = Object.prototype.hasOwnProperty
export interface LayerRegExp extends RegExp
{
fast_star?: boolean;
fast_slash?: boolean;
}
export interface LayerOptions
{
end?: boolean;
sensitive?: boolean;
mergeParams?: boolean;
strict?: boolean;
length: number;
}
/**
* Expose `Layer`.
*/
export class Layer<T extends Function>
{
private handler: T;
public name: string;
public params: any;
public path: string;
protected regexp: LayerRegExp;
public keys: pathRegexp.Key[];
private isErrorHandler: boolean;
private isRequestHandler: boolean;
constructor(path: string, options: LayerOptions, fn: T)
{
if (!(this instanceof Layer))
{
return new Layer<T>(path, options, fn)
}
log('new %o', path)
var opts: LayerOptions = options || { length: 2 }
this.handler = fn
this.name = fn.name || '<anonymous>'
this.params = undefined
this.path = undefined
this.regexp = pathToRegexp(path, this.keys = [], opts);
// set fast path flags
this.regexp.fast_star = path === '*'
this.regexp.fast_slash = path === '/' && opts.end === false
this.isErrorHandler = fn.length == 0 || fn.length >= (opts.length || 2) + 2;
this.isRequestHandler = fn.length == 0 || fn.length < (opts.length || 2) + 2;
}
public isApplicable<TRoute extends Route<T, this>>(req, route: TRoute)
{
return true;
}
public handle_error(error, ...args)
{
var fn = this.handler;
var next: NextFunction = args[args.length - 1];
if (!this.isErrorHandler)
{
log('skipping non error handler')
// not a standard error handler
return next(error);
}
try
{
fn.apply(null, [error].concat(args))
}
catch (err)
{
next(err);
}
}
public handle_request(...args)
{
var fn = this.handler;
var next: NextFunction = args[args.length - 1];
if (!this.isRequestHandler)
{
log('skipping non request handler')
// not a standard request handler
return next()
}
try
{
fn.apply(null, args)
}
catch (err)
{
next(err)
}
}
/**
* Check if this route matches `path`, if so
* populate `.params`.
*
* @param {String} path
* @return {Boolean}
* @api private
*/
public match(path: string): boolean
{
var match: RegExpExecArray;
log(this.regexp);
if (path != null)
{
// fast path non-ending match for / (any path matches)
if (this.regexp.fast_slash)
{
this.params = {}
this.path = ''
return true
}
// fast path for * (everything matched in a param)
if (this.regexp.fast_star)
{
this.params = { '0': decode_param(path) }
this.path = path
return true
}
// match the path
match = this.regexp.exec(path)
}
if (!match)
{
log(this.regexp);
this.params = undefined
this.path = undefined
return false
}
// store values
this.params = {}
this.path = match[0]
// iterate matches
var keys = this.keys
var params = this.params
for (var i = 1; i < match.length; i++)
{
var key = keys[i - 1]
var prop = key.name
var val = decode_param(match[i])
if (val !== undefined || !(hasOwnProperty.call(params, prop)))
{
params[prop] = val
}
}
return true
}
}
/**
* Decode param value.
*
* @param {string} val
* @return {string}
* @private
*/
function decode_param(val)
{
if (typeof val !== 'string' || val.length === 0)
{
return val
}
try
{
return decodeURIComponent(val)
}
catch (err)
{
if (err instanceof URIError)
{
err.message = 'Failed to decode param \'' + val + '\''
err['status'] = 400
}
throw err
}
}