use std::sync::Arc;
use car_proto::{HostApprovalRequest, HostApprovalStatus, ResolveHostApprovalRequest};
use crate::host::HostState;
pub const WS_METHOD_PREFIX: &str = "ws.method:";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResolveOutcome {
Resolved,
StillPending,
Error,
}
#[derive(Clone)]
pub struct ApprovalCore {
host: Arc<HostState>,
}
impl ApprovalCore {
pub fn new(host: Arc<HostState>) -> Self {
Self { host }
}
pub fn host(&self) -> &Arc<HostState> {
&self.host
}
pub fn is_eligible_pending(approval: &HostApprovalRequest) -> bool {
approval.status == HostApprovalStatus::Pending
&& !approval.action.starts_with(WS_METHOD_PREFIX)
&& approval.client_id.is_none()
}
pub async fn eligible_pending(&self) -> Vec<HostApprovalRequest> {
self.host
.approvals()
.await
.into_iter()
.filter(Self::is_eligible_pending)
.collect()
}
pub async fn is_id_eligible_pending(&self, approval_id: &str) -> bool {
self.host
.approvals()
.await
.into_iter()
.any(|a| a.id == approval_id && Self::is_eligible_pending(&a))
}
pub async fn resolve(
&self,
principal: &str,
approval_id: &str,
resolution: &str,
) -> ResolveOutcome {
let req = ResolveHostApprovalRequest {
approval_id: approval_id.to_string(),
resolution: resolution.to_string(),
};
match self.host.resolve_approval(principal, req).await {
Ok(returned) if returned.status == HostApprovalStatus::Resolved => {
ResolveOutcome::Resolved
}
Ok(returned) => {
tracing::warn!(
approval_id = %approval_id,
status = ?returned.status,
"in-process resolve returned a non-resolved row (fan-out); leaving pending"
);
ResolveOutcome::StillPending
}
Err(e) => {
tracing::warn!(approval_id = %approval_id, error = %e, "in-process resolve failed");
ResolveOutcome::Error
}
}
}
}