/
config_utils.js
155 lines (140 loc) · 5.25 KB
/
config_utils.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
/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
goog.provide('shaka.util.ConfigUtils');
goog.require('goog.asserts');
goog.require('shaka.log');
goog.require('shaka.util.ObjectUtils');
/** @export */
shaka.util.ConfigUtils = class {
/**
* @param {!Object} destination
* @param {!Object} source
* @param {!Object} template supplies default values
* @param {!Object} overrides
* Supplies override type checking. When the current path matches
* the key in this object, each sub-value must match the type in this
* object. If this contains an Object, it is used as the template.
* @param {string} path to this part of the config
* @return {boolean}
* @export
*/
static mergeConfigObjects(destination, source, template, overrides, path) {
goog.asserts.assert(destination, 'Destination config must not be null!');
/**
* @type {boolean}
* If true, don't validate the keys in the next level.
*/
const ignoreKeys = path in overrides;
let isValid = true;
for (const k in source) {
const subPath = path + '.' + k;
const subTemplate = ignoreKeys ? overrides[path] : template[k];
// The order of these checks is important.
if (!ignoreKeys && !(k in template)) {
shaka.log.alwaysError('Invalid config, unrecognized key ' + subPath);
isValid = false;
} else if (source[k] === undefined) {
// An explicit 'undefined' value causes the key to be deleted from the
// destination config and replaced with a default from the template if
// possible.
if (subTemplate === undefined || ignoreKeys) {
// There is nothing in the template, so delete.
delete destination[k];
} else {
// There is something in the template, so go back to that.
destination[k] = shaka.util.ObjectUtils.cloneObject(subTemplate);
}
} else if (subTemplate.constructor == Object &&
source[k] &&
source[k].constructor == Object) {
// These are plain Objects with no other constructor.
if (!destination[k]) {
// Initialize the destination with the template so that normal
// merging and type-checking can happen.
destination[k] = shaka.util.ObjectUtils.cloneObject(subTemplate);
}
const subMergeValid = shaka.util.ConfigUtils.mergeConfigObjects(
destination[k], source[k], subTemplate, overrides, subPath);
isValid = isValid && subMergeValid;
} else if (typeof source[k] != typeof subTemplate ||
source[k] == null ||
// Function cosntructors are not informative, and differ
// between sync and async functions. So don't look at
// constructor for function types.
(typeof source[k] != 'function' &&
source[k].constructor != subTemplate.constructor)) {
// The source is the wrong type. This check allows objects to be
// nulled, but does not allow null for any non-object fields.
shaka.log.alwaysError('Invalid config, wrong type for ' + subPath);
isValid = false;
} else if (typeof template[k] == 'function' &&
template[k].length != source[k].length) {
shaka.log.alwaysWarn(
'Unexpected number of arguments for ' + subPath);
destination[k] = source[k];
} else {
destination[k] = source[k];
}
}
return isValid;
}
/**
* Convert config from ('fieldName', value) format to a partial config object.
*
* E. g. from ('manifest.retryParameters.maxAttempts', 1) to
* { manifest: { retryParameters: { maxAttempts: 1 }}}.
*
* @param {string} fieldName
* @param {*} value
* @return {!Object}
* @export
*/
static convertToConfigObject(fieldName, value) {
const configObject = {};
let last = configObject;
let searchIndex = 0;
let nameStart = 0;
while (true) { // eslint-disable-line no-constant-condition
const idx = fieldName.indexOf('.', searchIndex);
if (idx < 0) {
break;
}
if (idx == 0 || fieldName[idx - 1] != '\\') {
const part = fieldName.substring(nameStart, idx).replace(/\\\./g, '.');
last[part] = {};
last = last[part];
nameStart = idx + 1;
}
searchIndex = idx + 1;
}
last[fieldName.substring(nameStart).replace(/\\\./g, '.')] = value;
return configObject;
}
/**
* Reference the input parameters so the compiler doesn't remove them from
* the calling function. Return whatever value is specified.
*
* This allows an empty or default implementation of a config callback that
* still bears the complete function signature even in compiled mode.
*
* The caller should look something like this:
*
* const callback = (a, b, c, d) => {
* return referenceParametersAndReturn(
[a, b, c, d],
a); // Can be anything, doesn't need to be one of the parameters
* };
*
* @param {!Array.<?>} parameters
* @param {T} returnValue
* @return {T}
* @template T
* @noinline
*/
static referenceParametersAndReturn(parameters, returnValue) {
return parameters && returnValue;
}
};