export const SUPPORTED_CATEGORIES = ['programming', 'general', 'pun', 'spooky', 'christmas'] as const;
export type Category = typeof SUPPORTED_CATEGORIES[number];
export const SUPPORTED_LANGS = ['en', 'zh'] as const;
export type Lang = typeof SUPPORTED_LANGS[number];
export const ALLOWED_BLACKLIST_FLAGS = ['nsfw', 'religious', 'political', 'racist', 'sexist', 'explicit'] as const;
export type BlacklistFlag = typeof ALLOWED_BLACKLIST_FLAGS[number];
export interface JokePreferences {
category: Category;
lang: Lang;
blacklist: BlacklistFlag[];
}
export interface JokeInputDefaults {
defaultCategory: Category;
lang: Lang;
}
export function normalizeCategory(category: unknown, fallback: Category = 'programming'): Category {
if (!category) return fallback;
const normalized = toKey(category);
if (!isCategory(normalized)) {
throw new Error(`Unsupported category: ${String(category)}`);
}
return normalized;
}
export function normalizeLang(lang: unknown, fallback: Lang = 'en'): Lang {
if (!lang) return fallback;
const normalized = toKey(lang);
if (!isLang(normalized)) {
throw new Error(`Unsupported language: ${String(lang)}`);
}
return normalized;
}
export function sanitizeBlacklist(value: unknown): BlacklistFlag[] {
if (!value) return [];
const list = Array.isArray(value) ? value : [value];
const seen = new Set<BlacklistFlag>();
const filtered: BlacklistFlag[] = [];
for (const entry of list) {
if (typeof entry !== 'string') continue;
const key = toKey(entry);
if (!isBlacklistFlag(key)) continue;
if (seen.has(key)) continue;
seen.add(key);
filtered.push(key);
}
return filtered;
}
export function validateGetJokeInput(input: unknown, defaults: JokeInputDefaults): JokePreferences {
if (input == null) {
return {
category: normalizeCategory(undefined, defaults.defaultCategory),
lang: normalizeLang(undefined, defaults.lang),
blacklist: [],
};
}
if (typeof input !== 'object' || Array.isArray(input)) {
throw new Error('getJoke input must be an object');
}
const record = input as Record<string, unknown>;
const category = normalizeCategory(record.category, defaults.defaultCategory);
const lang = normalizeLang(record.lang, defaults.lang);
const blacklist = sanitizeBlacklist(record.blacklist);
return { category, lang, blacklist };
}
function toKey(value: unknown): string {
return String(value).trim().toLowerCase();
}
function isCategory(value: string): value is Category {
return (SUPPORTED_CATEGORIES as readonly string[]).includes(value);
}
function isLang(value: string): value is Lang {
return (SUPPORTED_LANGS as readonly string[]).includes(value);
}
function isBlacklistFlag(value: string): value is BlacklistFlag {
return (ALLOWED_BLACKLIST_FLAGS as readonly string[]).includes(value);
}