Merge pull request #786 from strongloop/feature/define-middleware-phases

Implement `app.defineMiddlewarePhases`
This commit is contained in:
Miroslav Bajtoš 2014-11-12 08:16:49 +01:00
commit 4474f8b029
2 changed files with 129 additions and 0 deletions

View File

@ -54,6 +54,77 @@ proto.middlewareFromConfig = function(factory, config) {
this.middleware(config.phase, handler); this.middleware(config.phase, handler);
}; };
/**
* Register (new) middleware phases.
*
* If all names are new, then the phases are added just before "routes" phase.
* Otherwise the provided list of names is merged with the existing phases
* in such way that the order of phases is preserved.
*
* **Examples**
*
* ```js
* // built-in phases:
* // initial, session, auth, parse, routes, files, final
*
* app.defineMiddlewarePhases('custom');
* // new list of phases
* // initial, session, auth, parse, custom, routes, files, final
*
* app.defineMiddlewarePhases([
* 'initial', 'postinit', 'preauth', 'routes', 'subapps'
* ]);
* // new list of phases
* // initial, postinit, preauth, session, auth, parse, custom,
* // routes, subapps, files, final
* ```
*
* @param {string|Array.<string>} nameOrArray A phase name or a list of phase
* names to add.
*/
proto.defineMiddlewarePhases = function(nameOrArray) {
this.lazyrouter();
if (!Array.isArray(nameOrArray)) {
this._requestHandlingPhases.addBefore('routes', nameOrArray);
return;
}
var sourcePhases = nameOrArray;
if (!sourcePhases.length) return;
var targetPhases = this._requestHandlingPhases.getPhaseNames();
var targetIx = targetPhases.indexOf(nameOrArray[0]);
if (targetIx === -1) {
// the first new phase does not match any existing one
// add all phases before "routes"
sourcePhases.forEach(function(it) {
this._requestHandlingPhases.addBefore('routes', it);
}, this);
return;
}
// merge (zip) two arrays of phases
for (var sourceIx = 1; sourceIx < sourcePhases.length; sourceIx++) {
var nameToAdd = sourcePhases[sourceIx];
var previousPhase = sourcePhases[sourceIx - 1];
var existingIx = targetPhases.indexOf(nameToAdd, targetIx);
if (existingIx === -1) {
// A new phase - try to add it after the last one,
// unless it was already registered
if (targetPhases.indexOf(nameToAdd) !== -1) {
throw new Error('Phase ordering conflict: cannot add "' + nameToAdd +
'" after "' + previousPhase + '", because the opposite order was ' +
' already specified');
}
this._requestHandlingPhases.addAfter(previousPhase, nameToAdd);
} else {
// An existing phase - move the pointer
targetIx = existingIx;
}
}
};
/** /**
* Register a middleware handler to be executed in a given phase. * Register a middleware handler to be executed in a given phase.
* @param {string} name The phase name, e.g. "init" or "routes". * @param {string} name The phase name, e.g. "init" or "routes".

View File

@ -146,6 +146,64 @@ describe('app', function() {
}); });
}); });
describe.onServer('.defineMiddlewarePhases(nameOrArray)', function() {
var app;
beforeEach(function() {
app = loopback();
});
it('adds the phase just before "routes" by default', function(done) {
app.defineMiddlewarePhases('custom');
verifyMiddlewarePhases(['custom', 'routes'], done);
});
it('adds an array of phases just before "routes"', function(done) {
app.defineMiddlewarePhases(['custom1', 'custom2']);
verifyMiddlewarePhases(['custom1', 'custom2', 'routes'], done);
});
it('merges phases preserving the order', function(done) {
app.defineMiddlewarePhases([
'initial',
'postinit', 'preauth', // add
'auth', 'routes',
'subapps', // add
'final',
'last' // add
]);
verifyMiddlewarePhases([
'initial',
'postinit', 'preauth', // new
'auth', 'routes',
'subapps', // new
'files', 'final',
'last' // new
], done);
});
it('throws helpful error on ordering conflict', function() {
app.defineMiddlewarePhases(['first', 'second']);
expect(function() { app.defineMiddlewarePhases(['second', 'first']); })
.to.throw(/ordering conflict.*first.*second/);
});
function verifyMiddlewarePhases(names, done) {
var steps = [];
names.forEach(function(it) {
app.middleware(it, function(req, res, next) {
steps.push(it);
next();
});
});
executeMiddlewareHandlers(app, function(err) {
if (err) return done(err);
expect(steps).to.eql(names);
done();
});
}
});
describe('app.model(Model)', function() { describe('app.model(Model)', function() {
var app, db; var app, db;
beforeEach(function() { beforeEach(function() {