made_core/value_objects/budget/
budget_limits.rs1use super::{
2 BudgetQuantities, BudgetTokenCount, CostMicros, CurrencyCode, ExecutionDuration, ToolCallCount,
3};
4use crate::DomainError;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8pub struct BudgetLimits {
9 duration: Option<ExecutionDuration>,
10 tokens: Option<BudgetTokenCount>,
11 cost: Option<CostMicros>,
12 tool_calls: Option<ToolCallCount>,
13 currency: Option<CurrencyCode>,
14}
15impl BudgetLimits {
16 pub fn from_optional(
19 duration: Option<ExecutionDuration>,
20 tokens: Option<BudgetTokenCount>,
21 cost: Option<CostMicros>,
22 tool_calls: Option<ToolCallCount>,
23 currency: Option<CurrencyCode>,
24 ) -> Result<Self, DomainError> {
25 for (field, amount) in [
26 (
27 "budget_limits.duration_micros",
28 duration.map(ExecutionDuration::as_micros),
29 ),
30 ("budget_limits.tokens", tokens.map(BudgetTokenCount::value)),
31 ("budget_limits.cost_micros", cost.map(CostMicros::value)),
32 (
33 "budget_limits.tool_calls",
34 tool_calls.map(ToolCallCount::value),
35 ),
36 ] {
37 if amount == Some(0) {
38 return Err(DomainError::MustBeNonZero { field });
39 }
40 }
41 Self::new(
42 BudgetQuantities::new(
43 duration.unwrap_or_else(|| ExecutionDuration::from_micros(0)),
44 tokens.unwrap_or_else(|| BudgetTokenCount::new(0)),
45 cost.unwrap_or_else(|| CostMicros::new(0)),
46 tool_calls.unwrap_or_else(|| ToolCallCount::new(0)),
47 ),
48 currency,
49 )
50 }
51
52 pub fn new(
53 maximum: BudgetQuantities,
54 currency: Option<CurrencyCode>,
55 ) -> Result<Self, DomainError> {
56 if maximum.is_zero() {
57 return Err(DomainError::EmptyCollection {
58 field: "budget_limits",
59 });
60 }
61 if (maximum.cost().value() > 0) != currency.is_some() {
62 return Err(DomainError::InvariantViolated {
63 reason: "a cost budget and its currency must be declared together",
64 });
65 }
66 Ok(Self {
67 duration: (maximum.duration().as_micros() > 0).then_some(maximum.duration()),
68 tokens: (maximum.tokens().value() > 0).then_some(maximum.tokens()),
69 cost: (maximum.cost().value() > 0).then_some(maximum.cost()),
70 tool_calls: (maximum.tool_calls().value() > 0).then_some(maximum.tool_calls()),
71 currency,
72 })
73 }
74 #[must_use]
75 pub const fn duration(&self) -> Option<ExecutionDuration> {
76 self.duration
77 }
78 #[must_use]
79 pub const fn tokens(&self) -> Option<BudgetTokenCount> {
80 self.tokens
81 }
82 #[must_use]
83 pub const fn cost(&self) -> Option<CostMicros> {
84 self.cost
85 }
86 #[must_use]
87 pub const fn tool_calls(&self) -> Option<ToolCallCount> {
88 self.tool_calls
89 }
90 #[must_use]
91 pub const fn currency(&self) -> Option<&CurrencyCode> {
92 self.currency.as_ref()
93 }
94}