use std::io::Cursor;
use serde::{Deserialize, Serialize};
use tsoracle_openraft_toolkit::declare_raft_types_ext;
use crate::log_entry::HighWaterCommand;
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct OpenraftPeer {
pub addr: String,
pub service_endpoint: String,
pub admin_endpoint: String,
}
pub trait ServiceEndpoint {
fn service_endpoint(&self) -> Option<&str>;
}
impl ServiceEndpoint for OpenraftPeer {
fn service_endpoint(&self) -> Option<&str> {
if self.service_endpoint.is_empty() {
None
} else {
Some(&self.service_endpoint)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ApplyOutcome {
Advanced,
FormatActivated { target: u8 },
FormatActivationNoop { target: u8 },
FormatActivationTargetOutOfRange { target: u8 },
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct HighWaterApplied {
pub value: u64,
pub outcome: ApplyOutcome,
}
declare_raft_types_ext! {
pub TypeConfig:
Node = OpenraftPeer,
AppData = HighWaterCommand,
AppDataResponse = HighWaterApplied,
SnapshotData = Cursor<Vec<u8>>,
}
#[doc(hidden)]
pub type OpenraftEntry = openraft::type_config::alias::EntryOf<TypeConfig>;
#[doc(hidden)]
pub type OpenraftVote = openraft::type_config::alias::VoteOf<TypeConfig>;
#[doc(hidden)]
pub type OpenraftLogId = openraft::type_config::alias::LogIdOf<TypeConfig>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn openraft_peer_round_trips() {
let peer = OpenraftPeer {
addr: "10.0.0.1:50051".to_string(),
service_endpoint: String::new(),
admin_endpoint: "10.0.0.1:50053".to_string(),
};
let bytes = postcard::to_stdvec(&peer).expect("serialize");
let back: OpenraftPeer = postcard::from_bytes(&bytes).expect("deserialize");
assert_eq!(back, peer);
}
#[test]
fn openraft_peer_default_has_all_empty_fields() {
let peer = OpenraftPeer::default();
assert_eq!(peer.addr, "");
assert_eq!(peer.service_endpoint, "");
assert_eq!(peer.admin_endpoint, "");
}
#[test]
fn openraft_peer_round_trips_empty_addr() {
let peer = OpenraftPeer {
addr: String::new(),
service_endpoint: String::new(),
admin_endpoint: String::new(),
};
let bytes = postcard::to_stdvec(&peer).expect("serialize");
let back: OpenraftPeer = postcard::from_bytes(&bytes).expect("deserialize");
assert_eq!(back, peer);
}
#[test]
fn openraft_peer_pins_field_layout() {
let peer = OpenraftPeer {
addr: "a:1".into(),
service_endpoint: "b:2".into(),
admin_endpoint: "c:3".into(),
};
let bytes = postcard::to_stdvec(&peer).expect("serialize");
assert_eq!(
bytes,
vec![
3, b'a', b':', b'1', 3, b'b', b':', b'2', 3, b'c', b':', b'3'
]
);
}
#[test]
fn high_water_applied_round_trips() {
let applied = HighWaterApplied {
value: 12_345,
outcome: ApplyOutcome::Advanced,
};
let bytes = postcard::to_stdvec(&applied).expect("serialize");
let back: HighWaterApplied = postcard::from_bytes(&bytes).expect("deserialize");
assert_eq!(back, applied);
}
#[test]
fn high_water_applied_round_trips_zero_and_max() {
for v in [0u64, u64::MAX] {
let applied = HighWaterApplied {
value: v,
outcome: ApplyOutcome::Advanced,
};
let bytes = postcard::to_stdvec(&applied).expect("serialize");
let back: HighWaterApplied = postcard::from_bytes(&bytes).expect("deserialize");
assert_eq!(back, applied);
}
}
#[test]
fn apply_outcome_variants_round_trip() {
for outcome in [
ApplyOutcome::Advanced,
ApplyOutcome::FormatActivated { target: 4 },
ApplyOutcome::FormatActivationNoop { target: 4 },
ApplyOutcome::FormatActivationTargetOutOfRange { target: 1 },
] {
let applied = HighWaterApplied {
value: 42,
outcome: outcome.clone(),
};
let bytes = postcard::to_stdvec(&applied).expect("serialize");
let back: HighWaterApplied = postcard::from_bytes(&bytes).expect("deserialize");
assert_eq!(back, applied);
}
}
#[test]
fn apply_outcome_distinguishes_success_from_noop() {
let success = ApplyOutcome::FormatActivated { target: 5 };
let noop = ApplyOutcome::FormatActivationNoop { target: 5 };
assert_ne!(success, noop);
assert!(matches!(
success,
ApplyOutcome::FormatActivated { target: 5 }
));
assert!(matches!(
noop,
ApplyOutcome::FormatActivationNoop { target: 5 }
));
}
#[test]
fn openraft_peer_round_trips_all_fields() {
let peer = OpenraftPeer {
addr: "node-1:50052".to_string(),
service_endpoint: "http://node-1:50051".to_string(),
admin_endpoint: "node-1:50053".to_string(),
};
let bytes = postcard::to_stdvec(&peer).expect("serialize");
let back: OpenraftPeer = postcard::from_bytes(&bytes).expect("deserialize");
assert_eq!(back, peer);
}
#[test]
fn service_endpoint_is_some_when_set_none_when_empty() {
use crate::type_config::ServiceEndpoint;
let with = OpenraftPeer {
addr: "node-1:50052".to_string(),
service_endpoint: "http://node-1:50051".to_string(),
admin_endpoint: String::new(),
};
assert_eq!(with.service_endpoint(), Some("http://node-1:50051"));
let without = OpenraftPeer {
addr: "node-1:50052".to_string(),
service_endpoint: String::new(),
admin_endpoint: String::new(),
};
assert_eq!(without.service_endpoint(), None);
}
}