{"record":{"id":"53d4bf2f78242a9d","repo":"ZhuLinsen/daily_stock_analysis","slug":"validation-error-53d4bf","errorCode":"validation_error","errorMessage":"No fields provided for update","messagePattern":"No fields provided for update","errorType":"http","errorClass":"AlertServiceError","httpStatus":400,"severity":"warning","filePath":"src/services/alert_service.py","lineNumber":125,"sourceCode":"        self.db = db_manager or DatabaseManager.get_instance()\n        self.repo = AlertRepository(self.db)\n\n    def create_rule(self, payload: Dict[str, Any]) -> Dict[str, Any]:\n        fields = self._normalize_rule_payload(payload)\n        return self._serialize_rule(self.repo.create_rule(fields))\n\n    def get_rule(self, rule_id: int) -> Dict[str, Any]:\n        row = self.repo.get_rule(rule_id)\n        if row is None:\n            raise AlertNotFoundError(f\"Alert rule not found: {rule_id}\")\n        return self._serialize_rule(row)\n\n    def update_rule(self, rule_id: int, payload: Dict[str, Any]) -> Dict[str, Any]:\n        row = self.repo.get_rule(rule_id)\n        if row is None:\n            raise AlertNotFoundError(f\"Alert rule not found: {rule_id}\")\n        if not payload:\n            raise AlertServiceError(\"No fields provided for update\")\n        self._validate_rule_update_payload(payload)\n\n        merged = self._serialize_rule_base(row)\n        merged.update(payload)\n        fields = self._normalize_rule_payload(merged, source=merged.get(\"source\") or \"api\")\n        updated = self.repo.update_rule(rule_id, fields)\n        if updated is None:\n            raise AlertNotFoundError(f\"Alert rule not found: {rule_id}\")\n        return self._serialize_rule(updated)\n\n    def delete_rule(self, rule_id: int) -> bool:\n        return self.repo.delete_rule(rule_id)\n\n    def enable_rule(self, rule_id: int, enabled: bool) -> Dict[str, Any]:\n        updated = self.repo.update_rule(rule_id, {\"enabled\": enabled})\n        if updated is None:\n            raise AlertNotFoundError(f\"Alert rule not found: {rule_id}\")\n        return self._serialize_rule(updated)","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/services/alert_service.py#L107-L143","documentation":"AlertServiceError (code 'validation_error') raised in AlertService.update_rule (src/services/alert_service.py:125) when the update payload dict is empty ({}). The service refuses no-op updates rather than returning the row unchanged, forcing callers to express at least one field. Checked after the existence pre-check but before _validate_rule_update_payload.","triggerScenarios":"PUT with an empty JSON body {}; a diff-based client that computes changed fields and submits none when nothing changed; a PATCH builder that aggregates zero fields because all values matched the current rule.","commonSituations":"'Save' buttons that always fire a request even with no edits; client-side diffing that skips sending unchanged fields and ends up sending nothing; test helpers posting empty dicts.","solutions":["Skip the API call entirely when the diff is empty (client-side check: if not changed: return).","Or include at least one explicit field, e.g. {'enabled': current_enabled} if a touch is really intended.","Treat the error as benign: catch AlertServiceError with this message and show 'no changes'."],"exampleFix":"# before\nservice.update_rule(rule_id, {k: v for k, v in form.items() if v != original[k]})  # {} when nothing changed\n\n# after\nchanges = {k: v for k, v in form.items() if v != original[k]}\nif changes:\n    service.update_rule(rule_id, changes)\nelse:\n    logger.info('no changes for rule %s', rule_id)","handlingStrategy":"validation","validationCode":"changes = {k: v for k, v in payload.items() if k in EDITABLE_FIELDS and v != original.get(k)}\nif not changes:\n    logger.info('no changes for rule %s; skipping update', rule_id)\n    return original","typeGuard":"def has_updatable_fields(payload: dict, editable: set) -> bool:\n    return any(k in editable for k in payload)","tryCatchPattern":"try:\n    service.update_rule(rule_id, payload)\nexcept AlertServiceError as e:\n    if 'No fields provided' in str(e):\n        return ok('nothing to update')\n    raise","preventionTips":["Diff against the current rule client-side and skip empty updates.","Disable save buttons until a real change exists.","Treat this error as informational (HTTP 400 with a friendly message), not a crash."],"tags":["validation","alerts","empty-payload","update"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}