Skip to content

Commit

Permalink
fix(front-json): fix type safe json parser
Browse files Browse the repository at this point in the history
  • Loading branch information
SARDONYX-sard committed Oct 24, 2024
1 parent 7c47112 commit 63f7890
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { useCallback, useState } from 'react';

import type { WithHideCacheKey } from '@/lib/storage';
import { STORAGE, type WithHideCacheKey } from '@/lib/storage';

// Helper function to retrieve the cache value and parse it from localStorage
const getCacheValue = <T>(key: WithHideCacheKey, fallback: T): T => {
const cached = localStorage.getItem(key);
const cached = STORAGE.getSome(key);
if (cached === null) {
return fallback;
}

try {
return JSON.parse(cached);
return JSON.parse(cached) as T; // FIXME: unsafe type conversion
} catch {
return fallback;
}
Expand Down Expand Up @@ -43,7 +43,11 @@ export function useStorageState<T>(keyName: WithHideCacheKey, fallbackState: T)
const setState = useCallback(
(newValue: T) => {
setValue(newValue);
localStorage.setItem(keyName, JSON.stringify(newValue));

const jsonStr = JSON.stringify(newValue);
if (typeof jsonStr === 'string') {
STORAGE.setSome(keyName, jsonStr);
}
},
[keyName],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type Props = Omit<FormControlLabelProps, 'control' | 'label'>;

export const JsAutoRunButton = ({ ...props }: Props) => {
const { t } = useTranslation();
const [runScript, setRunScript] = useStorageState(HIDDEN_CACHE_OBJ.runScript, false);
const [runScript, setRunScript] = useStorageState<boolean>(HIDDEN_CACHE_OBJ.runScript, false);

const title = (
<>
Expand Down
16 changes: 15 additions & 1 deletion gui/frontend/src/lib/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { type Resource, use } from 'i18next';
import { initReactI18next } from 'react-i18next';

import { NOTIFY } from '@/lib/notify';
import { OBJECT } from '@/lib/object-utils';
import { STORAGE } from '@/lib/storage';
import { PUB_CACHE_OBJ } from '@/lib/storage/cacheKeys';

Expand All @@ -19,7 +20,20 @@ const RESOURCES = {
'ja-JP': {
translation: dictJaJp,
},
custom: { translation: NOTIFY.try(() => JSON.parse(STORAGE.get('custom-translation-dict') ?? '{}')) },
custom: {
translation:
NOTIFY.try(() => {
const dictStr = STORAGE.get('custom-translation-dict');
if (dictStr === null) {
return null;
}

const json = JSON.parse(dictStr);
if (OBJECT.isPropertyAccessible(json)) {
return json;
}
}) ?? {},
},
} as const satisfies Resource;

/**
Expand Down
20 changes: 16 additions & 4 deletions gui/frontend/src/lib/notify/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { NOTIFY } from '@/lib/notify';
import { OBJECT } from '@/lib/object-utils';
import { STORAGE } from '@/lib/storage';

import type { SnackbarOrigin } from 'notistack';
Expand Down Expand Up @@ -58,10 +59,21 @@ export const NOTIFY_CONFIG = {
*/
getOrDefault() {
const anchorOrigin = (() => {
const position: Partial<SnackbarOrigin> = NOTIFY.try(() => {
const jsonStr = STORAGE.get('snackbar-position');
return jsonStr ? JSON.parse(jsonStr) : {};
});
const position: Partial<SnackbarOrigin> =
NOTIFY.try(() => {
const jsonStr = STORAGE.get('snackbar-position');
if (jsonStr === null) {
return {};
}

const json = JSON.parse(jsonStr);
if (OBJECT.isPropertyAccessible(json)) {
return json;
}

return {};
}) ?? {};

return normalize(position) satisfies NotifyConfig['anchorOrigin'];
})();

Expand Down
5 changes: 5 additions & 0 deletions gui/frontend/src/lib/object-utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,9 @@ export const OBJECT = {
entries: <T extends { [key: string]: T[keyof T] }>(obj: T): [keyof T, T[keyof T]][] => {
return Object.entries(obj);
},

/** ref: [better-typescript-lib article(ja)](https://zenn.dev/uhyo/articles/better-typescript-lib-v2#better-typescript-lib-%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6) */
isPropertyAccessible(obj: unknown): obj is Record<string, unknown> {
return obj !== null;
},
} as const;
19 changes: 19 additions & 0 deletions gui/frontend/src/lib/storage/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ export const createStorage = <
*/
get: (key: CacheKey<K>) => localStorage.getItem(key),

/**
* Retrieves the value from localStorage for the given cache key.
* @param key - The cache key to retrieve.
* @example
* storage.get('snackbar-limit');
*/
getSome: (key: CacheKey<K> | HiddenCacheKey<H>) => localStorage.getItem(key),

/**
* Retrieves the value from localStorage for the given cache key.
* @param key - The cache key to retrieve.
Expand Down Expand Up @@ -69,6 +77,17 @@ export const createStorage = <
localStorage.setItem(key, value);
},

/**
* Sets a value in localStorage for a given cache key.
* @param key - The cache key to set.
* @param value - The value to store.
* @example
* storage.set('snackbar-limit', '5');
*/
setSome(key: CacheKey<K> | HiddenCacheKey<H>, value: string) {
localStorage.setItem(key, value);
},

/**
* Removes a value from localStorage for a given cache key.
* @param key - The cache key to remove.
Expand Down
8 changes: 2 additions & 6 deletions gui/frontend/src/services/api/backup.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import { save } from '@tauri-apps/plugin-dialog';

import { OBJECT } from '@/lib/object-utils';
import { CACHE_KEYS, type Cache, STORAGE } from '@/lib/storage';
import { PRIVATE_CACHE_OBJ } from '@/lib/storage/cacheKeys';

import { readFile, writeFile } from './fs';

const SETTINGS_FILE_NAME = 'settings';

/** ref: [better-typescript-lib article(ja)](https://zenn.dev/uhyo/articles/better-typescript-lib-v2#better-typescript-lib-%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6) */
function isPropertyAccessible(obj: unknown): obj is Record<string, unknown> {
return obj !== null;
}

export const BACKUP = {
/** @throws Error */
async import(): Promise<Cache | null> {
Expand All @@ -21,7 +17,7 @@ export const BACKUP = {
}

const json = JSON.parse(settings);
if (!isPropertyAccessible(json)) {
if (!OBJECT.isPropertyAccessible(json)) {
return null;
}

Expand Down

0 comments on commit 63f7890

Please sign in to comment.