conditional-middleware
Advanced tools
Comparing version 0.1.2 to 0.2.0
{ | ||
"name": "conditional-middleware", | ||
"version": "0.1.2", | ||
"version": "0.2.0", | ||
"description": "Conditional, composable middleware for connect, express, and other soon-to-be available server modules.", | ||
"main": "src/index.js", | ||
"scripts": { | ||
"test": "mocha", | ||
"test": "mocha $(find ./test -name 'test.js') --check-leaks", | ||
"publish": "git push origin --tags && git push origin", | ||
@@ -31,2 +31,3 @@ "release:prerelease": "npm version prerelease && npm publish", | ||
"chai": "^3.5.0", | ||
"compare-versions": "^3.0.0", | ||
"mocha": "^3.2.0", | ||
@@ -33,0 +34,0 @@ "rewire": "^2.5.2" |
# conditional-middleware | ||
Compose chains of middleware based on a condition. If the condition fails, the middleware does not execute - not even as a wrapper around `next()` calls. | ||
This very lightweight module allows you to compose chains of middleware based on a condition. If the condition fails, the middleware chain does not execute - not even as a wrapper around `next()` calls. | ||
@@ -11,13 +11,28 @@ ``` | ||
This module generates [connect-friendly](https://github.com/senchalabs/connect#mount-middleware) middleware and should be compatible with libraries like [Express](https://expressjs.com/), [flatiron/union](https://github.com/flatiron/union), et al. If you want support for others like Koa, please [create an issue](https://github.com/DesignByOnyx/conditional-middleware/issues) and I will do my best to add support as quickly as possible. | ||
- **[connect-friendly](https://github.com/senchalabs/connect#mount-middleware)** - [Express](https://expressjs.com/), [flatiron/union](https://github.com/flatiron/union), et al. | ||
``` | ||
// connect-friendly middleware: | ||
(req, res, next) => { ... } | ||
(err, req, res, next) => { ... } //-> must have exactly 4 arguments | ||
``` | ||
```js | ||
// connect-friendly middleware: | ||
(req, res, next) => { ... } | ||
(err, req, res, next) => { ... } //-> must have exactly 4 arguments | ||
``` | ||
- **[Koa]()** - Since KOA is so different, you must require a different file. Everything else is the same. **Note** - you must use use Node 7.6+ or Babel per Koa's requirements. | ||
> **Note:** I made this module with zero dependencies, so you can just copy and paste the code and modify if you don't want to wait on me. I ask that you please help make this better by submitting an issue so I know what you're wanting. | ||
```js | ||
// koa users must include like this | ||
const conditional = require('conditional-middleware/koa'); | ||
// koa style middleware | ||
(ctx, next) => { ... } | ||
``` | ||
## Basic Usage | ||
___ | ||
> **Koa Users:** The following examples use express. Anywhere you see '_express_' can be replaced with '_koa_'. Anywhere you see express-style middleware can be replaced with koa-style middleware. Also, remember you need to do this: | ||
> | ||
> | ||
> ```js | ||
> const conditional = require('conditional-middleware/koa'); | ||
> ``` | ||
___ | ||
@@ -43,3 +58,3 @@ In the following example, if `shouldHandleRequest` returns **false**, _middleware1_ and _middleware2_ will not execute. | ||
In the example above, `shouldHandleRequest` can return a promise: | ||
In the example above, `shouldHandleRequest` can return a promise. The promise must **`resolve`** to truthy or falsy. **You should never use `reject` to mean "condition failed"**. | ||
@@ -89,39 +104,2 @@ ```js | ||
### Create your own context | ||
Instead of using the `createContext` method above, you can create your own context by passing a 3rd param. The following code is equivalent to the example above: | ||
```js | ||
const express = require('express'); | ||
const conditional = require('conditional-middleware'); | ||
// context passed as third parameter (see below) | ||
const CONTEXT = "random_" + Math.random().slice(3); | ||
function isGithubWebhook (req) { | ||
return req.get('x-github-event'); | ||
} | ||
function isBasicAuth (req) { | ||
return req.get('Authorization').indexOf('Basic') === 0; | ||
} | ||
const app = express(); | ||
app.use(conditional(isGithubWebhook, [ | ||
validateGithubRequest, | ||
processGithubWebhook, | ||
middleware_1, | ||
... | ||
], CONTEXT); | ||
// else if | ||
app.use(conditional(isBasicAuth, [ | ||
validateAuthCredentials, | ||
middleware_2, | ||
... | ||
], CONTEXT); | ||
// else | ||
app.use(conditional(() => true, [ | ||
sendUnauthorizedResponse | ||
], CONTEXT); | ||
``` | ||
## But what if the condtion returns false? | ||
@@ -155,3 +133,3 @@ | ||
Yup, that works too! Remember, the `condtional` function will always return [connect-friendly](https://github.com/senchalabs/connect#mount-middleware) middleware, allowing you to nest conditionals as deep as you want. | ||
Yup, that works too! The `condtional` function will always return middleware, allowing you use it anywhere where you would use normally use middleware. | ||
@@ -185,3 +163,2 @@ ```js | ||
- [Koa](https://github.com/koajs/koa) support - [Vote on the issue here](https://github.com/DesignByOnyx/conditional-middleware/issues/1) | ||
- A new API? - [Cast your opinions here](https://github.com/DesignByOnyx/conditional-middleware/issues/2) | ||
- A new API? - [Cast your opinions here](https://github.com/DesignByOnyx/conditional-middleware/issues/2) |
'use strict'; | ||
// Converts a node-style callback function to a promise | ||
// This was easy enough to implement without needing another dependency | ||
// This was easy enough to implement without needing a dependency | ||
const promisify = fn => { | ||
@@ -16,3 +16,3 @@ return (...args) => { | ||
// Generates a reducer function for chaining middleware | ||
// Generates a reducer function for chaining middleware via promises | ||
const chainMiddleware = (req, res) => { | ||
@@ -29,3 +29,3 @@ return (promise, middleware) => { | ||
// (req, res, next) => { ... } | ||
return promise.then(() => pMiddleware(req, res)) | ||
return promise.then(() => pMiddleware(req, res)); | ||
}; | ||
@@ -41,12 +41,6 @@ }; | ||
} | ||
let result = condition(req); | ||
if (typeof result.then !== 'function') { | ||
result = Promise.resolve(result); | ||
} | ||
result.then(truthy => { | ||
Promise.resolve(condition(req)).then(truthy => { | ||
if (truthy) { | ||
if (context) { | ||
// tells others to skip | ||
req[context] = true; | ||
} | ||
// tells others to skip | ||
if (context) req[context] = true; | ||
return middlewares.reduce(chainMiddleware(req, res), Promise.resolve()) | ||
@@ -62,8 +56,3 @@ .then(next) | ||
conditionalMiddleware.createContext = fn => { | ||
// only needs to be pseudo-random | ||
const unguessable = Math.random().toString(36).slice(2); | ||
fn((condition, middlewares) => conditionalMiddleware(condition, middlewares, unguessable)); | ||
}; | ||
require('./create-context')(conditionalMiddleware); | ||
module.exports = conditionalMiddleware; |
251
test/test.js
@@ -0,80 +1,81 @@ | ||
'use strict'; | ||
const assert = require('chai').assert; | ||
const rewire = require('rewire'); | ||
const compareVersions = require('compare-versions'); | ||
const commonTests = require('./common'); | ||
describe('promisify helper', () => { | ||
let promisify; | ||
before(() => { | ||
promisify = rewire('../src/index').__get__('promisify'); | ||
}); | ||
const KOA_COMPAT = compareVersions(process.version.replace(/^v/, ''), '7.6.0'); | ||
const CONNECT = '../src/index'; | ||
it('converts node callback-style functions to promises', () => { | ||
const fn = promisify((foo, bar, next) => { | ||
assert.equal(foo, 'foo', 'foo argument is correct'); | ||
assert.equal(bar, 'bar', 'bar argument is correct'); | ||
next(null, 'success'); | ||
describe('connect helpers', () => { | ||
describe('promisify', () => { | ||
let promisify; | ||
before(() => { | ||
promisify = rewire(CONNECT).__get__('promisify'); | ||
}); | ||
const promise = fn('foo', 'bar'); | ||
assert.equal(typeof promise.then, 'function', 'fn returns a then-able object'); | ||
it('converts node callback-style functions to promises', () => { | ||
const fn = promisify((foo, bar, next) => { | ||
assert.equal(foo, 'foo', 'foo argument is correct'); | ||
assert.equal(bar, 'bar', 'bar argument is correct'); | ||
next(null, 'success'); | ||
}); | ||
return promise.then(result => { | ||
assert.equal(result, 'success', 'result was success'); | ||
const promise = fn('foo', 'bar'); | ||
assert.ok(promise instanceof Promise, 'returns a Promise'); | ||
return promise.then(result => { | ||
assert.equal(result, 'success', 'result was success'); | ||
}); | ||
}); | ||
}); | ||
it('rejects if the internal callback passes an error as the first argument', done => { | ||
const fn = promisify(next => next('failure')); | ||
it('rejects if the internal callback passes an error as the first argument', done => { | ||
const fn = promisify(next => next('failure')); | ||
fn().catch(err => { | ||
assert.equal(err, 'failure'); | ||
done(); | ||
fn().catch(err => { | ||
assert.equal(err, 'failure'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('chainMiddleware helper', () => { | ||
let chainMiddleware; | ||
before(() => { | ||
chainMiddleware = rewire('../src/index').__get__('chainMiddleware'); | ||
}); | ||
describe('chainMiddleware (connect)', () => { | ||
let chainMiddleware; | ||
before(() => { | ||
chainMiddleware = rewire(CONNECT).__get__('chainMiddleware'); | ||
}); | ||
it('chains middleware onto a promise', () => { | ||
const middleware = (req, res, next) => { | ||
next(null, 'notfoobar') | ||
}; | ||
const reducer = chainMiddleware({}, {}); | ||
const promise = [middleware].reduce(reducer, Promise.resolve('foobar')); | ||
assert.equal(typeof promise.then, 'function', 'returns then-able object'); | ||
assert.equal(typeof promise.catch, 'function', 'returns catch-able object'); | ||
return promise.then(result => { | ||
assert.equal(result, 'notfoobar', 'got the result we expected'); | ||
it('chains middleware onto a promise', () => { | ||
const middleware = (req, res, next) => next(null, 'notfoobar'); | ||
const reducer = chainMiddleware({}, {}); | ||
const promise = [middleware].reduce(reducer, Promise.resolve('foobar')); | ||
assert.ok(promise instanceof Promise, 'returns Promise'); | ||
return promise.then(result => { | ||
assert.equal(result, 'notfoobar', 'got the result we expected'); | ||
}); | ||
}); | ||
}); | ||
it('produces a catchable promise', done => { | ||
const middleware = (req, res, next) => { | ||
next('error') | ||
}; | ||
const reducer = chainMiddleware({}, {}); | ||
const promise = [middleware].reduce(reducer, Promise.resolve()); | ||
assert.equal(typeof promise.then, 'function', 'returns then-able object'); | ||
assert.equal(typeof promise.catch, 'function', 'returns catch-able object'); | ||
promise.catch(err => { | ||
assert.equal(err, 'error', 'got the error'); | ||
done(); | ||
it('produces a catchable promise', done => { | ||
const middleware = (req, res, next) => next('error'); | ||
const reducer = chainMiddleware({}, {}); | ||
const promise = [middleware].reduce(reducer, Promise.resolve()); | ||
assert.ok(promise instanceof Promise, 'returns Promise'); | ||
promise.catch(err => { | ||
assert.equal(err, 'error', 'got the error'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('works with error handler middleware (exactly 4 args)', done => { | ||
const middleware1 = (req, res, next) => { | ||
next({ message: 'error' }); | ||
}; | ||
const middleware2 = (err, req, res, next) => { | ||
err.message = 'very bad error'; | ||
next(err); | ||
}; | ||
const reducer = chainMiddleware({}, {}); | ||
[middleware1, middleware2].reduce(reducer, Promise.resolve()).catch(err => { | ||
assert.equal(err.message, 'very bad error', 'got the error'); | ||
done(); | ||
it('works with error handler middleware (exactly 4 args)', done => { | ||
const middleware1 = (req, res, next) => next({ message: 'error' }); | ||
const middleware2 = (err, req, res, next) => { | ||
err.message = 'very bad error'; | ||
next(err); | ||
}; | ||
const reducer = chainMiddleware({}, {}); | ||
[middleware1, middleware2].reduce(reducer, Promise.resolve()).catch(err => { | ||
assert.equal(err.message, 'very bad error', 'got the error'); | ||
done(); | ||
}); | ||
}); | ||
@@ -84,126 +85,6 @@ }); | ||
describe('conditionalMiddleware', () => { | ||
let conditional; | ||
before(() => { | ||
conditional = rewire('../src/index').__get__('conditionalMiddleware'); | ||
}); | ||
commonTests('connect-style middleware', CONNECT, () => [null, {}, {}]); | ||
it('works when the condition returns a boolean true', done => { | ||
const middleware = conditional(() => true, [(req, res, next) => { | ||
next(); | ||
}]); | ||
middleware({}, {}, done); | ||
}); | ||
it('skips when the condition returns a boolean false', done => { | ||
const middleware = conditional(() => false, [(req, res, next) => { | ||
next('Should not have made it here'); | ||
}]); | ||
middleware({}, {}, done); | ||
}); | ||
it('works when the condition returns a promise true', done => { | ||
const middleware = conditional(() => Promise.resolve(true), [(req, res, next) => { | ||
next(); | ||
}]); | ||
middleware({}, {}, done); | ||
}); | ||
it('skips when the condition returns a promise false', done => { | ||
const middleware = conditional(() => Promise.resolve(false), [(req, res, next) => { | ||
next('Should not have made it here'); | ||
}]); | ||
middleware({}, {}, done); | ||
}); | ||
it('calls the middleware in order if the condition passes', done => { | ||
const middleware = conditional(() => true, [ | ||
(req, res, next) => { | ||
if (req.mw2) { | ||
next(new Error('should not have hit middleware 2 yet!')); | ||
return; | ||
} | ||
req.mw1 = true; | ||
next(); | ||
}, | ||
(req, res, next) => { | ||
if (!req.mw1) { | ||
next(new Error('middleware 1 should have been called first')); | ||
return; | ||
} | ||
req.mw2 = true; | ||
next(); | ||
}, | ||
(req, res, next) => { | ||
if (!req.mw2) { | ||
next(new Error('middleware 2 should have been called by now')); | ||
return; | ||
} | ||
next(); | ||
} | ||
]); | ||
middleware({}, {}, done); | ||
}); | ||
it('does not run subsequent middleware chains within the same "context"', done => { | ||
const CONTEXT = 'arbitrary_context'; | ||
const request = {}; | ||
const middleware1 = conditional(req => true, [(req, res, next) => { | ||
next(); | ||
}], CONTEXT); | ||
const middleware2 = conditional(req => true, [(req, res, next) => { | ||
next(new Error('Should not have hit this middleware')); | ||
}], CONTEXT); | ||
middleware1(request, {}, (err) => { | ||
if (err) return done(err); | ||
assert.ok(request[CONTEXT], 'request object has context set'); | ||
middleware2(request, {}, done); | ||
}); | ||
}); | ||
describe('createContext method', () => { | ||
it('creates a context for the underlying middleware', done => { | ||
conditional.createContext(_conditional => { | ||
const request = {}; | ||
const middleware1 = _conditional(req => true, [(req, res, next) => { | ||
next(); | ||
}]); | ||
const middleware2 = _conditional(req => true, [(req, res, next) => { | ||
next(new Error('Should not have hit this middleware')); | ||
}]); | ||
middleware1(request, {}, (err) => { | ||
if (err) return done(err); | ||
middleware2(request, {}, done); | ||
}); | ||
}) | ||
}); | ||
}); | ||
describe('nesting conditionals', () => { | ||
it('works', done => { | ||
const middleware = conditional(() => true, [ | ||
(req, res, next) => { | ||
req.foo = true; | ||
next(); | ||
}, | ||
conditional(() => true, [ | ||
(req, res, next) => { | ||
req.bar = true; | ||
next(); | ||
} | ||
]), | ||
(req, res, next) => { | ||
assert.ok(req.foo, 'foo is set'); | ||
assert.ok(req.bar, 'bar is set'); | ||
next(); | ||
} | ||
]); | ||
middleware({}, {}, done); | ||
}); | ||
}); | ||
}); | ||
if (KOA_COMPAT) { | ||
require('./koa'); | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
51028
11
300
4
160
1