Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add router.render() back to support hypermedia usecase #1752

Merged
merged 2 commits into from Mar 4, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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();
});