{"record":{"id":"f55de431554f8de7","repo":"DIYgod/RSSHub","slug":"invalid-genre-ranking-type-type","errorCode":null,"errorMessage":"Invalid genre ranking type: ${type}","messagePattern":"Invalid genre ranking type: (.+?)","errorType":"validation","errorClass":"InvalidParameterError","httpStatus":503,"severity":"error","filePath":"lib/routes/syosetu/ranking.ts","lineNumber":208,"sourceCode":"\n    if (!isValid) {\n        throw new InvalidParameterError(`Invalid general ranking type: ${type}`);\n    }\n\n    return { period, novelType };\n}\n\nfunction parseGenreRankingType(type: string): { period: RankingPeriod; genre: number; novelType: NovelType } {\n    const [periodStr, genreStr, novelTypeStr = NovelType.TOTAL] = type.split('_', 3);\n\n    const period = periodStr as RankingPeriod;\n    const genre = Number(genreStr) as Genre;\n    const novelType = novelTypeStr as NovelType;\n\n    const isValid = [Object.values(RankingPeriod).includes(period), Object.values(Genre).includes(genre), Object.values(NovelType).includes(novelType), genre !== Genre.SonotaReplay, genre !== Genre.NonGenre].every(Boolean);\n\n    if (!isValid) {\n        throw new InvalidParameterError(`Invalid genre ranking type: ${type}`);\n    }\n\n    return { period, genre, novelType };\n}\n\nasync function handler(ctx: Context): Promise<Data> {\n    const { listType, type } = ctx.req.param();\n    const rankingType = listType as RankingType;\n    const limit = Math.min(Number(ctx.req.query('limit') ?? 300), 300);\n\n    const api = new NarouNovelFetch();\n    const searchParams: SearchParams = {\n        gzip: 5,\n        lim: limit,\n    };\n\n    let rankingUrl: string;\n    let rankingTitle: string;","sourceCodeStart":190,"sourceCodeEnd":226,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/syosetu/ranking.ts#L190-L226","documentation":"Thrown by parseGenreRankingType (syosetu/ranking.ts:208) as an InvalidParameterError when the genre-ranking `type` (format period_genre_novelType) is malformed OR the genre is one of the two explicitly-excluded values. Genre is a NUMERIC enum from the narou package. The validation rejects: invalid period, genre not in Genre enum, invalid novelType, genre === Genre.SonotaReplay, and genre === Genre.NonGenre (the latter two have no rankings on syosetu). novelType defaults to 'total' if omitted. Reached via /syosetu/ranking/genre/<type>.","triggerScenarios":"Non-numeric genre; genre number outside the Genre enum; using SonotaReplay or NonGenre; unknown period or novelType; wrong segment order.","commonSituations":"Hand-typing a genre number; trying to rank the NonGenre or その他リプレイ categories which syosetu itself does not rank; confusing the genre number with the biggenre number.","solutions":["Pick a valid numeric Genre from the route's parameters.type.options (which already filters out the two excluded genres).","Use the format <period>_<genre>_<novelType>, where novelType is optional (defaults to total)."],"exampleFix":"// before (SonotaReplay / NonGenre are excluded)\nGET /syosetu/ranking/genre/daily_<NonGenre>_total\n// after (use a ranked genre)\nGET /syosetu/ranking/genre/daily_<validGenre>_total","handlingStrategy":"validation","validationCode":"import { Genre } from 'narou';\nimport { RankingPeriod, NovelType } from './types/ranking';\n\nconst EXCLUDED = new Set<Genre>([Genre.SonotaReplay, Genre.NonGenre]);\n\nfunction isValidGenreType(type: string): boolean {\n  const [p, g, n = NovelType.TOTAL] = type.split('_', 3);\n  const genre = Number(g) as Genre;\n  return Object.values(RankingPeriod).includes(p as RankingPeriod)\n    && Object.values(Genre).includes(genre)\n    && !EXCLUDED.has(genre)\n    && Object.values(NovelType).includes(n as NovelType);\n}","typeGuard":"import { Genre } from 'narou';\n\nfunction isRankedGenre(genre: number): genre is Genre {\n  const numericGenres = Object.values(Genre).filter((v): v is number => typeof v === 'number');\n  return numericGenres.includes(genre)\n    && genre !== Genre.SonotaReplay\n    && genre !== Genre.NonGenre;\n}","tryCatchPattern":null,"preventionTips":["Pick the genre number from the route's parameters.type.options (already filters excluded genres).","Use format <period>_<genre>_<novelType>; novelType is optional (defaults to total).","Remember SonotaReplay and NonGenre are intentionally unsupported — do not try to force them."],"tags":["invalid-parameter","enum","genre","syosetu","ranking"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}