Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions lib/server-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,77 @@ proto.middlewareFromConfig = function(factory, config) {
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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be in loopback-phase?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to refactor the code to register phases by relative order to loopback-phase.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I'll land this patch and submit the refactoring in a new PR.

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.
* @param {string} name The phase name, e.g. "init" or "routes".
Expand Down
58 changes: 58 additions & 0 deletions test/app.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
var app, db;
beforeEach(function() {
Expand Down