node-ldapjs/lib/change.js

209 lines
5.0 KiB
JavaScript

// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var Attribute = require('./attribute');
var Protocol = require('./protocol');
///--- API
function Change(options) {
if (options) {
if (typeof (options) !== 'object')
throw new TypeError('options must be an object');
if (options.operation && typeof (options.operation) !== 'string')
throw new TypeError('options.operation must be a string');
} else {
options = {};
}
var self = this;
this._modification = false;
this.__defineGetter__('operation', function () {
switch (self._operation) {
case 0x00: return 'add';
case 0x01: return 'delete';
case 0x02: return 'replace';
default:
throw new Error('0x' + self._operation.toString(16) + ' is invalid');
}
});
this.__defineSetter__('operation', function (val) {
if (typeof (val) !== 'string')
throw new TypeError('operation must be a string');
switch (val.toLowerCase()) {
case 'add':
self._operation = 0x00;
break;
case 'delete':
self._operation = 0x01;
break;
case 'replace':
self._operation = 0x02;
break;
default:
throw new Error('Invalid operation type: 0x' + val.toString(16));
}
});
this.__defineGetter__('modification', function () {
return self._modification;
});
this.__defineSetter__('modification', function (attr) {
if (Attribute.isAttribute(attr)) {
self._modification = attr;
return;
}
// Does it have an attribute-like structure
if (Object.keys(attr).length == 2 &&
typeof (attr.type) === 'string' &&
Array.isArray(attr.vals)) {
self._modification = new Attribute({
type: attr.type,
vals: attr.vals
});
return;
}
var keys = Object.keys(attr);
if (keys.length > 1)
throw new Error('Only one attribute per Change allowed');
keys.forEach(function (k) {
var _attr = new Attribute({type: k});
if (Array.isArray(attr[k])) {
attr[k].forEach(function (v) {
_attr.addValue(v.toString());
});
} else {
_attr.addValue(attr[k].toString());
}
self._modification = _attr;
});
});
this.__defineGetter__('json', function () {
return {
operation: self.operation,
modification: self._modification ? self._modification.json : {}
};
});
this.operation = options.operation || options.type || 'add';
this.modification = options.modification || {};
}
module.exports = Change;
Change.isChange = function isChange(change) {
if (!change || typeof (change) !== 'object') {
return false;
}
if ((change instanceof Change) ||
((typeof (change.toBer) === 'function') &&
(change.modification !== undefined) &&
(change.operation !== undefined))) {
return true;
}
return false;
};
Change.compare = function (a, b) {
if (!Change.isChange(a) || !Change.isChange(b))
throw new TypeError('can only compare Changes');
if (a.operation < b.operation)
return -1;
if (a.operation > b.operation)
return 1;
return Attribute.compare(a.modification, b.modification);
};
/**
* Apply a Change to properties of an object.
*
* @param {Object} change the change to apply.
* @param {Object} obj the object to apply it to.
* @param {Boolean} scalar convert single-item arrays to scalars. Default: false
*/
Change.apply = function apply(change, obj, scalar) {
assert.string(change.operation);
assert.string(change.modification.type);
assert.ok(Array.isArray(change.modification.vals));
assert.object(obj);
var type = change.modification.type;
var vals = change.modification.vals;
var data = obj[type];
if (data !== undefined) {
if (!Array.isArray(data)) {
data = [data];
}
} else {
data = [];
}
switch (change.operation) {
case 'replace':
if (vals.length === 0) {
// replace empty is a delete
delete obj[type];
return obj;
} else {
data = vals;
}
break;
case 'add':
// add only new unique entries
var newValues = vals.filter(function (entry) {
return (data.indexOf(entry) === -1);
});
data = data.concat(newValues);
break;
case 'delete':
data = data.filter(function (entry) {
return (vals.indexOf(entry) === -1);
});
if (data.length === 0) {
// Erase the attribute if empty
delete obj[type];
return obj;
}
break;
default:
break;
}
if (scalar && data.length === 1) {
// store single-value outputs as scalars, if requested
obj[type] = data[0];
} else {
obj[type] = data;
}
return obj;
};
Change.prototype.parse = function (ber) {
assert.ok(ber);
ber.readSequence();
this._operation = ber.readEnumeration();
this._modification = new Attribute();
this._modification.parse(ber);
return true;
};
Change.prototype.toBer = function (ber) {
assert.ok(ber);
ber.startSequence();
ber.writeEnumeration(this._operation);
ber = this._modification.toBer(ber);
ber.endSequence();
return ber;
};