UNPKG

5.28 kBJavaScriptView Raw
1// Copyright 2011 Mark Cavage, Inc. All rights reserved.
2
3const assert = require('assert-plus')
4
5const Attribute = require('./attribute')
6// var Protocol = require('./protocol')
7
8/// --- API
9
10function Change (options) {
11 if (options) {
12 assert.object(options)
13 assert.optionalString(options.operation)
14 } else {
15 options = {}
16 }
17
18 this._modification = false
19 this.operation = options.operation || options.type || 'add'
20 this.modification = options.modification || {}
21}
22Object.defineProperties(Change.prototype, {
23 operation: {
24 get: function getOperation () {
25 switch (this._operation) {
26 case 0x00: return 'add'
27 case 0x01: return 'delete'
28 case 0x02: return 'replace'
29 default:
30 throw new Error('0x' + this._operation.toString(16) + ' is invalid')
31 }
32 },
33 set: function setOperation (val) {
34 assert.string(val)
35 switch (val.toLowerCase()) {
36 case 'add':
37 this._operation = 0x00
38 break
39 case 'delete':
40 this._operation = 0x01
41 break
42 case 'replace':
43 this._operation = 0x02
44 break
45 default:
46 throw new Error('Invalid operation type: 0x' + val.toString(16))
47 }
48 },
49 configurable: false
50 },
51 modification: {
52 get: function getModification () {
53 return this._modification
54 },
55 set: function setModification (val) {
56 if (Attribute.isAttribute(val)) {
57 this._modification = val
58 return
59 }
60 // Does it have an attribute-like structure
61 if (Object.keys(val).length === 2 &&
62 typeof (val.type) === 'string' &&
63 Array.isArray(val.vals)) {
64 this._modification = new Attribute({
65 type: val.type,
66 vals: val.vals
67 })
68 return
69 }
70
71 const keys = Object.keys(val)
72 if (keys.length > 1) {
73 throw new Error('Only one attribute per Change allowed')
74 } else if (keys.length === 0) {
75 return
76 }
77
78 const k = keys[0]
79 const _attr = new Attribute({ type: k })
80 if (Array.isArray(val[k])) {
81 val[k].forEach(function (v) {
82 _attr.addValue(v.toString())
83 })
84 } else if (Buffer.isBuffer(val[k])) {
85 _attr.addValue(val[k])
86 } else if (val[k] !== undefined && val[k] !== null) {
87 _attr.addValue(val[k].toString())
88 }
89 this._modification = _attr
90 },
91 configurable: false
92 },
93 json: {
94 get: function getJSON () {
95 return {
96 operation: this.operation,
97 modification: this._modification ? this._modification.json : {}
98 }
99 },
100 configurable: false
101 }
102})
103
104Change.isChange = function isChange (change) {
105 if (!change || typeof (change) !== 'object') {
106 return false
107 }
108 if ((change instanceof Change) ||
109 ((typeof (change.toBer) === 'function') &&
110 (change.modification !== undefined) &&
111 (change.operation !== undefined))) {
112 return true
113 }
114 return false
115}
116
117Change.compare = function (a, b) {
118 if (!Change.isChange(a) || !Change.isChange(b)) { throw new TypeError('can only compare Changes') }
119
120 if (a.operation < b.operation) { return -1 }
121 if (a.operation > b.operation) { return 1 }
122
123 return Attribute.compare(a.modification, b.modification)
124}
125
126/**
127 * Apply a Change to properties of an object.
128 *
129 * @param {Object} change the change to apply.
130 * @param {Object} obj the object to apply it to.
131 * @param {Boolean} scalar convert single-item arrays to scalars. Default: false
132 */
133Change.apply = function apply (change, obj, scalar) {
134 assert.string(change.operation)
135 assert.string(change.modification.type)
136 assert.ok(Array.isArray(change.modification.vals))
137 assert.object(obj)
138
139 const type = change.modification.type
140 const vals = change.modification.vals
141 let data = obj[type]
142 if (data !== undefined) {
143 if (!Array.isArray(data)) {
144 data = [data]
145 }
146 } else {
147 data = []
148 }
149 switch (change.operation) {
150 case 'replace':
151 if (vals.length === 0) {
152 // replace empty is a delete
153 delete obj[type]
154 return obj
155 } else {
156 data = vals
157 }
158 break
159 case 'add': {
160 // add only new unique entries
161 const newValues = vals.filter(function (entry) {
162 return (data.indexOf(entry) === -1)
163 })
164 data = data.concat(newValues)
165 break
166 }
167 case 'delete':
168 data = data.filter(function (entry) {
169 return (vals.indexOf(entry) === -1)
170 })
171 if (data.length === 0) {
172 // Erase the attribute if empty
173 delete obj[type]
174 return obj
175 }
176 break
177 default:
178 break
179 }
180 if (scalar && data.length === 1) {
181 // store single-value outputs as scalars, if requested
182 obj[type] = data[0]
183 } else {
184 obj[type] = data
185 }
186 return obj
187}
188
189Change.prototype.parse = function (ber) {
190 assert.ok(ber)
191
192 ber.readSequence()
193 this._operation = ber.readEnumeration()
194 this._modification = new Attribute()
195 this._modification.parse(ber)
196
197 return true
198}
199
200Change.prototype.toBer = function (ber) {
201 assert.ok(ber)
202
203 ber.startSequence()
204 ber.writeEnumeration(this._operation)
205 ber = this._modification.toBer(ber)
206 ber.endSequence()
207
208 return ber
209}
210
211/// --- Exports
212
213module.exports = Change