azure-table-node
Version:
Azure Table Storage client using JSON on the wire
225 lines (196 loc) • 7.49 kB
JavaScript
;
var crypto = require('crypto');
var _ = require('lodash');
var ACCOUNT_STRING_KEYS = ['TableEndpoint', 'AccountName', 'AccountKey'];
var ACCOUNT_STRING_SETTINGS_KEYS = ['accountUrl', 'accountName', 'accountKey'];
exports.parseAccountString = function parseAccountString(accountString) {
if (typeof accountString !== 'string') {
return null;
}
var trimmedAS = accountString.trim();
if (trimmedAS.length < 30) {
return null;
}
var splittedAS = trimmedAS.split(';');
if (splittedAS.length < 3) {
return null;
}
var entry, i, j, retrievedValues = {};
for (i = 0; i < splittedAS.length; ++i) {
entry = splittedAS[i].split('=');
if (entry.length < 2) {
return null;
}
// get back if within string
if (entry.length > 2) {
for (j = 2; j < entry.length; ++j) {
entry[1] += '='+entry[j];
}
}
if (ACCOUNT_STRING_KEYS.indexOf(entry[0]) !== -1) {
retrievedValues[entry[0]] = entry[1];
}
}
if (Object.keys(retrievedValues).length !== ACCOUNT_STRING_KEYS.length) {
return null;
}
// convert to settings keys
var finalValues = {};
for (i = 0; i < ACCOUNT_STRING_SETTINGS_KEYS.length; ++i) {
finalValues[ACCOUNT_STRING_SETTINGS_KEYS[i]] = retrievedValues[ACCOUNT_STRING_KEYS[i]];
}
return finalValues;
};
exports.base64Decode = function base64Decode(base64String) {
return new Buffer(base64String, 'base64');
};
exports.hmacSha256 = function hmacSha256(keyBuffer, stringToSign) {
return crypto.createHmac('sha256', keyBuffer).update(stringToSign, 'utf-8').digest('base64');
};
exports.prepareEntityPath = function prepareEntityPath(table, partitionKey, rowKey) {
return table + '(PartitionKey=\'' + encodeURIComponent(partitionKey.replace(/'/g, '\'\'')) + '\',RowKey=\'' + encodeURIComponent(rowKey.replace(/'/g, '\'\'')) + '\')';
};
exports.prepareSelectQS = function prepareSelectQS(fields) {
return fields.join(',');
};
var _guidRegExp = /^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$/;
exports.isGuid = function isGuid(value) {
return _guidRegExp.test(value);
};
exports.serializeEntity = function serializeEntity(entity) {
var finalObject = {}, value;
for (var key in entity) {
// __etag will be provided in different way, so skip it here too
if (!entity.hasOwnProperty(key) || key === '__etag' || key === 'Timestamp') {
continue;
}
if (entity[key] == null) { // this includes undefined
continue;
}
// they do not require any special handling
if (key === 'PartitionKey' || key === 'RowKey' ) {
finalObject[key] = entity[key];
continue;
}
value = entity[key];
if (typeof value === 'string') {
if (value.length === 36 && _guidRegExp.test(value)) {
finalObject[key] = value;
finalObject[key+'@odata.type'] = 'Edm.Guid';
} else {
finalObject[key] = value; // normal string, no need to pass type info
}
} else if (typeof value === 'number') {
if (value % 1 === 0) { // checks if it is integer
if (Math.abs(value) < 2147483648) {
finalObject[key] = value; // 32 bit can be passed as int without type info
} else {
finalObject[key] = value.toString();
finalObject[key+'@odata.type'] = 'Edm.Int64';
}
} else {
finalObject[key] = value; // float can be passed without type info
}
} else if (_.isDate(value)) {
finalObject[key] = value.toISOString();
finalObject[key+'@odata.type'] = 'Edm.DateTime';
} else if (_.isBoolean(value)) {
finalObject[key] = value;
} else if (value instanceof Buffer) {
finalObject[key] = value.toString('base64');
finalObject[key+'@odata.type'] = 'Edm.Binary';
} else { // to be on a safe side, convert anything else to string
finalObject[key] = value.toString();
}
}
return finalObject;
};
exports.deserializeEntity = function deserializeEntity(entity) {
var finalObject = {}, value, type;
for (var key in entity) {
// skip all the odata info keys too
if (!entity.hasOwnProperty(key) || key.indexOf('odata.') >= 0) {
continue;
}
value = entity[key];
// handle always returned values first
if (key === 'PartitionKey' || key === 'RowKey') {
finalObject[key] = value;
continue;
} else if (key === 'Timestamp') {
finalObject[key] = new Date(value);
continue;
}
// handle other properties but check if they need converting
type = entity[key+'@odata.type'];
if (!type) {
finalObject[key] = value;
} else {
if (type === 'Edm.Int64') {
finalObject[key] = parseInt(value, 10);
} else if (type === 'Edm.DateTime') {
finalObject[key] = new Date(value);
} else if (type === 'Edm.Binary') {
finalObject[key] = new Buffer(value, 'base64');
} else { // for all other do not convert
finalObject[key] = value;
}
}
}
if ('odata.etag' in entity) {
finalObject.__etag = entity['odata.etag'];
}
return finalObject;
};
var _defaultRetryOptions = {
retries: 3,
firstDelay: 2000,
nextDelayMult: 2,
variability: 0.2,
transientErrors: [500, 501, 502, 503, 'ETIMEDOUT', 'ECONNRESET', 'EADDRINUSE', 'ESOCKETTIMEDOUT', 'ECONNREFUSED']
};
function _retryLogic(requestOptions, nextReq) {
// requestOptions needs to be edited in place (it is safe to do so)
// but retry does not change it in any way
var retry = 0, delay, settings = this;
function retryFn(err, resp, nextResp) {
if (err) {
if (retry < settings.retries && (settings.transientErrors.indexOf(err.code) !== -1 || settings.transientErrors.indexOf(err.statusCode) !== -1)) {
retry += 1;
if (retry === 1) {
delay = settings.firstDelay;
} else {
delay *= settings.nextDelayMult;
}
delay += (Math.random() - 0.5) * settings.variability * delay;
delay = Math.floor(delay);
setTimeout(function() {
nextReq(retryFn);
}, delay);
} else {
err.retriesMade = retry;
nextResp(err, resp);
}
} else {
nextResp(err, resp);
}
}
nextReq(retryFn);
}
exports.generateRetryFunction = function generateRetryFunction(options) {
var finalOptions = !options ? _defaultRetryOptions : {
retries: options.retries > 0 ? options.retries : _defaultRetryOptions.retries,
firstDelay: options.firstDelay > 0 ? options.firstDelay : _defaultRetryOptions.firstDelay,
nextDelayMult: options.nextDelayMult > 0 ? options.nextDelayMult : _defaultRetryOptions.nextDelayMult,
variability: typeof options.variability === 'number' ? options.variability : _defaultRetryOptions.variability,
transientErrors: Array.isArray(options.transientErrors) ? options.transientErrors : _defaultRetryOptions.transientErrors
};
return _retryLogic.bind(finalOptions);
};
exports.makeSignature = function makeSignature(keyBuffer, values) {
return exports.hmacSha256(keyBuffer, values.join('\n'));
};
exports.isoDateWithoutMiliseconds = function isoDateWithoutMiliseconds(date) {
var raw = date.toJSON();
return raw.substr(0, raw.lastIndexOf('.')) + 'Z';
};