Skip to content

Commit

Permalink
Use zod for defining config schemas
Browse files Browse the repository at this point in the history
  • Loading branch information
whatuserever committed Feb 13, 2025
1 parent 8ab954a commit cafdc53
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 83 deletions.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,12 @@
"react-dom": "^18.3.1",
"svelte": "^4.2.19",
"web-vitals": "^4.2.3",
"ynab": "^1.19.0"
"ynab": "^1.19.0",
"zod": "^3.24.2"
},
"resolutions": {
"underscore": "1.12.1",
"ws": "8.17.1"
}
},
"packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
168 changes: 87 additions & 81 deletions packages/main/src/backend/configManager/configMigration/versions/v1.ts
Original file line number Diff line number Diff line change
@@ -1,88 +1,94 @@
import {
type Config,
type CsvConfig,
type GoogleSheetsConfig,
type JsonConfig,
OutputVendorName,
type YnabConfig,
} from '/@/backend/commonTypes';
import { z } from 'zod';

export type V1Config = Config;
export const outputVendorNameSchema = z.enum(['ynab', 'googleSheets', 'json', 'csv']);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function isV1Config(obj: any): obj is V1Config {
return (
typeof obj === 'object' &&
obj !== null &&
typeof obj.scraping === 'object' &&
typeof obj.scraping.numDaysBack === 'number' &&
typeof obj.scraping.showBrowser === 'boolean' &&
Array.isArray(obj.scraping.accountsToScrape) &&
typeof obj.scraping.timeout === 'number' &&
(obj.scraping.chromiumPath === undefined || typeof obj.scraping.chromiumPath === 'string') &&
(obj.scraping.maxConcurrency === undefined || typeof obj.scraping.maxConcurrency === 'number') &&
(obj.scraping.periodicScrapingIntervalHours === undefined ||
typeof obj.scraping.periodicScrapingIntervalHours === 'number') &&
(obj.useReactUI === undefined || typeof obj.useReactUI === 'boolean') &&
typeof obj.outputVendors === 'object' &&
(obj.outputVendors[OutputVendorName.GOOGLE_SHEETS] === undefined ||
isGoogleSheetsConfig(obj.outputVendors[OutputVendorName.GOOGLE_SHEETS])) &&
(obj.outputVendors[OutputVendorName.YNAB] === undefined ||
isYnabConfig(obj.outputVendors[OutputVendorName.YNAB])) &&
(obj.outputVendors[OutputVendorName.JSON] === undefined ||
isJsonConfig(obj.outputVendors[OutputVendorName.JSON])) &&
(obj.outputVendors[OutputVendorName.CSV] === undefined || isCsvConfig(obj.outputVendors[OutputVendorName.CSV]))
);
}
export const companyTypeSchema = z.enum([
'hapoalim',
'hapoalimBeOnline',
'beinleumi',
'union',
'amex',
'isracard',
'visaCal',
'max',
'leumiCard',
'otsarHahayal',
'discount',
'mercantile',
'mizrahi',
'leumi',
'massad',
'yahav',
'behatsdaa',
'beyahadBishvilha',
'oneZero',
'pagi',
]);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function isGoogleSheetsConfig(obj: any): obj is GoogleSheetsConfig {
return (
typeof obj === 'object' &&
obj !== null &&
typeof obj.active === 'boolean' &&
typeof obj.options === 'object' &&
typeof obj.options.credentials === 'object' &&
typeof obj.options.spreadsheetId === 'string'
);
}
export const googleSheetsConfigSchema = z.object({
active: z.boolean(),
options: z.object({
credentials: z.any(),
spreadsheetId: z.string(),
}),
});

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function isYnabConfig(obj: any): obj is YnabConfig {
return (
typeof obj === 'object' &&
obj !== null &&
typeof obj.active === 'boolean' &&
typeof obj.options === 'object' &&
typeof obj.options.accessToken === 'string' &&
typeof obj.options.accountNumbersToYnabAccountIds === 'object' &&
obj.options.accountNumbersToYnabAccountIds !== null &&
Object.keys(obj.options.accountNumbersToYnabAccountIds).every(
(key) => typeof key === 'string' && typeof obj.options.accountNumbersToYnabAccountIds[key] === 'string',
) &&
typeof obj.options.budgetId === 'string' &&
(obj.options.maxPayeeNameLength === undefined || typeof obj.options.maxPayeeNameLength === 'number')
);
}
export const ynabConfigSchema = z.object({
active: z.boolean(),
options: z.object({
accessToken: z.string(),
accountNumbersToYnabAccountIds: z.record(z.string(), z.string()),
budgetId: z.string(),
maxPayeeNameLength: z.number().optional(),
}),
});

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function isJsonConfig(obj: any): obj is JsonConfig {
return (
typeof obj === 'object' &&
obj !== null &&
typeof obj.active === 'boolean' &&
typeof obj.options === 'object' &&
typeof obj.options.filePath === 'string'
);
}
export const jsonConfigSchema = z.object({
active: z.boolean(),
options: z.object({
filePath: z.string(),
}),
});

export const csvConfigSchema = z.object({
active: z.boolean(),
options: z.object({
filePath: z.string(),
}),
});

export const outputVendorsSchema = z.object({
[outputVendorNameSchema.Values.googleSheets]: googleSheetsConfigSchema.optional(),
[outputVendorNameSchema.Values.ynab]: ynabConfigSchema.optional(),
[outputVendorNameSchema.Values.json]: jsonConfigSchema.optional(),
[outputVendorNameSchema.Values.csv]: csvConfigSchema.optional(),
});

export const accountToScrapeConfigSchema = z.object({
id: z.string(),
key: companyTypeSchema,
name: z.string(),
loginFields: z.any(),
active: z.boolean().optional(),
});

export const scrapingSchema = z.object({
numDaysBack: z.number(),
showBrowser: z.boolean(),
accountsToScrape: z.array(accountToScrapeConfigSchema),
chromiumPath: z.string().optional(),
maxConcurrency: z.number().optional(),
timeout: z.number(),
periodicScrapingIntervalHours: z.number().optional(),
});

export const v1ConfigSchema = z.object({
outputVendors: outputVendorsSchema,
scraping: scrapingSchema,
useReactUI: z.boolean().optional(),
});

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function isCsvConfig(obj: any): obj is CsvConfig {
return (
typeof obj === 'object' &&
obj !== null &&
typeof obj.active === 'boolean' &&
typeof obj.options === 'object' &&
typeof obj.options.filePath === 'string'
);
export function isV1Config(obj: unknown) {
const parseResult = v1ConfigSchema.safeParse(obj);
return parseResult.success;
}
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6493,3 +6493,8 @@ [email protected]:
version "3.23.8"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d"
integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==

zod@^3.24.2:
version "3.24.2"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.2.tgz#8efa74126287c675e92f46871cfc8d15c34372b3"
integrity sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==

0 comments on commit cafdc53

Please sign in to comment.