Skip to content

Commit fe432f9

Browse files
committed
feat(settings): don't throw exception if default is provided
Problem: If a setting value is invalid, `Settings.instance.get` throws an exception, even if a `defaultValue` is provided. We almost never want that behavior. Example: Settings.instance.get<object>('files.exclude', Number, 42) Solution: Copy the logic from `AnonymousSettings.get()`.
1 parent 4423833 commit fe432f9

File tree

2 files changed

+48
-19
lines changed

2 files changed

+48
-19
lines changed

src/shared/settings.ts

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,35 @@ export class Settings {
3131
) {}
3232

3333
/**
34-
* Reads a setting, applying the {@link TypeConstructor} if provided.
34+
* Gets a setting value, applying the {@link TypeConstructor} if provided.
3535
*
36-
* Note that the absence of a key is indistinguisable from the absence of a value.
37-
* That is, `undefined` looks the same across all calls. Any non-existent values are
38-
* simply returned as-is without passing through the type cast.
36+
* If the read fails or the setting value is invalid (does not conform to `type`):
37+
* - `defaultValue` is returned if it was provided
38+
* - else an exception is thrown
39+
*
40+
* @note An unknown setting is indistinguisable from a missing value: both return `undefined`.
41+
* Non-existent values are returned as-is without passing through the type cast.
42+
*
43+
* @param key Setting name
44+
* @param type Expected setting type
45+
* @param defaultValue Value returned if setting is missing or invalid
3946
*/
4047
public get(key: string): unknown
4148
public get<T>(key: string, type: TypeConstructor<T>): T | undefined
4249
public get<T>(key: string, type: TypeConstructor<T>, defaultValue: T): T
4350
public get<T>(key: string, type?: TypeConstructor<T>, defaultValue?: T) {
44-
const value = this.getConfig().get(key, defaultValue)
51+
try {
52+
const value = this.getConfig().get(key, defaultValue)
4553

46-
return !type || value === undefined ? value : cast(value, type)
54+
return !type || value === undefined ? value : cast(value, type)
55+
} catch (e) {
56+
if (arguments.length <= 2) {
57+
throw ToolkitError.chain(e, `Failed to read setting "${key}"`)
58+
}
59+
getLogger().error('settings: failed to read "%s": %s', key, (e as Error).message)
60+
61+
return defaultValue
62+
}
4763
}
4864

4965
/**
@@ -62,8 +78,8 @@ export class Settings {
6278
await this.getConfig().update(key, value, this.updateTarget)
6379

6480
return true
65-
} catch (error) {
66-
getLogger().warn(`Settings: failed to update "${key}": %s`, error)
81+
} catch (e) {
82+
getLogger().warn('settings: failed to update "%s": %s', key, (e as Error).message)
6783

6884
return false
6985
}
@@ -224,15 +240,24 @@ function createSettingsClass<T extends TypeDescriptor>(section: string, descript
224240
return this.config.keys()
225241
}
226242

243+
/**
244+
* Gets a setting value.
245+
*
246+
* If the read fails or the setting value is invalid (does not conform to the type defined in package.json):
247+
* - `defaultValue` is returned if it was provided
248+
* - else an exception is thrown
249+
*
250+
* @param key Setting name
251+
* @param defaultValue Value returned if setting is missing or invalid
252+
*/
227253
public get<K extends keyof Inner>(key: K & string, defaultValue?: Inner[K]) {
228254
try {
229255
return this.getOrThrow(key, defaultValue)
230256
} catch (e) {
231257
if (arguments.length === 1) {
232258
throw ToolkitError.chain(e, `Failed to read key "${section}.${key}"`)
233259
}
234-
235-
this.logErr(`using default for key "${key}", read failed: %s`, e)
260+
this.logErr('failed to read "%s": %s', key, (e as Error).message)
236261

237262
return defaultValue as Inner[K]
238263
}
@@ -243,8 +268,8 @@ function createSettingsClass<T extends TypeDescriptor>(section: string, descript
243268
await this.config.update(key, value)
244269

245270
return true
246-
} catch (error) {
247-
this.log(`failed to update field "${key}": %s`, error)
271+
} catch (e) {
272+
this.log('failed to update "%s": %s', key, (e as Error).message)
248273

249274
return false
250275
}
@@ -255,8 +280,8 @@ function createSettingsClass<T extends TypeDescriptor>(section: string, descript
255280
await this.config.update(key, undefined)
256281

257282
return true
258-
} catch (error) {
259-
this.log(`failed to delete field "${key}": %s`, error)
283+
} catch (e) {
284+
this.log('failed to delete "%s": %s', key, (e as Error).message)
260285

261286
return false
262287
}
@@ -265,8 +290,8 @@ function createSettingsClass<T extends TypeDescriptor>(section: string, descript
265290
public async reset() {
266291
try {
267292
return await this.config.reset()
268-
} catch (error) {
269-
this.log(`failed to reset settings: %s`, error)
293+
} catch (e) {
294+
this.log('failed to reset settings: %s', (e as Error).message)
270295
}
271296
}
272297

@@ -317,7 +342,7 @@ function createSettingsClass<T extends TypeDescriptor>(section: string, descript
317342
}
318343

319344
for (const key of props.filter(isDifferent)) {
320-
this.log(`key "${key}" changed`)
345+
this.log('key "%s" changed', key)
321346
emitter.fire({ key })
322347
}
323348
})
@@ -509,8 +534,8 @@ export class PromptSettings extends Settings.define(
509534
public async isPromptEnabled(promptName: PromptName): Promise<boolean> {
510535
try {
511536
return !this.getOrThrow(promptName, false)
512-
} catch (error) {
513-
this.log(`prompt check for "${promptName}" failed: %s`, error)
537+
} catch (e) {
538+
this.log('prompt check for "%s" failed: %s', promptName, (e as Error).message)
514539
await this.reset()
515540

516541
return true

src/test/shared/settingsConfiguration.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ describe('Settings', function () {
7272
assert.throws(() => sut.get(settingKey, String))
7373
assert.throws(() => sut.get(settingKey, Object))
7474
assert.throws(() => sut.get(settingKey, Boolean))
75+
// Wrong type, but defaultValue was given:
76+
assert.deepStrictEqual(sut.get(settingKey, String, ''), '')
77+
assert.deepStrictEqual(sut.get(settingKey, Object, {}), {})
78+
assert.deepStrictEqual(sut.get(settingKey, Boolean, true), true)
7579
})
7680
})
7781

0 commit comments

Comments
 (0)