use std::future::Future;
use crate::client::CyclesClient;
use crate::error::Error;
use crate::models::common::{Action, Amount, CyclesMetrics, Subject};
use crate::models::enums::CommitOveragePolicy;
use crate::models::request::{CommitRequest, ReservationCreateRequest};
#[derive(Debug, Clone)]
pub struct GuardContext {
pub decision: crate::models::Decision,
pub caps: Option<crate::models::Caps>,
pub reservation_id: crate::models::ReservationId,
pub affected_scopes: Vec<String>,
}
pub struct WithCyclesConfig {
estimate: Amount,
subject: Option<Subject>,
action_kind: String,
action_name: String,
action_tags: Option<Vec<String>>,
ttl_ms: u64,
grace_period_ms: Option<u64>,
overage_policy: Option<CommitOveragePolicy>,
metrics: Option<CyclesMetrics>,
}
impl WithCyclesConfig {
pub fn new(estimate: Amount) -> Self {
Self {
estimate,
subject: None,
action_kind: "unknown".into(),
action_name: "unknown".into(),
action_tags: None,
ttl_ms: 60_000,
grace_period_ms: None,
overage_policy: None,
metrics: None,
}
}
pub fn action(mut self, kind: impl Into<String>, name: impl Into<String>) -> Self {
self.action_kind = kind.into();
self.action_name = name.into();
self
}
pub fn subject(mut self, subject: Subject) -> Self {
self.subject = Some(subject);
self
}
pub fn ttl_ms(mut self, ttl_ms: u64) -> Self {
self.ttl_ms = ttl_ms;
self
}
pub fn grace_period_ms(mut self, grace_period_ms: u64) -> Self {
self.grace_period_ms = Some(grace_period_ms);
self
}
pub fn overage_policy(mut self, policy: CommitOveragePolicy) -> Self {
self.overage_policy = Some(policy);
self
}
pub fn action_tags(mut self, tags: Vec<String>) -> Self {
self.action_tags = Some(tags);
self
}
pub fn metrics(mut self, metrics: CyclesMetrics) -> Self {
self.metrics = Some(metrics);
self
}
}
pub async fn with_cycles<F, Fut, T>(
client: &CyclesClient,
config: WithCyclesConfig,
f: F,
) -> Result<T, Error>
where
F: FnOnce(GuardContext) -> Fut,
Fut: Future<Output = Result<(T, Amount), Box<dyn std::error::Error + Send + Sync>>>,
{
let subject = config.subject.unwrap_or_default();
let reserve_req = ReservationCreateRequest {
idempotency_key: crate::models::IdempotencyKey::random(),
subject,
action: Action {
kind: config.action_kind,
name: config.action_name,
tags: config.action_tags,
},
estimate: config.estimate,
ttl_ms: config.ttl_ms,
grace_period_ms: config.grace_period_ms,
overage_policy: config.overage_policy,
dry_run: false,
metadata: None,
};
let guard = client.reserve(reserve_req).await?;
let ctx = GuardContext {
decision: guard.decision(),
caps: guard.caps().cloned(),
reservation_id: guard.reservation_id().clone(),
affected_scopes: guard.affected_scopes().to_vec(),
};
match f(ctx).await {
Ok((result, actual)) => {
let commit_req = CommitRequest {
idempotency_key: crate::models::IdempotencyKey::random(),
actual,
metrics: config.metrics,
metadata: None,
};
guard.commit(commit_req).await?;
Ok(result)
}
Err(e) => {
let _ = guard.release(format!("guarded_function_failed: {e}")).await;
Err(Error::Validation(format!("guarded function failed: {e}")))
}
}
}