/*jshint laxcomma: true, smarttabs: true, node: true, esnext: true*/
'use strict'
/**
* Simple router class for directing requests
* @module skyring/lib/server/router
* @author Eric Satterwhite
* @since 1.0.0
* @requires skyring/lib/server/route
* @requires skyring/lib/server/request
* @requires skyring/lib/server/response
*/
const Route = require('./route')
const Request = require('./request')
const Response = require('./response')
const debug = require('debug')('skyring:server:router')
/**
* @constructor
* @alias module:skyring/lib/server/router
* @param {module:skyring/lib/server/node} node The node linked to the application hashring to pass with each request
* @param {module:skyring/lib/timer} timer A timer instance associated with the application hashring to pass with each request
* @example var x = new Router(node, timers)
router.handle(req, res)
*/
function Router( node, timers ) {
this.routes = new Map()
this.route_options = new Map()
this.node = node
this.timers = timers
}
/**
* Adds a new get handler to the router a new get handler to the router
* @param {String} path The url path to route on
* @param {Function} handler The handler function to call when the route is matched
**/
Router.prototype.get = function get( path, fn ) {
return this.route( path, 'GET', fn )
}
/**
* Adds a new put handler to the router
* @param {String} path The url path to route on
* @param {Function} handler The handler function to call when the route is matched
**/
Router.prototype.put = function put( path, fn ) {
return this.route( path, 'PUT', fn)
}
/**
* Adds a new post handler to the router
* @param {String} path The url path to route on
* @param {Function} handler The handler function to call when the route is matched
**/
Router.prototype.post = function post( path, fn ) {
return this.route( path, 'POST', fn)
}
/**
* Adds a new patch handler to the router
* @param {String} path The url path to route on
* @param {Function} handler The handler function to call when the route is matched
**/
Router.prototype.patch = function patch( path, fn ) {
return this.route( path, 'PATCH', fn)
}
/**
* Adds a new delete handler to the router
* @param {String} path The url path to route on
* @param {Function} handler The handler function to call when the route is matched
**/
Router.prototype.delete = function( path, fn ) {
return this.route( path, 'DELETE', fn )
}
/**
* Adds a new opts handler to the router
* @param {String} path The url path to route on
* @param {Function} handler The handler function to call when the route is matched
**/
Router.prototype.options = function options( path, fn ) {
return this.route( path, 'OPTIONS', fn )
}
/**
* Adds a new route handler to the router
* @param {String} path The url path to route on
* @param {String} m handlerethod The http method to associate to the route
* @param {Function} The handler function to call when the route is matched
* @returns {module:skyring/lib/server/route}
**/
Router.prototype.route = function route(path, method, fn) {
const _method = method.toUpperCase()
const map = this.routes.get(_method) || new Map()
if (map.has(path)) {
const rte = map.get(path)
rte.use(fn)
return rte
}
const rte = new Route(path, _method)
rte.use(fn)
map.set(path, rte)
this.routes.set(_method, map)
return rte
}
/**
* Entrypoint for an incoming request
* Customer properties are attached to an `$` object on the request rather than the request
* itself to avoid V8 deopts / perf penalties
* @param {http.IncomingMessage} req
* @param {http.ServerResponse} res
* @example
http.createServer((req, res) => {
router.handle(req, res)
})
**/
Router.prototype.handle = function handle(req, res) {
req.$ = new Request(req)
res.$ = new Response(res)
req.$.timers = this.timers
const path = req.$.path
const method = req.method.toUpperCase()
const map = this.routes.get(method)
if (map) {
let rte = map.get(path)
if (rte) {
req.$.params = Object.create(null)
return this.handleRoute(rte, req, res)
}
for (const route of map.values()) {
const params = route.match(path)
if (params) {
req.$.params = params
return this.handleRoute(route, req, res)
}
}
}
return notFound(req, res)
}
/**
* Responsible for executing the middleware stack on the route ( including the end handler )
* @param {module:skyring/lib/server/route} route
* @param {http.IncomingMessage} req
* @param {http.ServerResponse} res
**/
Router.prototype.handleRoute = function handleRoute(route, req, res) {
debug('routing ', route.method, route.path)
route.process(req, res, this.node, (err) => {
if (err) return res.$.error(err)
if (res.$.body) return res.$.json(res.$.body)
return res.$.end()
})
}
function notFound( req, res ) {
res.writeHead(404,{
'Content-Type': 'application/json'
})
res.end(JSON.stringify({message: 'Not Found' }))
}
module.exports = Router