rfc6570
Version:
RFC 6570 compliant uri template parser, builder and router
353 lines (281 loc) • 11 kB
JavaScript
!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.rfc6570=e():"undefined"!=typeof global?global.rfc6570=e():"undefined"!=typeof self&&(self.rfc6570=e())}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/* jshint node:true */
var UriTemplate = require('./UriTemplate');
function Router() {
var routes = [];
this.add = function (template, handler) {
routes.push({
template: new UriTemplate(template),
handler: handler
}); //
}; //add
this.handle = function (url) {
return routes.some(function (route) {
var data = route.template.parse(url);
return data && route.handler(data) !== false;
});
}; //exec
} //Router
module.exports = Router;
},{"./UriTemplate":2}],2:[function(require,module,exports){
/* 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
},{}],3:[function(require,module,exports){
/* jshint node:true */
module.exports = {
Router: require('./Router'),
UriTemplate: require('./UriTemplate')
};
},{"./Router":1,"./UriTemplate":2}]},{},[3])
(3)
});
;