{"record":{"id":"9bdf28ed67ebc98b","repo":"DIYgod/RSSHub","slug":"invalid-period-period","errorCode":null,"errorMessage":"Invalid period: ${period}","messagePattern":"Invalid period: (.+?)","errorType":"validation","errorClass":"InvalidParameterError","httpStatus":503,"severity":"warning","filePath":"lib/routes/syosetu/ranking-isekai.ts","lineNumber":72,"sourceCode":"    return searchParams;\n}\n\nexport async function handleIsekaiRanking(type: string, limit: number): Promise<Data> {\n    const { period, category, novelType } = parseIsekaiRankingType(type);\n    const rankingUrl = `https://yomou.syosetu.com/rank/isekailist/type/${type}`;\n    const rankingTitle = `[${periodToJapanese[period]}] 異世界転生/転移${isekaiCategoryToJapanese[category]}ランキング - ${novelTypeToJapanese[novelType]} BEST${limit}`;\n\n    const searchParams = getIsekaiSearchParams(period, category, novelType, limit);\n    const api = new NarouNovelFetch();\n\n    const [tenseiResult, tenniResult] = await Promise.all([new SearchBuilder({ ...searchParams, istensei: 1 }, api).execute(), new SearchBuilder({ ...searchParams, istenni: 1 }, api).execute()]);\n\n    const combinedNovels = [...tenseiResult.values, ...tenniResult.values];\n    const uniqueNovels = new Map(combinedNovels.map((novel) => [novel.ncode, novel])).values().toArray();\n\n    const pointField = periodToPointField[period];\n    if (!pointField) {\n        throw new InvalidParameterError(`Invalid period: ${period}`);\n    }\n\n    const items = uniqueNovels\n        .toSorted((a, b) => (b[pointField] || 0) - (a[pointField] || 0))\n        .map((novel, index) => ({\n            title: `#${index + 1} ${novel.title}`,\n            link: `https://ncode.syosetu.com/${String(novel.ncode).toLowerCase()}`,\n            description: renderDescription({ novel }),\n            author: novel.writer,\n            category: novel.keyword.split(/[\\s/\\u{FF0F}]/u).filter(Boolean),\n        }));\n\n    return {\n        title: `小説家になろう - ${rankingTitle}`,\n        link: rankingUrl,\n        item: items.slice(0, limit) as DataItem[],\n        language: 'ja',\n    };","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/syosetu/ranking-isekai.ts#L54-L90","documentation":"Thrown by handleIsekaiRanking (syosetu/ranking-isekai.ts:72) as an InvalidParameterError when periodToPointField[period] is falsy. periodToPointField (types/ranking.ts:32) has entries for ALL six RankingPeriod values, so in the normal flow this is UNREACHABLE — parseIsekaiRankingType already validated period. It is a defensive guard against periodToPointField and the RankingPeriod enum drifting out of sync (a new period added to the enum but not to the lookup map).","triggerScenarios":"A maintainer adds a new RankingPeriod member without adding it to periodToPointField (while remembering periodToOrder/periodToJapanese); direct call to handleIsekaiRanking with an unvalidated type.","commonSituations":"Enum/map sync drift after extending RankingPeriod; refactor introduces a bypass of parseIsekaiRankingType.","solutions":["When adding a RankingPeriod, update ALL four maps in types/ranking.ts: periodToOrder, periodToPointField, periodToJapanese, and any downstream usage.","Do not bypass parseIsekaiRankingType when invoking handleIsekaiRanking."],"exampleFix":"// before: new period 'alltime' added to RankingPeriod but not periodToPointField\n// after: add the mapping\nexport const periodToPointField = {\n    ...\n    [RankingPeriod.ALLTIME]: 'alltime_point',\n} as const;","handlingStrategy":"validation","validationCode":"import { RankingPeriod, periodToPointField } from './types/ranking';\n\nfunction hasPointField(period: string): boolean {\n  return Boolean((periodToPointField as Record<string, unknown>)[period]);\n}","typeGuard":"const PERIOD_POINT_FIELDS = ['pt','weekly_point','monthly_point','quarter_point','yearly_point','global_point'] as const;\n\nfunction isRankedPeriod(period: string): boolean {\n  return (PERIOD_POINT_FIELDS as readonly string[]).length > 0\n    && ['daily','weekly','monthly','quarter','yearly','total'].includes(period);\n}","tryCatchPattern":null,"preventionTips":["When adding a RankingPeriod member, update periodToOrder, periodToPointField, and periodToJapanese together.","Prefer a single Record<RankingPeriod, ...> over separate maps to keep them in sync.","Do not bypass parseIsekaiRankingType when calling handleIsekaiRanking."],"tags":["unreachable","defensive-guard","enum-sync","syosetu"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}