config: make get always return a clone

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2022-01-08 16:23:26 +01:00
parent a95e788059
commit f5343e7c1a
3 changed files with 55 additions and 6 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/config': patch
---
The `ConfigReader#get` method now always returns a deep clone of the configuration data.
+47
View File
@@ -673,4 +673,51 @@ describe('ConfigReader.get()', () => {
},
});
});
it('should return deep clones of the backing data', () => {
const data1 = {
foo: {
bar: [],
baz: {},
},
};
const data2 = {
x: {
y: {
z: {},
},
},
};
const reader = ConfigReader.fromConfigs([
{ data: data1, context: '1' },
{ data: data2, context: '2' },
]);
reader.get<any>().foo.bar.push(1);
reader.get<any>('foo').bar.push(1);
reader.get<any>('foo.bar').push(1);
reader.get<any>().foo.baz.x = 1;
reader.get<any>('foo').baz.x = 1;
reader.get<any>('foo.baz').x = 1;
reader.get<any>().x.y.z.w = 1;
reader.get<any>('x').y.z.w = 1;
reader.get<any>('x.y').z.w = 1;
reader.get<any>('x.y.z').w = 1;
const readerSingle = ConfigReader.fromConfigs([
{ data: data1, context: '1' },
]);
readerSingle.get<any>().foo.bar.push(1);
readerSingle.get<any>('foo').bar.push(1);
readerSingle.get<any>('foo.bar').push(1);
readerSingle.get<any>().foo.baz.x = 1;
readerSingle.get<any>('foo').baz.x = 1;
readerSingle.get<any>('foo.baz').x = 1;
expect(data1.foo.bar).toEqual([]);
expect(data1.foo.baz).toEqual({});
expect(data2.x.y.z).toEqual({});
});
});
+3 -6
View File
@@ -126,7 +126,7 @@ export class ConfigReader implements Config {
/** {@inheritdoc Config.getOptional} */
getOptional<T = JsonValue>(key?: string): T | undefined {
const value = this.readValue(key);
const value = cloneDeep(this.readValue(key));
const fallbackValue = this.fallback?.getOptional<T>(key);
if (value === undefined) {
@@ -153,11 +153,8 @@ export class ConfigReader implements Config {
// Avoid merging arrays and primitive values, since that's how merging works for other
// methods for reading config.
return mergeWith(
{},
{ value: cloneDeep(fallbackValue) },
{ value },
(into, from) => (!isObject(from) || !isObject(into) ? from : undefined),
return mergeWith({}, { value: fallbackValue }, { value }, (into, from) =>
!isObject(from) || !isObject(into) ? from : undefined,
).value as T;
}