Parse the return value of a JS BudgetGuard callback into a [`BudgetDecision`](a3s_code_core::budget::BudgetDecision). Accepted JS shapes mirror Python's: - `null` / `undefined` / `{ decision: 'allow' }` → Allow - `{ decision: 'soft', resource, consumed, limit, message? }` → SoftLimit - `{ decision: 'deny', resourc
(
val: napi::JsUnknown,
)
| 5244 | /// - `{ decision: 'soft', resource, consumed, limit, message? }` → SoftLimit |
| 5245 | /// - `{ decision: 'deny', resource, reason }` → Deny |
| 5246 | fn parse_js_budget_decision( |
| 5247 | val: napi::JsUnknown, |
| 5248 | ) -> napi::Result<a3s_code_core::budget::BudgetDecision> { |
| 5249 | use a3s_code_core::budget::BudgetDecision; |
| 5250 | use napi::{JsObject, ValueType}; |
| 5251 | |
| 5252 | match val.get_type()? { |
| 5253 | ValueType::Null | ValueType::Undefined => Ok(BudgetDecision::Allow), |
| 5254 | ValueType::Object => { |
| 5255 | let obj = unsafe { val.cast::<JsObject>() }; |
| 5256 | let decision: String = obj |
| 5257 | .get_named_property::<napi::JsString>("decision") |
| 5258 | .ok() |
| 5259 | .and_then(|s| s.into_utf8().ok()) |
| 5260 | .and_then(|s| s.into_owned().ok()) |
| 5261 | .unwrap_or_else(|| "allow".to_string()); |
| 5262 | match decision.as_str() { |
| 5263 | "deny" => { |
| 5264 | let resource = obj |
| 5265 | .get_named_property::<napi::JsString>("resource") |
| 5266 | .ok() |
| 5267 | .and_then(|s| s.into_utf8().ok()) |
| 5268 | .and_then(|s| s.into_owned().ok()) |
| 5269 | .unwrap_or_else(|| "unspecified".to_string()); |
| 5270 | let reason = obj |
| 5271 | .get_named_property::<napi::JsString>("reason") |
| 5272 | .ok() |
| 5273 | .and_then(|s| s.into_utf8().ok()) |
| 5274 | .and_then(|s| s.into_owned().ok()) |
| 5275 | .unwrap_or_else(|| "denied by host".to_string()); |
| 5276 | Ok(BudgetDecision::Deny { resource, reason }) |
| 5277 | } |
| 5278 | "soft" => { |
| 5279 | let resource = obj |
| 5280 | .get_named_property::<napi::JsString>("resource") |
| 5281 | .ok() |
| 5282 | .and_then(|s| s.into_utf8().ok()) |
| 5283 | .and_then(|s| s.into_owned().ok()) |
| 5284 | .unwrap_or_else(|| "unspecified".to_string()); |
| 5285 | let consumed = obj |
| 5286 | .get_named_property::<napi::JsNumber>("consumed") |
| 5287 | .ok() |
| 5288 | .and_then(|n| n.get_double().ok()) |
| 5289 | .unwrap_or(0.0); |
| 5290 | let limit = obj |
| 5291 | .get_named_property::<napi::JsNumber>("limit") |
| 5292 | .ok() |
| 5293 | .and_then(|n| n.get_double().ok()) |
| 5294 | .unwrap_or(0.0); |
| 5295 | let message = obj |
| 5296 | .get_named_property::<napi::JsString>("message") |
| 5297 | .ok() |
| 5298 | .and_then(|s| s.into_utf8().ok()) |
| 5299 | .and_then(|s| s.into_owned().ok()); |
| 5300 | Ok(BudgetDecision::SoftLimit { |
| 5301 | resource, |
| 5302 | consumed, |
| 5303 | limit, |