packages/config: added get and getOptional

This commit is contained in:
Patrik Oldsberg
2020-06-18 11:58:53 +02:00
parent 2ef5380c5b
commit 02b74c376a
4 changed files with 208 additions and 0 deletions
+3
View File
@@ -29,6 +29,9 @@
"postpack": "backstage-cli postpack",
"clean": "backstage-cli clean"
},
"dependencies": {
"lodash": "^4.17.15"
},
"devDependencies": {
"@types/jest": "^25.2.2",
"@types/node": "^12.0.0"
+172
View File
@@ -38,17 +38,26 @@ const DATA = {
function expectValidValues(config: ConfigReader) {
expect(config.keys()).toEqual(Object.keys(DATA));
expect(config.get('zero')).toBe(0);
expect(config.getNumber('zero')).toBe(0);
expect(config.getNumber('one')).toBe(1);
expect(config.getOptional('true')).toBe(true);
expect(config.getBoolean('true')).toBe(true);
expect(config.getBoolean('false')).toBe(false);
expect(config.getString('string')).toBe('string');
expect(config.get('strings')).toEqual(['string1', 'string2']);
expect(config.getStringArray('strings')).toEqual(['string1', 'string2']);
expect(config.getConfig('nested').getNumber('one')).toBe(1);
expect(config.get('nested')).toEqual({
one: 1,
string: 'string',
strings: ['string1', 'string2'],
});
expect(config.getConfig('nested').getString('string')).toBe('string');
expect(
config.getOptionalConfig('nested')!.getStringArray('strings'),
).toEqual(['string1', 'string2']);
expect(config.getOptional('missing')).toBe(undefined);
expect(config.getOptionalConfig('missing')).toBe(undefined);
expect(config.getOptionalConfigArray('missing')).toBe(undefined);
expect(config.getNumber('zero')).toBe(0);
@@ -360,3 +369,166 @@ describe('ConfigReader with fallback', () => {
).toBeUndefined();
});
});
describe('ConfigReader.get()', () => {
const config1 = {
a: {
x: 'x1',
y: ['y11', 'y12', 'y13'],
z: false,
},
b: {
x: 'x1',
y: ['y11'],
},
};
const config2 = {
b: {
x: 'x2',
y: ['y21', 'y22'],
z: 'z2',
},
c: {
c1: {
c2: 'c2',
},
},
};
const config3 = {
c: {
c1: 'c1',
},
};
const configs = [
{
data: config1,
context: '1',
},
{
data: config2,
context: '2',
},
{
data: config3,
context: '3',
},
];
it('should be able to select sub-configs', () => {
expect(new ConfigReader(config1).get('a')).toEqual(config1.a);
expect(new ConfigReader(config1).get('b')).toEqual(config1.b);
expect(new ConfigReader(config2).get('b')).toEqual(config2.b);
expect(new ConfigReader(config2).get('c')).toEqual(config2.c);
expect(new ConfigReader(config3).get('c')).toEqual(config3.c);
expect(new ConfigReader(config2).get('c.c1')).toEqual(config2.c.c1);
expect(new ConfigReader(config2).getConfig('c').get('c1')).toEqual(
config2.c.c1,
);
});
it('should merge in fallback configs', () => {
expect(ConfigReader.fromConfigs([configs[0], configs[1]]).get('a')).toEqual(
{
x: 'x1',
y: ['y11', 'y12', 'y13'],
z: false,
},
);
expect(ConfigReader.fromConfigs([configs[0], configs[1]]).get('b')).toEqual(
{
x: 'x1',
y: ['y11'],
z: 'z2',
},
);
expect(ConfigReader.fromConfigs([configs[0], configs[1]]).get('c')).toEqual(
{
c1: {
c2: 'c2',
},
},
);
expect(ConfigReader.fromConfigs([configs[0], configs[1]]).get('a')).toEqual(
{
x: 'x1',
y: ['y11', 'y12', 'y13'],
z: false,
},
);
expect(ConfigReader.fromConfigs([configs[0], configs[1]]).get('b')).toEqual(
{
x: 'x1',
y: ['y11'],
z: 'z2',
},
);
expect(ConfigReader.fromConfigs([configs[0], configs[1]]).get('c')).toEqual(
{
c1: {
c2: 'c2',
},
},
);
expect(
ConfigReader.fromConfigs([configs[2], configs[1]]).getOptional('b'),
).toEqual({
x: 'x2',
y: ['y21', 'y22'],
z: 'z2',
});
expect(
ConfigReader.fromConfigs([configs[2], configs[1]]).getOptional('c'),
).toEqual({
c1: 'c1',
});
});
it('should not merge non-objects', () => {
const config = ConfigReader.fromConfigs([
{
data: {
a: ['1', '2'],
c: [],
d: {
x: 'x',
},
e: ['3'],
f: 'foo',
g: { z: 'z' },
h: {
a: 'a1',
c: 'c1',
},
},
context: '1',
},
{
data: {
a: ['x', 'y', 'z'],
b: ['1'],
c: ['1'],
d: ['2'],
e: {
y: 'y',
},
f: { x: 'x' },
g: 'bar',
h: {
a: 'a2',
b: 'b2',
},
},
context: '2',
},
]);
expect(config.get('a')).toEqual(['1', '2']);
expect(config.get('b')).toEqual(['1']);
expect(config.get('c')).toEqual([]);
expect(config.get('d')).toEqual({ x: 'x' });
expect(config.get('e')).toEqual(['3']);
expect(config.get('f')).toEqual('foo');
expect(config.get('g')).toEqual({ z: 'z' });
expect(config.get('h')).toEqual({ a: 'a1', b: 'b2', c: 'c1' });
});
});
+30
View File
@@ -15,6 +15,8 @@
*/
import { AppConfig, Config, JsonValue, JsonObject } from './types';
import cloneDeep from 'lodash/cloneDeep';
import mergeWith from 'lodash/mergeWith';
// Update the same pattern in config-loader package if this is changed
const CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z][a-z0-9]*)*$/i;
@@ -78,6 +80,34 @@ export class ConfigReader implements Config {
return [...new Set([...localKeys, ...fallbackKeys])];
}
get(key: string): JsonValue {
const value = this.getOptional(key);
if (value === undefined) {
throw new Error(errors.missing(this.fullKey(key)));
}
return value;
}
getOptional(key: string): JsonValue | undefined {
const value = this.readValue(key);
const fallbackValue = this.fallback?.getOptional(key);
if (value === undefined) {
return fallbackValue;
} else if (fallbackValue === undefined) {
return value;
}
// 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),
).value;
}
getConfig(key: string): ConfigReader {
const value = this.getOptionalConfig(key);
if (value === undefined) {
+3
View File
@@ -32,6 +32,9 @@ export type AppConfig = {
export type Config = {
keys(): string[];
get(key: string): JsonValue;
getOptional(key: string): JsonValue | undefined;
getConfig(key: string): Config;
getOptionalConfig(key: string): Config | undefined;