packages/config: added get and getOptional
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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' });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user