213 lines
5.1 KiB
JavaScript
213 lines
5.1 KiB
JavaScript
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
|
|
|
const assert = require('assert-plus')
|
|
|
|
const Attribute = require('./attribute')
|
|
|
|
/// --- API
|
|
|
|
function Change (options) {
|
|
if (options) {
|
|
assert.object(options)
|
|
assert.optionalString(options.operation)
|
|
} else {
|
|
options = {}
|
|
}
|
|
|
|
this._modification = false
|
|
this.operation = options.operation || options.type || 'add'
|
|
this.modification = options.modification || {}
|
|
}
|
|
Object.defineProperties(Change.prototype, {
|
|
operation: {
|
|
get: function getOperation () {
|
|
switch (this._operation) {
|
|
case 0x00: return 'add'
|
|
case 0x01: return 'delete'
|
|
case 0x02: return 'replace'
|
|
default:
|
|
throw new Error('0x' + this._operation.toString(16) + ' is invalid')
|
|
}
|
|
},
|
|
set: function setOperation (val) {
|
|
assert.string(val)
|
|
switch (val.toLowerCase()) {
|
|
case 'add':
|
|
this._operation = 0x00
|
|
break
|
|
case 'delete':
|
|
this._operation = 0x01
|
|
break
|
|
case 'replace':
|
|
this._operation = 0x02
|
|
break
|
|
default:
|
|
throw new Error('Invalid operation type: 0x' + val.toString(16))
|
|
}
|
|
},
|
|
configurable: false
|
|
},
|
|
modification: {
|
|
get: function getModification () {
|
|
return this._modification
|
|
},
|
|
set: function setModification (val) {
|
|
if (Attribute.isAttribute(val)) {
|
|
this._modification = val
|
|
return
|
|
}
|
|
// Does it have an attribute-like structure
|
|
if (Object.keys(val).length === 2 &&
|
|
typeof (val.type) === 'string' &&
|
|
Array.isArray(val.vals)) {
|
|
this._modification = new Attribute({
|
|
type: val.type,
|
|
vals: val.vals
|
|
})
|
|
return
|
|
}
|
|
|
|
const keys = Object.keys(val)
|
|
if (keys.length > 1) {
|
|
throw new Error('Only one attribute per Change allowed')
|
|
} else if (keys.length === 0) {
|
|
return
|
|
}
|
|
|
|
const k = keys[0]
|
|
const _attr = new Attribute({ type: k })
|
|
if (Array.isArray(val[k])) {
|
|
val[k].forEach(function (v) {
|
|
_attr.addValue(v.toString())
|
|
})
|
|
} else if (Buffer.isBuffer(val[k])) {
|
|
_attr.addValue(val[k])
|
|
} else if (val[k] !== undefined && val[k] !== null) {
|
|
_attr.addValue(val[k].toString())
|
|
}
|
|
this._modification = _attr
|
|
},
|
|
configurable: false
|
|
},
|
|
json: {
|
|
get: function getJSON () {
|
|
return {
|
|
operation: this.operation,
|
|
modification: this._modification ? this._modification.json : {}
|
|
}
|
|
},
|
|
configurable: false
|
|
}
|
|
})
|
|
|
|
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)
|
|
|
|
const type = change.modification.type
|
|
const vals = change.modification.vals
|
|
let 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
|
|
const 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
|
|
}
|
|
|
|
/// --- Exports
|
|
|
|
module.exports = Change
|