1 |
|
2 |
|
3 | const assert = require('assert-plus')
|
4 |
|
5 | const Attribute = require('./attribute')
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | function 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 | }
|
22 | Object.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 |
|
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 |
|
104 | Change.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 |
|
117 | Change.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 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 | Change.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 |
|
153 | delete obj[type]
|
154 | return obj
|
155 | } else {
|
156 | data = vals
|
157 | }
|
158 | break
|
159 | case 'add': {
|
160 |
|
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 |
|
173 | delete obj[type]
|
174 | return obj
|
175 | }
|
176 | break
|
177 | default:
|
178 | break
|
179 | }
|
180 | if (scalar && data.length === 1) {
|
181 |
|
182 | obj[type] = data[0]
|
183 | } else {
|
184 | obj[type] = data
|
185 | }
|
186 | return obj
|
187 | }
|
188 |
|
189 | Change.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 |
|
200 | Change.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 |
|
212 |
|
213 | module.exports = Change
|