diff --git a/packages/config-loader/src/loader.test.ts b/packages/config-loader/src/loader.test.ts index 66894c7dec..4a7c9f238c 100644 --- a/packages/config-loader/src/loader.test.ts +++ b/packages/config-loader/src/loader.test.ts @@ -30,6 +30,14 @@ jest.mock('fs-extra', () => { sessionKey: development-key `, '/root/secrets/session-key.txt': 'abc123', + '/secret-port/app-config.yaml': ` + backend: + listen: + port: + $secret: + file: secrets/port.txt + `, + '/secret-port/secrets/port.txt': '12345', }; return { diff --git a/packages/config/src/reader.test.ts b/packages/config/src/reader.test.ts index 41cdc92938..3fe74aa637 100644 --- a/packages/config/src/reader.test.ts +++ b/packages/config/src/reader.test.ts @@ -88,7 +88,7 @@ function expectInvalidValues(config: ConfigReader) { "Invalid type in config for key 'string' in 'ctx', got string, wanted boolean", ); expect(() => config.getNumber('string')).toThrow( - "Invalid type in config for key 'string' in 'ctx', got string, wanted number", + "Unable to convert config value for key 'string' in 'ctx' to a number", ); expect(() => config.getString('one')).toThrow( "Invalid type in config for key 'one' in 'ctx', got number, wanted string", @@ -580,4 +580,17 @@ describe('ConfigReader.get()', () => { }, }); }); + + it('coerces number strings to numbers', () => { + const config = ConfigReader.fromConfigs([ + { + data: { + port: '123', + }, + context: '1', + }, + ]); + + expect(config.getNumber('port')).toEqual(123); + }); }); diff --git a/packages/config/src/reader.ts b/packages/config/src/reader.ts index 65143bbd95..eb8c91e366 100644 --- a/packages/config/src/reader.ts +++ b/packages/config/src/reader.ts @@ -49,6 +49,9 @@ const errors = { missing(key: string) { return `Missing required config value at '${key}'`; }, + convert(key: string, context: string, expected: string) { + return `Unable to convert config value for key '${key}' in '${context}' to a ${expected}`; + }, }; export class ConfigReader implements Config { @@ -183,10 +186,22 @@ export class ConfigReader implements Config { } getOptionalNumber(key: string): number | undefined { - return this.readConfigValue( + const value = this.readConfigValue( key, - value => typeof value === 'number' || { expected: 'number' }, + val => + typeof val === 'number' || + typeof val === 'string' || { expected: 'number' }, ); + if (typeof value === 'number' || value === undefined) { + return value; + } + const number = Number(value); + if (!Number.isFinite(number)) { + throw new Error( + errors.convert(this.fullKey(key), this.context, 'number'), + ); + } + return number; } getBoolean(key: string): boolean {