UNPKG

service-model

Version:

An object oriented web service framework inspired by Windows Communication Foundation.

233 lines (232 loc) 8.77 kB
// The contents of this file are based on source code from: // https://github.com/strongloop/express/blob/master/lib/router/layer.js var querystring_1 = require("querystring"); var url_1 = require("./url"); var regExpUtil_1 = require("./common/regExpUtil"); /** * A class that represents a template for matching and parsing urls. The template describes a relative URL and can only * include path and query components. The path can include parameters. Only the path is used for matching (the query is * ignored for matching.) The query can describe optional parameters. Duplicate parameter names are not allowed. * * ### Examples * The following are examples of valid templates: * * ``` * "/blog" * ``` * Matches "/blog". No parameters are assigned. * * ``` * "/blog/{blogId}" * ``` * Matches "/blog/1". Assigns parameter `blogId` the value "1". * * ``` * "/blog/{blogId}/article/{articleId}" * ``` * Matches "/blog/1/article/2". Assigns parameter `blogId` the value "1" and `articleId` the value "2". * * ``` * "/blog/{blogId}/article/{articleId}?page={pageNum}" * ``` * Matches "/blog/1/article/2" and "/blog/1/article/2?page=3". Assigns parameter `blogId` the value "1" and * `articleId` the value "2". If "page" is include in the query then assigns parameter `pageNum` the value "3". * * ``` * "/download/{file}.{ext}" * ``` * Matches "/download/some-file.jpg". Assigns parameter `file` the value "some-file" and `ext` the value "jpg". */ var UrlTemplate = (function () { /** * Constructs a url template. * @param template The template. */ function UrlTemplate(template) { template = url_1.Url.normalize(template); if (template.indexOf("#") != -1) { throw new Error("Hash not supported in template."); } var queryIndex = template.indexOf("?"); this._parsePath(queryIndex == -1 ? template : template.substring(0, queryIndex)); if (queryIndex != -1) { this._parseQuery(template.substring(queryIndex + 1)); } } /** * Parses the template path. * @param text The path to parse * @hidden */ UrlTemplate.prototype._parsePath = function (text) { var tokens = [], i = 0, end, l = text.length; while (i < l) { end = text.indexOf("{", i); if (end == -1) end = l; if (i == end) { throw new Error("Invalid path. Parameters must be separated by a literal."); } tokens.push({ value: text.substring(i, end) }); if (end < l) { i = end + 1; end = text.indexOf("}", i); if (end == -1) { throw new Error("Invalid path. Unmatched '{'."); } tokens.push({ value: text.substring(i, end), parameter: true }); } i = end + 1; } var expression = "^"; for (i = 0, l = tokens.length; i < l; i++) { var token = tokens[i]; if (token.parameter) { expression += "([^/]+)"; if (!this._pathParams) { this._pathParams = []; } else if (this._pathParams.indexOf(token.value) != -1) { throw new Error("Invalid path. Duplicate parameter '" + token.value + "'."); } this._pathParams.push(token.value); } else { expression += regExpUtil_1.escape(token.value); } } this._pattern = new RegExp(expression + "$", "i"); }; /** * Parses the template query string * @param text The query string to parse * @hidden */ UrlTemplate.prototype._parseQuery = function (text) { var query = querystring_1.parse(text); this._queryParams = new Map(); var seen = new Set(); for (var name_1 in query) { if (query[name_1] !== undefined) { var value = query[name_1]; var error; if (!value) { error = "Missing value"; } else if (value.length < 3 || value[0] != "{" || value[value.length - 1] != "}") { error = "Value must be a parameter name in the format '{name}'"; } else { var param = value.substring(1, value.length - 1); if ((this._pathParams && this._pathParams.indexOf(param) != -1) || seen.has(param)) { error = "Duplicate parameter name '" + param + "'"; } } if (error) { throw new Error("Invalid query '" + name_1 + "': " + error + "."); } // save the map between the query param and the operation param, trimming off the leading colon this._queryParams.set(name_1, param); seen.add(param); } } }; /** * Returns true if the url matches the template. * @param baseAddress The base url. * @param candidate The url to check. */ UrlTemplate.prototype.match = function (candidate) { if (!candidate || candidate.pathname === null) return false; return this._pattern.test(candidate.pathname); }; /** * Parses parameter values from the given url and returns a map of parameter name to value. * @param url The url to parse. */ UrlTemplate.prototype.parse = function (url) { var args = new Map(); if (url) { var m = this._pattern.exec(url.pathname); if (m) { // Pull any parameters out of the path if (this._pathParams) { for (var i = 1; i < m.length; i++) { var key = this._pathParams[i - 1]; var value = this._decodeParam(m[i]); if (value !== undefined) { args.set(key, value); } } } // Look for optional query parameters if (this._queryParams && url.query) { var queryParams = querystring_1.parse(url.query); for (var p in queryParams) { if (queryParams[p] !== undefined) { var key = this._queryParams.get(p); if (key) { args.set(key, queryParams[p]); } } } } } } return args; }; UrlTemplate.prototype.prefix = function (addressOrPrefixTemplate) { if (!addressOrPrefixTemplate) return this; // create a new instance of UrlTemplate without calling constructor var ret = Object.create(UrlTemplate.prototype); var pattern; if (addressOrPrefixTemplate instanceof UrlTemplate) { pattern = UrlTemplate._getTemplatePattern(addressOrPrefixTemplate); } else { pattern = UrlTemplate._getAddressPattern(addressOrPrefixTemplate); } // special case for empty path if (this._pattern.source == "^\\/$") { // just use the prefix ret._pattern = new RegExp("^" + pattern + "$", "i"); } else { // make sure to trim the ^ from the existing RegExp ret._pattern = new RegExp("^" + pattern + this._pattern.source.substring(1), "i"); } // copy over other fields (UrlTemplate is immutable so this is OK) ret._pathParams = this._pathParams; ret._queryParams = this._queryParams; return ret; }; UrlTemplate._getTemplatePattern = function (prefixTemplate) { return prefixTemplate._pattern.source.substring(1, prefixTemplate._pattern.source.length - 1); }; UrlTemplate._getAddressPattern = function (address) { return !address.pathname ? "" : regExpUtil_1.escape(address.pathname); }; /** * Decodes a parameter value from the pathname. * @param val The value to decode. * @hidden */ UrlTemplate.prototype._decodeParam = function (val) { if (typeof val !== 'string' || val.length === 0) { return null; } try { return decodeURIComponent(val); } catch (err) { if (err instanceof URIError) { return undefined; } throw err; } }; return UrlTemplate; })(); exports.UrlTemplate = UrlTemplate;