liminal_server/services/
budgets.rs1use super::lys_http::{LysHttp, segment};
15use super::{BudgetPolicy, BudgetSource, ServiceEffectError};
16use std::sync::Arc;
17
18pub struct LysBudgets {
20 http: Arc<dyn LysHttp>,
21 bearer: String,
22}
23
24impl std::fmt::Debug for LysBudgets {
25 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 formatter
27 .debug_struct("LysBudgets")
28 .field("http", &self.http)
29 .finish_non_exhaustive()
30 }
31}
32
33impl LysBudgets {
34 #[must_use]
37 pub const fn new(http: Arc<dyn LysHttp>, bearer: String) -> Self {
38 Self { http, bearer }
39 }
40}
41
42fn budget_set(budgets: &serde_json::Value) -> Result<bool, ServiceEffectError> {
44 budgets
45 .get("version")
46 .and_then(serde_json::Value::as_u64)
47 .map(|version| version > 0)
48 .ok_or_else(|| {
49 ServiceEffectError(
50 "lys_could_not_be_asked: the budget answer carries no version".to_owned(),
51 )
52 })
53}
54
55fn stops_kept(stops: &serde_json::Value) -> Result<bool, ServiceEffectError> {
57 stops
58 .get("stops")
59 .and_then(serde_json::Value::as_array)
60 .map(|kept| !kept.is_empty())
61 .ok_or_else(|| {
62 ServiceEffectError(
63 "lys_could_not_be_asked: the stops answer carries no stops".to_owned(),
64 )
65 })
66}
67
68impl BudgetSource for LysBudgets {
69 fn read(&self, name: &str) -> Result<Option<BudgetPolicy>, ServiceEffectError> {
70 let id = segment(name);
71 let budget_route = format!("budgets/agent/{id}");
72 let budgets = self.http.get(&self.bearer, &budget_route)?;
73 if !budgets.succeeded() {
74 return Err(budgets.refused(&budget_route));
75 }
76 let stop_route = format!("agents/{id}/stops");
77 let stops = self.http.get(&self.bearer, &stop_route)?;
78 if !stops.succeeded() {
79 return Err(stops.refused(&stop_route));
80 }
81 if !budget_set(&budgets.body)? && !stops_kept(&stops.body)? {
82 return Ok(None);
83 }
84 Ok(Some(BudgetPolicy {
85 holder: format!("agent:{name}"),
86 budgets: budgets.body,
87 stops: stops.body,
88 }))
89 }
90}