{"record":{"id":"98fc6de43aa77000","repo":"koala73/worldmonitor","slug":"invalid-airport-delay-route","errorCode":null,"errorMessage":"Invalid airport delay route","messagePattern":"Invalid airport delay route","errorType":"http","errorClass":"ApiError","httpStatus":400,"severity":"error","filePath":"server/worldmonitor/aviation/v1/list-airport-delays.ts","lineNumber":47,"sourceCode":"const FAA_CACHE_KEY = 'aviation:delays:faa:v1';\nconst INTL_CACHE_KEY = 'aviation:delays:intl:v3';\n\nconst FAA_AIRPORT_SET = new Set(FAA_AIRPORTS);\nconst INTL_AIRPORT_SET = new Set(AVIATIONSTACK_AIRPORTS);\n\nconst ALLOWED_QUERY_PARAMS = new Set(['page_size', 'cursor', 'region', 'min_severity', 'jmespath', '_debug', 'rpc']);\n\nexport async function listAirportDelays(\n  ctx: ServerContext,\n  req: ListAirportDelaysRequest,\n): Promise<ListAirportDelaysResponse> {\n  const seenParams = new Set<string>();\n  for (const [key, value] of new URL(ctx.request.url).searchParams) {\n    if (!ALLOWED_QUERY_PARAMS.has(key)) throw new ApiError(400, `Unsupported airport delay parameter: ${key}`, '');\n    if (seenParams.has(key)) throw new ApiError(400, `Duplicate airport delay parameter: ${key}`, '');\n    seenParams.add(key);\n    if (key === 'page_size' && value !== '0') throw new ApiError(400, 'Airport delay page_size must be 0', '');\n    if (key === 'rpc' && value !== 'list-airport-delays') throw new ApiError(400, 'Invalid airport delay route', '');\n  }\n  if ((req.pageSize ?? 0) !== 0 || req.cursor\n    || (req.region && req.region !== 'AIRPORT_REGION_UNSPECIFIED')\n    || (req.minSeverity && req.minSeverity !== 'FLIGHT_DELAY_SEVERITY_UNSPECIFIED')) {\n    throw new ApiError(400, 'Airport delay filters are not supported', '');\n  }\n  // 1. FAA (US) — seed-only read\n  // faaSourceCovered = the seed cache hit AND returned a valid alerts array.\n  // A miss/parse-error means we have no telemetry for any FAA airport this\n  // tick — we MUST NOT publish synthetic \"normal\" rows for them. See #3707.\n  // PERF: the three inputs below are independent (different Redis keys / an\n  // independent fetcher) and merge only afterwards — start them concurrently\n  // instead of paying three serial round-trips per request.\n  const faaRead = (async (): Promise<{ faaAlerts: AirportDelayAlert[]; faaSourceCovered: boolean; available: boolean }> => {\n    let faaAlerts: AirportDelayAlert[] = [];\n    let faaSourceCovered = false;\n    try {\n      const seed = await readCachedJson(FAA_CACHE_KEY, true);","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/koala73/worldmonitor/blob/e586b8b4b80f595aa7ece295eec10d76f2921240/server/worldmonitor/aviation/v1/list-airport-delays.ts#L29-L65","documentation":"This is a generic request-validation sentinel error raised at the top of listAirportDelays when the request's query string contains a parameter not in ALLOWED_QUERY_PARAMS (page_size, cursor, region, min_severity, jmespath, _debug, rpc). The handler iterates URL search params and, on the first unrecognized key, throws 'Invalid airport delay route' to reject the call rather than silently ignoring unknown inputs. It fires when a client calls the airport-delays endpoint with a typo'd, deprecated, or unsupported query parameter (e.g. ?airport=KJFK or ?limit=10); the faulting input is the offending query parameter name in the request URL.","triggerScenarios":"GET .../list-airport-delays?rpc=some-other-rpc or a misspelled/mismatched rpc value like ?rpc=list-airport-delay.","commonSituations":"Copy-pasting a query string from another endpoint; stale hardcoded RPC names after a rename; proxy or gateway rewriting the rpc param.","solutions":["Send rpc=list-airport-delays exactly, matching case and spelling","Update hardcoded route names after any endpoint rename","Derive the rpc param from the same constant used for the path instead of a separate literal"],"exampleFix":"// before\nfetch(url + '?rpc=list-airport-delay');\n// after\nfetch(url + '?rpc=list-airport-delays');","handlingStrategy":"validation","validationCode":"if (params.has('rpc') && params.get('rpc') !== 'list-airport-delays') throw new Error('bad rpc param');","typeGuard":"const isAirportDelayRoute = (p) => p.get('rpc') === 'list-airport-delays';","tryCatchPattern":"try { await listAirportDelays(ctx, req); } catch (e) { if (e instanceof ApiError && e.status === 400 && e.message === 'Invalid airport delay route') { fixRpcParam(); } else throw e; }","preventionTips":["Share a single constant for the rpc value between path and query builder","Update all literals when endpoints are renamed","Add a test comparing rpc param against the route constant","Never hardcode rpc strings inline in call sites"],"tags":["http","routing","validation"],"backgroundTag":"invalid-query-parameter","analyzedSha":"e586b8b4b80f595aa7ece295eec10d76f2921240","analyzedAt":"2026-09-22T01:50:49.965Z","contentChangedAt":"2026-09-22T01:50:49.965Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}