pax_global_header00006660000000000000000000000064147711163620014522gustar00rootroot0000000000000052 comment=e6d6b609fc355e558174ccd5b1db646f739fe88c pillarjs-router-e6d6b60/000077500000000000000000000000001477111636200152765ustar00rootroot00000000000000pillarjs-router-e6d6b60/.editorconfig000066400000000000000000000002631477111636200177540ustar00rootroot00000000000000# http://editorconfig.org root = true [*] charset = utf-8 insert_final_newline = true trim_trailing_whitespace = true [{*.js,*.json,*.yml}] indent_size = 2 indent_style = space pillarjs-router-e6d6b60/.git-blame-ignore-revs000066400000000000000000000000511477111636200213720ustar00rootroot0000000000000029a69f3cf7cb5acbdde0a9151b885d46423b0c72 pillarjs-router-e6d6b60/.github/000077500000000000000000000000001477111636200166365ustar00rootroot00000000000000pillarjs-router-e6d6b60/.github/workflows/000077500000000000000000000000001477111636200206735ustar00rootroot00000000000000pillarjs-router-e6d6b60/.github/workflows/ci.yml000066400000000000000000000031131477111636200220070ustar00rootroot00000000000000name: ci on: push: branches: - master paths-ignore: - '*.md' pull_request: paths-ignore: - '*.md' permissions: contents: read jobs: test: name: Test - Node.js ${{ matrix.node-version }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: node-version: [18, 19, 20, 21, 22, 23] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} check-latest: true - name: Install Node.js dependencies run: npm install - name: Run tests run: npm run test-ci - name: Lint code run: npm run lint - name: Upload code coverage uses: actions/upload-artifact@v4 with: name: coverage-node-${{ matrix.node-version }} path: ./coverage/lcov.info retention-days: 1 coverage: needs: test runs-on: ubuntu-latest permissions: contents: read checks: write steps: - uses: actions/checkout@v4 - name: Install lcov shell: bash run: sudo apt-get -y install lcov - name: Collect coverage reports uses: actions/download-artifact@v4 with: path: ./coverage pattern: coverage-node-* - name: Merge coverage reports shell: bash run: find ./coverage -name lcov.info -exec printf '-a %q\n' {} \; | xargs lcov -o ./lcov.info - name: Upload coverage report uses: coverallsapp/github-action@v2 with: file: ./lcov.info pillarjs-router-e6d6b60/.github/workflows/scorecard.yml000066400000000000000000000056451477111636200233750ustar00rootroot00000000000000# This workflow uses actions that are not certified by GitHub. They are provided # by a third-party and are governed by separate terms of service, privacy # policy, and support documentation. name: Scorecard supply-chain security on: # For Branch-Protection check. Only the default branch is supported. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection branch_protection_rule: # To guarantee Maintained check is occasionally updated. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained schedule: - cron: '16 21 * * 1' push: branches: [ "master" ] # Declare default permissions as read only. permissions: read-all jobs: analysis: name: Scorecard analysis runs-on: ubuntu-latest permissions: # Needed to upload the results to code-scanning dashboard. security-events: write # Needed to publish results and get a badge (see publish_results below). id-token: write # Uncomment the permissions below if installing in a private repository. # contents: read # actions: read steps: - name: "Checkout code" uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: "Run analysis" uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 with: results_file: results.sarif results_format: sarif # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: # - you want to enable the Branch-Protection check on a *public* repository, or # - you are installing Scorecard on a *private* repository # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. # repo_token: ${{ secrets.SCORECARD_TOKEN }} # Public repositories: # - Publish results to OpenSSF REST API for easy access by consumers # - Allows the repository to include the Scorecard badge. # - See https://github.com/ossf/scorecard-action#publishing-results. # For private repositories: # - `publish_results` will always be set to `false`, regardless # of the value entered here. publish_results: true # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: SARIF file path: results.sarif retention-days: 5 # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 with: sarif_file: results.sarifpillarjs-router-e6d6b60/.gitignore000066400000000000000000000001051477111636200172620ustar00rootroot00000000000000.nyc_output/ coverage/ node_modules/ npm-debug.log package-lock.json pillarjs-router-e6d6b60/HISTORY.md000066400000000000000000000146531477111636200167720ustar00rootroot000000000000002.2.0 / 2025-03-26 ================== * Remove `setImmediate` support check * Restore `debug` dependency 2.1.0 / 2025-02-10 ================== * Updated `engines` field to Node@18 or higher * Remove `Object.setPrototypeOf` polyfill * Use `Array.flat` instead of `array-flatten` package * Replace `methods` dependency with standard library * deps: parseurl@^1.3.3 * deps: is-promise@^4.0.0 * Replace `utils-merge` dependency with `Object.assign` * deps: Remove unused dep `after` 2.0.0 / 2024-09-09 ================== * Drop support for node <18 * deps: path-to-regexp@^8.0.0 - Drop support for partial capture group `router.route('/user(s?)/:user/:op')` but still have optional non-capture `/user{s}/:user/:op` - `:name?` becomes `{:name}` - `:name*` becomes `*name`. - The splat change also changes splat from strings to an array of strings - Optional splats become `{*name}` - `:name+` becomes `*name` and thus equivalent to `*name` so I dropped those tests - Strings as regular expressions are fully removed, need to be converted to native regular expressions 2.0.0-beta.2 / 2024-03-20 ========================= This incorporates all changes after 1.3.5 up to 1.3.8. * Add support for returned, rejected Promises to `router.param` 2.0.0-beta.1 / 2020-03-29 ========================= This incorporates all changes after 1.3.3 up to 1.3.5. * Internalize private `router.process_params` method * Remove `debug` dependency * deps: array-flatten@3.0.0 * deps: parseurl@~1.3.3 * deps: path-to-regexp@3.2.0 - Add new `?`, `*`, and `+` parameter modifiers. - Matching group expressions are only RegExp syntax. `(*)` is no longer valid and must be written as `(.*)`, for example. - Named matching groups no longer available by position in `req.params`. `/:foo(.*)` only captures as `req.params.foo` and not available as `req.params[0]`. - Regular expressions can only be used in a matching group. `/\\d+` is no longer valid and must be written as `/(\\d+)`. - Matching groups are now literal regular expressions. `:foo` named captures can no longer be included inside a capture group. - Special `*` path segment behavior removed. `/foo/*/bar` will match a literal `*` as the middle segment. * deps: setprototypeof@1.2.0 2.0.0-alpha.1 / 2018-07-27 ========================== * Add basic support for returned, rejected Promises - Rejected Promises from middleware functions `next(error)` * Drop support for Node.js below 0.10 * deps: debug@3.1.0 - Add `DEBUG_HIDE_DATE` environment variable - Change timer to per-namespace instead of global - Change non-TTY date format - Remove `DEBUG_FD` environment variable support - Support 256 namespace colors 1.3.8 / 2023-02-24 ================== * Fix routing requests without method 1.3.7 / 2022-04-28 ================== * Fix hanging on large stack of sync routes 1.3.6 / 2021-11-15 ================== * Fix handling very large stacks of sync middleware * deps: safe-buffer@5.2.1 1.3.5 / 2020-03-24 ================== * Fix incorrect middleware execution with unanchored `RegExp`s * perf: use plain object for internal method map 1.3.4 / 2020-01-24 ================== * deps: array-flatten@3.0.0 * deps: parseurl@~1.3.3 * deps: setprototypeof@1.2.0 1.3.3 / 2018-07-06 ================== * Fix JSDoc for `Router` constructor 1.3.2 / 2017-09-24 ================== * deps: debug@2.6.9 * deps: parseurl@~1.3.2 - perf: reduce overhead for full URLs - perf: unroll the "fast-path" `RegExp` * deps: setprototypeof@1.1.0 * deps: utils-merge@1.0.1 1.3.1 / 2017-05-19 ================== * deps: debug@2.6.8 - Fix `DEBUG_MAX_ARRAY_LENGTH` - deps: ms@2.0.0 1.3.0 / 2017-02-25 ================== * Add `next("router")` to exit from router * Fix case where `router.use` skipped requests routes did not * Use `%o` in path debug to tell types apart * deps: setprototypeof@1.0.3 * perf: add fast match path for `*` route 1.2.0 / 2017-02-17 ================== * Skip routing when `req.url` is not set * deps: debug@2.6.1 - Allow colors in workers - Deprecated `DEBUG_FD` environment variable set to `3` or higher - Fix error when running under React Native - Use same color for same namespace - deps: ms@0.7.2 1.1.5 / 2017-01-28 ================== * deps: array-flatten@2.1.1 * deps: setprototypeof@1.0.2 - Fix using fallback even when native method exists 1.1.4 / 2016-01-21 ================== * deps: array-flatten@2.0.0 * deps: methods@~1.1.2 - perf: enable strict mode * deps: parseurl@~1.3.1 - perf: enable strict mode 1.1.3 / 2015-08-02 ================== * Fix infinite loop condition using `mergeParams: true` * Fix inner numeric indices incorrectly altering parent `req.params` * deps: array-flatten@1.1.1 - perf: enable strict mode * deps: path-to-regexp@0.1.7 - Fix regression with escaped round brackets and matching groups 1.1.2 / 2015-07-06 ================== * Fix hiding platform issues with `decodeURIComponent` - Only `URIError`s are a 400 * Fix using `*` before params in routes * Fix using capture groups before params in routes * deps: path-to-regexp@0.1.6 * perf: enable strict mode * perf: remove argument reassignments in routing * perf: skip attempting to decode zero length string * perf: use plain for loops 1.1.1 / 2015-05-25 ================== * Fix issue where `next('route')` in `router.param` would incorrectly skip values * deps: array-flatten@1.1.0 * deps: debug@~2.2.0 - deps: ms@0.7.1 1.1.0 / 2015-04-22 ================== * Use `setprototypeof` instead of `__proto__` * deps: debug@~2.1.3 - Fix high intensity foreground color for bold - deps: ms@0.7.0 1.0.0 / 2015-01-13 ================== * Fix crash from error within `OPTIONS` response handler * deps: array-flatten@1.0.2 - Remove redundant code path 1.0.0-beta.3 / 2015-01-11 ========================= * Fix duplicate methods appearing in OPTIONS responses * Fix OPTIONS responses to include the HEAD method properly * Remove support for leading colon in `router.param(name, fn)` * Use `array-flatten` for flattening arrays * deps: debug@~2.1.1 * deps: methods@~1.1.1 1.0.0-beta.2 / 2014-11-19 ========================= * Match routes iteratively to prevent stack overflows 1.0.0-beta.1 / 2014-11-16 ========================= * Initial release ported from Express 4.x - Altered to work without Express pillarjs-router-e6d6b60/LICENSE000066400000000000000000000021501477111636200163010ustar00rootroot00000000000000(The MIT License) Copyright (c) 2013 Roman Shtylman Copyright (c) 2014-2022 Douglas Christopher Wilson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pillarjs-router-e6d6b60/README.md000066400000000000000000000305601477111636200165610ustar00rootroot00000000000000# router [![NPM Version][npm-image]][npm-url] [![NPM Downloads][downloads-image]][downloads-url] [![Node.js Version][node-version-image]][node-version-url] [![Build Status][ci-image]][ci-url] [![Test Coverage][coveralls-image]][coveralls-url] Simple middleware-style router ## Installation This is a [Node.js](https://nodejs.org/en/) module available through the [npm registry](https://www.npmjs.com/). Installation is done using the [`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally): ```bash $ npm install router ``` ## API ```js var finalhandler = require('finalhandler') var http = require('http') var Router = require('router') var router = Router() router.get('/', function (req, res) { res.setHeader('Content-Type', 'text/plain; charset=utf-8') res.end('Hello World!') }) var server = http.createServer(function (req, res) { router(req, res, finalhandler(req, res)) }) server.listen(3000) ``` This module is currently an extracted version from the Express project, but with the main change being it can be used with a plain `http.createServer` object or other web frameworks by removing Express-specific API calls. ## Router(options) Options - `strict` - When `false` trailing slashes are optional (default: `false`) - `caseSensitive` - When `true` the routing will be case sensitive. (default: `false`) - `mergeParams` - When `true` any `req.params` passed to the router will be merged into the router's `req.params`. (default: `false`) ([example](#example-using-mergeparams)) Returns a function with the signature `router(req, res, callback)` where `callback([err])` must be provided to handle errors and fall-through from not handling requests. ### router.use([path], ...middleware) Use the given [middleware function](#middleware) for all http methods on the given `path`, defaulting to the root path. `router` does not automatically see `use` as a handler. As such, it will not consider it one for handling `OPTIONS` requests. * Note: If a `path` is specified, that `path` is stripped from the start of `req.url`. ```js router.use(function (req, res, next) { // do your things // continue to the next middleware // the request will stall if this is not called next() // note: you should NOT call `next` if you have begun writing to the response }) ``` [Middleware](#middleware) can themselves use `next('router')` at any time to exit the current router instance completely, invoking the top-level callback. ### router\[method](path, ...[middleware], handler) The [http methods](https://github.com/jshttp/methods/blob/master/index.js) provide the routing functionality in `router`. Method middleware and handlers follow usual [middleware](#middleware) behavior, except they will only be called when the method and path match the request. ```js // handle a `GET` request router.get('/', function (req, res) { res.setHeader('Content-Type', 'text/plain; charset=utf-8') res.end('Hello World!') }) ``` [Middleware](#middleware) given before the handler have one additional trick, they may invoke `next('route')`. Calling `next('route')` bypasses the remaining middleware and the handler mounted for this route, passing the request to the next route suitable for handling this request. Route handlers and middleware can themselves use `next('router')` at any time to exit the current router instance completely, invoking the top-level callback. ### router.param(name, param_middleware) Maps the specified path parameter `name` to a specialized param-capturing middleware. This function positions the middleware in the same stack as `.use`. The function can optionally return a `Promise` object. If a `Promise` object is returned from the function, the router will attach an `onRejected` callback using `.then`. If the promise is rejected, `next` will be called with the rejected value, or an error if the value is falsy. Parameter mapping is used to provide pre-conditions to routes which use normalized placeholders. For example a _:user_id_ parameter could automatically load a user's information from the database without any additional code: ```js router.param('user_id', function (req, res, next, id) { User.find(id, function (err, user) { if (err) { return next(err) } else if (!user) { return next(new Error('failed to load user')) } req.user = user // continue processing the request next() }) }) ``` ### router.route(path) Creates an instance of a single `Route` for the given `path`. (See `Router.Route` below) Routes can be used to handle http `methods` with their own, optional middleware. Using `router.route(path)` is a recommended approach to avoiding duplicate route naming and thus typo errors. ```js var api = router.route('/api/') ``` ## Router.Route(path) Represents a single route as an instance that can be used to handle http `methods` with it's own, optional middleware. ### route\[method](handler) These are functions which you can directly call on a route to register a new `handler` for the `method` on the route. ```js // handle a `GET` request var status = router.route('/status') status.get(function (req, res) { res.setHeader('Content-Type', 'text/plain; charset=utf-8') res.end('All Systems Green!') }) ``` ### route.all(handler) Adds a handler for all HTTP methods to this route. The handler can behave like middleware and call `next` to continue processing rather than responding. ```js router.route('/') .all(function (req, res, next) { next() }) .all(checkSomething) .get(function (req, res) { res.setHeader('Content-Type', 'text/plain; charset=utf-8') res.end('Hello World!') }) ``` ## Middleware Middleware (and method handlers) are functions that follow specific function parameters and have defined behavior when used with `router`. The most common format is with three parameters - "req", "res" and "next". - `req` - This is a [HTTP incoming message](https://nodejs.org/api/http.html#http_http_incomingmessage) instance. - `res` - This is a [HTTP server response](https://nodejs.org/api/http.html#http_class_http_serverresponse) instance. - `next` - Calling this function that tells `router` to proceed to the next matching middleware or method handler. It accepts an error as the first argument. The function can optionally return a `Promise` object. If a `Promise` object is returned from the function, the router will attach an `onRejected` callback using `.then`. If the promise is rejected, `next` will be called with the rejected value, or an error if the value is falsy. Middleware and method handlers can also be defined with four arguments. When the function has four parameters defined, the first argument is an error and subsequent arguments remain, becoming - "err", "req", "res", "next". These functions are "error handling middleware", and can be used for handling errors that occurred in previous handlers (E.g. from calling `next(err)`). This is most used when you want to define arbitrary rendering of errors. ```js router.get('/error_route', function (req, res, next) { return next(new Error('Bad Request')) }) router.use(function (err, req, res, next) { res.end(err.message) //= > "Bad Request" }) ``` Error handling middleware will **only** be invoked when an error was given. As long as the error is in the pipeline, normal middleware and handlers will be bypassed - only error handling middleware will be invoked with an error. ## Examples ```js // import our modules var http = require('http') var Router = require('router') var finalhandler = require('finalhandler') var compression = require('compression') var bodyParser = require('body-parser') // store our message to display var message = 'Hello World!' // initialize the router & server and add a final callback. var router = Router() var server = http.createServer(function onRequest (req, res) { router(req, res, finalhandler(req, res)) }) // use some middleware and compress all outgoing responses router.use(compression()) // handle `GET` requests to `/message` router.get('/message', function (req, res) { res.statusCode = 200 res.setHeader('Content-Type', 'text/plain; charset=utf-8') res.end(message + '\n') }) // create and mount a new router for our API var api = Router() router.use('/api/', api) // add a body parsing middleware to our API api.use(bodyParser.json()) // handle `PATCH` requests to `/api/set-message` api.patch('/set-message', function (req, res) { if (req.body.value) { message = req.body.value res.statusCode = 200 res.setHeader('Content-Type', 'text/plain; charset=utf-8') res.end(message + '\n') } else { res.statusCode = 400 res.setHeader('Content-Type', 'text/plain; charset=utf-8') res.end('Invalid API Syntax\n') } }) // make our http server listen to connections server.listen(8080) ``` You can get the message by running this command in your terminal, or navigating to `127.0.0.1:8080` in a web browser. ```bash curl http://127.0.0.1:8080 ``` You can set the message by sending it a `PATCH` request via this command: ```bash curl http://127.0.0.1:8080/api/set-message -X PATCH -H "Content-Type: application/json" -d '{"value":"Cats!"}' ``` ### Example using mergeParams ```js var http = require('http') var Router = require('router') var finalhandler = require('finalhandler') // this example is about the mergeParams option var opts = { mergeParams: true } // make a router with out special options var router = Router(opts) var server = http.createServer(function onRequest (req, res) { // set something to be passed into the router req.params = { type: 'kitten' } router(req, res, finalhandler(req, res)) }) router.get('/', function (req, res) { res.statusCode = 200 res.setHeader('Content-Type', 'text/plain; charset=utf-8') // with respond with the the params that were passed in res.end(req.params.type + '\n') }) // make another router with our options var handler = Router(opts) // mount our new router to a route that accepts a param router.use('/:path', handler) handler.get('/', function (req, res) { res.statusCode = 200 res.setHeader('Content-Type', 'text/plain; charset=utf-8') // will respond with the param of the router's parent route res.end(req.params.path + '\n') }) // make our http server listen to connections server.listen(8080) ``` Now you can get the type, or what path you are requesting: ```bash curl http://127.0.0.1:8080 > kitten curl http://127.0.0.1:8080/such_path > such_path ``` ### Example of advanced `.route()` usage This example shows how to implement routes where there is a custom handler to execute when the path matched, but no methods matched. Without any special handling, this would be treated as just a generic non-match by `router` (which typically results in a 404), but with a custom handler, a `405 Method Not Allowed` can be sent. ```js var http = require('http') var finalhandler = require('finalhandler') var Router = require('router') // create the router and server var router = new Router() var server = http.createServer(function onRequest (req, res) { router(req, res, finalhandler(req, res)) }) // register a route and add all methods router.route('/pet/:id') .get(function (req, res) { // this is GET /pet/:id res.setHeader('Content-Type', 'application/json') res.end(JSON.stringify({ name: 'tobi' })) }) .delete(function (req, res) { // this is DELETE /pet/:id res.end() }) .all(function (req, res) { // this is called for all other methods not // defined above for /pet/:id res.statusCode = 405 res.end() }) // make our http server listen to connections server.listen(8080) ``` ## License [MIT](LICENSE) [ci-image]: https://badgen.net/github/checks/pillarjs/router/master?label=ci [ci-url]: https://github.com/pillarjs/router/actions/workflows/ci.yml [npm-image]: https://img.shields.io/npm/v/router.svg [npm-url]: https://npmjs.org/package/router [node-version-image]: https://img.shields.io/node/v/router.svg [node-version-url]: http://nodejs.org/download/ [coveralls-image]: https://img.shields.io/coveralls/pillarjs/router/master.svg [coveralls-url]: https://coveralls.io/r/pillarjs/router?branch=master [downloads-image]: https://img.shields.io/npm/dm/router.svg [downloads-url]: https://npmjs.org/package/router pillarjs-router-e6d6b60/index.js000066400000000000000000000367721477111636200167620ustar00rootroot00000000000000/*! * router * Copyright(c) 2013 Roman Shtylman * Copyright(c) 2014-2022 Douglas Christopher Wilson * MIT Licensed */ 'use strict' /** * Module dependencies. * @private */ const isPromise = require('is-promise') const Layer = require('./lib/layer') const { METHODS } = require('node:http') const parseUrl = require('parseurl') const Route = require('./lib/route') const debug = require('debug')('router') const deprecate = require('depd')('router') /** * Module variables. * @private */ const slice = Array.prototype.slice const flatten = Array.prototype.flat const methods = METHODS.map((method) => method.toLowerCase()) /** * Expose `Router`. */ module.exports = Router /** * Expose `Route`. */ module.exports.Route = Route /** * Initialize a new `Router` with the given `options`. * * @param {object} [options] * @return {Router} which is a callable function * @public */ function Router (options) { if (!(this instanceof Router)) { return new Router(options) } const opts = options || {} function router (req, res, next) { router.handle(req, res, next) } // inherit from the correct prototype Object.setPrototypeOf(router, this) router.caseSensitive = opts.caseSensitive router.mergeParams = opts.mergeParams router.params = {} router.strict = opts.strict router.stack = [] return router } /** * Router prototype inherits from a Function. */ /* istanbul ignore next */ Router.prototype = function () {} /** * Map the given param placeholder `name`(s) to the given callback. * * Parameter mapping is used to provide pre-conditions to routes * which use normalized placeholders. For example a _:user_id_ parameter * could automatically load a user's information from the database without * any additional code. * * The callback uses the same signature as middleware, the only difference * being that the value of the placeholder is passed, in this case the _id_ * of the user. Once the `next()` function is invoked, just like middleware * it will continue on to execute the route, or subsequent parameter functions. * * Just like in middleware, you must either respond to the request or call next * to avoid stalling the request. * * router.param('user_id', function(req, res, next, id){ * User.find(id, function(err, user){ * if (err) { * return next(err) * } else if (!user) { * return next(new Error('failed to load user')) * } * req.user = user * next() * }) * }) * * @param {string} name * @param {function} fn * @public */ Router.prototype.param = function param (name, fn) { if (!name) { throw new TypeError('argument name is required') } if (typeof name !== 'string') { throw new TypeError('argument name must be a string') } if (!fn) { throw new TypeError('argument fn is required') } if (typeof fn !== 'function') { throw new TypeError('argument fn must be a function') } let params = this.params[name] if (!params) { params = this.params[name] = [] } params.push(fn) return this } /** * Dispatch a req, res into the router. * * @private */ Router.prototype.handle = function handle (req, res, callback) { if (!callback) { throw new TypeError('argument callback is required') } debug('dispatching %s %s', req.method, req.url) let idx = 0 let methods const protohost = getProtohost(req.url) || '' let removed = '' const self = this let slashAdded = false let sync = 0 const paramcalled = {} // middleware and routes const stack = this.stack // manage inter-router variables const parentParams = req.params const parentUrl = req.baseUrl || '' let done = restore(callback, req, 'baseUrl', 'next', 'params') // setup next layer req.next = next // for options requests, respond with a default if nothing else responds if (req.method === 'OPTIONS') { methods = [] done = wrap(done, generateOptionsResponder(res, methods)) } // setup basic req values req.baseUrl = parentUrl req.originalUrl = req.originalUrl || req.url next() function next (err) { let layerError = err === 'route' ? null : err // remove added slash if (slashAdded) { req.url = req.url.slice(1) slashAdded = false } // restore altered req.url if (removed.length !== 0) { req.baseUrl = parentUrl req.url = protohost + removed + req.url.slice(protohost.length) removed = '' } // signal to exit router if (layerError === 'router') { setImmediate(done, null) return } // no more matching layers if (idx >= stack.length) { setImmediate(done, layerError) return } // max sync stack if (++sync > 100) { return setImmediate(next, err) } // get pathname of request const path = getPathname(req) if (path == null) { return done(layerError) } // find next matching layer let layer let match let route while (match !== true && idx < stack.length) { layer = stack[idx++] match = matchLayer(layer, path) route = layer.route if (typeof match !== 'boolean') { // hold on to layerError layerError = layerError || match } if (match !== true) { continue } if (!route) { // process non-route handlers normally continue } if (layerError) { // routes do not match with a pending error match = false continue } const method = req.method const hasMethod = route._handlesMethod(method) // build up automatic options response if (!hasMethod && method === 'OPTIONS' && methods) { methods.push.apply(methods, route._methods()) } // don't even bother matching route if (!hasMethod && method !== 'HEAD') { match = false } } // no match if (match !== true) { return done(layerError) } // store route for dispatch on change if (route) { req.route = route } // Capture one-time layer values req.params = self.mergeParams ? mergeParams(layer.params, parentParams) : layer.params const layerPath = layer.path // this should be done for the layer processParams(self.params, layer, paramcalled, req, res, function (err) { if (err) { next(layerError || err) } else if (route) { layer.handleRequest(req, res, next) } else { trimPrefix(layer, layerError, layerPath, path) } sync = 0 }) } function trimPrefix (layer, layerError, layerPath, path) { if (layerPath.length !== 0) { // Validate path is a prefix match if (layerPath !== path.substring(0, layerPath.length)) { next(layerError) return } // Validate path breaks on a path separator const c = path[layerPath.length] if (c && c !== '/') { next(layerError) return } // Trim off the part of the url that matches the route // middleware (.use stuff) needs to have the path stripped debug('trim prefix (%s) from url %s', layerPath, req.url) removed = layerPath req.url = protohost + req.url.slice(protohost.length + removed.length) // Ensure leading slash if (!protohost && req.url[0] !== '/') { req.url = '/' + req.url slashAdded = true } // Setup base URL (no trailing slash) req.baseUrl = parentUrl + (removed[removed.length - 1] === '/' ? removed.substring(0, removed.length - 1) : removed) } debug('%s %s : %s', layer.name, layerPath, req.originalUrl) if (layerError) { layer.handleError(layerError, req, res, next) } else { layer.handleRequest(req, res, next) } } } /** * Use the given middleware function, with optional path, defaulting to "/". * * Use (like `.all`) will run for any http METHOD, but it will not add * handlers for those methods so OPTIONS requests will not consider `.use` * functions even if they could respond. * * The other difference is that _route_ path is stripped and not visible * to the handler function. The main effect of this feature is that mounted * handlers can operate without any code changes regardless of the "prefix" * pathname. * * @public */ Router.prototype.use = function use (handler) { let offset = 0 let path = '/' // default path to '/' // disambiguate router.use([handler]) if (typeof handler !== 'function') { let arg = handler while (Array.isArray(arg) && arg.length !== 0) { arg = arg[0] } // first arg is the path if (typeof arg !== 'function') { offset = 1 path = handler } } const callbacks = flatten.call(slice.call(arguments, offset), Infinity) if (callbacks.length === 0) { throw new TypeError('argument handler is required') } for (let i = 0; i < callbacks.length; i++) { const fn = callbacks[i] if (typeof fn !== 'function') { throw new TypeError('argument handler must be a function') } // add the middleware debug('use %o %s', path, fn.name || '') const layer = new Layer(path, { sensitive: this.caseSensitive, strict: false, end: false }, fn) layer.route = undefined this.stack.push(layer) } return this } /** * Create a new Route for the given path. * * Each route contains a separate middleware stack and VERB handlers. * * See the Route api documentation for details on adding handlers * and middleware to routes. * * @param {string} path * @return {Route} * @public */ Router.prototype.route = function route (path) { const route = new Route(path) const layer = new Layer(path, { sensitive: this.caseSensitive, strict: this.strict, end: true }, handle) function handle (req, res, next) { route.dispatch(req, res, next) } layer.route = route this.stack.push(layer) return route } // create Router#VERB functions methods.concat('all').forEach(function (method) { Router.prototype[method] = function (path) { const route = this.route(path) route[method].apply(route, slice.call(arguments, 1)) return this } }) /** * Generate a callback that will make an OPTIONS response. * * @param {OutgoingMessage} res * @param {array} methods * @private */ function generateOptionsResponder (res, methods) { return function onDone (fn, err) { if (err || methods.length === 0) { return fn(err) } trySendOptionsResponse(res, methods, fn) } } /** * Get pathname of request. * * @param {IncomingMessage} req * @private */ function getPathname (req) { try { return parseUrl(req).pathname } catch (err) { return undefined } } /** * Get get protocol + host for a URL. * * @param {string} url * @private */ function getProtohost (url) { if (typeof url !== 'string' || url.length === 0 || url[0] === '/') { return undefined } const searchIndex = url.indexOf('?') const pathLength = searchIndex !== -1 ? searchIndex : url.length const fqdnIndex = url.substring(0, pathLength).indexOf('://') return fqdnIndex !== -1 ? url.substring(0, url.indexOf('/', 3 + fqdnIndex)) : undefined } /** * Match path to a layer. * * @param {Layer} layer * @param {string} path * @private */ function matchLayer (layer, path) { try { return layer.match(path) } catch (err) { return err } } /** * Merge params with parent params * * @private */ function mergeParams (params, parent) { if (typeof parent !== 'object' || !parent) { return params } // make copy of parent for base const obj = Object.assign({}, parent) // simple non-numeric merging if (!(0 in params) || !(0 in parent)) { return Object.assign(obj, params) } let i = 0 let o = 0 // determine numeric gap in params while (i in params) { i++ } // determine numeric gap in parent while (o in parent) { o++ } // offset numeric indices in params before merge for (i--; i >= 0; i--) { params[i + o] = params[i] // create holes for the merge when necessary if (i < o) { delete params[i] } } return Object.assign(obj, params) } /** * Process any parameters for the layer. * * @private */ function processParams (params, layer, called, req, res, done) { // captured parameters from the layer, keys and values const keys = layer.keys // fast track if (!keys || keys.length === 0) { return done() } let i = 0 let paramIndex = 0 let key let paramVal let paramCallbacks let paramCalled // process params in order // param callbacks can be async function param (err) { if (err) { return done(err) } if (i >= keys.length) { return done() } paramIndex = 0 key = keys[i++] paramVal = req.params[key] paramCallbacks = params[key] paramCalled = called[key] if (paramVal === undefined || !paramCallbacks) { return param() } // param previously called with same value or error occurred if (paramCalled && (paramCalled.match === paramVal || (paramCalled.error && paramCalled.error !== 'route'))) { // restore value req.params[key] = paramCalled.value // next param return param(paramCalled.error) } called[key] = paramCalled = { error: null, match: paramVal, value: paramVal } paramCallback() } // single param callbacks function paramCallback (err) { const fn = paramCallbacks[paramIndex++] // store updated value paramCalled.value = req.params[key] if (err) { // store error paramCalled.error = err param(err) return } if (!fn) return param() try { const ret = fn(req, res, paramCallback, paramVal, key) if (isPromise(ret)) { if (!(ret instanceof Promise)) { deprecate('parameters that are Promise-like are deprecated, use a native Promise instead') } ret.then(null, function (error) { paramCallback(error || new Error('Rejected promise')) }) } } catch (e) { paramCallback(e) } } param() } /** * Restore obj props after function * * @private */ function restore (fn, obj) { const props = new Array(arguments.length - 2) const vals = new Array(arguments.length - 2) for (let i = 0; i < props.length; i++) { props[i] = arguments[i + 2] vals[i] = obj[props[i]] } return function () { // restore vals for (let i = 0; i < props.length; i++) { obj[props[i]] = vals[i] } return fn.apply(this, arguments) } } /** * Send an OPTIONS response. * * @private */ function sendOptionsResponse (res, methods) { const options = Object.create(null) // build unique method map for (let i = 0; i < methods.length; i++) { options[methods[i]] = true } // construct the allow list const allow = Object.keys(options).sort().join(', ') // send response res.setHeader('Allow', allow) res.setHeader('Content-Length', Buffer.byteLength(allow)) res.setHeader('Content-Type', 'text/plain') res.setHeader('X-Content-Type-Options', 'nosniff') res.end(allow) } /** * Try to send an OPTIONS response. * * @private */ function trySendOptionsResponse (res, methods, next) { try { sendOptionsResponse(res, methods) } catch (err) { next(err) } } /** * Wrap a function * * @private */ function wrap (old, fn) { return function proxy () { const args = new Array(arguments.length + 1) args[0] = old for (let i = 0, len = arguments.length; i < len; i++) { args[i + 1] = arguments[i] } fn.apply(this, args) } } pillarjs-router-e6d6b60/lib/000077500000000000000000000000001477111636200160445ustar00rootroot00000000000000pillarjs-router-e6d6b60/lib/layer.js000066400000000000000000000115101477111636200175140ustar00rootroot00000000000000/*! * router * Copyright(c) 2013 Roman Shtylman * Copyright(c) 2014-2022 Douglas Christopher Wilson * MIT Licensed */ 'use strict' /** * Module dependencies. * @private */ const isPromise = require('is-promise') const pathRegexp = require('path-to-regexp') const debug = require('debug')('router:layer') const deprecate = require('depd')('router') /** * Module variables. * @private */ const TRAILING_SLASH_REGEXP = /\/+$/ const MATCHING_GROUP_REGEXP = /\((?:\?<(.*?)>)?(?!\?)/g /** * Expose `Layer`. */ module.exports = Layer function Layer (path, options, fn) { if (!(this instanceof Layer)) { return new Layer(path, options, fn) } debug('new %o', path) const opts = options || {} this.handle = fn this.keys = [] this.name = fn.name || '' this.params = undefined this.path = undefined this.slash = path === '/' && opts.end === false function matcher (_path) { if (_path instanceof RegExp) { const keys = [] let name = 0 let m // eslint-disable-next-line no-cond-assign while (m = MATCHING_GROUP_REGEXP.exec(_path.source)) { keys.push({ name: m[1] || name++, offset: m.index }) } return function regexpMatcher (p) { const match = _path.exec(p) if (!match) { return false } const params = {} for (let i = 1; i < match.length; i++) { const key = keys[i - 1] const prop = key.name const val = decodeParam(match[i]) if (val !== undefined) { params[prop] = val } } return { params, path: match[0] } } } return pathRegexp.match((opts.strict ? _path : loosen(_path)), { sensitive: opts.sensitive, end: opts.end, trailing: !opts.strict, decode: decodeParam }) } this.matchers = Array.isArray(path) ? path.map(matcher) : [matcher(path)] } /** * Handle the error for the layer. * * @param {Error} error * @param {Request} req * @param {Response} res * @param {function} next * @api private */ Layer.prototype.handleError = function handleError (error, req, res, next) { const fn = this.handle if (fn.length !== 4) { // not a standard error handler return next(error) } try { // invoke function const ret = fn(error, req, res, next) // wait for returned promise if (isPromise(ret)) { if (!(ret instanceof Promise)) { deprecate('handlers that are Promise-like are deprecated, use a native Promise instead') } ret.then(null, function (error) { next(error || new Error('Rejected promise')) }) } } catch (err) { next(err) } } /** * Handle the request for the layer. * * @param {Request} req * @param {Response} res * @param {function} next * @api private */ Layer.prototype.handleRequest = function handleRequest (req, res, next) { const fn = this.handle if (fn.length > 3) { // not a standard request handler return next() } try { // invoke function const ret = fn(req, res, next) // wait for returned promise if (isPromise(ret)) { if (!(ret instanceof Promise)) { deprecate('handlers that are Promise-like are deprecated, use a native Promise instead') } ret.then(null, function (error) { next(error || new Error('Rejected promise')) }) } } catch (err) { next(err) } } /** * Check if this route matches `path`, if so * populate `.params`. * * @param {String} path * @return {Boolean} * @api private */ Layer.prototype.match = function match (path) { let match if (path != null) { // fast path non-ending match for / (any path matches) if (this.slash) { this.params = {} this.path = '' return true } let i = 0 while (!match && i < this.matchers.length) { // match the path match = this.matchers[i](path) i++ } } if (!match) { this.params = undefined this.path = undefined return false } // store values this.params = match.params this.path = match.path this.keys = Object.keys(match.params) return true } /** * Decode param value. * * @param {string} val * @return {string} * @private */ function decodeParam (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 } } /** * Loosens the given path for path-to-regexp matching. */ function loosen (path) { if (path instanceof RegExp || path === '/') { return path } return Array.isArray(path) ? path.map(function (p) { return loosen(p) }) : String(path).replace(TRAILING_SLASH_REGEXP, '') } pillarjs-router-e6d6b60/lib/route.js000066400000000000000000000107101477111636200175370ustar00rootroot00000000000000/*! * router * Copyright(c) 2013 Roman Shtylman * Copyright(c) 2014-2022 Douglas Christopher Wilson * MIT Licensed */ 'use strict' /** * Module dependencies. * @private */ const debug = require('debug')('router:route') const Layer = require('./layer') const { METHODS } = require('node:http') /** * Module variables. * @private */ const slice = Array.prototype.slice const flatten = Array.prototype.flat const methods = METHODS.map((method) => method.toLowerCase()) /** * Expose `Route`. */ module.exports = Route /** * Initialize `Route` with the given `path`, * * @param {String} path * @api private */ function Route (path) { debug('new %o', path) this.path = path this.stack = [] // route handlers for various http methods this.methods = Object.create(null) } /** * @private */ Route.prototype._handlesMethod = function _handlesMethod (method) { if (this.methods._all) { return true } // normalize name let name = typeof method === 'string' ? method.toLowerCase() : method if (name === 'head' && !this.methods.head) { name = 'get' } return Boolean(this.methods[name]) } /** * @return {array} supported HTTP methods * @private */ Route.prototype._methods = function _methods () { const methods = Object.keys(this.methods) // append automatic head if (this.methods.get && !this.methods.head) { methods.push('head') } for (let i = 0; i < methods.length; i++) { // make upper case methods[i] = methods[i].toUpperCase() } return methods } /** * dispatch req, res into this route * * @private */ Route.prototype.dispatch = function dispatch (req, res, done) { let idx = 0 const stack = this.stack let sync = 0 if (stack.length === 0) { return done() } let method = typeof req.method === 'string' ? req.method.toLowerCase() : req.method if (method === 'head' && !this.methods.head) { method = 'get' } req.route = this next() function next (err) { // signal to exit route if (err && err === 'route') { return done() } // signal to exit router if (err && err === 'router') { return done(err) } // no more matching layers if (idx >= stack.length) { return done(err) } // max sync stack if (++sync > 100) { return setImmediate(next, err) } let layer let match // find next matching layer while (match !== true && idx < stack.length) { layer = stack[idx++] match = !layer.method || layer.method === method } // no match if (match !== true) { return done(err) } if (err) { layer.handleError(err, req, res, next) } else { layer.handleRequest(req, res, next) } sync = 0 } } /** * Add a handler for all HTTP verbs to this route. * * Behaves just like middleware and can respond or call `next` * to continue processing. * * You can use multiple `.all` call to add multiple handlers. * * function check_something(req, res, next){ * next() * } * * function validate_user(req, res, next){ * next() * } * * route * .all(validate_user) * .all(check_something) * .get(function(req, res, next){ * res.send('hello world') * }) * * @param {array|function} handler * @return {Route} for chaining * @api public */ Route.prototype.all = function all (handler) { const callbacks = flatten.call(slice.call(arguments), Infinity) if (callbacks.length === 0) { throw new TypeError('argument handler is required') } for (let i = 0; i < callbacks.length; i++) { const fn = callbacks[i] if (typeof fn !== 'function') { throw new TypeError('argument handler must be a function') } const layer = Layer('/', {}, fn) layer.method = undefined this.methods._all = true this.stack.push(layer) } return this } methods.forEach(function (method) { Route.prototype[method] = function (handler) { const callbacks = flatten.call(slice.call(arguments), Infinity) if (callbacks.length === 0) { throw new TypeError('argument handler is required') } for (let i = 0; i < callbacks.length; i++) { const fn = callbacks[i] if (typeof fn !== 'function') { throw new TypeError('argument handler must be a function') } debug('%s %s', method, this.path) const layer = Layer('/', {}, fn) layer.method = method this.methods[method] = true this.stack.push(layer) } return this } }) pillarjs-router-e6d6b60/package.json000066400000000000000000000021661477111636200175710ustar00rootroot00000000000000{ "name": "router", "description": "Simple middleware-style router", "version": "2.2.0", "author": "Douglas Christopher Wilson ", "contributors": [ "Blake Embrey " ], "license": "MIT", "repository": "pillarjs/router", "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" }, "devDependencies": { "finalhandler": "^2.1.0", "mocha": "10.2.0", "nyc": "15.1.0", "run-series": "^1.1.9", "standard": "^17.1.0", "supertest": "6.3.3" }, "files": [ "lib/", "LICENSE", "HISTORY.md", "README.md", "index.js" ], "engines": { "node": ">= 18" }, "scripts": { "lint": "standard", "test": "mocha --reporter spec --bail --check-leaks test/", "test:debug": "mocha --reporter spec --bail --check-leaks test/ --inspect --inspect-brk", "test-ci": "nyc --reporter=lcov --reporter=text npm test", "test-cov": "nyc --reporter=text npm test", "version": "node scripts/version-history.js && git add HISTORY.md" } } pillarjs-router-e6d6b60/scripts/000077500000000000000000000000001477111636200167655ustar00rootroot00000000000000pillarjs-router-e6d6b60/scripts/version-history.js000066400000000000000000000030211477111636200225030ustar00rootroot00000000000000'use strict' const fs = require('fs') const path = require('path') const HISTORY_FILE_PATH = path.join(__dirname, '..', 'HISTORY.md') const MD_HEADER_REGEXP = /^====*$/ const VERSION = process.env.npm_package_version const VERSION_PLACEHOLDER_REGEXP = /^(?:unreleased|(\d+\.)+x)$/ const historyFileLines = fs.readFileSync(HISTORY_FILE_PATH, 'utf-8').split('\n') if (!MD_HEADER_REGEXP.test(historyFileLines[1])) { console.error('Missing header in HISTORY.md') process.exit(1) } if (!VERSION_PLACEHOLDER_REGEXP.test(historyFileLines[0])) { console.error('Missing placeholder version in HISTORY.md') process.exit(1) } if (historyFileLines[0].indexOf('x') !== -1) { const versionCheckRegExp = new RegExp('^' + historyFileLines[0].replace('x', '.+') + '$') if (!versionCheckRegExp.test(VERSION)) { console.error('Version %s does not match placeholder %s', VERSION, historyFileLines[0]) process.exit(1) } } historyFileLines[0] = VERSION + ' / ' + getLocaleDate() historyFileLines[1] = repeat('=', historyFileLines[0].length) fs.writeFileSync(HISTORY_FILE_PATH, historyFileLines.join('\n')) function getLocaleDate () { const now = new Date() return zeroPad(now.getFullYear(), 4) + '-' + zeroPad(now.getMonth() + 1, 2) + '-' + zeroPad(now.getDate(), 2) } function repeat (str, length) { let out = '' for (let i = 0; i < length; i++) { out += str } return out } function zeroPad (number, length) { let num = number.toString() while (num.length < length) { num = '0' + num } return num } pillarjs-router-e6d6b60/test/000077500000000000000000000000001477111636200162555ustar00rootroot00000000000000pillarjs-router-e6d6b60/test/auto-head.js000066400000000000000000000022711477111636200204640ustar00rootroot00000000000000const { it, describe } = require('mocha') const Router = require('..') const utils = require('./support/utils') const createServer = utils.createServer const request = utils.request describe('HEAD', function () { it('should invoke get without head', function (done) { const router = Router() const server = createServer(router) router.get('/users', sethit(1), saw) request(server) .head('/users') .expect('Content-Type', 'text/plain') .expect('x-fn-1', 'hit') .expect(200, done) }) it('should invoke head if prior to get', function (done) { const router = Router() const server = createServer(router) router.head('/users', sethit(1), saw) router.get('/users', sethit(2), saw) request(server) .head('/users') .expect('Content-Type', 'text/plain') .expect('x-fn-1', 'hit') .expect(200, done) }) }) function saw (req, res) { const msg = 'saw ' + req.method + ' ' + req.url res.statusCode = 200 res.setHeader('Content-Type', 'text/plain') res.end(msg) } function sethit (num) { const name = 'x-fn-' + String(num) return function hit (req, res, next) { res.setHeader(name, 'hit') next() } } pillarjs-router-e6d6b60/test/auto-options.js000066400000000000000000000053021477111636200212540ustar00rootroot00000000000000const { it, describe } = require('mocha') const Router = require('..') const utils = require('./support/utils') const createServer = utils.createServer const request = utils.request describe('OPTIONS', function () { it('should respond with defined routes', function (done) { const router = Router() const server = createServer(router) router.delete('/', saw) router.get('/users', saw) router.post('/users', saw) router.put('/users', saw) request(server) .options('/users') .expect('Allow', 'GET, HEAD, POST, PUT') .expect(200, 'GET, HEAD, POST, PUT', done) }) it('should not contain methods multiple times', function (done) { const router = Router() const server = createServer(router) router.delete('/', saw) router.get('/users', saw) router.put('/users', saw) router.get('/users', saw) request(server) .options('/users') .expect('GET, HEAD, PUT') .expect('Allow', 'GET, HEAD, PUT', done) }) it('should not include "all" routes', function (done) { const router = Router() const server = createServer(router) router.get('/', saw) router.get('/users', saw) router.put('/users', saw) router.all('/users', sethit(1)) request(server) .options('/users') .expect('x-fn-1', 'hit') .expect('Allow', 'GET, HEAD, PUT') .expect(200, 'GET, HEAD, PUT', done) }) it('should not respond if no matching path', function (done) { const router = Router() const server = createServer(router) router.get('/users', saw) request(server) .options('/') .expect(404, done) }) it('should do nothing with explicit options route', function (done) { const router = Router() const server = createServer(router) router.get('/users', saw) router.options('/users', saw) request(server) .options('/users') .expect(200, 'saw OPTIONS /users', done) }) describe('when error occurs in respone handler', function () { it('should pass error to callback', function (done) { const router = Router() const server = createServer(function hander (req, res, next) { res.writeHead(200) router(req, res, function (err) { res.end(String(Boolean(err))) }) }) router.get('/users', saw) request(server) .options('/users') .expect(200, 'true', done) }) }) }) function saw (req, res) { const msg = 'saw ' + req.method + ' ' + req.url res.statusCode = 200 res.setHeader('Content-Type', 'text/plain') res.end(msg) } function sethit (num) { const name = 'x-fn-' + String(num) return function hit (req, res, next) { res.setHeader(name, 'hit') next() } } pillarjs-router-e6d6b60/test/fqdn-url.js000066400000000000000000000037701477111636200203520ustar00rootroot00000000000000const { it, describe } = require('mocha') const Router = require('..') const utils = require('./support/utils') const createServer = utils.createServer const rawrequest = utils.rawrequest describe('FQDN url', function () { it('should not obscure FQDNs', function (done) { const router = new Router() const server = createServer(router) router.use(saw) rawrequest(server) .get('http://example.com/foo') .expect(200, 'saw GET http://example.com/foo', done) }) it('should strip/restore FQDN req.url', function (done) { const router = new Router() const server = createServer(router) router.use('/blog', setsaw(1)) router.use(saw) rawrequest(server) .get('http://example.com/blog/post/1') .expect('x-saw-1', 'GET http://example.com/post/1') .expect(200, 'saw GET http://example.com/blog/post/1', done) }) it('should ignore FQDN in search', function (done) { const router = new Router() const server = createServer(router) router.use('/proxy', setsaw(1)) router.use(saw) rawrequest(server) .get('/proxy?url=http://example.com/blog/post/1') .expect('x-saw-1', 'GET /?url=http://example.com/blog/post/1') .expect(200, 'saw GET /proxy?url=http://example.com/blog/post/1', done) }) it('should ignore FQDN in path', function (done) { const router = new Router() const server = createServer(router) router.use('/proxy', setsaw(1)) router.use(saw) rawrequest(server) .get('/proxy/http://example.com/blog/post/1') .expect('x-saw-1', 'GET /http://example.com/blog/post/1') .expect(200, 'saw GET /proxy/http://example.com/blog/post/1', done) }) }) function setsaw (num) { const name = 'x-saw-' + String(num) return function hit (req, res, next) { res.setHeader(name, req.method + ' ' + req.url) next() } } function saw (req, res) { const msg = 'saw ' + req.method + ' ' + req.url res.statusCode = 200 res.setHeader('Content-Type', 'text/plain') res.end(msg) } pillarjs-router-e6d6b60/test/param.js000066400000000000000000000261731477111636200177240ustar00rootroot00000000000000const { it, describe } = require('mocha') const series = require('run-series') const Router = require('..') const utils = require('./support/utils') const assert = utils.assert const createHitHandle = utils.createHitHandle const shouldHitHandle = utils.shouldHitHandle const shouldNotHitHandle = utils.shouldNotHitHandle const createServer = utils.createServer const request = utils.request describe('Router', function () { describe('.param(name, fn)', function () { it('should reject missing name', function () { const router = new Router() assert.throws(router.param.bind(router), /argument name is required/) }) it('should reject bad name', function () { const router = new Router() assert.throws(router.param.bind(router, 42), /argument name must be a string/) }) it('should reject missing fn', function () { const router = new Router() assert.throws(router.param.bind(router, 'id'), /argument fn is required/) }) it('should reject bad fn', function () { const router = new Router() assert.throws(router.param.bind(router, 'id', 42), /argument fn must be a function/) }) it('should map logic for a path param', function (done) { const router = new Router() const server = createServer(router) router.param('id', function parseId (req, res, next, val) { req.params.id = Number(val) next() }) router.get('/user/:id', function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('get user ' + req.params.id) }) series([ function (cb) { request(server) .get('/user/2') .expect(200, 'get user 2', cb) }, function (cb) { request(server) .get('/user/bob') .expect(200, 'get user NaN', cb) } ], done) }) it('should allow chaining', function (done) { const router = new Router() const server = createServer(router) router.param('id', function parseId (req, res, next, val) { req.params.id = Number(val) next() }) router.param('id', function parseId (req, res, next, val) { req.itemId = Number(val) next() }) router.get('/user/:id', function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('get user ' + req.params.id + ' (' + req.itemId + ')') }) request(server) .get('/user/2') .expect(200, 'get user 2 (2)', done) }) it('should automatically decode path value', function (done) { const router = new Router() const server = createServer(router) router.param('user', function parseUser (req, res, next, user) { req.user = user next() }) router.get('/user/:id', function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('get user ' + req.params.id) }) request(server) .get('/user/%22bob%2Frobert%22') .expect('get user "bob/robert"', done) }) it('should 400 on invalid path value', function (done) { const router = new Router() const server = createServer(router) router.param('user', function parseUser (req, res, next, user) { req.user = user next() }) router.get('/user/:id', function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('get user ' + req.params.id) }) request(server) .get('/user/%bob') .expect(400, /URIError: Failed to decode param/, done) }) it('should only invoke fn when necessary', function (done) { const router = new Router() const server = createServer(router) router.param('id', function parseId (req, res, next, val) { res.setHeader('x-id', val) next() }) router.param('user', function parseUser (req, res, next, user) { throw new Error('boom') }) router.get('/user/:user', saw) router.put('/user/:id', saw) series([ function (cb) { request(server) .get('/user/bob') .expect(500, /Error: boom/, cb) }, function (cb) { request(server) .put('/user/bob') .expect('x-id', 'bob') .expect(200, 'saw PUT /user/bob', cb) } ], done) }) it('should only invoke fn once per request', function (done) { const router = new Router() const server = createServer(router) router.param('user', function parseUser (req, res, next, user) { req.count = (req.count || 0) + 1 req.user = user next() }) router.get('/user/:user', sethit(1)) router.get('/user/:user', sethit(2)) router.use(function (req, res) { res.end('get user ' + req.user + ' ' + req.count + ' times') }) request(server) .get('/user/bob') .expect('get user bob 1 times', done) }) it('should keep changes to req.params value', function (done) { const router = new Router() const server = createServer(router) router.param('id', function parseUser (req, res, next, val) { req.count = (req.count || 0) + 1 req.params.id = Number(val) next() }) router.get('/user/:id', function (req, res, next) { res.setHeader('x-user-id', req.params.id) next() }) router.get('/user/:id', function (req, res) { res.end('get user ' + req.params.id + ' ' + req.count + ' times') }) request(server) .get('/user/01') .expect('get user 1 1 times', done) }) it('should invoke fn if path value differs', function (done) { const router = new Router() const server = createServer(router) router.param('user', function parseUser (req, res, next, user) { req.count = (req.count || 0) + 1 req.user = user req.vals = (req.vals || []).concat(user) next() }) router.get('/:user/bob', sethit(1)) router.get('/user/:user', sethit(2)) router.use(function (req, res) { res.end('get user ' + req.user + ' ' + req.count + ' times: ' + req.vals.join(', ')) }) request(server) .get('/user/bob') .expect('get user bob 2 times: user, bob', done) }) it('should catch exception in fn', function (done) { const router = new Router() const server = createServer(router) router.param('user', function parseUser (req, res, next, user) { throw new Error('boom') }) router.get('/user/:user', function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('get user ' + req.params.id) }) request(server) .get('/user/bob') .expect(500, /Error: boom/, done) }) it('should catch exception in chained fn', function (done) { const router = new Router() const server = createServer(router) router.param('user', function parseUser (req, res, next, user) { process.nextTick(next) }) router.param('user', function parseUser (req, res, next, user) { throw new Error('boom') }) router.get('/user/:user', function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('get user ' + req.params.id) }) request(server) .get('/user/bob') .expect(500, /Error: boom/, done) }) describe('promise support', function () { it('should pass rejected promise value', function (done) { const router = new Router() const server = createServer(router) router.param('user', function parseUser (req, res, next, user) { return Promise.reject(new Error('boom')) }) router.get('/user/:user', function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('get user ' + req.params.id) }) request(server) .get('/user/bob') .expect(500, /Error: boom/, done) }) it('should pass rejected promise without value', function (done) { const router = new Router() const server = createServer(router) router.use(function createError (req, res, next) { return Promise.reject() // eslint-disable-line prefer-promise-reject-errors }) router.param('user', function parseUser (req, res, next, user) { return Promise.reject() // eslint-disable-line prefer-promise-reject-errors }) router.get('/user/:user', function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('get user ' + req.params.id) }) request(server) .get('/user/bob') .expect(500, /Error: Rejected promise/, done) }) }) describe('next("route")', function () { it('should cause route with param to be skipped', function (done) { const router = new Router() const server = createServer(router) router.param('id', function parseId (req, res, next, val) { const id = Number(val) if (isNaN(id)) { return next('route') } req.params.id = id next() }) router.get('/user/:id', function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('get user ' + req.params.id) }) router.get('/user/new', function (req, res) { res.statusCode = 400 res.setHeader('Content-Type', 'text/plain') res.end('cannot get a new user') }) series([ function (cb) { request(server) .get('/user/2') .expect(200, 'get user 2', cb) }, function (cb) { request(server) .get('/user/bob') .expect(404, cb) }, function (cb) { request(server) .get('/user/new') .expect(400, 'cannot get a new user', cb) } ], done) }) it('should invoke fn if path value differs', function (done) { const router = new Router() const server = createServer(router) router.param('user', function parseUser (req, res, next, user) { req.count = (req.count || 0) + 1 req.user = user req.vals = (req.vals || []).concat(user) next(user === 'user' ? 'route' : null) }) router.get('/:user/bob', createHitHandle(1)) router.get('/user/:user', createHitHandle(2)) router.use(function (req, res) { res.end('get user ' + req.user + ' ' + req.count + ' times: ' + req.vals.join(', ')) }) request(server) .get('/user/bob') .expect(shouldNotHitHandle(1)) .expect(shouldHitHandle(2)) .expect('get user bob 2 times: user, bob', done) }) }) }) }) function sethit (num) { const name = 'x-fn-' + String(num) return function hit (req, res, next) { res.setHeader(name, 'hit') next() } } function saw (req, res) { const msg = 'saw ' + req.method + ' ' + req.url res.statusCode = 200 res.setHeader('Content-Type', 'text/plain') res.end(msg) } pillarjs-router-e6d6b60/test/req.params.js000066400000000000000000000114421477111636200206660ustar00rootroot00000000000000const { it, describe } = require('mocha') const Router = require('..') const utils = require('./support/utils') const createServer = utils.createServer const request = utils.request describe('req.params', function () { it('should default to empty object', function (done) { const router = Router() const server = createServer(router) router.get('/', sawParams) request(server) .get('/') .expect(200, '{}', done) }) it('should not exist outside the router', function (done) { const router = Router() const server = createServer(function (req, res, next) { router(req, res, function (err) { if (err) return next(err) sawParams(req, res) }) }) router.get('/', hitParams(1)) request(server) .get('/') .expect('x-params-1', '{}') .expect(200, '', done) }) it('should overwrite value outside the router', function (done) { const router = Router() const server = createServer(function (req, res, next) { req.params = { foo: 'bar' } router(req, res, done) }) router.get('/', sawParams) request(server) .get('/') .expect(200, '{}', done) }) it('should restore previous value outside the router', function (done) { const router = Router() const server = createServer(function (req, res, next) { req.params = { foo: 'bar' } router(req, res, function (err) { if (err) return next(err) sawParams(req, res) }) }) router.get('/', hitParams(1)) request(server) .get('/') .expect('x-params-1', '{}') .expect(200, '{"foo":"bar"}', done) }) describe('when "mergeParams: true"', function () { it('should merge outside object with params', function (done) { const router = Router({ mergeParams: true }) const server = createServer(function (req, res, next) { req.params = { foo: 'bar' } router(req, res, function (err) { if (err) return next(err) sawParams(req, res) }) }) router.get('/:fizz', hitParams(1)) request(server) .get('/buzz') .expect('x-params-1', '{"foo":"bar","fizz":"buzz"}') .expect(200, '{"foo":"bar"}', done) }) it('should ignore non-object outside object', function (done) { const router = Router({ mergeParams: true }) const server = createServer(function (req, res, next) { req.params = 42 router(req, res, function (err) { if (err) return next(err) sawParams(req, res) }) }) router.get('/:fizz', hitParams(1)) request(server) .get('/buzz') .expect('x-params-1', '{"fizz":"buzz"}') .expect(200, '42', done) }) it('should overwrite outside keys that are the same', function (done) { const router = Router({ mergeParams: true }) const server = createServer(function (req, res, next) { req.params = { foo: 'bar' } router(req, res, function (err) { if (err) return next(err) sawParams(req, res) }) }) router.get('/:foo', hitParams(1)) request(server) .get('/buzz') .expect('x-params-1', '{"foo":"buzz"}') .expect(200, '{"foo":"bar"}', done) }) describe('with numeric properties in req.params', function () { it('should merge numeric properties by offsetting', function (done) { const router = Router({ mergeParams: true }) const server = createServer(function (req, res, next) { req.params = { 0: 'foo', 1: 'bar' } router(req, res, function (err) { if (err) return next(err) sawParams(req, res) }) }) router.get(/\/([^/]*)/, hitParams(1)) request(server) .get('/buzz') .expect('x-params-1', '{"0":"foo","1":"bar","2":"buzz"}') .expect(200, '{"0":"foo","1":"bar"}', done) }) it('should merge with same numeric properties', function (done) { const router = Router({ mergeParams: true }) const server = createServer(function (req, res, next) { req.params = { 0: 'foo' } router(req, res, function (err) { if (err) return next(err) sawParams(req, res) }) }) router.get(/\/([^/]*)/, hitParams(1)) request(server) .get('/bar') .expect('x-params-1', '{"0":"foo","1":"bar"}') .expect(200, '{"0":"foo"}', done) }) }) }) }) function hitParams (num) { const name = 'x-params-' + String(num) return function hit (req, res, next) { res.setHeader(name, JSON.stringify(req.params)) next() } } function sawParams (req, res) { res.statusCode = 200 res.setHeader('Content-Type', 'application/json') res.end(JSON.stringify(req.params)) } pillarjs-router-e6d6b60/test/route.js000066400000000000000000000641501477111636200177570ustar00rootroot00000000000000const { it, describe } = require('mocha') const series = require('run-series') const Router = require('..') const utils = require('./support/utils') const assert = utils.assert const createHitHandle = utils.createHitHandle const createServer = utils.createServer const request = utils.request const shouldHaveBody = utils.shouldHaveBody const shouldHitHandle = utils.shouldHitHandle const shouldNotHaveBody = utils.shouldNotHaveBody const shouldNotHitHandle = utils.shouldNotHitHandle const methods = utils.methods describe('Router', function () { describe('.route(path)', function () { it('should return a new route', function () { const router = new Router() const route = router.route('/foo') assert.equal(route.path, '/foo') }) it('should respond to multiple methods', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route.get(saw) route.post(saw) series([ function (cb) { request(server) .get('/foo') .expect(200, 'saw GET /foo', cb) }, function (cb) { request(server) .post('/foo') .expect(200, 'saw POST /foo', cb) }, function (cb) { request(server) .put('/foo') .expect(404, cb) } ], done) }) it('should route without method', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(function (req, res, next) { req.method = undefined router(req, res, next) }) route.post(createHitHandle(1)) route.all(createHitHandle(2)) route.get(createHitHandle(3)) router.get('/foo', createHitHandle(4)) router.use(saw) request(server) .get('/foo') .expect(shouldNotHitHandle(1)) .expect(shouldHitHandle(2)) .expect(shouldNotHitHandle(3)) .expect(shouldNotHitHandle(4)) .expect(200, 'saw undefined /foo', done) }) it('should stack', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route.post(createHitHandle(1)) route.all(createHitHandle(2)) route.get(createHitHandle(3)) router.use(saw) series([ function (cb) { request(server) .get('/foo') .expect('x-fn-2', 'hit') .expect('x-fn-3', 'hit') .expect(200, 'saw GET /foo', cb) }, function (cb) { request(server) .post('/foo') .expect('x-fn-1', 'hit') .expect('x-fn-2', 'hit') .expect(200, 'saw POST /foo', cb) }, function (cb) { request(server) .put('/foo') .expect('x-fn-2', 'hit') .expect(200, 'saw PUT /foo', cb) } ], done) }) it('should not error on empty route', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) assert.ok(route) series([ function (cb) { request(server) .get('/foo') .expect(404, cb) }, function (cb) { request(server) .head('/foo') .expect(404, cb) } ], done) }) it('should not invoke singular error route', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route.all(function handleError (err, req, res, next) { throw err || new Error('boom!') }) request(server) .get('/foo') .expect(404, done) }) it('should not stack overflow with a large sync stack', function (done) { this.timeout(5000) // long-running test const router = new Router() const route = router.route('/foo') const server = createServer(router) for (let i = 0; i < 6000; i++) { route.all(function (req, res, next) { next() }) } route.get(helloWorld) request(server) .get('/foo') .expect(200, 'hello, world', done) }) describe('.all(...fn)', function () { it('should reject no arguments', function () { const router = new Router() const route = router.route('/') assert.throws(route.all.bind(route), /argument handler is required/) }) it('should reject empty array', function () { const router = new Router() const route = router.route('/') assert.throws(route.all.bind(route, []), /argument handler is required/) }) it('should reject invalid fn', function () { const router = new Router() const route = router.route('/') assert.throws(route.all.bind(route, 2), /argument handler must be a function/) }) it('should respond to all methods', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route.all(saw) series([ function (cb) { request(server) .get('/foo') .expect(200, 'saw GET /foo', cb) }, function (cb) { request(server) .post('/foo') .expect(200, 'saw POST /foo', cb) }, function (cb) { request(server) .put('/foo') .expect(200, 'saw PUT /foo', cb) } ], done) }) it('should accept multiple arguments', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route.all(createHitHandle(1), createHitHandle(2), helloWorld) request(server) .get('/foo') .expect('x-fn-1', 'hit') .expect('x-fn-2', 'hit') .expect(200, 'hello, world', done) }) it('should accept single array of handlers', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route.all([createHitHandle(1), createHitHandle(2), helloWorld]) request(server) .get('/foo') .expect('x-fn-1', 'hit') .expect('x-fn-2', 'hit') .expect(200, 'hello, world', done) }) it('should accept nested arrays of handlers', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route.all([[createHitHandle(1), createHitHandle(2)], createHitHandle(3)], helloWorld) request(server) .get('/foo') .expect('x-fn-1', 'hit') .expect('x-fn-2', 'hit') .expect('x-fn-3', 'hit') .expect(200, 'hello, world', done) }) }) methods.slice().sort().forEach(function (method) { if (method === 'connect') { // CONNECT is tricky and supertest doesn't support it return } if (method === 'query' && process.version.startsWith('v21')) { return } const body = method !== 'head' ? shouldHaveBody(Buffer.from('hello, world')) : shouldNotHaveBody() describe('.' + method + '(...fn)', function () { it('should respond to a ' + method.toUpperCase() + ' request', function (done) { const router = new Router() const route = router.route('/') const server = createServer(router) route[method](helloWorld) request(server)[method]('/') .expect(200) .expect(body) .end(done) }) it('should reject no arguments', function () { const router = new Router() const route = router.route('/') assert.throws(route[method].bind(route), /argument handler is required/) }) it('should reject empty array', function () { const router = new Router() const route = router.route('/') assert.throws(route[method].bind(route, []), /argument handler is required/) }) it('should reject invalid fn', function () { const router = new Router() const route = router.route('/') assert.throws(route[method].bind(route, 2), /argument handler must be a function/) }) it('should accept multiple arguments', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route[method](createHitHandle(1), createHitHandle(2), helloWorld) request(server)[method]('/foo') .expect(200) .expect('x-fn-1', 'hit') .expect('x-fn-2', 'hit') .expect(body) .end(done) }) it('should accept single array of handlers', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route[method]([createHitHandle(1), createHitHandle(2), helloWorld]) request(server)[method]('/foo') .expect(200) .expect('x-fn-1', 'hit') .expect('x-fn-2', 'hit') .expect(body) .end(done) }) it('should accept nested arrays of handlers', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route[method]([[createHitHandle(1), createHitHandle(2)], createHitHandle(3)], helloWorld) request(server)[method]('/foo') .expect(200) .expect('x-fn-1', 'hit') .expect('x-fn-2', 'hit') .expect('x-fn-3', 'hit') .expect(body) .end(done) }) }) }) describe('error handling', function () { it('should handle errors from next(err)', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route.all(function createError (req, res, next) { next(new Error('boom!')) }) route.all(helloWorld) route.all(function handleError (err, req, res, next) { res.statusCode = 500 res.end('caught: ' + err.message) }) request(server) .get('/foo') .expect(500, 'caught: boom!', done) }) it('should handle errors thrown', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route.all(function createError (req, res, next) { throw new Error('boom!') }) route.all(helloWorld) route.all(function handleError (err, req, res, next) { res.statusCode = 500 res.end('caught: ' + err.message) }) request(server) .get('/foo') .expect(500, 'caught: boom!', done) }) it('should handle errors thrown in error handlers', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route.all(function createError (req, res, next) { throw new Error('boom!') }) route.all(function handleError (err, req, res, next) { throw new Error('ouch: ' + err.message) }) route.all(function handleError (err, req, res, next) { res.statusCode = 500 res.end('caught: ' + err.message) }) request(server) .get('/foo') .expect(500, 'caught: ouch: boom!', done) }) }) describe('next("route")', function () { it('should invoke next handler', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route.get(function handle (req, res, next) { res.setHeader('x-next', 'route') next('route') }) router.use(saw) request(server) .get('/foo') .expect('x-next', 'route') .expect(200, 'saw GET /foo', done) }) it('should invoke next route', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route.get(function handle (req, res, next) { res.setHeader('x-next', 'route') next('route') }) router.route('/foo').all(saw) request(server) .get('/foo') .expect('x-next', 'route') .expect(200, 'saw GET /foo', done) }) it('should skip next handlers in route', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route.all(createHitHandle(1)) route.get(function goNext (req, res, next) { res.setHeader('x-next', 'route') next('route') }) route.all(createHitHandle(2)) router.use(saw) request(server) .get('/foo') .expect(shouldHitHandle(1)) .expect('x-next', 'route') .expect(shouldNotHitHandle(2)) .expect(200, 'saw GET /foo', done) }) it('should not invoke error handlers', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route.all(function goNext (req, res, next) { res.setHeader('x-next', 'route') next('route') }) route.all(function handleError (err, req, res, next) { res.statusCode = 500 res.end('caught: ' + err.message) }) request(server) .get('/foo') .expect('x-next', 'route') .expect(404, done) }) }) describe('next("router")', function () { it('should exit the router', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) function handle (req, res, next) { res.setHeader('x-next', 'router') next('router') } route.get(handle, createHitHandle(1)) router.use(saw) request(server) .get('/foo') .expect('x-next', 'router') .expect(shouldNotHitHandle(1)) .expect(404, done) }) it('should not invoke error handlers', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route.all(function goNext (req, res, next) { res.setHeader('x-next', 'router') next('router') }) route.all(function handleError (err, req, res, next) { res.statusCode = 500 res.end('caught: ' + err.message) }) router.use(function handleError (err, req, res, next) { res.statusCode = 500 res.end('caught: ' + err.message) }) request(server) .get('/foo') .expect('x-next', 'router') .expect(404, done) }) }) describe('promise support', function () { it('should pass rejected promise value', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route.all(function createError (req, res, next) { return Promise.reject(new Error('boom!')) }) route.all(helloWorld) route.all(function handleError (err, req, res, next) { res.statusCode = 500 res.end('caught: ' + err.message) }) request(server) .get('/foo') .expect(500, 'caught: boom!', done) }) it('should pass rejected promise without value', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route.all(function createError (req, res, next) { return Promise.reject() // eslint-disable-line prefer-promise-reject-errors }) route.all(helloWorld) route.all(function handleError (err, req, res, next) { res.statusCode = 500 res.end('caught: ' + err.message) }) request(server) .get('/foo') .expect(500, 'caught: Rejected promise', done) }) it('should ignore resolved promise', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route.all(function createError (req, res, next) { saw(req, res) return Promise.resolve('foo') }) route.all(function () { done(new Error('Unexpected route invoke')) }) request(server) .get('/foo') .expect(200, 'saw GET /foo', done) }) describe('error handling', function () { it('should pass rejected promise value', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route.all(function createError (req, res, next) { return Promise.reject(new Error('boom!')) }) route.all(function handleError (err, req, res, next) { return Promise.reject(new Error('caught: ' + err.message)) }) route.all(function handleError (err, req, res, next) { res.statusCode = 500 res.end('caught again: ' + err.message) }) request(server) .get('/foo') .expect(500, 'caught again: caught: boom!', done) }) it('should pass rejected promise without value', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route.all(function createError (req, res, next) { return Promise.reject(new Error('boom!')) }) route.all(function handleError (err, req, res, next) { assert.equal(err.message, 'boom!') return Promise.reject() // eslint-disable-line prefer-promise-reject-errors }) route.all(function handleError (err, req, res, next) { res.statusCode = 500 res.end('caught again: ' + err.message) }) request(server) .get('/foo') .expect(500, 'caught again: Rejected promise', done) }) it('should ignore resolved promise', function (done) { const router = new Router() const route = router.route('/foo') const server = createServer(router) route.all(function createError (req, res, next) { return Promise.reject(new Error('boom!')) }) route.all(function handleError (err, req, res, next) { res.statusCode = 500 res.end('caught: ' + err.message) return Promise.resolve('foo') }) route.all(function () { done(new Error('Unexpected route invoke')) }) request(server) .get('/foo') .expect(500, 'caught: boom!', done) }) }) }) describe('path', function () { describe('using ":name"', function () { it('should name a capture group', function (done) { const router = new Router() const route = router.route('/:foo') const server = createServer(router) route.all(sendParams) request(server) .get('/bar') .expect(200, { foo: 'bar' }, done) }) it('should match single path segment', function (done) { const router = new Router() const route = router.route('/:foo') const server = createServer(router) route.all(sendParams) request(server) .get('/bar/bar') .expect(404, done) }) it('should work multiple times', function (done) { const router = new Router() const route = router.route('/:foo/:bar') const server = createServer(router) route.all(sendParams) request(server) .get('/fizz/buzz') .expect(200, { foo: 'fizz', bar: 'buzz' }, done) }) it('should work inside literal parentheses', function (done) { const router = new Router() const route = router.route('/:user\\(:op\\)') const server = createServer(router) route.all(sendParams) request(server) .get('/tj(edit)') .expect(200, { user: 'tj', op: 'edit' }, done) }) it('should work within arrays', function (done) { const router = new Router() const route = router.route(['/user/:user/poke', '/user/:user/pokes']) const server = createServer(router) route.all(sendParams) series([ function (cb) { request(server) .get('/user/tj/poke') .expect(200, { user: 'tj' }, cb) }, function (cb) { request(server) .get('/user/tj/pokes') .expect(200, { user: 'tj' }, cb) } ], done) }) }) describe('using "{:name}"', function () { it('should name an optional parameter', function (done) { const router = new Router() const route = router.route('{/:foo}') const server = createServer(router) route.all(sendParams) series([ function (cb) { request(server) .get('/bar') .expect(200, { foo: 'bar' }, cb) }, function (cb) { request(server) .get('/') .expect(200, {}, cb) } ], done) }) it('should work in any segment', function (done) { const router = new Router() const route = router.route('/user{/:foo}/delete') const server = createServer(router) route.all(sendParams) series([ function (cb) { request(server) .get('/user/bar/delete') .expect(200, { foo: 'bar' }, cb) }, function (cb) { request(server) .get('/user/delete') .expect(200, {}, cb) } ], done) }) }) describe('using "*name"', function () { it('should name a zero-or-more repeated parameter', function (done) { const router = new Router() const route = router.route('{/*foo}') const server = createServer(router) route.all(sendParams) series([ function (cb) { request(server) .get('/') .expect(200, {}, cb) }, function (cb) { request(server) .get('/bar') .expect(200, { foo: ['bar'] }, cb) }, function (cb) { request(server) .get('/fizz/buzz') .expect(200, { foo: ['fizz', 'buzz'] }, cb) } ], done) }) it('should work in any segment', function (done) { const router = new Router() const route = router.route('/user{/*foo}/delete') const server = createServer(router) route.all(sendParams) series([ function (cb) { request(server) .get('/user/delete') .expect(200, {}, cb) }, function (cb) { request(server) .get('/user/bar/delete') .expect(200, { foo: ['bar'] }, cb) }, function (cb) { request(server) .get('/user/fizz/buzz/delete') .expect(200, { foo: ['fizz', 'buzz'] }, cb) } ], done) }) }) describe('using regular expression with param name "(?pattern)"', function () { it('should limit capture group to regexp match', function (done) { const router = new Router() const route = router.route(/\/(?[0-9]+)/) const server = createServer(router) route.all(sendParams) series([ function (cb) { request(server) .get('/foo') .expect(404, cb) }, function (cb) { request(server) .get('/42') .expect(200, { foo: '42' }, cb) } ], done) }) }) describe('using "(regexp)"', function () { it('should add capture group using regexp', function (done) { const router = new Router() const route = router.route(/\/page_([0-9]+)/) const server = createServer(router) route.all(sendParams) series([ function (cb) { request(server) .get('/page_foo') .expect(404, cb) }, function (cb) { request(server) .get('/page_42') .expect(200, { 0: '42' }, cb) } ], done) }) it('should not treat regexp as literal regexp', function () { const router = new Router() assert.throws(function () { router.route('/([a-z]+:n[0-9]+)') }, /TypeError: Unexpected \( at/) }) }) }) }) }) function helloWorld (req, res) { res.statusCode = 200 res.setHeader('Content-Type', 'text/plain') res.end('hello, world') } function saw (req, res) { const msg = 'saw ' + req.method + ' ' + req.url res.statusCode = 200 res.setHeader('Content-Type', 'text/plain') res.end(msg) } function sendParams (req, res) { res.statusCode = 200 res.setHeader('Content-Type', 'application/json') res.end(JSON.stringify(req.params)) } pillarjs-router-e6d6b60/test/router.js000066400000000000000000001176641477111636200201520ustar00rootroot00000000000000const { it, describe } = require('mocha') const series = require('run-series') const Router = require('..') const utils = require('./support/utils') const assert = utils.assert const createHitHandle = utils.createHitHandle const createServer = utils.createServer const rawrequest = utils.rawrequest const request = utils.request const shouldHaveBody = utils.shouldHaveBody const shouldHitHandle = utils.shouldHitHandle const shouldNotHaveBody = utils.shouldNotHaveBody const shouldNotHitHandle = utils.shouldNotHitHandle const methods = utils.methods describe('Router', function () { it('should return a function', function () { assert.equal(typeof Router(), 'function') }) it('should return a function using new', function () { assert.equal(typeof (new Router()), 'function') }) it('should reject missing callback', function () { const router = new Router() assert.throws(function () { router({}, {}) }, /argument callback is required/) }) it('should invoke callback without "req.url"', function (done) { const router = new Router() router.use(saw) router({}, {}, done) }) describe('.all(path, fn)', function () { it('should be chainable', function () { const router = new Router() assert.equal(router.all('/', helloWorld), router) }) it('should respond to all methods', function (done) { const router = new Router() const server = createServer(router) router.all('/', helloWorld) series(methods.map(function (method) { return function (cb) { if (method === 'connect') { // CONNECT is tricky and supertest doesn't support it return cb() } if (method === 'query' && process.version.startsWith('v21')) { return cb() } const body = method !== 'head' ? shouldHaveBody(Buffer.from('hello, world')) : shouldNotHaveBody() request(server)[method]('/') .expect(200) .expect(body) .end(cb) } }), done) }) it('should support array of paths', function (done) { const router = new Router() const server = createServer(router) router.all(['/foo', '/bar'], saw) series([ function (cb) { request(server) .get('/') .expect(404, cb) }, function (cb) { request(server) .get('/foo') .expect(200, 'saw GET /foo', cb) }, function (cb) { request(server) .get('/bar') .expect(200, 'saw GET /bar', cb) } ], done) }) it('should support regexp path', function (done) { const router = new Router() const server = createServer(router) router.all(/^\/[a-z]oo$/, saw) series([ function (cb) { request(server) .get('/') .expect(404, cb) }, function (cb) { request(server) .get('/foo') .expect(200, 'saw GET /foo', cb) }, function (cb) { request(server) .get('/zoo') .expect(200, 'saw GET /zoo', cb) } ], done) }) it('should support parameterized path', function (done) { const router = new Router() const server = createServer(router) router.all('/:thing', saw) series([ function (cb) { request(server) .get('/') .expect(404, cb) }, function (cb) { request(server) .get('/foo') .expect(200, 'saw GET /foo', cb) }, function (cb) { request(server) .get('/bar') .expect(200, 'saw GET /bar', cb) }, function (cb) { request(server) .get('/foo/bar') .expect(404, cb) } ], done) }) it('should not stack overflow with many registered routes', function (done) { this.timeout(5000) // long-running test const router = new Router() const server = createServer(router) for (let i = 0; i < 6000; i++) { router.get('/thing' + i, helloWorld) } router.get('/', helloWorld) request(server) .get('/') .expect(200, 'hello, world', done) }) it('should not stack overflow with a large sync stack', function (done) { this.timeout(5000) // long-running test const router = new Router() const server = createServer(router) for (let i = 0; i < 6000; i++) { router.get('/foo', function (req, res, next) { next() }) } router.get('/foo', helloWorld) request(server) .get('/foo') .expect(200, 'hello, world', done) }) describe('with "caseSensitive" option', function () { it('should not match paths case-sensitively by default', function (done) { const router = new Router() const server = createServer(router) router.all('/foo/bar', saw) series([ function (cb) { request(server) .get('/foo/bar') .expect(200, 'saw GET /foo/bar', cb) }, function (cb) { request(server) .get('/FOO/bar') .expect(200, 'saw GET /FOO/bar', cb) }, function (cb) { request(server) .get('/FOO/BAR') .expect(200, 'saw GET /FOO/BAR', cb) } ], done) }) it('should not match paths case-sensitively when false', function (done) { const router = new Router({ caseSensitive: false }) const server = createServer(router) router.all('/foo/bar', saw) series([ function (cb) { request(server) .get('/foo/bar') .expect(200, 'saw GET /foo/bar', cb) }, function (cb) { request(server) .get('/FOO/bar') .expect(200, 'saw GET /FOO/bar', cb) }, function (cb) { request(server) .get('/FOO/BAR') .expect(200, 'saw GET /FOO/BAR', cb) } ], done) }) it('should match paths case-sensitively when true', function (done) { const router = new Router({ caseSensitive: true }) const server = createServer(router) router.all('/foo/bar', saw) series([ function (cb) { request(server) .get('/foo/bar') .expect(200, 'saw GET /foo/bar', cb) }, function (cb) { request(server) .get('/FOO/bar') .expect(404, cb) }, function (cb) { request(server) .get('/FOO/BAR') .expect(404, cb) } ], done) }) }) describe('with "strict" option', function () { it('should accept optional trailing slashes by default', function (done) { const router = new Router() const server = createServer(router) router.all('/foo', saw) series([ function (cb) { request(server) .get('/foo') .expect(200, 'saw GET /foo', cb) }, function (cb) { request(server) .get('/foo/') .expect(200, 'saw GET /foo/', cb) } ], done) }) it('should accept optional trailing slashes when false', function (done) { const router = new Router({ strict: false }) const server = createServer(router) router.all('/foo', saw) series([ function (cb) { request(server) .get('/foo') .expect(200, 'saw GET /foo', cb) }, function (cb) { request(server) .get('/foo/') .expect(200, 'saw GET /foo/', cb) } ], done) }) it('should not accept optional trailing slashes when true', function (done) { const router = new Router({ strict: true }) const server = createServer(router) router.all('/foo', saw) series([ function (cb) { request(server) .get('/foo') .expect(200, 'saw GET /foo', cb) }, function (cb) { request(server) .get('/foo/') .expect(404, cb) } ], done) }) }) }) methods.slice().sort().forEach(function (method) { if (method === 'connect') { // CONNECT is tricky and supertest doesn't support it return } if (method === 'query' && process.version.startsWith('v21')) { return } const body = method !== 'head' ? shouldHaveBody(Buffer.from('hello, world')) : shouldNotHaveBody() describe('.' + method + '(path, ...fn)', function () { it('should be chainable', function () { const router = new Router() assert.equal(router[method]('/', helloWorld), router) }) it('should respond to a ' + method.toUpperCase() + ' request', function (done) { const router = new Router() const server = createServer(router) router[method]('/', helloWorld) request(server)[method]('/') .expect(200) .expect(body) .end(done) }) it('should reject invalid fn', function () { const router = new Router() assert.throws(router[method].bind(router, '/', 2), /argument handler must be a function/) }) it('should support array of paths', function (done) { const router = new Router() const server = createServer(router) router[method](['/foo', '/bar'], createHitHandle(1), helloWorld) series([ function (cb) { request(server)[method]('/') .expect(404) .expect(shouldNotHitHandle(1)) .end(cb) }, function (cb) { request(server)[method]('/foo') .expect(200) .expect(shouldHitHandle(1)) .expect(body) .end(cb) }, function (cb) { request(server)[method]('/bar') .expect(200) .expect(shouldHitHandle(1)) .expect(body) .end(cb) } ], done) }) it('should support parameterized path', function (done) { const router = new Router() const server = createServer(router) router[method]('/:thing', createHitHandle(1), helloWorld) series([ function (cb) { request(server)[method]('/') .expect(404) .expect(shouldNotHitHandle(1)) .end(cb) }, function (cb) { request(server)[method]('/foo') .expect(200) .expect(shouldHitHandle(1)) .expect(body) .end(cb) }, function (cb) { request(server)[method]('/bar') .expect(200) .expect(shouldHitHandle(1)) .expect(body) .end(cb) }, function (cb) { request(server)[method]('/foo/bar') .expect(404) .expect(shouldNotHitHandle(1)) .end(cb) } ], done) }) it('should accept multiple arguments', function (done) { const router = new Router() const server = createServer(router) router[method]('/', createHitHandle(1), createHitHandle(2), helloWorld) request(server)[method]('/') .expect(200) .expect(shouldHitHandle(1)) .expect(shouldHitHandle(2)) .expect(body) .end(done) }) describe('req.baseUrl', function () { it('should be empty', function (done) { const router = new Router() const server = createServer(router) router[method]('/foo', function handle (req, res) { res.setHeader('x-url-base', JSON.stringify(req.baseUrl)) res.end() }) request(server)[method]('/foo') .expect('x-url-base', '""') .expect(200, done) }) }) describe('req.route', function () { it('should be a Route', function (done) { const router = new Router() const server = createServer(router) router[method]('/foo', function handle (req, res) { res.setHeader('x-is-route', String(req.route instanceof Router.Route)) res.end() }) request(server)[method]('/foo') .expect('x-is-route', 'true') .expect(200, done) }) it('should be the matched route', function (done) { const router = new Router() const server = createServer(router) router[method]('/foo', function handle (req, res) { res.setHeader('x-is-route', String(req.route.path === '/foo')) res.end() }) request(server)[method]('/foo') .expect('x-is-route', 'true') .expect(200, done) }) }) }) }) describe('.use(...fn)', function () { it('should reject missing functions', function () { const router = new Router() assert.throws(router.use.bind(router), /argument handler is required/) }) it('should reject empty array', function () { const router = new Router() assert.throws(router.use.bind(router, []), /argument handler is required/) }) it('should reject non-functions', function () { const router = new Router() assert.throws(router.use.bind(router, '/', 'hello'), /argument handler must be a function/) assert.throws(router.use.bind(router, '/', 5), /argument handler must be a function/) assert.throws(router.use.bind(router, '/', null), /argument handler must be a function/) assert.throws(router.use.bind(router, '/', new Date()), /argument handler must be a function/) }) it('should be chainable', function () { const router = new Router() assert.equal(router.use(helloWorld), router) }) it('should invoke function for all requests', function (done) { const router = new Router() const server = createServer(router) router.use(saw) series([ function (cb) { request(server) .get('/') .expect(200, 'saw GET /', cb) }, function (cb) { request(server) .put('/') .expect(200, 'saw PUT /', cb) }, function (cb) { request(server) .post('/foo') .expect(200, 'saw POST /foo', cb) }, function (cb) { rawrequest(server) .options('*') .expect(200, 'saw OPTIONS *', cb) } ], done) }) it('should not invoke for blank URLs', function (done) { const router = new Router() const server = createServer(function hander (req, res, next) { req.url = '' router(req, res, next) }) router.use(saw) request(server) .get('/') .expect(404, done) }) it('should support another router', function (done) { const inner = new Router() const router = new Router() const server = createServer(router) inner.use(saw) router.use(inner) request(server) .get('/') .expect(200, 'saw GET /', done) }) it('should accept multiple arguments', function (done) { const router = new Router() const server = createServer(router) router.use(createHitHandle(1), createHitHandle(2), helloWorld) request(server) .get('/') .expect(shouldHitHandle(1)) .expect(shouldHitHandle(2)) .expect(200, 'hello, world', done) }) it('should accept single array of middleware', function (done) { const router = new Router() const server = createServer(router) router.use([createHitHandle(1), createHitHandle(2), helloWorld]) request(server) .get('/') .expect(shouldHitHandle(1)) .expect(shouldHitHandle(2)) .expect(200, 'hello, world', done) }) it('should accept nested arrays of middleware', function (done) { const router = new Router() const server = createServer(router) router.use([[createHitHandle(1), createHitHandle(2)], createHitHandle(3)], helloWorld) request(server) .get('/') .expect(shouldHitHandle(1)) .expect(shouldHitHandle(2)) .expect(shouldHitHandle(3)) .expect(200, 'hello, world', done) }) it('should not invoke singular error function', function (done) { const router = new Router() const server = createServer(router) router.use(function handleError (err, req, res, next) { throw err || new Error('boom!') }) request(server) .get('/') .expect(404, done) }) it('should not stack overflow with a large sync stack', function (done) { this.timeout(5000) // long-running test const router = new Router() const server = createServer(router) for (let i = 0; i < 6000; i++) { router.use(function (req, res, next) { next() }) } router.use(helloWorld) request(server) .get('/') .expect(200, 'hello, world', done) }) describe('error handling', function () { it('should invoke error function after next(err)', function (done) { const router = new Router() const server = createServer(router) router.use(function handle (req, res, next) { next(new Error('boom!')) }) router.use(sawError) request(server) .get('/') .expect(200, 'saw Error: boom!', done) }) it('should invoke error function after throw err', function (done) { const router = new Router() const server = createServer(router) router.use(function handle (req, res, next) { throw new Error('boom!') }) router.use(sawError) request(server) .get('/') .expect(200, 'saw Error: boom!', done) }) it('should not invoke error functions above function', function (done) { const router = new Router() const server = createServer(router) router.use(sawError) router.use(function handle (req, res, next) { throw new Error('boom!') }) request(server) .get('/') .expect(500, done) }) }) describe('next("route")', function () { it('should invoke next handler', function (done) { const router = new Router() const server = createServer(router) router.use(function handle (req, res, next) { res.setHeader('x-next', 'route') next('route') }) router.use(saw) request(server) .get('/') .expect('x-next', 'route') .expect(200, 'saw GET /', done) }) it('should invoke next function', function (done) { const router = new Router() const server = createServer(router) function goNext (req, res, next) { res.setHeader('x-next', 'route') next('route') } router.use(createHitHandle(1), goNext, createHitHandle(2), saw) request(server) .get('/') .expect(shouldHitHandle(1)) .expect('x-next', 'route') .expect(shouldHitHandle(2)) .expect(200, 'saw GET /', done) }) it('should not invoke error handlers', function (done) { const router = new Router() const server = createServer(router) router.use(function handle (req, res, next) { res.setHeader('x-next', 'route') next('route') }) router.use(sawError) request(server) .get('/') .expect('x-next', 'route') .expect(404, done) }) }) describe('next("router")', function () { it('should exit the router', function (done) { const router = new Router() const server = createServer(router) function handle (req, res, next) { res.setHeader('x-next', 'router') next('router') } router.use(handle, createHitHandle(1)) router.use(saw) request(server) .get('/') .expect('x-next', 'router') .expect(shouldNotHitHandle(1)) .expect(404, done) }) it('should not invoke error handlers', function (done) { const router = new Router() const server = createServer(router) router.use(function handle (req, res, next) { res.setHeader('x-next', 'router') next('route') }) router.use(sawError) request(server) .get('/') .expect('x-next', 'router') .expect(404, done) }) }) describe('promise support', function () { it('should pass rejected promise value', function (done) { const router = new Router() const server = createServer(router) router.use(function createError (req, res, next) { return Promise.reject(new Error('boom!')) }) router.use(sawError) request(server) .get('/') .expect(200, 'saw Error: boom!', done) }) it('should pass rejected promise without value', function (done) { const router = new Router() const server = createServer(router) router.use(function createError (req, res, next) { return Promise.reject() // eslint-disable-line prefer-promise-reject-errors }) router.use(sawError) request(server) .get('/') .expect(200, 'saw Error: Rejected promise', done) }) it('should ignore resolved promise', function (done) { const router = new Router() const server = createServer(router) router.use(function createError (req, res, next) { saw(req, res) return Promise.resolve('foo') }) router.use(function () { done(new Error('Unexpected middleware invoke')) }) request(server) .get('/foo') .expect(200, 'saw GET /foo', done) }) describe('error handling', function () { it('should pass rejected promise value', function (done) { const router = new Router() const server = createServer(router) router.use(function createError (req, res, next) { return Promise.reject(new Error('boom!')) }) router.use(function handleError (err, req, res, next) { return Promise.reject(new Error('caught: ' + err.message)) }) router.use(sawError) request(server) .get('/') .expect(200, 'saw Error: caught: boom!', done) }) it('should pass rejected promise without value', function (done) { const router = new Router() const server = createServer(router) router.use(function createError (req, res, next) { return Promise.reject() // eslint-disable-line prefer-promise-reject-errors }) router.use(function handleError (err, req, res, next) { return Promise.reject(new Error('caught: ' + err.message)) }) router.use(sawError) request(server) .get('/') .expect(200, 'saw Error: caught: Rejected promise', done) }) it('should ignore resolved promise', function (done) { const router = new Router() const server = createServer(router) router.use(function createError (req, res, next) { return Promise.reject(new Error('boom!')) }) router.use(function handleError (err, req, res, next) { sawError(err, req, res, next) return Promise.resolve('foo') }) router.use(function () { done(new Error('Unexpected middleware invoke')) }) request(server) .get('/foo') .expect(200, 'saw Error: boom!', done) }) }) }) describe('req.baseUrl', function () { it('should be empty', function (done) { const router = new Router() const server = createServer(router) router.use(sawBase) request(server) .get('/foo/bar') .expect(200, 'saw ', done) }) }) }) describe('.use(path, ...fn)', function () { it('should be chainable', function () { const router = new Router() assert.equal(router.use('/', helloWorld), router) }) it('should invoke when req.url starts with path', function (done) { const router = new Router() const server = createServer(router) router.use('/foo', saw) series([ function (cb) { request(server) .get('/') .expect(404, cb) }, function (cb) { request(server) .post('/foo') .expect(200, 'saw POST /', cb) }, function (cb) { request(server) .post('/foo/bar') .expect(200, 'saw POST /bar', cb) } ], done) }) it('should match if path has trailing slash', function (done) { const router = new Router() const server = createServer(router) router.use('/foo/', saw) series([ function (cb) { request(server) .get('/') .expect(404, cb) }, function (cb) { request(server) .post('/foo') .expect(200, 'saw POST /', cb) }, function (cb) { request(server) .post('/foo/bar') .expect(200, 'saw POST /bar', cb) } ], done) }) it('should support array of paths', function (done) { const router = new Router() const server = createServer(router) router.use(['/foo/', '/bar'], saw) series([ function (cb) { request(server) .get('/') .expect(404, cb) }, function (cb) { request(server) .get('/foo') .expect(200, 'saw GET /', cb) }, function (cb) { request(server) .get('/bar') .expect(200, 'saw GET /', cb) } ], done) }) it('should support regexp path', function (done) { const router = new Router() const server = createServer(router) router.use(/^\/[a-z]oo$/, saw) series([ function (cb) { request(server) .get('/') .expect(404, cb) }, function (cb) { request(server) .get('/foo') .expect(200, 'saw GET /', cb) }, function (cb) { request(server) .get('/fooo') .expect(404, cb) }, function (cb) { request(server) .get('/zoo/bear') .expect(404, cb) }, function (cb) { request(server) .get('/get/zoo') .expect(404, cb) } ], done) }) it('should support regexp path with params', function (done) { const router = new Router() const server = createServer(router) router.use(/^\/([a-z]oo)$/, function (req, res, next) { createHitHandle(req.params[0])(req, res, next) }, saw) router.use(/^\/([a-z]oo)\/(?bear)$/, function (req, res, next) { createHitHandle(req.params[0] + req.params.animal)(req, res, next) }, saw) series([ function (cb) { request(server) .get('/') .expect(404, cb) }, function (cb) { request(server) .get('/foo') .expect(shouldHitHandle('foo')) .expect(200, 'saw GET /', cb) }, function (cb) { request(server) .get('/zoo') .expect(shouldHitHandle('zoo')) .expect(200, 'saw GET /', cb) }, function (cb) { request(server) .get('/fooo') .expect(404, cb) }, function (cb) { request(server) .get('/zoo/bear') .expect(shouldHitHandle('zoobear')) .expect(200, cb) }, function (cb) { request(server) .get('/get/zoo') .expect(404, cb) } ], done) }) it('should ensure regexp matches path prefix', function (done) { const router = new Router() const server = createServer(router) router.use(/\/api.*/, createHitHandle(1)) router.use(/api/, createHitHandle(2)) router.use(/\/test/, createHitHandle(3)) router.use(helloWorld) request(server) .get('/test/api/1234') .expect(shouldNotHitHandle(1)) .expect(shouldNotHitHandle(2)) .expect(shouldHitHandle(3)) .expect(200, done) }) it('should support parameterized path', function (done) { const router = new Router() const server = createServer(router) router.use('/:thing', saw) series([ function (cb) { request(server) .get('/') .expect(404, cb) }, function (cb) { request(server) .get('/foo') .expect(200, 'saw GET /', cb) }, function (cb) { request(server) .get('/bar') .expect(200, 'saw GET /', cb) }, function (cb) { request(server) .get('/foo/bar') .expect(200, 'saw GET /bar', cb) } ], done) }) it('should accept multiple arguments', function (done) { const router = new Router() const server = createServer(router) router.use('/foo', createHitHandle(1), createHitHandle(2), helloWorld) request(server) .get('/foo') .expect(shouldHitHandle(1)) .expect(shouldHitHandle(2)) .expect(200, 'hello, world', done) }) describe('with "caseSensitive" option', function () { it('should not match paths case-sensitively by default', function (done) { const router = new Router() const server = createServer(router) router.use('/foo', saw) series([ function (cb) { request(server) .get('/foo/bar') .expect(200, 'saw GET /bar', cb) }, function (cb) { request(server) .get('/FOO/bar') .expect(200, 'saw GET /bar', cb) }, function (cb) { request(server) .get('/FOO/BAR') .expect(200, 'saw GET /BAR', cb) } ], done) }) it('should not match paths case-sensitively when false', function (done) { const router = new Router({ caseSensitive: false }) const server = createServer(router) router.use('/foo', saw) series([ function (cb) { request(server) .get('/foo/bar') .expect(200, 'saw GET /bar', cb) }, function (cb) { request(server) .get('/FOO/bar') .expect(200, 'saw GET /bar', cb) }, function (cb) { request(server) .get('/FOO/BAR') .expect(200, 'saw GET /BAR', cb) } ], done) }) it('should match paths case-sensitively when true', function (done) { const router = new Router({ caseSensitive: true }) const server = createServer(router) router.use('/foo', saw) series([ function (cb) { request(server) .get('/foo/bar') .expect(200, 'saw GET /bar', cb) }, function (cb) { request(server) .get('/FOO/bar') .expect(404, cb) }, function (cb) { request(server) .get('/FOO/BAR') .expect(404, cb) } ], done) }) }) describe('with "strict" option', function () { it('should accept optional trailing slashes by default', function (done) { const router = new Router() const server = createServer(router) router.use('/foo', saw) series([ function (cb) { request(server) .get('/foo') .expect(200, 'saw GET /', cb) }, function (cb) { request(server) .get('/foo/') .expect(200, 'saw GET /', cb) } ], done) }) it('should accept optional trailing slashes when false', function (done) { const router = new Router({ strict: false }) const server = createServer(router) router.use('/foo', saw) series([ function (cb) { request(server) .get('/foo') .expect(200, 'saw GET /', cb) }, function (cb) { request(server) .get('/foo/') .expect(200, 'saw GET /', cb) } ], done) }) it('should accept optional trailing slashes when true', function (done) { const router = new Router({ strict: true }) const server = createServer(router) router.use('/foo', saw) series([ function (cb) { request(server) .get('/foo') .expect(200, 'saw GET /', cb) }, function (cb) { request(server) .get('/foo/') .expect(200, 'saw GET /', cb) } ], done) }) }) describe('next("route")', function () { it('should invoke next handler', function (done) { const router = new Router() const server = createServer(router) router.use('/foo', function handle (req, res, next) { res.setHeader('x-next', 'route') next('route') }) router.use('/foo', saw) request(server) .get('/foo') .expect('x-next', 'route') .expect(200, 'saw GET /', done) }) it('should invoke next function', function (done) { const router = new Router() const server = createServer(router) function goNext (req, res, next) { res.setHeader('x-next', 'route') next('route') } router.use('/foo', createHitHandle(1), goNext, createHitHandle(2), saw) request(server) .get('/foo') .expect(shouldHitHandle(1)) .expect('x-next', 'route') .expect(shouldHitHandle(2)) .expect(200, 'saw GET /', done) }) }) describe('req.baseUrl', function () { it('should contain the stripped path', function (done) { const router = new Router() const server = createServer(router) router.use('/foo', sawBase) request(server) .get('/foo/bar') .expect(200, 'saw /foo', done) }) it('should contain the stripped path for multiple levels', function (done) { const router1 = new Router() const router2 = new Router() const server = createServer(router1) router1.use('/foo', router2) router2.use('/bar', sawBase) request(server) .get('/foo/bar/baz') .expect(200, 'saw /foo/bar', done) }) it('should contain the stripped path for multiple levels with regular expressions', function (done) { const router1 = new Router() const router2 = new Router() const server = createServer(router1) router1.use(/^\/foo/, router2) router2.use(/^\/bar/, sawBase) request(server) .get('/foo/bar/baz') .expect(200, 'saw /foo/bar', done) }) it('should be altered correctly', function (done) { const router = new Router() const server = createServer(router) const sub1 = new Router() const sub2 = new Router() const sub3 = new Router() sub3.get('/zed', setsawBase(1)) sub2.use('/baz', sub3) sub1.use('/', setsawBase(2)) sub1.use('/bar', sub2) sub1.use('/bar', setsawBase(3)) router.use(setsawBase(4)) router.use('/foo', sub1) router.use(setsawBase(5)) router.use(helloWorld) request(server) .get('/foo/bar/baz/zed') .expect('x-saw-base-1', '/foo/bar/baz') .expect('x-saw-base-2', '/foo') .expect('x-saw-base-3', '/foo/bar') .expect('x-saw-base-4', '') .expect('x-saw-base-5', '') .expect(200, done) }) }) describe('req.url', function () { it('should strip path from req.url', function (done) { const router = new Router() const server = createServer(router) router.use('/foo', saw) request(server) .get('/foo/bar') .expect(200, 'saw GET /bar', done) }) it('should restore req.url after stripping', function (done) { const router = new Router() const server = createServer(router) router.use('/foo', setsaw(1)) router.use(saw) request(server) .get('/foo/bar') .expect('x-saw-1', 'GET /bar') .expect(200, 'saw GET /foo/bar', done) }) it('should strip/restore with trailing stash', function (done) { const router = new Router() const server = createServer(router) router.use('/foo', setsaw(1)) router.use(saw) request(server) .get('/foo/') .expect('x-saw-1', 'GET /') .expect(200, 'saw GET /foo/', done) }) }) }) describe('request rewriting', function () { it('should support altering req.method', function (done) { const router = new Router() const server = createServer(router) router.put('/foo', createHitHandle(1)) router.post('/foo', createHitHandle(2), function (req, res, next) { req.method = 'PUT' next() }) router.post('/foo', createHitHandle(3)) router.put('/foo', createHitHandle(4)) router.use(saw) request(server) .post('/foo') .expect(shouldNotHitHandle(1)) .expect(shouldHitHandle(2)) .expect(shouldNotHitHandle(3)) .expect(shouldHitHandle(4)) .expect(200, 'saw PUT /foo', done) }) it('should support altering req.url', function (done) { const router = new Router() const server = createServer(router) router.get('/bar', createHitHandle(1)) router.get('/foo', createHitHandle(2), function (req, res, next) { req.url = '/bar' next() }) router.get('/foo', createHitHandle(3)) router.get('/bar', createHitHandle(4)) router.use(saw) request(server) .get('/foo') .expect(shouldNotHitHandle(1)) .expect(shouldHitHandle(2)) .expect(shouldNotHitHandle(3)) .expect(shouldHitHandle(4)) .expect(200, 'saw GET /bar', done) }) }) }) function helloWorld (req, res) { res.statusCode = 200 res.setHeader('Content-Type', 'text/plain') res.end('hello, world') } function setsaw (num) { const name = 'x-saw-' + String(num) return function saw (req, res, next) { res.setHeader(name, req.method + ' ' + req.url) next() } } function setsawBase (num) { const name = 'x-saw-base-' + String(num) return function sawBase (req, res, next) { res.setHeader(name, String(req.baseUrl)) next() } } function saw (req, res) { const msg = 'saw ' + req.method + ' ' + req.url res.statusCode = 200 res.setHeader('Content-Type', 'text/plain') res.end(msg) } function sawError (err, req, res, next) { const msg = 'saw ' + err.name + ': ' + err.message res.statusCode = 200 res.setHeader('Content-Type', 'text/plain') res.end(msg) } function sawBase (req, res) { const msg = 'saw ' + req.baseUrl res.statusCode = 200 res.setHeader('Content-Type', 'text/plain') res.end(msg) } pillarjs-router-e6d6b60/test/support/000077500000000000000000000000001477111636200177715ustar00rootroot00000000000000pillarjs-router-e6d6b60/test/support/utils.js000066400000000000000000000061471477111636200214770ustar00rootroot00000000000000const assert = require('assert') const finalhandler = require('finalhandler') const http = require('http') const { METHODS } = require('node:http') const request = require('supertest') const methods = METHODS.map((method) => method.toLowerCase()) exports.assert = assert exports.createHitHandle = createHitHandle exports.createServer = createServer exports.rawrequest = rawrequest exports.request = request exports.shouldHaveBody = shouldHaveBody exports.shouldNotHaveBody = shouldNotHaveBody exports.shouldHitHandle = shouldHitHandle exports.shouldNotHitHandle = shouldNotHitHandle exports.methods = methods function createHitHandle (num) { const name = 'x-fn-' + String(num) return function hit (req, res, next) { res.setHeader(name, 'hit') next() } } function createServer (router) { return http.createServer(function onRequest (req, res) { router(req, res, finalhandler(req, res)) }) } function rawrequest (server) { const _headers = {} let _method let _path const _test = {} methods.forEach(function (method) { _test[method] = go.bind(null, method) }) function expect (status, body, callback) { if (arguments.length === 2) { _headers[status.toLowerCase()] = body return this } let _server if (!server.address()) { _server = server.listen(0, onListening) return } onListening.call(server) function onListening () { const addr = this.address() const port = addr.port const req = http.request({ host: '127.0.0.1', method: _method, path: _path, port }) req.on('response', function (res) { let buf = '' res.setEncoding('utf8') res.on('data', function (s) { buf += s }) res.on('end', function () { let err = null try { for (const key in _headers) { assert.equal(res.headers[key], _headers[key]) } assert.equal(res.statusCode, status) assert.equal(buf, body) } catch (e) { err = e } if (_server) { _server.close() } callback(err) }) }) req.end() } } function go (method, path) { _method = method _path = path return { expect } } return _test } function shouldHaveBody (buf) { return function (res) { const body = !Buffer.isBuffer(res.body) ? Buffer.from(res.text) : res.body assert.ok(body, 'response has body') assert.strictEqual(body.toString('hex'), buf.toString('hex')) } } function shouldHitHandle (num) { const header = 'x-fn-' + String(num) return function (res) { assert.equal(res.headers[header], 'hit', 'should hit handle ' + num) } } function shouldNotHaveBody () { return function (res) { assert.ok(res.text === '' || res.text === undefined) } } function shouldNotHitHandle (num) { return shouldNotHaveHeader('x-fn-' + String(num)) } function shouldNotHaveHeader (header) { return function (res) { assert.ok(!(header.toLowerCase() in res.headers), 'should not have header ' + header) } }