feat: Add hardened error handling and retries (#2451)
- Closes #1830 - Closes #2099
This commit is contained in:
committed by
dermotduffy
parent
4bc787e2b7
commit
47bcce93d3
@@ -1,4 +1,5 @@
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { ConfigParseError } from './error.js';
|
||||
import { copyConfig, isConfigUpgradeable } from '../../config/management.js';
|
||||
import { setProfiles } from '../../config/profiles/set-profiles.js';
|
||||
import {
|
||||
@@ -56,7 +57,7 @@ export class ConfigManager {
|
||||
|
||||
public setConfig(inputConfig?: RawAdvancedCameraCardConfig): void {
|
||||
if (!inputConfig) {
|
||||
throw new Error(localize('error.invalid_configuration'));
|
||||
throw new ConfigParseError(localize('error.invalid_configuration'));
|
||||
}
|
||||
|
||||
const parseResult = advancedCameraCardConfigSchema.safeParse(inputConfig);
|
||||
@@ -67,7 +68,7 @@ export class ConfigManager {
|
||||
if (isConfigUpgradeable(inputConfig)) {
|
||||
upgradeMessage = `${localize('error.upgrade_available')}. `;
|
||||
}
|
||||
throw new Error(
|
||||
throw new ConfigParseError(
|
||||
upgradeMessage +
|
||||
`${localize('error.invalid_configuration')}: ` +
|
||||
(hint ?? localize('error.invalid_configuration_no_hint')),
|
||||
@@ -109,7 +110,6 @@ export class ConfigManager {
|
||||
this._api.getInitializationManager().uninitialize(InitializationAspect.VIEW);
|
||||
this._api.getViewManager().reset();
|
||||
|
||||
this._api.getMessageManager().reset();
|
||||
this._api.getStatusBarItemManager().removeAllDynamicStatusBarItems();
|
||||
|
||||
this._processOverrideConfig();
|
||||
@@ -118,11 +118,28 @@ export class ConfigManager {
|
||||
}
|
||||
|
||||
private _processOverrideConfig(): void {
|
||||
const overriddenConfig = this._getOverriddenConfig();
|
||||
/* istanbul ignore if: No (current) way to reach this code -- @preserve */
|
||||
if (!this._config) {
|
||||
return;
|
||||
}
|
||||
|
||||
let overriddenConfig: AdvancedCameraCardConfig;
|
||||
try {
|
||||
overriddenConfig = this._overridesManager.getConfig(this._config);
|
||||
} catch (ev) {
|
||||
this._api.getIssueManager().trigger('config_error', { error: ev });
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear any prior config error on success. This method runs both from
|
||||
// setConfig() and from condition-change-driven override recomputations;
|
||||
// resetting here ensures a transient override error is cleared when
|
||||
// conditions change to produce a valid config again.
|
||||
this._api.getIssueManager().reset('config_error');
|
||||
|
||||
// Save on Lit re-rendering costs by only updating the configuration if it
|
||||
// actually changes.
|
||||
if (!overriddenConfig || isEqual(overriddenConfig, this._overriddenConfig)) {
|
||||
if (isEqual(overriddenConfig, this._overriddenConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -189,20 +206,6 @@ export class ConfigManager {
|
||||
/* async */ this._initializeBackgroundAndUpdate(previousConfig);
|
||||
}
|
||||
|
||||
private _getOverriddenConfig(): AdvancedCameraCardConfig | null {
|
||||
/* istanbul ignore if: No (current) way to reach this code -- @preserve */
|
||||
if (!this._config) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return this._overridesManager.getConfig(this._config);
|
||||
} catch (ev) {
|
||||
this._api.getMessageManager().setErrorIfHigherPriority(ev);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize config dependent items in the background. For items that the
|
||||
* card hard requires, use InitializationManager instead.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { AdvancedCameraCardError } from '../../types.js';
|
||||
|
||||
class ConfigError extends AdvancedCameraCardError {}
|
||||
|
||||
export class ConfigParseError extends ConfigError {}
|
||||
@@ -7,6 +7,6 @@ export const setFoldersFromConfig = (api: CardConfigLoaderAPI): void => {
|
||||
.getFoldersManager()
|
||||
.addFolders(api.getConfigManager().getConfig()?.folders ?? []);
|
||||
} catch (ev) {
|
||||
api.getMessageManager().setErrorIfHigherPriority(ev);
|
||||
api.getIssueManager().trigger('config_error', { error: ev });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { merge } from 'lodash-es';
|
||||
import { get, merge } from 'lodash-es';
|
||||
import { ConditionsManager } from '../../conditions/conditions-manager';
|
||||
import { ConditionStateManagerReadonlyInterface } from '../../conditions/types';
|
||||
import {
|
||||
@@ -92,9 +92,24 @@ export class OverridesManager {
|
||||
|
||||
const parseResult = advancedCameraCardConfigSchema.safeParse(output);
|
||||
if (!parseResult.success) {
|
||||
// Surface one co-located failure object per Zod issue — path, the value
|
||||
// the user actually wrote, and the most informative "expected" field for
|
||||
// this issue code. Avoids dumping the full merged config (which is
|
||||
// mostly schema defaults the user never wrote) and keeps the reader from
|
||||
// cross-referencing two parallel arrays.
|
||||
const failures = parseResult.error.issues.map((issue) => ({
|
||||
path: issue.path.map(String).join('.'),
|
||||
received: get(output, issue.path),
|
||||
expected:
|
||||
issue.code === 'invalid_value'
|
||||
? issue.values
|
||||
: issue.code === 'invalid_type'
|
||||
? issue.expected
|
||||
: issue.message,
|
||||
}));
|
||||
throw new OverrideConfigurationError(
|
||||
localize('error.invalid_configuration_override'),
|
||||
[parseResult.error.issues, output],
|
||||
{ failures },
|
||||
);
|
||||
}
|
||||
return parseResult.data;
|
||||
|
||||
Reference in New Issue
Block a user