From aaa83da0f8bcc3715149dc706f4500f77ef12169 Mon Sep 17 00:00:00 2001 From: Anders Kaseorg Date: Tue, 20 Jul 2021 18:48:28 -0700 Subject: [PATCH] config-util: Use zod for type-safe validation. Signed-off-by: Anders Kaseorg --- app/common/config-schemata.ts | 38 ++++++++++++++++++++++++++ app/common/config-util.ts | 50 +++++++++-------------------------- app/common/dnd-util.ts | 15 ++++++----- 3 files changed, 60 insertions(+), 43 deletions(-) create mode 100644 app/common/config-schemata.ts diff --git a/app/common/config-schemata.ts b/app/common/config-schemata.ts new file mode 100644 index 00000000..afee9da5 --- /dev/null +++ b/app/common/config-schemata.ts @@ -0,0 +1,38 @@ +import * as z from "zod"; + +export const dndSettingsSchemata = { + showNotification: z.boolean(), + silent: z.boolean(), + flashTaskbarOnMessage: z.boolean(), +}; + +export const configSchemata = { + ...dndSettingsSchemata, + appLanguage: z.string().nullable(), + autoHideMenubar: z.boolean(), + autoUpdate: z.boolean(), + badgeOption: z.boolean(), + betaUpdate: z.boolean(), + customCSS: z.string().or(z.literal(false)).nullable(), + dnd: z.boolean(), + dndPreviousSettings: z.object(dndSettingsSchemata).partial(), + dockBouncing: z.boolean(), + downloadsPath: z.string(), + enableSpellchecker: z.boolean(), + errorReporting: z.boolean(), + lastActiveTab: z.number(), + promptDownload: z.boolean(), + proxyBypass: z.string(), + proxyPAC: z.string(), + proxyRules: z.string(), + quitOnClose: z.boolean(), + showSidebar: z.boolean(), + spellcheckerLanguages: z.string().array().nullable(), + startAtLogin: z.boolean(), + startMinimized: z.boolean(), + systemProxyRules: z.string(), + trayIcon: z.boolean(), + useManualProxy: z.boolean(), + useProxy: z.boolean(), + useSystemProxy: z.boolean(), +}; diff --git a/app/common/config-util.ts b/app/common/config-util.ts index 3fa5f48f..66843abe 100644 --- a/app/common/config-util.ts +++ b/app/common/config-util.ts @@ -3,40 +3,16 @@ import fs from "fs"; import path from "path"; import {JsonDB} from "node-json-db"; +import {DataError} from "node-json-db/dist/lib/Errors"; +import type * as z from "zod"; -import type {DNDSettings} from "./dnd-util"; +import {configSchemata} from "./config-schemata"; import * as EnterpriseUtil from "./enterprise-util"; import Logger from "./logger-util"; -export interface Config extends DNDSettings { - appLanguage: string | null; - autoHideMenubar: boolean; - autoUpdate: boolean; - badgeOption: boolean; - betaUpdate: boolean; - customCSS: string | false | null; - dnd: boolean; - dndPreviousSettings: Partial; - dockBouncing: boolean; - downloadsPath: string; - enableSpellchecker: boolean; - errorReporting: boolean; - lastActiveTab: number; - promptDownload: boolean; - proxyBypass: string; - proxyPAC: string; - proxyRules: string; - quitOnClose: boolean; - showSidebar: boolean; - spellcheckerLanguages: string[] | null; - startAtLogin: boolean; - startMinimized: boolean; - systemProxyRules: string; - trayIcon: boolean; - useManualProxy: boolean; - useProxy: boolean; - useSystemProxy: boolean; -} +export type Config = { + [Key in keyof typeof configSchemata]: z.output; +}; /* To make the util runnable in both main and renderer process */ const {app, dialog} = process.type === "renderer" ? electron.remote : electron; @@ -52,7 +28,7 @@ reloadDB(); export function getConfigItem( key: Key, defaultValue: Config[Key], -): Config[Key] { +): z.output { try { db.reload(); } catch (error: unknown) { @@ -60,13 +36,13 @@ export function getConfigItem( logger.error(error); } - const value = db.getData("/")[key]; - if (value === undefined) { + try { + return configSchemata[key].parse(db.getObject(`/${key}`)); + } catch (error: unknown) { + if (!(error instanceof DataError)) throw error; setConfigItem(key, defaultValue); return defaultValue; } - - return value; } // This function returns whether a key exists in the configuration file (settings.json) @@ -78,8 +54,7 @@ export function isConfigItemExists(key: string): boolean { logger.error(error); } - const value = db.getData("/")[key]; - return value !== undefined; + return db.exists(`/${key}`); } export function setConfigItem( @@ -92,6 +67,7 @@ export function setConfigItem( return; } + configSchemata[key].parse(value); db.push(`/${key}`, value, true); db.save(); } diff --git a/app/common/dnd-util.ts b/app/common/dnd-util.ts index 8a70d3f3..fed94add 100644 --- a/app/common/dnd-util.ts +++ b/app/common/dnd-util.ts @@ -1,12 +1,15 @@ +import type * as z from "zod"; + +import type {dndSettingsSchemata} from "./config-schemata"; import * as ConfigUtil from "./config-util"; -type SettingName = keyof DNDSettings; +export type DNDSettings = { + [Key in keyof typeof dndSettingsSchemata]: z.output< + typeof dndSettingsSchemata[Key] + >; +}; -export interface DNDSettings { - showNotification: boolean; - silent: boolean; - flashTaskbarOnMessage: boolean; -} +type SettingName = keyof DNDSettings; interface Toggle { dnd: boolean;