use std::str::FromStr;
use std::time::Duration;
use frame_core::action::ActionRow;
use frame_core::capability::{
CapabilityCheckError, CapabilityChecker, CapabilityDenied, CheckVerdict,
};
use frame_core::component::ComponentId;
use serde::Serialize;
use serde::de::DeserializeOwned;
use thiserror::Error;
use crate::error::CallError;
use crate::handle::ConversationHandle;
use crate::id::ConversationId;
use crate::outcome::RequestOutcome;
use crate::store::ResumeStore;
#[must_use]
pub fn conversation_for_action(row: &ActionRow) -> ConversationId {
ConversationId::from_str(&row.dispatch_ordinal().to_string())
.unwrap_or_else(|_| unreachable!("a decimal u64 always parses as a conversation id"))
}
#[derive(Debug)]
pub enum ActionDispatchOutcome<R> {
Denied(CapabilityDenied),
Completed(RequestOutcome<R>),
}
#[derive(Debug, Error)]
pub enum ActionDispatchError {
#[error(
"capability checker for component {checker} cannot dispatch action of component {action}"
)]
CheckerBinding {
checker: ComponentId,
action: ComponentId,
},
#[error("handle is attached to conversation {attached} but action dispatch derives {derived}")]
ConversationBinding {
attached: ConversationId,
derived: ConversationId,
},
#[error(transparent)]
Check(#[from] CapabilityCheckError),
#[error(transparent)]
Call(#[from] CallError),
}
pub fn dispatch_action<Q, R, S>(
checker: &CapabilityChecker,
row: &ActionRow,
handle: &mut ConversationHandle<S>,
request: &Q,
deadline: Duration,
) -> Result<ActionDispatchOutcome<R>, ActionDispatchError>
where
Q: Serialize,
R: DeserializeOwned,
S: ResumeStore,
{
if checker.component_id() != row.key.component_id {
return Err(ActionDispatchError::CheckerBinding {
checker: checker.component_id(),
action: row.key.component_id,
});
}
let derived = conversation_for_action(row);
if handle.conversation() != derived {
return Err(ActionDispatchError::ConversationBinding {
attached: handle.conversation(),
derived,
});
}
for requirement in &row.requirements {
match checker.check(requirement)? {
CheckVerdict::Allowed => {}
CheckVerdict::Denied(denial) => {
return Ok(ActionDispatchOutcome::Denied(denial));
}
}
}
let outcome = handle.request(request, deadline)?;
Ok(ActionDispatchOutcome::Completed(outcome))
}