{"record":{"id":"17346e3694b4f1fa","repo":"ZhuLinsen/daily_stock_analysis","slug":"invalid-request","errorCode":"invalid_request","errorMessage":"stock_code 不能为空","messagePattern":"stock_code 不能为空","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"api/v1/endpoints/history.py","lineNumber":318,"sourceCode":"    \"/by-code/{stock_code}\",\n    response_model=DeleteHistoryResponse,\n    responses={\n        200: {\"description\": \"删除成功\"},\n        400: {\"description\": \"股票代码不能为空\", \"model\": ErrorResponse},\n        404: {\"description\": \"未找到记录\", \"model\": ErrorResponse},\n        500: {\"description\": \"服务器错误\", \"model\": ErrorResponse},\n    },\n    summary=\"按股票代码删除历史分析记录\",\n    description=\"删除指定股票代码的所有分析历史记录（支持代码变体归一化匹配）\",\n)\ndef delete_history_by_code(\n    stock_code: str,\n    db_manager: DatabaseManager = Depends(get_database_manager),\n) -> DeleteHistoryResponse:\n    try:\n        candidates = HistoryService._history_code_filter_candidates(stock_code)\n        if not candidates:\n            raise HTTPException(\n                status_code=400,\n                detail={\"error\": \"invalid_request\", \"message\": \"stock_code 不能为空\"},\n            )\n\n        deleted = 0\n        while True:\n            records, _ = db_manager.get_analysis_history_paginated(\n                code=candidates,\n                limit=_DELETE_BY_CODE_BATCH_SIZE,\n            )\n            record_ids = [r.id for r in records if r.id is not None]\n            if not record_ids:\n                break\n\n            batch_deleted = db_manager.delete_analysis_history_records(record_ids)\n            if batch_deleted == 0:\n                raise RuntimeError(\"history deletion made no progress\")\n            deleted += batch_deleted","sourceCodeStart":300,"sourceCodeEnd":336,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/api/v1/endpoints/history.py#L300-L336","documentation":"This is a 400 invalid_request raised by DELETE /api/v1/history/by-code/{stock_code} when the stock_code path parameter normalizes to nothing. The endpoint calls HistoryService._history_code_filter_candidates(stock_code), which strips whitespace and returns [] only when the input is empty or whitespace-only (src/services/history_service.py:99-102). Any non-blank string, even a malformed code, produces at least one candidate, so this error fires almost exclusively on blank input.","triggerScenarios":"Calling DELETE /history/by-code/ (empty path segment), /history/by-code/%20, or /history/by-code/%00-style whitespace-only values. A URL-encoded space or a client that builds the path from an unset/None stock variable (rendered as empty string) also triggers it.","commonSituations":"Frontend builds the delete URL from a state field that has not been initialized yet (empty string template interpolation); automated scripts iterating a list where some entries are empty strings; trailing-slash routing that maps to an empty path parameter","solutions":["Ensure the client has a non-empty stock code before issuing the request (guard with if (!stockCode?.trim()) ...).","Verify the exact route: the code must be supplied as the stock_code path segment, e.g. DELETE /api/v1/history/by-code/600519.","If the code comes from user input, trim it and reject blank values in the UI/script before calling the API.","If you still get 400 with a visible code, confirm no proxy is stripping the path segment."],"exampleFix":"// before\nconst res = await fetch(`/api/v1/history/by-code/${code}`, { method: 'DELETE' });\n\n// after\nconst trimmed = (code ?? '').trim();\nif (!trimmed) throw new Error('stock_code is required');\nconst res = await fetch(`/api/v1/history/by-code/${encodeURIComponent(trimmed)}`, { method: 'DELETE' });","handlingStrategy":"validation","validationCode":"const code = String(stockCode ?? '').trim();\nif (!code) {\n  throw new Error('stock_code must be a non-empty stock code');\n}\nawait fetch(`/api/v1/history/by-code/${encodeURIComponent(code)}`, { method: 'DELETE' });","typeGuard":"const isNonEmptyStockCode = (v: unknown): v is string =>\n  typeof v === 'string' && v.trim().length > 0;","tryCatchPattern":"try { const res = await delByCode(code); if (res.status === 400) handleInvalidRequest(); } catch (e) { /* network only */ }","preventionTips":["Trim and require non-empty stock codes before building the delete URL.","URL-encode path parameters to avoid whitespace/encoding edge cases.","Never template a URL from possibly-undefined state; default to disabled submit buttons."],"tags":["http-400","validation","history-api","stock-code"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}