{"record":{"id":"9550fe1a74e06242","repo":"DIYgod/RSSHub","slug":"invalid-subsite-sub","errorCode":null,"errorMessage":"Invalid subsite: ${sub}","messagePattern":"Invalid subsite: (.+?)","errorType":"validation","errorClass":"InvalidParameterError","httpStatus":503,"severity":"error","filePath":"lib/routes/syosetu/ranking-r18.ts","lineNumber":167,"sourceCode":"    const rankingUrl = `${baseUrl}/rank/list/type/${type}`;\n    const api = new NarouNovelFetch();\n\n    const limit = Math.min(Number(ctx.req.query('limit') ?? 300), 300);\n    const { period, novelType } = parseRankingType(type);\n\n    const searchParams: SearchParams = {\n        gzip: 5,\n        lim: limit,\n        order: periodToOrder[period],\n    };\n\n    // TOTAL: Skip type filter to get all types combined\n    if (novelType !== NovelType.TOTAL) {\n        searchParams.type = novelType;\n    }\n\n    if (!Object.hasOwn(syosetuSubToNocgenre, sub)) {\n        throw new InvalidParameterError(`Invalid subsite: ${sub}`);\n    }\n    const nocgenre = syosetuSubToNocgenre[sub];\n\n    const builder = new SearchBuilderR18(searchParams, api).r18Site(nocgenre);\n    const result = await builder.execute();\n\n    const items = result.values.map((novel, index) => ({\n        title: `#${index + 1} ${novel.title}`,\n        link: `https://novel18.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: `小説家になろう (${sub}) - ${getRankingTitle(type, limit)}`,\n        link: rankingUrl,\n        item: items as DataItem[],","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/syosetu/ranking-r18.ts#L149-L185","documentation":"Thrown by syosetu/ranking-r18.ts:167 as an InvalidParameterError when the `sub` path param is not a key of syosetuSubToNocgenre (types/ranking-r18.ts:25). Valid R18 subsites: noc (Nocturne), mnlt (Moonlight), mnlt-bl (Moonlight BL), mid (Midnight). NOTE the footgun: mnlt-bl IS valid here because ranking-r18.ts's SyosetuSub enum includes MOONLIGHT_BL, but search.ts's SyosetuSub enum does NOT — the same-named enum has different members across files. The check runs AFTER parseRankingType, so an invalid `type` throws first.","triggerScenarios":"/syosetu/rankingr18/<sub>/<type> with sub not in {noc, mnlt, mnlt-bl, mid}: typos like 'mnltbl' (missing hyphen), 'nocturne', 'moonlight', or 'bl' alone.","commonSituations":"Using the full site name instead of the code; forgetting the hyphen in mnlt-bl; assuming the subsite code set is identical across all syosetu routes.","solutions":["Use noc, mnlt, mnlt-bl, or mid (lowercase, hyphenated exactly where shown).","Remember mnlt-bl is valid ONLY in the rankingr18 route — not in the search route."],"exampleFix":"// before\nGET /syosetu/rankingr18/mnltbl/daily_total\n// after\nGET /syosetu/rankingr18/mnlt-bl/daily_total","handlingStrategy":"validation","validationCode":"import { syosetuSubToNocgenre } from './types/ranking-r18';\n\nfunction isValidR18Sub(sub: string): boolean {\n  return Object.hasOwn(syosetuSubToNocgenre, sub);\n}","typeGuard":"import { SyosetuSub } from './types/ranking-r18';\n\nconst VALID_R18_SUBS = Object.values(SyosetuSub); // noc, mnlt, mnlt-bl, mid\n\nfunction isR18Sub(sub: string): sub is SyosetuSub {\n  return (VALID_R18_SUBS as readonly string[]).includes(sub);\n}","tryCatchPattern":null,"preventionTips":["Use noc, mnlt, mnlt-bl, or mid — exactly hyphenated where shown.","Remember mnlt-bl is valid ONLY in rankingr18, not in the search route.","Do not assume the subsite code set is identical across syosetu routes."],"tags":["invalid-parameter","enum","syosetu","r18","subsite","footgun"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}