// @ts-nocheck import { cloneDeep, each, get, has, isArray, isEqual, isNumber, isObject, isString, set, unset } from "lodash"; import * as mm from "micromatch"; import { err, ok, Result } from "neverthrow"; import { z, ZodError, ZodIssueCode, ZodType } from 'zod'; import { DEFAULT_SETTINGS, PluginData, Settings, settingsSchema } from "../settings/versions"; import { isSettingsV0, isSettingsV1, migrateFromV0ToV1 } from "../settings/versions/migration"; import { InfioSettings } from '../types/settings'; type JSONObject = Record; export function checkForErrors(settings: InfioSettings) { const errors = new Map(); const parsingResult = parseWithSchema(settingsSchema, settings); if (parsingResult.isOk()) { return errors; } if (parsingResult.error instanceof ZodError) { for (const issue of parsingResult.error.issues) { errors.set(issue.path.join('.'), issue.message); } } else { throw parsingResult.error; } return errors; } export function fixStructureAndValueErrors( schema: T, value: any | null | undefined, defaultValue: z.infer, ): Result, Error> { if (value === null || value === undefined) { value = {}; } let result = parseWithSchema(schema, value); if (result.isErr()) { value = addMissingKeys(value, result.error, defaultValue); value = removeUnrecognizedKeys(value, result.error); result = parseWithSchema(schema, value); } if (result.isErr() && value !== null && value !== undefined) { value = replaceValuesWithErrorsByDefaultValue(value, result.error, defaultValue); result = parseWithSchema(schema, value); } return result; } export function parseWithSchema( schema: T, value: JSONObject | null | undefined ): Result, ZodError> { const parsingResult = schema.safeParse(value); return parsingResult.success ? ok(parsingResult.data) : err(parsingResult.error); } function addMissingKeys(value: JSONObject, error: ZodError, defaultValue: T): JSONObject { const invalidTypeIssues = error.issues.filter(issue => issue.code === ZodIssueCode.invalid_type); const errorPaths = invalidTypeIssues .map(issue => issue.path) .map(path => reduceArrayPathToFirstObjectPath(path)) .map(path => path.join('.')); return replaceValueWithDefaultValue(value, errorPaths, defaultValue); } function replaceValueWithDefaultValue( value: any, paths: string[], defaultValue: T, ): V { const result = cloneDeep(value) as any; paths.forEach(path => { const originalValue = has(defaultValue, path) ? get(defaultValue, path) : undefined; set(result, path, originalValue); }); return result; } function removeUnrecognizedKeys(value: JSONObject | null | undefined, error: ZodError): JSONObject { if (typeof value !== 'object' || value === null || value === undefined) { return {}; } // Zod unrecognized_keys issues consist of two parts: // - path to the nested object where the unrecognized key was found // - the key itself which is unrecognized const unrecognizedPaths = error.issues .filter(issue => issue.code === ZodIssueCode.unrecognized_keys) // Array path will be handled separately by the value replacement function .filter(issue => !isAnArrayPath(issue.path)) .flatMap(issue => { // @ts-ignore const keys = issue.keys; return keys.map(key => [...issue.path, key].join('.')); }); unrecognizedPaths.forEach(path => { unset(value, path); }); return value; } function replaceValuesWithErrorsByDefaultValue( value: JSONObject, error: ZodError, defaultValue: T ): T { const errorPaths = error.issues .map(issue => issue.path) .map(path => reduceArrayPathToFirstObjectPath(path)) .map(path => path.join('.')); return replaceValueWithDefaultValue(value, errorPaths, defaultValue); } function reduceArrayPathToFirstObjectPath(path: (string | number)[]): (string | number)[] { const result: (string | number)[] = []; for (const key of path) { if (typeof key === 'number') { break; } result.push(key); } return result; } function isAnArrayPath(path: (string | number)[]): boolean { return path.some(key => typeof key === 'number'); } export function serializeSettings(settings: Settings): PluginData { return { settings: settings }; } export function deserializeSettings(data: JSONObject | null | undefined): Result { let settings: any; if (data === null || data === undefined || !data.hasOwnProperty("settings")) { settings = {}; } else { settings = data.settings; } if (isSettingsV0(settings)) { console.log("Migrating settings from v0 to v1"); settings = migrateFromV0ToV1(settings); } if (!isSettingsV1(settings)) { console.log("Fixing settings structure and value errors"); return fixStructureAndValueErrors(settingsSchema, settings, DEFAULT_SETTINGS); } return parseWithSchema(settingsSchema, settings); } export function isRegexValid(value: string): boolean { try { const regex = new RegExp(value); regex.test(""); return true; } catch (e) { return false; } } export function isValidIgnorePattern(value: string): boolean { try { mm.isMatch("", value); return true; } catch (e) { return false; } } export function findEqualPaths(obj1: any, obj2: any, basePath = ''): string[] { let paths: string[] = []; if ( basePath === '' && ( !isObject(obj1) || !isObject(obj2) || isArray(obj1) || isArray(obj2) || isNumber(obj1) || isNumber(obj2) || isString(obj1) || isString(obj2) ) ) { return []; } // Function to iterate over keys and compare values function iterateKeys(value: any, key: string | number): void { const path = basePath ? `${basePath}.${key}` : `${key}`; if (isObject(value) && isObject(get(obj2, key))) { // Recursively find paths for nested objects paths = paths.concat(findEqualPaths(value, get(obj2, key), path)); } else if (isEqual(value, get(obj2, key))) { // Add path to array if values are equal paths.push(path); } } // If both are arrays, iterate using each index if (isArray(obj1) && isArray(obj2)) { each(obj1, (value, index) => iterateKeys(value, `[${index}]`)); } else { // Iterate over keys of the first object each(obj1, iterateKeys); } return paths; }