UNPKG

rfc6570

Version:

RFC 6570 compliant uri template parser, builder and router

312 lines (251 loc) 9.46 kB
/* jshint node:true */ module.exports = UriTemplate; var operatorOptions = { "": { "prefix": "", "seperator": ",", "assignment": false, "assignEmpty": false, "encode": percentEncode }, "+": { "prefix": "", "seperator": ",", "assignment": false, "assignEmpty": false, "encode": encodeURI }, "#": { "prefix": "#", "seperator": ",", "assignment": false, "assignEmpty": false, "encode": encodeURI }, ".": { "prefix": ".", "seperator": ".", "assignment": false, "assignEmpty": false, "encode": percentEncode }, "/": { "prefix": "/", "seperator": "/", "assignment": false, "encode": encodeURIComponent }, ";": { "prefix": ";", "seperator": ";", "assignment": true, "assignEmpty": false, "encode": encodeURIComponent }, "?": { "prefix": "?", "seperator": "&", "assignment": true, "assignEmpty": true, "encode": encodeURIComponent }, "&": { "prefix": "&", "seperator": "&", "assignment": true, "assignEmpty": true, "encode": encodeURIComponent } }; //operatorOptions function percentEncode(value) { /* http://tools.ietf.org/html/rfc3986#section-2.3 */ var unreserved = "-._~"; if (isUndefined(value)) return ''; value = value.toString(); return Array.prototype.map.call(value, function (ch) { var charCode = ch.charCodeAt(0); if (charCode >= 0x30 && charCode <= 0x39) return ch; if (charCode >= 0x41 && charCode <= 0x5a) return ch; if (charCode >= 0x61 && charCode <= 0x7a) return ch; if (~unreserved.indexOf(ch)) return ch; return '%' + charCode.toString(16).toUpperCase(); }).join(''); } //percentEncode function isDefined(value) { return !isUndefined(value); } //isDefined function isUndefined(value) { /* http://tools.ietf.org/html/rfc6570#section-2.3 */ if (value === null) return true; if (value === undefined) return true; if (Array.isArray(value)) { if (value.length === 0) return true; } return false; } //isUndefined function UriTemplate(template) { /* http://tools.ietf.org/html/rfc6570#section-2.2 expression = "{" [ operator ] variable-list "}" operator = op-level2 / op-level3 / op-reserve op-level2 = "+" / "#" op-level3 = "." / "/" / ";" / "?" / "&" op-reserve = "=" / "," / "!" / "@" / "|" */ var reTemplate = /\{([\+#\.\/;\?&=\,!@\|]?)([A-Za-z0-9_\,\.\:\*]+?)\}/g; var reVariable = /^([\$_a-z][\$_a-z0-9]*)((?:\:[1-9][0-9]?[0-9]?[0-9]?)?)(\*?)$/i; var match; var pieces = []; var glues = []; var offset = 0; var pieceCount = 0; while ( !! (match = reTemplate.exec(template))) { glues.push(template.substring(offset, match.index)); /* The operator characters equals ("="), comma (","), exclamation ("!"), at sign ("@"), and pipe ("|") are reserved for future extensions. */ if (match[1] && ~'=,!@|'.indexOf(match[1])) { throw "operator '" + match[1] + "' is reserved for future extensions"; } offset = match.index; pieces.push({ operator: match[1], variables: match[2].split(',').map(variableMapper) }); offset += match[0].length; pieceCount++; } function variableMapper(variable) { var match = reVariable.exec(variable); return { name: match[1], maxLength: match[2] && parseInt(match[2].substring(1), 10), composite: !! match[3] }; } glues.push(template.substring(offset)); this.parse = function (str) { var data = {}; var offset = 0; var offsets = []; if (!glues.every(function (glue, glueIndex) { var index; if (glueIndex > 0 && glue === '') index = str.length; else index = str.indexOf(glue, offset); offset = index; offsets.push(offset); offset += glue.length; return~ index; })) return false; if (!pieces.every(function (piece, pieceIndex) { var options = operatorOptions[piece.operator]; var value, values; var offsetBegin = offsets[pieceIndex] + glues[pieceIndex].length; var offsetEnd = offsets[pieceIndex + 1]; value = str.substring(offsetBegin, offsetEnd); if (value.length === 0) return true; if (value.substring(0, options.prefix.length) !== options.prefix) return false; value = value.substring(options.prefix.length); values = value.split(options.seperator); if (!piece.variables.every(function (variable, variableIndex) { var value = values[variableIndex]; var name; if (value === undefined) return true; name = variable.name; if (options.assignment) { if (value.substring(0, name.length) !== name) return false; value = value.substring(name.length); if (value.length === 0 && options.assignEmpty) return false; if (value.length > 0) { if (value[0] !== '=') return false; value = value.substring(1); } } value = decodeURIComponent(value); data[name] = value; return true; })) return false; return true; })) return false; return data; }; //parse this.stringify = function (data) { var str = ''; data = data || {}; str += glues[0]; if (!pieces.every(function (piece, pieceIndex) { var options = operatorOptions[piece.operator]; var parts; parts = piece.variables.map(function (variable) { var value = data[variable.name]; if (!Array.isArray(value)) value = [value]; value = value.filter(isDefined); if (isUndefined(value)) return null; if (variable.composite) { value = value.map(function (value) { if (typeof value === 'object') { value = Object.keys(value).map(function (key) { var keyValue = value[key]; if (variable.maxLength) keyValue = keyValue.substring(0, variable.maxLength); keyValue = options.encode(keyValue); if (keyValue) keyValue = key + '=' + keyValue; else { keyValue = key; if (options.assignEmpty) keyValue += '='; } return keyValue; }).join(options.seperator); } else { if (variable.maxLength) value = value.substring(0, variable.maxLength); value = options.encode(value); if (options.assignment) { if (value) value = variable.name + '=' + value; else { value = variable.name; if (options.assignEmpty) value += '='; } } } return value; }); value = value.join(options.seperator); } else { value = value.map(function (value) { if (typeof value === 'object') { return Object.keys(value).map(function (key) { var keyValue = value[key]; if (variable.maxLength) keyValue = keyValue.substring(0, variable.maxLength); return key + ',' + options.encode(keyValue); }).join(','); } else { if (variable.maxLength) value = value.substring(0, variable.maxLength); return options.encode(value); } }); value = value.join(','); if (options.assignment) { if (value) value = variable.name + '=' + value; else { value = variable.name; if (options.assignEmpty) value += '='; } } } return value; }); parts = parts.filter(isDefined); if (isDefined(parts)) { str += options.prefix; str += parts.join(options.seperator); } str += glues[pieceIndex + 1]; return true; })) return false; return str; }; //stringify } //UriTemplate