From d471182708b55fd617008416da6908c724e33687 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 28 May 2020 12:57:33 +0200 Subject: [PATCH] packages/core-api: add fallback capability to ConfigReader --- .../ConfigApi/ConfigReader.test.ts | 133 +++++++++++------- .../implementations/ConfigApi/ConfigReader.ts | 20 +-- 2 files changed, 97 insertions(+), 56 deletions(-) diff --git a/packages/core-api/src/apis/implementations/ConfigApi/ConfigReader.test.ts b/packages/core-api/src/apis/implementations/ConfigApi/ConfigReader.test.ts index 206527b95d..a9731c0d2c 100644 --- a/packages/core-api/src/apis/implementations/ConfigApi/ConfigReader.test.ts +++ b/packages/core-api/src/apis/implementations/ConfigApi/ConfigReader.test.ts @@ -36,6 +36,59 @@ const DATA = { nestlings: [{ boolean: true }, { string: 'string' }, { number: 42 }] as {}[], }; +function expectValidValues(config: ConfigReader) { + expect(config.getNumber('zero')).toBe(0); + expect(config.getNumber('one')).toBe(1); + expect(config.getBoolean('true')).toBe(true); + expect(config.getBoolean('false')).toBe(false); + expect(config.getString('string')).toBe('string'); + expect(config.getStringArray('strings')).toEqual(['string1', 'string2']); + expect(config.getConfig('nested').getNumber('one')).toBe(1); + expect(config.getConfig('nested').getString('string')).toBe('string'); + expect(config.getConfig('nested').getStringArray('strings')).toEqual([ + 'string1', + 'string2', + ]); + + const [config1, config2, config3] = config.getConfigArray('nestlings'); + expect(config1.getBoolean('boolean')).toBe(true); + expect(config2.getString('string')).toBe('string'); + expect(config3.getNumber('number')).toBe(42); +} + +function expectInvalidValues(config: ConfigReader) { + expect(() => config.getNumber('string')).toThrow( + 'Invalid type in config for key string, got string, wanted number', + ); + expect(() => config.getString('one')).toThrow( + 'Invalid type in config for key one, got number, wanted string', + ); + expect(() => config.getNumber('true')).toThrow( + 'Invalid type in config for key true, got boolean, wanted number', + ); + expect(() => config.getStringArray('null')).toThrow( + 'Invalid type in config for key null, got null, wanted string-array', + ); + expect(() => config.getString('emptyString')).toThrow( + 'Invalid type in config for key emptyString, got empty-string, wanted string', + ); + expect(() => config.getStringArray('badStrings')).toThrow( + 'Invalid type in config for key badStrings[1], got empty-string, wanted string', + ); + expect(() => config.getStringArray('worseStrings')).toThrow( + 'Invalid type in config for key worseStrings[1], got number, wanted string', + ); + expect(() => config.getStringArray('worstStrings')).toThrow( + 'Invalid type in config for key worstStrings[2], got object, wanted string', + ); + expect(() => config.getConfig('one')).toThrow( + 'Invalid type in config for key one, got number, wanted object', + ); + expect(() => config.getConfigArray('one')).toThrow( + 'Invalid type in config for key one, got number, wanted object-array', + ); +} + describe('ConfigReader', () => { it('should read empty config with valid keys', () => { const config = new ConfigReader({}); @@ -70,57 +123,41 @@ describe('ConfigReader', () => { it('should read valid values', () => { const config = new ConfigReader(DATA); - expect(config.getNumber('zero')).toBe(0); - expect(config.getNumber('one')).toBe(1); - expect(config.getBoolean('true')).toBe(true); - expect(config.getBoolean('false')).toBe(false); - expect(config.getString('string')).toBe('string'); - expect(config.getStringArray('strings')).toEqual(['string1', 'string2']); - expect(config.getConfig('nested').getNumber('one')).toBe(1); - expect(config.getConfig('nested').getString('string')).toBe('string'); - expect(config.getConfig('nested').getStringArray('strings')).toEqual([ - 'string1', - 'string2', - ]); - - const [config1, config2, config3] = config.getConfigArray('nestlings'); - expect(config1.getBoolean('boolean')).toBe(true); - expect(config2.getString('string')).toBe('string'); - expect(config3.getNumber('number')).toBe(42); + expectValidValues(config); }); it('should fail to read invalid values', () => { const config = new ConfigReader(DATA); - - expect(() => config.getNumber('string')).toThrow( - 'Invalid type in config for key string, got string, wanted number', - ); - expect(() => config.getString('one')).toThrow( - 'Invalid type in config for key one, got number, wanted string', - ); - expect(() => config.getNumber('true')).toThrow( - 'Invalid type in config for key true, got boolean, wanted number', - ); - expect(() => config.getStringArray('null')).toThrow( - 'Invalid type in config for key null, got null, wanted string-array', - ); - expect(() => config.getString('emptyString')).toThrow( - 'Invalid type in config for key emptyString, got empty-string, wanted string', - ); - expect(() => config.getStringArray('badStrings')).toThrow( - 'Invalid type in config for key badStrings[1], got empty-string, wanted string', - ); - expect(() => config.getStringArray('worseStrings')).toThrow( - 'Invalid type in config for key worseStrings[1], got number, wanted string', - ); - expect(() => config.getStringArray('worstStrings')).toThrow( - 'Invalid type in config for key worstStrings[2], got object, wanted string', - ); - expect(() => config.getConfig('one')).toThrow( - 'Invalid type in config for key one, got number, wanted object', - ); - expect(() => config.getConfigArray('one')).toThrow( - 'Invalid type in config for key one, got number, wanted object-array', - ); + expectInvalidValues(config); + }); +}); + +describe('ConfigReader with fallback', () => { + it('should behave as if without fallback', () => { + const config = new ConfigReader({}, new ConfigReader(DATA)); + expect(config.getString('x')).toBeUndefined(); + expect(() => config.getString('.')).toThrow(/^Invalid config key/); + expect(() => config.getString('a.')).toThrow(/^Invalid config key/); + }); + + it('should read values from itself', () => { + const config = new ConfigReader(DATA, new ConfigReader({})); + expectValidValues(config); + expectInvalidValues(config); + }); + + it('should read values from a fallback', () => { + const config = new ConfigReader({}, new ConfigReader(DATA)); + expectValidValues(config); + expectInvalidValues(config); + }); + + it('should read values from multiple levels of fallbacks', () => { + const config = new ConfigReader( + {}, + new ConfigReader({}, new ConfigReader({}, new ConfigReader(DATA))), + ); + expectValidValues(config); + expectInvalidValues(config); }); }); diff --git a/packages/core-api/src/apis/implementations/ConfigApi/ConfigReader.ts b/packages/core-api/src/apis/implementations/ConfigApi/ConfigReader.ts index ac725ba59d..7a5cf3b185 100644 --- a/packages/core-api/src/apis/implementations/ConfigApi/ConfigReader.ts +++ b/packages/core-api/src/apis/implementations/ConfigApi/ConfigReader.ts @@ -62,17 +62,21 @@ function validateString( export class ConfigReader implements ConfigApi { static nullReader = new ConfigReader({}); - constructor(private readonly data: JsonObject) {} + constructor( + private readonly data: JsonObject, + private readonly fallback?: ConfigApi, + ) {} getConfig(key: string): Config { const value = this.readValue(key); + const fallbackConfig = this.fallback?.getConfig(key); if (isObject(value)) { - return new ConfigReader(value); + return new ConfigReader(value, fallbackConfig); } if (value !== undefined) { throw new TypeError(typeErrorMessage(key, typeOf(value), 'object')); } - return ConfigReader.nullReader; + return fallbackConfig ?? ConfigReader.nullReader; } getConfigArray(key: string): Config[] { @@ -92,7 +96,7 @@ export class ConfigReader implements ConfigApi { typeErrorMessage(key, typeOf(values), 'object-array'), ); } - return []; + return this.fallback?.getConfigArray(key) ?? []; } getNumber(key: string): number | undefined { @@ -103,7 +107,7 @@ export class ConfigReader implements ConfigApi { if (value !== undefined) { throw new TypeError(typeErrorMessage(key, typeOf(value), 'number')); } - return undefined; + return this.fallback?.getNumber(key); } getBoolean(key: string): boolean | undefined { @@ -114,7 +118,7 @@ export class ConfigReader implements ConfigApi { if (value !== undefined) { throw new TypeError(typeErrorMessage(key, typeOf(value), 'boolean')); } - return undefined; + return this.fallback?.getBoolean(key); } getString(key: string): string | undefined { @@ -122,7 +126,7 @@ export class ConfigReader implements ConfigApi { if (validateString(key, value)) { return value; } - return undefined; + return this.fallback?.getString(key); } getStringArray(key: string): string[] | undefined { @@ -141,7 +145,7 @@ export class ConfigReader implements ConfigApi { typeErrorMessage(key, typeOf(values), 'string-array'), ); } - return undefined; + return this.fallback?.getStringArray(key); } private readValue(key: string): JsonValue | undefined {