forked from victorherraiz/cloud-config-client
-
Notifications
You must be signed in to change notification settings - Fork 0
/
config.js
230 lines (212 loc) · 5.91 KB
/
config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
'use strict'
/**
* @module Config
*/
/**
* Retrieve sources from configuration files.
*
* @param {module:Spring~ConfigData} data - spring response data.
* @return {Array<module:Spring~ConfigSource>} list of sources.
*/
function getSources (data) {
return data.propertySources.map(file => ({ ...file.source }))
}
function updateCurrent (current, name, indexes, value) {
if (indexes.length) {
if (!current[name]) current[name] = []
current = current[name]
for (const index of indexes.slice(0, -1)) {
if (!current[index]) current[index] = []
current = current[index]
}
const last = indexes[indexes.length - 1]
if (!current[last]) current[last] = value
return current[last]
} else if (name) {
if (!current[name]) current[name] = value
return current[name]
}
return current
}
const keyRE = /^(\S+?)(?:\[\d+\])+$/
const indexesRE = /\[\d+\]/g
/**
* Returns a list of keys to be used when convert Config to JS Object
*
* Provide `overlappingArrays` value to set how to process overlapping arrays from different property sources:
* * `merge`(default) — to merge arrays together
* * `replace` — use array from most specific property source
*
* **Example:**
* ```
* rawData = [
* {"key01[0]" : 5},
* {"key01[0]" : "string1", "key01[1]" : "string2"}
* ]
*
* getObjectKeys(rawData, 'merge') // ["key01[0]", "key01[1]"]
* getObjectKeys(rawData, 'replace') // ["key01[0]"]
* ```
*
* @param {module:Spring~ConfigData} rawData
* @param {"merge"|"replace"} overlappingArrays what strategy to use when processing overlapping arrays from different sources
*/
function getObjectKeys (rawData, overlappingArrays = 'merge') {
const sources = getSources(rawData)
if (overlappingArrays === 'replace') {
const replaceKeys = sources.reverse().map((source) => {
const keys = {}
for (const item in source) {
const match = keyRE.exec(item)
const name = match ? match[1] : item
if (!(name in keys)) {
keys[name] = []
}
keys[name].push(item)
}
return keys
})
return ([]).concat(...Object.values(Object.assign(...replaceKeys)))
}
const uniqueKeysSet = sources.reduce((acc, source) => {
for (const item in source) {
acc.add(item)
}
return acc
}, new Set())
return Array.from(uniqueKeysSet)
}
function replacer (ctx) {
return (val) => typeof val === 'string'
? val.replace(
/\$\{(.+?)(?::(.+?))?\}/g,
(match, group, def) => ctx[group] || def || match)
: val
}
/**
* Configuration handler
* @class
*/
class Config {
/**
* Function to apply to every property in configuration
*
* @callback forEachFunc
* @param {string} key - property key
* @param {*} value - property value
*/
/**
* Create config object.
*
* @param {module:Spring~ConfigData} data - spring response data
*/
constructor (data, context) {
this._raw = data
const properties = getSources(this._raw).reduce((accum, value) => Object.assign({}, value, accum), {})
if (context) {
const rep = replacer(context)
this._properties = Object.keys(properties).reduce((acc, key) => {
acc[key] = rep(acc[key])
return acc
}, properties)
} else {
this._properties = properties
}
}
/**
* Computed configuration properties
*
* @type {module:Spring~ConfigSource}
*
* @since 1.0.0
*/
get properties () {
return Object.assign({}, this._properties)
}
/**
* Raw spring response data
*
* @type {module:Spring~ConfigData}
*
* @since 1.0.0
*/
get raw () {
return JSON.parse(JSON.stringify(this._raw))
}
/**
* Retrieve a configuration property by key
*
* @param {...string} keyParts - parts to join as key
* @return {*} property value
*
* @since 1.0.0
*/
get (keyParts) {
const key = Array.prototype.slice.call(arguments).join('.')
return this._properties[key]
}
/**
* Iterate over configuration properties
*
* @param {module:Config~forEachFunc} callback - function to apply to every property
* @param {boolean} [includeOverridden=false] whether to include overridden properties
*
* @since 1.0.0
*/
forEach (callback, includeOverridden) {
const _include = includeOverridden || false
const sources = _include ? getSources(this._raw) : [this._properties]
sources.forEach((source) => {
for (const prop in source) {
if (Object.prototype.hasOwnProperty.call(source, prop)) {
callback(prop, source[prop])
}
}
})
}
/**
* Returns the configuration as an object
*
* @param {Object} [options] - configure the conversion process:
* @param {'merge' | 'replace'} options.overlappingArrays - define strategy to deal with arrays from different
* property sources that have the same key — merge or use the most specific(`replace`)
*
* @return {object} full configuration object
*
* @since 1.3.0
*/
toObject (options = { overlappingArrays: 'merge' }) {
const obj = {}
for (const key of getObjectKeys(this._raw, options.overlappingArrays)) {
let current = obj
let name = null
let indexes = []
for (const item of key.split('.')) {
current = updateCurrent(current, name, indexes, {})
const match = keyRE.exec(item)
if (match) {
name = match[1]
indexes = item.match(indexesRE)
.map(str => Number.parseInt(str.slice(1, -1), 10))
} else {
name = item
indexes = []
}
}
updateCurrent(current, name, indexes, this._properties[key])
}
return obj
}
/**
* Returns a string representation of raw response data
*
* @param {number} [spaces] - number spaces to use in formatting
* @returns {string} - raw response data as string
*
* @since 1.0.0
*/
toString (spaces) {
return JSON.stringify(this._raw, null, spaces)
}
}
module.exports = Config