{"record":{"id":"dd23c2b82b24855e","repo":"ZhuLinsen/daily_stock_analysis","slug":"invalid-stock-code","errorCode":"invalid_stock_code","errorMessage":"股票代码不能为空","messagePattern":"股票代码不能为空","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"api/v1/endpoints/stocks.py","lineNumber":98,"sourceCode":"    r\"|(?:SH|SZ|BJ)\\d{6}\"                     # exchange-prefixed A-share\n    r\"|\\d{6}\\.(?:SH|SZ|SS|BJ)\"                # exchange-suffixed A-share\n    r\"|\\d{1,5}\\.HK\"                           # HK suffix format\n    r\"|HK\\d{1,5}\"                             # HK prefix format\n    r\"|\\d{5}\"                                 # bare 5-digit HK code\n    r\"|[A-Z]{1,5}(?:\\.(?:US|[A-Z]))?\"         # US ticker\n    r\")$\",\n    re.IGNORECASE,\n)\n\n\ndef _validate_and_normalize_stock_code(code: str) -> str:\n    \"\"\"Validate stock code format and return canonical form.\n\n    Raises HTTPException(400) if the code does not match supported formats.\n    \"\"\"\n    stripped = code.strip()\n    if not stripped:\n        raise HTTPException(\n            status_code=400,\n            detail={\"error\": \"invalid_stock_code\", \"message\": \"股票代码不能为空\"},\n        )\n    if not _STOCK_CODE_RE.match(stripped):\n        raise HTTPException(\n            status_code=400,\n            detail={\n                \"error\": \"invalid_stock_code\",\n                \"message\": f\"'{stripped}' 不是合法的股票代码格式\",\n            },\n        )\n    return normalize_stock_code(stripped)\n\n\ndef _watchlist_match_key(code: str) -> str:\n    \"\"\"Return the equivalence key used for watchlist add/remove matching.\"\"\"\n    normalized = normalize_stock_code(code.strip())\n    if re.fullmatch(r\"\\d{5}\", normalized):","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/api/v1/endpoints/stocks.py#L80-L116","documentation":"Raised by _validate_and_normalize_stock_code in the watchlist add/remove endpoints when the submitted stock_code is empty or only whitespace after stripping. It is a client-side validation error returned as HTTP 400 with error code invalid_stock_code. The check runs before any regex matching or persistence, so it never touches backend state.","triggerScenarios":"POST /api/v1/stocks/watchlist/add or /watchlist/remove with body {\"stock_code\": \"\"} or {\"stock_code\": \"   \"}; a frontend form submitted without user input; an automation script passing an unset variable that resolves to an empty string.","commonSituations":"Web form submit button enabled with an empty input field; JS code sending request.stock_code before assigning it; whitespace-only input pasted from a spreadsheet; default value of a dropdown/select left blank.","solutions":["Trim the input client-side and disable the submit action when stock_code.strip() is empty.","Add a required-field check in the request schema (e.g. Pydantic min_length=1 with a strip validator) so FastAPI rejects it with 422 before the handler runs.","If scripting, assert the variable is non-empty before calling the endpoint."],"exampleFix":"// before\nconst code = codeInput.value;\nawait fetch(`/api/v1/stocks/watchlist/add`, {method:'POST', body: JSON.stringify({stock_code: code})});\n\n// after\nconst code = codeInput.value.trim();\nif (!code) { setFormError('请输入股票代码'); return; }\nawait fetch(`/api/v1/stocks/watchlist/add`, {method:'POST', body: JSON.stringify({stock_code: code})});","handlingStrategy":"validation","validationCode":"const code = String(rawInput || '').trim();\nif (!code) {\n  showFormError('股票代码不能为空');\n  return;\n}\n// safe to POST /watchlist/add","typeGuard":"function isNonEmptyStockCode(v: unknown): v is string {\n  return typeof v === 'string' && v.trim().length > 0;\n}","tryCatchPattern":null,"preventionTips":["Trim input before every watchlist API call.","Mark the stock_code field required in the UI and disable submit while empty.","Add a Pydantic min_length/strip validator on WatchlistRequest so the schema layer rejects empties."],"tags":["validation","fastapi","watchlist","http-400"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}