use sim_kernel::{Datum, Error, Result, Symbol};
pub type GuestValueProjection<T> = dyn Fn(&T) -> Result<Datum>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BoundedLane<T> {
Absent,
Complete(Vec<T>),
Truncated {
items: Vec<T>,
omitted: usize,
},
}
impl<T> BoundedLane<T> {
pub fn capture(items: Vec<T>, limit: usize) -> Self {
if items.len() <= limit {
Self::Complete(items)
} else {
let omitted = items.len() - limit;
Self::Truncated {
items: items.into_iter().take(limit).collect(),
omitted,
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FailureLocation {
pub source: Symbol,
pub start: usize,
pub end: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CanonicalFailure {
pub class: Symbol,
pub detail: Datum,
pub location: Option<FailureLocation>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CanonicalOutcome {
Success(Datum),
Failure(CanonicalFailure),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CanonicalObservation {
pub outcome: Option<CanonicalOutcome>,
pub events: BoundedLane<Datum>,
pub receipts: BoundedLane<Datum>,
pub browse: BoundedLane<Datum>,
}
pub fn project_guest_value<T>(
value: &T,
projection: Option<&GuestValueProjection<T>>,
) -> Result<Datum> {
projection.ok_or_else(|| {
Error::Eval("guest value has no canonical data face or profile projection".to_owned())
})?(value)
}
#[cfg(test)]
mod tests {
use super::*;
struct HostValue {
semantic: i64,
host_format: &'static str,
}
fn semantic_projection(value: &HostValue) -> Result<Datum> {
let _host_only_formatting = value.host_format;
Ok(Datum::String(value.semantic.to_string()))
}
fn observation(receipts: Vec<Datum>, location: FailureLocation) -> CanonicalObservation {
CanonicalObservation {
outcome: Some(CanonicalOutcome::Failure(CanonicalFailure {
class: Symbol::qualified("test", "rejected"),
detail: Datum::String("invalid-input".to_owned()),
location: Some(location),
})),
events: BoundedLane::Complete(vec![Datum::String("started".to_owned())]),
receipts: BoundedLane::capture(receipts, 4),
browse: BoundedLane::Complete(Vec::new()),
}
}
#[test]
fn guest_projection_ignores_host_formatting_and_is_mandatory() {
let terse = HostValue {
semantic: 7,
host_format: "7",
};
let verbose = HostValue {
semantic: 7,
host_format: "HostValue(7)",
};
assert_eq!(
project_guest_value(&terse, Some(&semantic_projection)).unwrap(),
project_guest_value(&verbose, Some(&semantic_projection)).unwrap()
);
assert!(project_guest_value(&terse, None).is_err());
}
#[test]
fn receipt_order_and_failure_location_are_semantic() {
let location = FailureLocation {
source: Symbol::qualified("fixture", "source"),
start: 2,
end: 5,
};
let first = Datum::String("first".to_owned());
let second = Datum::String("second".to_owned());
let baseline = observation(vec![first.clone(), second.clone()], location.clone());
assert_ne!(baseline, observation(vec![second, first], location.clone()));
assert_ne!(
baseline,
observation(
vec![
Datum::String("first".to_owned()),
Datum::String("second".to_owned())
],
FailureLocation {
start: 3,
..location
}
)
);
}
#[test]
fn absent_empty_and_truncated_lanes_remain_distinct() {
let empty = BoundedLane::<Datum>::capture(Vec::new(), 1);
let truncated = BoundedLane::capture(vec![Datum::Bool(true), Datum::Bool(false)], 1);
assert_ne!(BoundedLane::Absent, empty);
assert_ne!(empty, truncated);
assert_eq!(
truncated,
BoundedLane::Truncated {
items: vec![Datum::Bool(true)],
omitted: 1,
}
);
}
}