tsoracle_driver_openraft/
log_codec.rs1use tsoracle_codec::{CodecError, decode_postcard_exact, encode_postcard};
36use tsoracle_openraft_toolkit::LogStoreCodec;
37
38use crate::type_config::{OpenraftLogId, OpenraftVote, TypeConfig};
39
40#[derive(Debug, Clone, Copy, Default)]
45pub struct OpenraftLogCodec;
46
47impl LogStoreCodec<TypeConfig> for OpenraftLogCodec {
48 fn encode_entry(
49 version: u8,
50 entry: &<TypeConfig as openraft::RaftTypeConfig>::Entry,
51 ) -> Result<Vec<u8>, CodecError> {
52 if version < tsoracle_openraft_toolkit::BATCH_WRITE_VERSION {
58 if let openraft::EntryPayload::Normal(
59 crate::log_entry::HighWaterCommand::AdvanceDenseBatch { .. },
60 ) = &entry.payload
61 {
62 return Err(CodecError::NotRepresentable { version });
63 }
64 }
65 encode_postcard(entry)
66 }
67
68 fn decode_entry(
69 _version: u8,
70 body: &[u8],
71 ) -> Result<<TypeConfig as openraft::RaftTypeConfig>::Entry, CodecError> {
72 decode_postcard_exact(body)
73 }
74
75 fn encode_vote(_version: u8, vote: &OpenraftVote) -> Result<Vec<u8>, CodecError> {
76 encode_postcard(vote)
77 }
78
79 fn decode_vote(_version: u8, body: &[u8]) -> Result<OpenraftVote, CodecError> {
80 decode_postcard_exact(body)
81 }
82
83 fn encode_log_id(_version: u8, log_id: &OpenraftLogId) -> Result<Vec<u8>, CodecError> {
84 encode_postcard(log_id)
85 }
86
87 fn decode_log_id(_version: u8, body: &[u8]) -> Result<OpenraftLogId, CodecError> {
88 decode_postcard_exact(body)
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use crate::log_entry::HighWaterCommand;
96 use tsoracle_consensus::AdvancePayload;
97
98 #[test]
99 fn entry_body_matches_raw_postcard() {
100 let lid = openraft::testing::log_id::<TypeConfig>(1, 1, 1);
104 let entry: <TypeConfig as openraft::RaftTypeConfig>::Entry =
105 openraft::entry::RaftEntry::new_normal(
106 lid,
107 HighWaterCommand::Advance(AdvancePayload { at_least: 5 }),
108 );
109 let via_provider =
110 <OpenraftLogCodec as LogStoreCodec<TypeConfig>>::encode_entry(4, &entry).unwrap();
111 let raw = postcard::to_stdvec(&entry).unwrap();
112 assert_eq!(via_provider, raw);
113 }
114
115 #[test]
116 fn vote_body_matches_raw_postcard() {
117 let vote: OpenraftVote = openraft::Vote::new_committed(7, 3);
118 let via_provider =
119 <OpenraftLogCodec as LogStoreCodec<TypeConfig>>::encode_vote(4, &vote).unwrap();
120 assert_eq!(via_provider, postcard::to_stdvec(&vote).unwrap());
121 let back: OpenraftVote =
122 <OpenraftLogCodec as LogStoreCodec<TypeConfig>>::decode_vote(4, &via_provider).unwrap();
123 assert_eq!(back, vote);
124 }
125
126 #[test]
127 fn log_id_body_matches_raw_postcard() {
128 let log_id: OpenraftLogId = openraft::testing::log_id::<TypeConfig>(2, 1, 9);
129 let via_provider =
130 <OpenraftLogCodec as LogStoreCodec<TypeConfig>>::encode_log_id(4, &log_id).unwrap();
131 assert_eq!(via_provider, postcard::to_stdvec(&log_id).unwrap());
132 }
133
134 #[test]
135 fn encode_entry_rejects_batch_below_batch_version() {
136 use crate::log_entry::{DenseAdvance, HighWaterCommand};
137 use tsoracle_openraft_toolkit::{BATCH_WRITE_VERSION, DENSE_WRITE_VERSION};
138 let lid = openraft::testing::log_id::<TypeConfig>(1, 1, 1);
139 let entry: <TypeConfig as openraft::RaftTypeConfig>::Entry =
140 openraft::entry::RaftEntry::new_normal(
141 lid,
142 HighWaterCommand::AdvanceDenseBatch {
143 entries: vec![DenseAdvance {
144 key: tsoracle_core::SeqKey::try_new("k").unwrap(),
145 count: 1,
146 }],
147 },
148 );
149 use tsoracle_openraft_toolkit::BASELINE_WRITE_VERSION;
153 for below in [BASELINE_WRITE_VERSION, DENSE_WRITE_VERSION] {
154 assert!(
155 matches!(
156 <OpenraftLogCodec as LogStoreCodec<TypeConfig>>::encode_entry(below, &entry),
157 Err(CodecError::NotRepresentable { .. })
158 ),
159 "AdvanceDenseBatch must be refused at write version {below}",
160 );
161 }
162 assert!(
164 <OpenraftLogCodec as LogStoreCodec<TypeConfig>>::encode_entry(
165 BATCH_WRITE_VERSION,
166 &entry
167 )
168 .is_ok()
169 );
170 }
171}