{"record":{"id":"06e9ee437365003a","repo":"Hmbown/CodeWhale","slug":"burn-rate-is-optional-when-set-it-must-be-a-positive-hr","errorCode":null,"errorMessage":"Burn rate is optional. When set, it must be a positive $/hr.","messagePattern":"Burn rate is optional\\. When set, it must be a positive \\$/hr\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/operate.rs","lineNumber":255,"sourceCode":"    {\n        return Ok(None);\n    }\n    if let Some(\"unbounded\") = value.get(\"kind\").and_then(|kind| kind.as_str()) {\n        return Ok(None);\n    }\n    let amount = if value.is_number() || value.is_string() {\n        json_number(value)\n    } else {\n        json_number(\n            value\n                .get(\"amountUsdPerHour\")\n                .or_else(|| value.get(\"usdPerHour\"))\n                .or_else(|| value.get(\"amount\"))\n                .unwrap_or(&serde_json::Value::Null),\n        )\n    };\n    if amount.is_none() {\n        anyhow::bail!(\"Burn rate is optional. When set, it must be a positive $/hr.\");\n    }\n    parse_burn_amount(amount)\n}\n\nfn json_number(value: &serde_json::Value) -> Option<f64> {\n    value\n        .as_f64()\n        .or_else(|| value.as_i64().map(|n| n as f64))\n        .or_else(|| value.as_str().and_then(|s| s.parse().ok()))\n}\n\nfn parse_burn_amount(amount: Option<f64>) -> Result<Option<OperateBurnRate>> {\n    let Some(amount) = amount else {\n        return Ok(None);\n    };\n    if !amount.is_finite() || amount <= 0.0 {\n        anyhow::bail!(\"Burn rate is optional. When set, it must be a positive $/hr.\");\n    }","sourceCodeStart":237,"sourceCodeEnd":273,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/operate.rs#L237-L273","documentation":"`parse_burn_rate` parses a burn-rate value out of an operate patch JSON. Burn rate itself is optional, but if the incoming JSON provides none of the recognized keys (`usdPerHour`, `amount`, or the primary key), the value is treated as `None` and rejected: providing the field in a way that resolves to nothing is invalid. The message states the contract — optional, but positive $/hr when present.","triggerScenarios":"Calling `parse_burn_rate` (via `apply_operate_patch` or tests) with a JSON object that has a burn-rate field present but with no recognizable amount key, so `amount` is `None` — e.g. `\"burnRate\": {}` or `\"burnRate\": null` explicitly supplied.","commonSituations":"Hand-writing an operate patch with an empty or null burn-rate object; a UI sending `usdPerHour` under a wrong key name the parser doesn't know; API version drift where the payload key changed.","solutions":["Either omit the burn-rate field entirely (it is optional) or supply a positive numeric value under a recognized key (`usdPerHour` / `amount`).","Fix the payload key name so the number lands under `usdPerHour` or `amount`.","If you intended zero, remove the field — zero is not accepted either."],"exampleFix":"// before\n{\"burnRate\": {}}\n// after\n{\"burnRate\": {\"usdPerHour\": 5.0}}\n// or remove the field entirely","handlingStrategy":"validation","validationCode":"fn burn_rate_payload_ok(v: &serde_json::Value) -> bool {\n    let obj = match v.get(\"burnRate\") { Some(o) => o, None => return true }; // optional\n    obj.get(\"usdPerHour\").or_else(|| obj.get(\"amount\"))\n      .map(|n| n.as_f64().map_or(false, |x| x.is_finite() && x > 0.0))\n      .unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"match parse_burn_rate(&patch) {\n    Err(e) if e.to_string().contains(\"Burn rate is optional\") => {\n        // drop or correct the burnRate field and resubmit\n    }\n    other => other,\n}","preventionTips":["Omit burnRate entirely when unknown; never send an empty object or null.","Always put the number under `usdPerHour` or `amount`.","Validate patch JSON shape before applying."],"tags":["validation","json","burn-rate"],"backgroundTag":"invalid-config-value","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T06:17:15.046Z"}