-1

Let's say that I have the following URL path:

/api/resource/:id

Given the context object {id: 1} the path will be transformed to /api/resource/1.

Now I want to build a more complex path template:

/api/resource/:id(/relationships/:name)?

Here the relationships member is optional so that the both contexts can match:

  • {id: 1, name: 'rel1'} will build the string /api/resource/1/relationships/rel1.
  • {id: 1} should still build the string /api/resource/1.

I would like to be able to build such strings using a limited set of the regular expressions magic.

How to implement such a template engine?

Spack
  • 464
  • 4
  • 22

3 Answers3

1

In node.js, you would just define two different templates:

routes.route('/api/resource/:id')
  .get....

routes.route('/api/resource/:id/relationships/:name')
  .get...

But, as trincot mentioned, it would be helpful if you stated what framework you were using to build this functionality.

Jeff Breadner
  • 1,366
  • 9
  • 19
1

You could use a regular expression in a replace with a callback function:

function applyTemplate(template, obj) {
    return template.replace(/\(([^()]*):(\w+)([^()]*)\)\?|:(\w+)/g, 
        function (match, before, name1, after, name2) {
            let name = name1 || name2;
            return name in obj ? (before||'') + obj[name] + (after||'') : '';
        });
}

var result,
    template = "/api/resource/:id(/relationships/:name)?";

result = applyTemplate(template, {id: 1, name: 'rel1'});
console.log(result);

result = applyTemplate(template, {id: 1});
console.log(result);

This will not deal with nested parentheses of the form ( )?. If you need that, then I would suggest to have a loop that first parses all those optional expressions until there are none left:

function applyTemplate(template, obj) {
    var repeat = true;
    while (repeat) {
        repeat = false;
        template = template.replace(/\(([^()]*):(\w+)([^()]*)\)\?/g, function (match, before, name, after) {
            repeat = true;
            return name in obj ? before + obj[name] + after : '';
        });
    }
    // Now replace the non-optionals:
    return template.replace(/:(\w+)/g, function (match, name) {
        return obj[name] || '';
    });
}

var result,
    // Nested example
    template = "/api/resource/:id(/relationships(/option/:option)?/:name)?";

result = applyTemplate(template, {id: 1, option: 'special', name: 'rel1'});
console.log(result);
result = applyTemplate(template, {id: 1, name: 'rel1'});
console.log(result);
result = applyTemplate(template, {id: 1});
console.log(result);
trincot
  • 317,000
  • 35
  • 244
  • 286
0

Actually, I've found the path-to-regex that does exactly what I want.

Spack
  • 464
  • 4
  • 22