use std::sync::{Arc, Mutex, MutexGuard};
use sim_kernel::{
CapabilityName, CapabilitySet, Cx, Datum, DatumStore, Error, Event, EventKind, EventLedger,
Expr, Ref, Result, Symbol,
};
use super::RequestOrigin;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ReadEvalOutcome {
Admitted,
CapDenied,
TrustDenied,
MissingPower,
ShapeDenied,
}
impl ReadEvalOutcome {
fn as_symbol(&self) -> Symbol {
match self {
Self::Admitted => Symbol::new("admitted"),
Self::CapDenied => Symbol::new("cap-denied"),
Self::TrustDenied => Symbol::new("trust-denied"),
Self::MissingPower => Symbol::new("missing-power"),
Self::ShapeDenied => Symbol::new("shape-denied"),
}
}
fn from_symbol(symbol: &Symbol) -> Result<Self> {
match symbol.name.as_ref() {
"admitted" => Ok(Self::Admitted),
"cap-denied" => Ok(Self::CapDenied),
"trust-denied" => Ok(Self::TrustDenied),
"missing-power" => Ok(Self::MissingPower),
"shape-denied" => Ok(Self::ShapeDenied),
other => Err(Error::Eval(format!(
"unknown read-eval decision outcome {other}"
))),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ReadEvalDecision {
pub origin: RequestOrigin,
pub codec: Symbol,
pub expected_shape: Option<Symbol>,
pub requires: Vec<CapabilityName>,
pub requested: Vec<CapabilityName>,
pub active: Vec<CapabilityName>,
pub outcome: ReadEvalOutcome,
}
pub fn read_eval_decision_run() -> Ref {
Ref::Symbol(Symbol::qualified("read-eval", "decisions"))
}
#[derive(Clone)]
pub(super) struct ReadEvalLedger {
events: Arc<Mutex<EventLedger>>,
run: Ref,
}
impl Default for ReadEvalLedger {
fn default() -> Self {
Self {
events: Arc::new(Mutex::new(EventLedger::new())),
run: read_eval_decision_run(),
}
}
}
impl ReadEvalLedger {
pub(super) fn record(&self, cx: &mut Cx, decision: &ReadEvalDecision) -> Result<Event> {
let reference = decision_ref(cx, decision)?;
self.lock()?
.push(self.run.clone(), EventKind::Trace(reference))
}
pub(super) fn events_for_run(&self, run: &Ref) -> Result<Vec<Event>> {
Ok(self.lock()?.events_for_run(run).to_vec())
}
pub(super) fn decisions(&self, cx: &Cx) -> Result<Vec<ReadEvalDecision>> {
self.decisions_for_run(cx, &self.run)
}
pub(super) fn decisions_for_run(&self, cx: &Cx, run: &Ref) -> Result<Vec<ReadEvalDecision>> {
self.events_for_run(run)?
.iter()
.filter_map(|event| match &event.kind {
EventKind::Trace(reference) => Some(decision_from_ref(cx, reference)),
_ => None,
})
.collect()
}
fn lock(&self) -> Result<MutexGuard<'_, EventLedger>> {
self.events
.lock()
.map_err(|_| Error::PoisonedLock("read-eval decision ledger"))
}
}
fn decision_ref(cx: &mut Cx, decision: &ReadEvalDecision) -> Result<Ref> {
let id = cx.datum_store_mut().intern(decision_datum(decision))?;
Ok(Ref::Content(id))
}
fn decision_from_ref(cx: &Cx, reference: &Ref) -> Result<ReadEvalDecision> {
let Ref::Content(id) = reference else {
return Err(Error::Eval(
"read-eval decision trace does not reference content".to_owned(),
));
};
let datum = cx
.datum_store()
.get(id)?
.ok_or_else(|| Error::Eval("read-eval decision content is missing".to_owned()))?;
decision_from_datum(datum)
}
fn decision_datum(decision: &ReadEvalDecision) -> Datum {
Datum::Node {
tag: decision_tag(),
fields: vec![
(Symbol::new("origin"), origin_datum(&decision.origin)),
(Symbol::new("codec"), Datum::Symbol(decision.codec.clone())),
(
Symbol::new("expected-shape"),
option_symbol_datum(decision.expected_shape.as_ref()),
),
(
Symbol::new("requires"),
capabilities_datum(&decision.requires),
),
(
Symbol::new("requested"),
capabilities_datum(&decision.requested),
),
(Symbol::new("active"), capabilities_datum(&decision.active)),
(
Symbol::new("outcome"),
Datum::Symbol(decision.outcome.as_symbol()),
),
],
}
}
fn decision_from_datum(datum: &Datum) -> Result<ReadEvalDecision> {
let Datum::Node { tag, fields } = datum else {
return Err(Error::Eval(
"read-eval decision trace payload must be a datum node".to_owned(),
));
};
if tag != &decision_tag() {
return Err(Error::Eval(
"trace payload is not a read-eval decision".to_owned(),
));
}
Ok(ReadEvalDecision {
origin: origin_from_datum(field(fields, "origin")?)?,
codec: symbol_field(fields, "codec")?.clone(),
expected_shape: option_symbol_from_datum(field(fields, "expected-shape")?)?,
requires: capabilities_from_datum(field(fields, "requires")?)?,
requested: capabilities_from_datum(field(fields, "requested")?)?,
active: capabilities_from_datum(field(fields, "active")?)?,
outcome: ReadEvalOutcome::from_symbol(symbol_field(fields, "outcome")?)?,
})
}
fn origin_datum(origin: &RequestOrigin) -> Datum {
Datum::Node {
tag: Symbol::qualified("read-eval", "origin"),
fields: vec![
(Symbol::new("tag"), Datum::Symbol(origin.tag.clone())),
(
Symbol::new("detail"),
origin.detail.as_ref().map_or(Datum::Nil, expr_datum_lossy),
),
],
}
}
fn origin_from_datum(datum: &Datum) -> Result<RequestOrigin> {
let Datum::Node { tag, fields } = datum else {
return Err(Error::Eval(
"read-eval decision origin must be a datum node".to_owned(),
));
};
if tag != &Symbol::qualified("read-eval", "origin") {
return Err(Error::Eval(
"read-eval decision origin has the wrong tag".to_owned(),
));
}
let detail = match field(fields, "detail")? {
Datum::Nil => None,
other => Some(Expr::from(other.clone())),
};
Ok(RequestOrigin {
tag: symbol_field(fields, "tag")?.clone(),
detail,
})
}
fn expr_datum_lossy(expr: &Expr) -> Datum {
Datum::try_from(expr.clone()).unwrap_or_else(|_| Datum::String(format!("{expr:?}")))
}
fn capabilities_datum(capabilities: &[CapabilityName]) -> Datum {
Datum::Vector(
capabilities
.iter()
.map(|capability| Datum::String(capability.as_str().to_owned()))
.collect(),
)
}
fn capabilities_from_datum(datum: &Datum) -> Result<Vec<CapabilityName>> {
let Datum::Vector(items) = datum else {
return Err(Error::Eval(
"read-eval decision capabilities must be a vector".to_owned(),
));
};
items
.iter()
.map(|item| match item {
Datum::String(name) => Ok(CapabilityName::new(name.clone())),
_ => Err(Error::Eval(
"read-eval decision capability must be a string".to_owned(),
)),
})
.collect()
}
fn option_symbol_datum(symbol: Option<&Symbol>) -> Datum {
symbol.cloned().map_or(Datum::Nil, Datum::Symbol)
}
fn option_symbol_from_datum(datum: &Datum) -> Result<Option<Symbol>> {
match datum {
Datum::Nil => Ok(None),
Datum::Symbol(symbol) => Ok(Some(symbol.clone())),
_ => Err(Error::Eval(
"read-eval decision optional symbol field is malformed".to_owned(),
)),
}
}
fn symbol_field<'a>(fields: &'a [(Symbol, Datum)], name: &str) -> Result<&'a Symbol> {
match field(fields, name)? {
Datum::Symbol(symbol) => Ok(symbol),
_ => Err(Error::Eval(format!(
"read-eval decision field {name} must be a symbol"
))),
}
}
fn field<'a>(fields: &'a [(Symbol, Datum)], name: &str) -> Result<&'a Datum> {
fields
.iter()
.find_map(|(field, value)| (field.name.as_ref() == name).then_some(value))
.ok_or_else(|| Error::Eval(format!("read-eval decision missing {name} field")))
}
fn decision_tag() -> Symbol {
Symbol::qualified("read-eval", "decision")
}
pub(super) fn decision_from_request(
request: &super::ReadEvalRequest,
active: &CapabilitySet,
outcome: ReadEvalOutcome,
) -> ReadEvalDecision {
ReadEvalDecision {
origin: request.origin.clone(),
codec: request.codec.clone(),
expected_shape: request.expected_shape.symbol(),
requires: request.requires.clone(),
requested: request.allow.iter().cloned().collect(),
active: active.iter().cloned().collect(),
outcome,
}
}