diff --git a/.changeset/tidy-flies-cheer.md b/.changeset/tidy-flies-cheer.md new file mode 100644 index 0000000000..78de902e63 --- /dev/null +++ b/.changeset/tidy-flies-cheer.md @@ -0,0 +1,7 @@ +--- +'@backstage/config': patch +--- + +Adds the ability to coerce values to their boolean representatives. +Values such as `"true"` `1` `on` and `y` will become `true` when using `getBoolean` and the opposites `false`. +This happens particularly when such parameters are used with environmental substitution as environment variables are always strings. diff --git a/packages/config/src/reader.test.ts b/packages/config/src/reader.test.ts index c0627a200b..c4c2a4ecbf 100644 --- a/packages/config/src/reader.test.ts +++ b/packages/config/src/reader.test.ts @@ -22,6 +22,15 @@ const DATA = { one: 1, true: true, false: false, + yes: 'yes', + no: 'no', + y: 'y', + n: 'n', + on: 'on', + off: 'off', + zeroString: '0', + oneString: '1', + stringFalse: 'false', null: null, string: 'string', emptyString: '', @@ -53,6 +62,17 @@ function expectValidValues(config: ConfigReader) { expect(config.getOptional('true')).toBe(true); expect(config.getBoolean('true')).toBe(true); expect(config.getBoolean('false')).toBe(false); + expect(config.getBoolean('stringFalse')).toBe(false); + expect(config.getBoolean('zero')).toBe(false); + expect(config.getBoolean('one')).toBe(true); + expect(config.getBoolean('zeroString')).toBe(false); + expect(config.getBoolean('oneString')).toBe(true); + expect(config.getBoolean('yes')).toBe(true); + expect(config.getBoolean('no')).toBe(false); + expect(config.getBoolean('y')).toBe(true); + expect(config.getBoolean('n')).toBe(false); + expect(config.getBoolean('on')).toBe(true); + expect(config.getBoolean('off')).toBe(false); expect(config.getString('string')).toBe('string'); expect(config.get('strings')).toEqual(['string1', 'string2']); expect(config.getStringArray('strings')).toEqual(['string1', 'string2']); @@ -86,7 +106,7 @@ function expectValidValues(config: ConfigReader) { function expectInvalidValues(config: ConfigReader) { expect(() => config.getBoolean('string')).toThrow( - "Invalid type in config for key 'string' in 'ctx', got string, wanted boolean", + "Unable to convert config value for key 'string' in 'ctx' to a boolean", ); expect(() => config.getNumber('string')).toThrow( "Unable to convert config value for key 'string' in 'ctx' to a number", diff --git a/packages/config/src/reader.ts b/packages/config/src/reader.ts index 1ce54590b5..4a570b6977 100644 --- a/packages/config/src/reader.ts +++ b/packages/config/src/reader.ts @@ -280,10 +280,25 @@ export class ConfigReader implements Config { /** {@inheritdoc Config.getOptionalBoolean} */ getOptionalBoolean(key: string): boolean | undefined { - return this.readConfigValue( + const value = this.readConfigValue( key, - value => typeof value === 'boolean' || { expected: 'boolean' }, + val => + typeof val === 'boolean' || + typeof val === 'number' || + typeof val === 'string' || { expected: 'boolean' }, ); + if (typeof value === 'boolean' || value === undefined) { + return value; + } + const valueString = String(value).trim(); + + if (/^(?:y|yes|true|1|on)$/i.test(valueString)) { + return true; + } + if (/^(?:n|no|false|0|off)$/i.test(valueString)) { + return false; + } + throw new Error(errors.convert(this.fullKey(key), this.context, 'boolean')); } /** {@inheritdoc Config.getString} */