Skip to content

Commit

Permalink
feat: add router.render() back to support hypermedia usecase (#1752)
Browse files Browse the repository at this point in the history
* feat: add router.render() to support hypermedia link generation usecase, fixes #1684
  • Loading branch information
rajatkumar committed Mar 4, 2019
1 parent d901e43 commit 0700cfd
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 0 deletions.
49 changes: 49 additions & 0 deletions lib/router.js
Expand Up @@ -114,6 +114,55 @@ Router.prototype.lookupByName = function lookupByName(name, req, res) {
return route.chain.run.bind(route.chain);
};

/**
* Takes an object of route params and query params, and 'renders' a URL.
*
* @public
* @function render
* @param {String} routeName - the route name
* @param {Object} params - an object of route params
* @param {Object} query - an object of query params
* @returns {String} URL
* @example
* server.get({
* name: 'cities',
* path: '/countries/:name/states/:state/cities'
* }, (req, res, next) => ...));
* let cities = server.router.render('cities', {
* name: 'Australia',
* state: 'New South Wales'
* });
* // cities: '/countries/Australia/states/New%20South%20Wales/cities'
*/
Router.prototype.render = function render(routeName, params, query) {
var self = this;

function pathItem(match, key) {
if (params.hasOwnProperty(key) === false) {
throw new Error(
'Route <' + routeName + '> is missing parameter <' + key + '>'
);
}
return '/' + encodeURIComponent(params[key]);
}

function queryItem(key) {
return encodeURIComponent(key) + '=' + encodeURIComponent(query[key]);
}

var route = self._registry.get()[routeName];

if (!route) {
return null;
}

var _path = route.spec.path;
var _url = _path.replace(/\/:([A-Za-z0-9_]+)(\([^\\]+?\))?/g, pathItem);
var items = Object.keys(query || {}).map(queryItem);
var queryString = items.length > 0 ? '?' + items.join('&') : '';
return _url + queryString;
};

/**
* Adds a route.
*
Expand Down
141 changes: 141 additions & 0 deletions test/router.test.js
Expand Up @@ -371,3 +371,144 @@ test('toString() with ignoreTrailingSlash', function(t) {
);
t.end();
});

// Tests router.render()
var mockResponse = function respond(req, res, next) {
res.send(200);
};

test('render route', function(t) {
var server = restify.createServer();
server.get({ name: 'countries', path: '/countries' }, mockResponse);
server.get({ name: 'country', path: '/countries/:name' }, mockResponse);
server.get(
{ name: 'cities', path: '/countries/:name/states/:state/cities' },
mockResponse
);

var countries = server.router.render('countries', {});
t.equal(countries, '/countries');

var country = server.router.render('country', { name: 'Australia' });
t.equal(country, '/countries/Australia');

var cities = server.router.render('cities', {
name: 'Australia',
state: 'New South Wales'
});
t.equal(cities, '/countries/Australia/states/New%20South%20Wales/cities');

t.end();
});

test('render route (missing params)', function(t) {
var server = restify.createServer();
server.get(
{ name: 'cities', path: '/countries/:name/states/:state/cities' },
mockResponse
);

try {
server.router.render('cities', { name: 'Australia' });
} catch (ex) {
// server is expected to throw an error
// hence catching it here
t.equal(ex, 'Error: Route <cities> is missing parameter <state>');
}

t.end();
});

test('GH #704: render route (special charaters)', function(t) {
var server = restify.createServer();
server.get({ name: 'my-route', path: '/countries/:name' }, mockResponse);

var link = server.router.render('my-route', { name: 'AustraliaIsC@@!' });
// special charaacters are URI encoded
t.equal(link, '/countries/AustraliaIsC%40%40!');

t.end();
});

test('GH #704: render route (with sub-regex param)', function(t) {
var server = restify.createServer();
server.get(
{
name: 'my-route',
path: '/countries/:code([A-Z]{2,3})'
},
mockResponse
);

var link = server.router.render('my-route', { code: 'FR' });
t.equal(link, '/countries/FR');

link = server.router.render('my-route', { code: '111' });
t.equal(link, '/countries/111');

t.end();
});

test('GH-796: render route (with multiple sub-regex param)', function(t) {
var server = restify.createServer();
server.get(
{
name: 'my-route',
path: '/countries/:code([A-Z]{2,3})/:area([0-9]+)'
},
mockResponse
);

var link = server.router.render('my-route', { code: '111', area: 42 });
t.equal(link, '/countries/111/42');
t.end();
});

test('render route (with encode)', function(t) {
var server = restify.createServer();
server.get({ name: 'my-route', path: '/countries/:name' }, mockResponse);

var link = server.router.render('my-route', { name: 'Trinidad & Tobago' });
t.equal(link, '/countries/Trinidad%20%26%20Tobago');

t.end();
});

test('render route (query string)', function(t) {
var server = restify.createServer();
server.get({ name: 'country', path: '/countries/:name' }, mockResponse);

var country1 = server.router.render(
'country',
{
name: 'Australia'
},
{
state: 'New South Wales',
'cities/towns': 5
}
);

t.equal(
country1,
'/countries/Australia?state=New%20South%20Wales&cities%2Ftowns=5'
);

var country2 = server.router.render(
'country',
{
name: 'Australia'
},
{
state: 'NSW & VIC',
'cities&towns': 5
}
);

t.equal(
country2,
'/countries/Australia?state=NSW%20%26%20VIC&cities%26towns=5'
);

t.end();
});

0 comments on commit 0700cfd

Please sign in to comment.