From c791fcd96b90c496b4a2f3a6dea20483af02181c Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 23 Mar 2023 10:59:28 +0100 Subject: [PATCH] Allow simple type mismatches and coerce values accordingly. Signed-off-by: Eric Peterson --- .changeset/config-the-dane.md | 7 +++++++ packages/config-loader/src/lib/schema/compile.test.ts | 2 +- packages/config-loader/src/lib/schema/compile.ts | 1 + packages/config-loader/src/lib/schema/load.test.ts | 6 +++--- packages/config/src/reader.test.ts | 2 ++ 5 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 .changeset/config-the-dane.md diff --git a/.changeset/config-the-dane.md b/.changeset/config-the-dane.md new file mode 100644 index 0000000000..f93dd2fe2d --- /dev/null +++ b/.changeset/config-the-dane.md @@ -0,0 +1,7 @@ +--- +'@backstage/config-loader': minor +--- + +Configuration validation is now more permissive when it comes to config whose values are `string` but whose schemas declare them to be `boolean` or `number`. + +For example, configuration was previously marked invalid when a string `'true'` was set on a property expecting type `boolean` or a string `'146'` was set on a property expecting type `number` (as when providing configuration via variable substitution sourced from environment variables). Now, such configurations will be considered valid and their values will be coerced to the right type at read-time. diff --git a/packages/config-loader/src/lib/schema/compile.test.ts b/packages/config-loader/src/lib/schema/compile.test.ts index 999dabd307..827fba9197 100644 --- a/packages/config-loader/src/lib/schema/compile.test.ts +++ b/packages/config-loader/src/lib/schema/compile.test.ts @@ -28,7 +28,7 @@ describe('compileConfigSchemas', () => { value: { type: 'object', properties: { b: { type: 'number' } } }, }, ]); - expect(validate([{ data: { a: 1 }, context: 'test' }])).toEqual({ + expect(validate([{ data: { a: [1] }, context: 'test' }])).toEqual({ errors: [ { keyword: 'type', diff --git a/packages/config-loader/src/lib/schema/compile.ts b/packages/config-loader/src/lib/schema/compile.ts index c1059a28ae..d193e7fbf2 100644 --- a/packages/config-loader/src/lib/schema/compile.ts +++ b/packages/config-loader/src/lib/schema/compile.ts @@ -45,6 +45,7 @@ export function compileConfigSchemas( const ajv = new Ajv({ allErrors: true, allowUnionTypes: true, + coerceTypes: true, schemas: { 'https://backstage.io/schema/config-v1': true, }, diff --git a/packages/config-loader/src/lib/schema/load.test.ts b/packages/config-loader/src/lib/schema/load.test.ts index 475339be4e..d84bb3871b 100644 --- a/packages/config-loader/src/lib/schema/load.test.ts +++ b/packages/config-loader/src/lib/schema/load.test.ts @@ -103,7 +103,7 @@ describe('loadConfigSchema', () => { }, ]); expect(() => - schema2.process([...configs, { data: { key1: 3 }, context: 'test2' }]), + schema2.process([...configs, { data: { key1: [3] }, context: 'test2' }]), ).toThrow( 'Config validation failed, Config must be string { type=string } at /key1', ); @@ -202,11 +202,11 @@ describe('loadConfigSchema', () => { context: 'test', }, ]); - expect(() => schema.process(mkConfig({ y: 1 }))).toThrow( + expect(() => schema.process(mkConfig({ y: [1] }))).toThrow( 'Config validation failed, Config must be string { type=string } at /nested/0/y', ); expect(() => - schema.process(mkConfig({ y: 1 }), { visibility: ['frontend'] }), + schema.process(mkConfig({ y: [1] }), { visibility: ['frontend'] }), ).toThrow( 'Config validation failed, Config must be string { type=string } at /nested/0/y', ); diff --git a/packages/config/src/reader.test.ts b/packages/config/src/reader.test.ts index c4c2a4ecbf..90b9b28b41 100644 --- a/packages/config/src/reader.test.ts +++ b/packages/config/src/reader.test.ts @@ -59,6 +59,8 @@ function expectValidValues(config: ConfigReader) { expect(config.has('nested.null')).toBe(true); expect(config.getNumber('zero')).toBe(0); expect(config.getNumber('one')).toBe(1); + expect(config.getNumber('zeroString')).toBe(0); + expect(config.getNumber('oneString')).toBe(1); expect(config.getOptional('true')).toBe(true); expect(config.getBoolean('true')).toBe(true); expect(config.getBoolean('false')).toBe(false);