use serde::{Deserialize, Serialize};
mod sealed {
pub trait Sealed {}
}
pub trait Phase: sealed::Sealed + Send + Sync + 'static {
fn name() -> &'static str;
}
pub struct Spawned;
impl sealed::Sealed for Spawned {}
impl Phase for Spawned {
fn name() -> &'static str {
"Spawned"
}
}
pub struct Subscribed;
impl sealed::Sealed for Subscribed {}
impl Phase for Subscribed {
fn name() -> &'static str {
"Subscribed"
}
}
pub struct Synced;
impl sealed::Sealed for Synced {}
impl Phase for Synced {
fn name() -> &'static str {
"Synced"
}
}
pub struct Live;
impl sealed::Sealed for Live {}
impl Phase for Live {
fn name() -> &'static str {
"Live"
}
}
pub trait Snapshot: Send + Sync + 'static {
fn size_bytes(&self) -> usize {
0
}
}
impl Snapshot for Vec<u8> {
fn size_bytes(&self) -> usize {
self.len()
}
}
impl Snapshot for String {
fn size_bytes(&self) -> usize {
self.len()
}
}
#[derive(Debug, thiserror::Error, Serialize, Deserialize)]
pub enum AttachError {
#[error("snapshot failed: {0}")]
SnapshotFailed(String),
#[error("subscribe failed: {0}")]
SubscribeFailed(String),
#[error("no such entity: {0}")]
NoSuchEntity(String),
#[error("transport: {0}")]
Transport(String),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EngateSpec {
pub name: String,
pub producer: TypePath,
pub consumer: TypePath,
#[serde(default = "default_true_bool")]
pub history_required: bool,
#[serde(default)]
pub attestation_fixture: Option<String>,
}
fn default_true_bool() -> bool {
true
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TypePath {
pub crate_name: String,
pub path: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn phase_names_round_trip() {
assert_eq!(Spawned::name(), "Spawned");
assert_eq!(Subscribed::name(), "Subscribed");
assert_eq!(Synced::name(), "Synced");
assert_eq!(Live::name(), "Live");
}
#[test]
fn vec_u8_snapshot_size() {
let v: Vec<u8> = vec![1, 2, 3, 4, 5];
assert_eq!(<Vec<u8> as Snapshot>::size_bytes(&v), 5);
}
#[test]
fn attach_error_display() {
let e = AttachError::SnapshotFailed("disk full".into());
assert_eq!(e.to_string(), "snapshot failed: disk full");
}
#[test]
fn engate_spec_round_trips_through_serde() {
let s = EngateSpec {
name: "mado-tear-pane".into(),
producer: TypePath {
crate_name: "tear-client".into(),
path: "tear_client::PaneProducer".into(),
},
consumer: TypePath {
crate_name: "mado".into(),
path: "mado::terminal::Terminal".into(),
},
history_required: true,
attestation_fixture: Some("fixtures/mado-tear.engate.json".into()),
};
let yaml = serde_json::to_string(&s).unwrap();
let back: EngateSpec = serde_json::from_str(&yaml).unwrap();
assert_eq!(s, back);
}
#[test]
fn engate_spec_history_required_defaults_to_true() {
let json = r#"{
"name": "x",
"producer": { "crate_name": "p-crate", "path": "p::Type" },
"consumer": { "crate_name": "c-crate", "path": "c::Type" }
}"#;
let s: EngateSpec = serde_json::from_str(json).unwrap();
assert!(s.history_required);
assert!(s.attestation_fixture.is_none());
}
#[test]
fn engate_spec_clone_equals_original() {
let s = EngateSpec {
name: "n".into(),
producer: TypePath { crate_name: "p".into(), path: "p::T".into() },
consumer: TypePath { crate_name: "c".into(), path: "c::T".into() },
history_required: false,
attestation_fixture: None,
};
assert_eq!(s, s.clone());
}
#[test]
fn type_path_equality_per_field() {
let a = TypePath { crate_name: "x".into(), path: "x::Y".into() };
let b = TypePath { crate_name: "x".into(), path: "x::Y".into() };
let c = TypePath { crate_name: "x".into(), path: "x::Z".into() };
let d = TypePath { crate_name: "z".into(), path: "x::Y".into() };
assert_eq!(a, b);
assert_ne!(a, c);
assert_ne!(a, d);
}
#[test]
fn all_attach_error_variants_constructible_and_displayable() {
let errs = [
AttachError::SnapshotFailed("disk full".into()),
AttachError::SubscribeFailed("permission".into()),
AttachError::NoSuchEntity("pane-x".into()),
AttachError::Transport("connection reset".into()),
];
for e in errs {
let s = e.to_string();
assert!(!s.is_empty(), "Display non-empty for {e:?}");
}
}
#[test]
fn attach_error_round_trips_through_serde() {
let e = AttachError::Transport("connection reset by peer".into());
let json = serde_json::to_string(&e).unwrap();
let back: AttachError = serde_json::from_str(&json).unwrap();
assert_eq!(e.to_string(), back.to_string());
}
#[test]
fn snapshot_impl_for_string() {
let s: String = "hello".into();
assert_eq!(<String as Snapshot>::size_bytes(&s), 5);
}
#[test]
fn snapshot_default_size_bytes_is_zero() {
struct EmptyMarker;
impl Snapshot for EmptyMarker {}
assert_eq!(<EmptyMarker as Snapshot>::size_bytes(&EmptyMarker), 0);
}
}