use openraft::RaftTypeConfig;
use openraft::type_config::alias::LogIdOf;
use openraft::type_config::alias::VoteOf;
use serde::Serialize;
use serde::de::DeserializeOwned;
use tsoracle_codec::{CodecError, decode_postcard_exact, encode_postcard};
pub trait LogStoreCodec<C: RaftTypeConfig> {
fn encode_entry(version: u8, entry: &C::Entry) -> Result<Vec<u8>, CodecError>;
fn decode_entry(version: u8, body: &[u8]) -> Result<C::Entry, CodecError>;
fn encode_vote(version: u8, vote: &VoteOf<C>) -> Result<Vec<u8>, CodecError>;
fn decode_vote(version: u8, body: &[u8]) -> Result<VoteOf<C>, CodecError>;
fn encode_log_id(version: u8, log_id: &LogIdOf<C>) -> Result<Vec<u8>, CodecError>;
fn decode_log_id(version: u8, body: &[u8]) -> Result<LogIdOf<C>, CodecError>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct DefaultLogStoreCodec;
impl<C> LogStoreCodec<C> for DefaultLogStoreCodec
where
C: RaftTypeConfig,
C::Entry: Serialize + DeserializeOwned,
VoteOf<C>: Serialize + DeserializeOwned,
LogIdOf<C>: Serialize + DeserializeOwned,
{
fn encode_entry(_version: u8, entry: &C::Entry) -> Result<Vec<u8>, CodecError> {
encode_postcard(entry)
}
fn decode_entry(_version: u8, body: &[u8]) -> Result<C::Entry, CodecError> {
decode_postcard_exact(body)
}
fn encode_vote(_version: u8, vote: &VoteOf<C>) -> Result<Vec<u8>, CodecError> {
encode_postcard(vote)
}
fn decode_vote(_version: u8, body: &[u8]) -> Result<VoteOf<C>, CodecError> {
decode_postcard_exact(body)
}
fn encode_log_id(_version: u8, log_id: &LogIdOf<C>) -> Result<Vec<u8>, CodecError> {
encode_postcard(log_id)
}
fn decode_log_id(_version: u8, body: &[u8]) -> Result<LogIdOf<C>, CodecError> {
decode_postcard_exact(body)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::declare_raft_types_ext;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProbePeer {
addr: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProbeData {
n: u64,
}
impl std::fmt::Display for ProbeData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ProbeData({})", self.n)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProbeApplied;
declare_raft_types_ext! {
pub ProbeConfig:
Node = ProbePeer,
AppData = ProbeData,
AppDataResponse = ProbeApplied,
SnapshotData = std::io::Cursor<Vec<u8>>,
}
#[test]
fn default_codec_vote_body_matches_raw_postcard() {
let vote: VoteOf<ProbeConfig> = openraft::Vote::new_committed(7, 3);
let via_provider =
<DefaultLogStoreCodec as LogStoreCodec<ProbeConfig>>::encode_vote(3, &vote)
.expect("encode vote");
let raw = postcard::to_stdvec(&vote).expect("raw postcard");
assert_eq!(via_provider, raw);
let back: VoteOf<ProbeConfig> =
<DefaultLogStoreCodec as LogStoreCodec<ProbeConfig>>::decode_vote(3, &via_provider)
.expect("decode vote");
assert_eq!(back, vote);
}
#[test]
fn default_codec_rejects_trailing_bytes() {
let vote: VoteOf<ProbeConfig> = openraft::Vote::new_committed(1, 1);
let mut body =
<DefaultLogStoreCodec as LogStoreCodec<ProbeConfig>>::encode_vote(3, &vote).unwrap();
body.extend_from_slice(&[0xAB, 0xCD]);
assert!(matches!(
<DefaultLogStoreCodec as LogStoreCodec<ProbeConfig>>::decode_vote(3, &body),
Err(CodecError::TrailingBytes { extra: 2 })
));
}
}