use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RoleCostReservationRequest {
pub reservation_id: Uuid,
pub role: String,
pub code_cap_usd: Option<Decimal>,
pub period: String,
pub period_start: DateTime<Utc>,
pub budget_usd: Option<Decimal>,
pub ttl_secs: u32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "outcome", rename_all = "snake_case")]
pub enum RoleCostAdmission {
Admitted {
reservation_id: Uuid,
amount_usd: Decimal,
cap_usd: Option<Decimal>,
spent_usd: Decimal,
reserved_usd: Decimal,
},
Refused {
cap_usd: Decimal,
spent_usd: Decimal,
reserved_usd: Decimal,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RoleCostSettlement {
pub reservation_id: Uuid,
pub period: String,
pub period_start: DateTime<Utc>,
pub cost_usd: Decimal,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AgentRoleRecord {
pub id: Uuid,
pub name: String,
pub code_cap_usd: Option<Decimal>,
pub operator_cap_usd: Option<Decimal>,
pub period: String,
pub period_start: DateTime<Utc>,
pub spent_usd: Decimal,
pub reserved_usd: Decimal,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
impl AgentRoleRecord {
pub fn effective_cap_usd(&self) -> Option<Decimal> {
effective_cap(self.code_cap_usd, self.operator_cap_usd)
}
}
pub fn effective_cap(code: Option<Decimal>, operator: Option<Decimal>) -> Option<Decimal> {
match (code, operator) {
(Some(code), Some(operator)) => Some(code.min(operator)),
(code, operator) => code.or(operator),
}
}
#[cfg(test)]
mod tests {
use serde_json::{from_value, json, to_value};
use super::*;
#[test]
fn operator_cap_only_lowers_the_code_cap() {
let ten = Some(Decimal::TEN);
let twenty = Some(Decimal::from(20));
assert_eq!(effective_cap(ten, twenty), ten);
assert_eq!(effective_cap(twenty, ten), ten);
assert_eq!(effective_cap(ten, Some(Decimal::ZERO)), Some(Decimal::ZERO));
}
#[test]
fn a_single_cap_applies_alone() {
assert_eq!(effective_cap(Some(Decimal::ONE), None), Some(Decimal::ONE));
assert_eq!(effective_cap(None, Some(Decimal::TWO)), Some(Decimal::TWO));
assert_eq!(effective_cap(None, None), None);
}
#[test]
fn admission_is_tagged_by_outcome() {
let refused = RoleCostAdmission::Refused {
cap_usd: Decimal::ONE,
spent_usd: Decimal::ONE,
reserved_usd: Decimal::ZERO,
};
let value = to_value(&refused).unwrap();
assert_eq!(value["outcome"], json!("refused"));
assert_eq!(from_value::<RoleCostAdmission>(value).unwrap(), refused);
}
}