From 3cc4154b9a9f4254c4a37e74a32bf3c3c723d363 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 3 Jun 2020 11:43:17 +0200 Subject: [PATCH] packages/config: refactor to make it easier to extend with more reader methods --- packages/config/src/reader.ts | 150 +++++++++++++++++----------------- 1 file changed, 75 insertions(+), 75 deletions(-) diff --git a/packages/config/src/reader.ts b/packages/config/src/reader.ts index 6596b72087..dfdf4cd921 100644 --- a/packages/config/src/reader.ts +++ b/packages/config/src/reader.ts @@ -32,29 +32,12 @@ function typeOf(value: JsonValue | undefined): string { if (type === 'number' && isNaN(value as number)) { return 'nan'; } + if (type === 'string' && value === '') { + return 'empty-string'; + } return type; } -function typeErrorMessage(key: string, got: string, wanted: string) { - return `Invalid type in config for key ${key}, got ${got}, wanted ${wanted}`; -} - -function validateString( - key: string, - value: JsonValue | undefined, -): value is string { - if (typeof value === 'string' && value.length > 0) { - return true; - } - if (value === '') { - throw new TypeError(typeErrorMessage(key, 'empty-string', 'string')); - } - if (value !== undefined) { - throw new TypeError(typeErrorMessage(key, typeOf(value), 'string')); - } - return false; -} - export class ConfigReader implements Config { private static readonly nullReader = new ConfigReader({}); @@ -72,91 +55,108 @@ export class ConfigReader implements Config { constructor( private readonly data: JsonObject, - private readonly fallback?: Config, + private readonly fallback?: ConfigReader, ) {} - getConfig(key: string): Config { + getConfig(key: string): ConfigReader { const value = this.readValue(key); const fallbackConfig = this.fallback?.getConfig(key); if (isObject(value)) { return new ConfigReader(value, fallbackConfig); } if (value !== undefined) { - throw new TypeError(typeErrorMessage(key, typeOf(value), 'object')); + throw new TypeError( + `Invalid type in config for key ${key}, got ${typeOf( + value, + )}, wanted object`, + ); } return fallbackConfig ?? ConfigReader.nullReader; } - getConfigArray(key: string): Config[] { - const values = this.readValue(key); - if (Array.isArray(values)) { - return values.map((value, index) => { - if (isObject(value)) { - return new ConfigReader(value); + getConfigArray(key: string): ConfigReader[] { + const configs = this.readConfigValue(key, values => { + if (!Array.isArray(values)) { + return { expected: 'object-array' }; + } + + for (const [index, value] of values.entries()) { + if (!isObject(value)) { + return { expected: 'object-array', value, key: `${key}[${index}]` }; } - throw new TypeError( - typeErrorMessage(`${key}[${index}]`, typeOf(value), 'object'), - ); - }); - } - if (values !== undefined) { - throw new TypeError( - typeErrorMessage(key, typeOf(values), 'object-array'), - ); - } - return this.fallback?.getConfigArray(key) ?? []; + } + return true; + }); + + return (configs ?? []).map(obj => new ConfigReader(obj)); } getNumber(key: string): number | undefined { - const value = this.readValue(key); - if (typeof value === 'number' && !isNaN(value)) { - return value; - } - if (value !== undefined) { - throw new TypeError(typeErrorMessage(key, typeOf(value), 'number')); - } - return this.fallback?.getNumber(key); + return this.readConfigValue( + key, + value => typeof value === 'number' || { expected: 'number' }, + ); } getBoolean(key: string): boolean | undefined { - const value = this.readValue(key); - if (typeof value === 'boolean') { - return value; - } - if (value !== undefined) { - throw new TypeError(typeErrorMessage(key, typeOf(value), 'boolean')); - } - return this.fallback?.getBoolean(key); + return this.readConfigValue( + key, + value => typeof value === 'boolean' || { expected: 'boolean' }, + ); } getString(key: string): string | undefined { - const value = this.readValue(key); - if (validateString(key, value)) { - return value; - } - return this.fallback?.getString(key); + return this.readConfigValue( + key, + value => + (typeof value === 'string' && value !== '') || { expected: 'string' }, + ); } getStringArray(key: string): string[] | undefined { - const values = this.readValue(key); - if (Array.isArray(values)) { + return this.readConfigValue(key, values => { + if (!Array.isArray(values)) { + return { expected: 'string-array' }; + } for (const [index, value] of values.entries()) { - const iKey = `${key}[${index}]`; - if (!validateString(iKey, value)) { - throw new TypeError(typeErrorMessage(iKey, typeOf(value), 'string')); + if (typeof value !== 'string' || value === '') { + return { expected: 'string-array', value, key: `${key}[${index}]` }; } } - return values as string[]; - } - if (values !== undefined) { - throw new TypeError( - typeErrorMessage(key, typeOf(values), 'string-array'), - ); - } - return this.fallback?.getStringArray(key); + return true; + }); } - protected readValue(key: string): JsonValue | undefined { + private readConfigValue( + key: string, + validate: ( + value: JsonValue, + ) => { expected: string; value?: JsonValue; key?: string } | true, + ): T | undefined { + const value = this.readValue(key); + + if (value === undefined) { + return this.fallback?.readConfigValue(key, validate); + } + if (value !== undefined) { + const result = validate(value); + if (result !== true) { + const { + key: keyName = key, + value: theValue = value, + expected, + } = result; + const typeName = typeOf(theValue); + throw new TypeError( + `Invalid type in config for key ${keyName}, got ${typeName}, wanted ${expected}`, + ); + } + } + + return value as T; + } + + private readValue(key: string): JsonValue | undefined { const parts = key.split('.'); let value: JsonValue | undefined = this.data;