#![forbid(unsafe_code)]
#![deny(missing_docs)]
use sim_kernel::{
CapabilityName, Cx, Error, Ref, Result,
effect::{Effect, resolve_effect},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ExecutionMode {
Observation,
Recorded,
Reviewed,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OperationDeclaration {
pub operation: String,
pub subject: Ref,
pub capability: CapabilityName,
pub mode: ExecutionMode,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Approval {
pub id: String,
pub subject: Ref,
pub decision: ApprovalDecision,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ApprovalDecision {
Approve,
Deny,
}
pub trait ApprovalVerifier {
fn verify(&self, approval: &Approval) -> Result<()>;
}
pub trait ApprovalUse {
fn consume(&self, approval: &Approval) -> Result<()>;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GateRecord {
pub operation: String,
pub subject: Ref,
pub capability: CapabilityName,
pub mode: ExecutionMode,
pub approval: Option<String>,
pub result: Ref,
}
pub trait GateRecordSink {
fn record(&self, record: GateRecord) -> Result<()>;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SinkFailurePolicy {
FailClosed,
PreserveResult,
}
pub struct GateContext<'a> {
pub approval: Option<&'a Approval>,
pub verifier: &'a dyn ApprovalVerifier,
pub approval_use: &'a dyn ApprovalUse,
pub sink: &'a dyn GateRecordSink,
pub sink_failure: SinkFailurePolicy,
}
pub fn guard_operation<F>(
cx: &mut Cx,
declaration: &OperationDeclaration,
effect: Effect,
gate: GateContext<'_>,
perform: F,
) -> Result<Ref>
where
F: FnOnce(&mut Cx, &Effect) -> Result<Ref>,
{
if !effect
.requires
.iter()
.any(|capability| capability == &declaration.capability)
{
return Err(Error::Eval(format!(
"operation {} effect omits declared capability {}",
declaration.operation,
declaration.capability.as_str()
)));
}
resolve_effect(cx, effect, |cx, effect| {
let approval_id = match declaration.mode {
ExecutionMode::Observation | ExecutionMode::Recorded => None,
ExecutionMode::Reviewed => {
let approval = gate.approval.ok_or_else(|| {
Error::Eval(format!(
"operation {} requires approval",
declaration.operation
))
})?;
if approval.subject != declaration.subject {
return Err(Error::Eval(format!(
"approval {} subject does not match operation {} subject",
approval.id, declaration.operation
)));
}
if approval.decision != ApprovalDecision::Approve {
return Err(Error::Eval(format!(
"approval {} does not approve",
approval.id
)));
}
gate.verifier.verify(approval)?;
gate.approval_use.consume(approval)?;
Some(approval.id.clone())
}
};
let result = perform(cx, effect)?;
let record = GateRecord {
operation: declaration.operation.clone(),
subject: declaration.subject.clone(),
capability: declaration.capability.clone(),
mode: declaration.mode,
approval: approval_id,
result: result.clone(),
};
match gate.sink.record(record) {
Ok(()) => Ok(result),
Err(_) if gate.sink_failure == SinkFailurePolicy::PreserveResult => Ok(result),
Err(error) => Err(error),
}
})
}
#[cfg(test)]
mod tests;