{"record":{"id":"7fc39b4424b96fe1","repo":"jamiepine/voicebox","slug":"str-e-7fc39b","errorCode":null,"errorMessage":"{str(e)}","messagePattern":"\\{str\\(e\\)\\}","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"backend/routes/stories.py","lineNumber":32,"sourceCode":"router = APIRouter()\n\n\n@router.get(\"/stories\", response_model=list[models.StoryResponse])\nasync def list_stories(db: Session = Depends(get_db)):\n    \"\"\"List all stories.\"\"\"\n    return await stories.list_stories(db)\n\n\n@router.post(\"/stories\", response_model=models.StoryResponse)\nasync def create_story(\n    data: models.StoryCreate,\n    db: Session = Depends(get_db),\n):\n    \"\"\"Create a new story.\"\"\"\n    try:\n        return await stories.create_story(data, db)\n    except Exception as e:\n        raise HTTPException(status_code=400, detail=str(e))\n\n\n@router.get(\"/stories/{story_id}\", response_model=models.StoryDetailResponse)\nasync def get_story(\n    story_id: str,\n    db: Session = Depends(get_db),\n):\n    \"\"\"Get a story with all its items.\"\"\"\n    story = await stories.get_story(story_id, db)\n    if not story:\n        raise HTTPException(status_code=404, detail=\"Story not found\")\n    return story\n\n\n@router.put(\"/stories/{story_id}\", response_model=models.StoryResponse)\nasync def update_story(\n    story_id: str,\n    data: models.StoryCreate,","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/routes/stories.py#L14-L50","documentation":"Returned by POST /stories when stories.create_story raises any Exception (HTTP 400). The handler catches a broad Exception and forwards str(e) as detail, so the message is whatever the service threw. Because the catch is non-specific, the detail could range from validation to DB constraint errors.","triggerScenarios":"Submitting a StoryCreate body whose fields violate a constraint inside create_story (e.g., invalid title length, duplicate slug, malformed metadata). Also any unexpected service-layer exception surfaces here as 400.","commonSituations":"Missing required StoryCreate fields that pass Pydantic but fail DB constraints. Duplicate unique key (slug/title). Unserializable metadata payload. The broad catch can mask server-side bugs as 400.","solutions":["Read detail — it is str(e) from the service and usually names the failing constraint.","Validate the StoryCreate payload (title, required fields, metadata serializability) before posting.","If detail looks like an internal traceback rather than a user error, suspect a server bug and check backend logs.","Avoid collisions with existing story slugs/titles if uniqueness is enforced."],"exampleFix":"// before\nawait api.post('/stories', story);\n\n// after\nensureStoryCreate(story); // checks title non-empty, metadata JSON-serializable\nawait api.post('/stories', story);","handlingStrategy":"validation","validationCode":"function validateStoryCreate(s) {\n  if (!s || typeof s.title !== 'string' || !s.title.trim()) throw new Error('title required');\n  if (s.metadata != null) JSON.stringify(s.metadata);\n}","typeGuard":"function isStoryCreate(v) { return !!v && typeof v.title === 'string' && v.title.trim().length > 0; }","tryCatchPattern":"try { await api.post('/stories', story); }\ncatch (e) { if (e.response?.status === 400) showUser(e.response.data.detail); throw e; }","preventionTips":["Validate title and required fields before posting.","Ensure metadata is JSON-serializable and within size limits.","Avoid duplicate slugs/titles if uniqueness is enforced.","If detail looks like a traceback, suspect a server bug, not user error."],"tags":["fastapi","http","validation","stories","rest","broad-catch"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}