nodedb_cluster/rpc_codec/
calvin_submit.rs1use super::discriminants::*;
21use super::execute::TypedClusterError;
22use super::header::write_frame;
23use super::raft_rpc::RaftRpc;
24use crate::error::{ClusterError, Result};
25
26#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
40pub struct SubmitCalvinTxnRequest {
41 pub tx_class_bytes: Vec<u8>,
43 pub deadline_remaining_ms: u64,
45 pub trace_id: [u8; 16],
46}
47
48#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
62pub struct SubmitCalvinTxnResponse {
63 pub error: Option<TypedClusterError>,
64 pub payload_bytes: Option<Vec<u8>>,
65}
66
67#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
80pub struct SubmitCalvinInboxRequest {
81 pub tx_class_bytes: Vec<u8>,
83 pub deadline_remaining_ms: u64,
85 pub trace_id: [u8; 16],
86}
87
88#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
98pub struct SubmitCalvinInboxResponse {
99 pub inbox_seq: u64,
100 pub epoch: u64,
101 pub position: u32,
102 pub participants: u64,
103 pub error: Option<TypedClusterError>,
104}
105
106macro_rules! to_bytes {
109 ($msg:expr) => {
110 rkyv::to_bytes::<rkyv::rancor::Error>($msg)
111 .map(|b| b.to_vec())
112 .map_err(|e| ClusterError::Codec {
113 detail: format!("rkyv serialize: {e}"),
114 })
115 };
116}
117
118macro_rules! from_bytes {
119 ($payload:expr, $T:ty, $name:expr) => {{
120 let mut aligned = rkyv::util::AlignedVec::<16>::with_capacity($payload.len());
121 aligned.extend_from_slice($payload);
122 rkyv::from_bytes::<$T, rkyv::rancor::Error>(&aligned).map_err(|e| ClusterError::Codec {
123 detail: format!("rkyv deserialize {}: {e}", $name),
124 })
125 }};
126}
127
128pub(super) fn encode_submit_calvin_txn_req(
129 msg: &SubmitCalvinTxnRequest,
130 out: &mut Vec<u8>,
131) -> Result<()> {
132 write_frame(RPC_SUBMIT_CALVIN_TXN_REQ, &to_bytes!(msg)?, out)
133}
134pub(super) fn encode_submit_calvin_txn_resp(
135 msg: &SubmitCalvinTxnResponse,
136 out: &mut Vec<u8>,
137) -> Result<()> {
138 write_frame(RPC_SUBMIT_CALVIN_TXN_RESP, &to_bytes!(msg)?, out)
139}
140
141pub(super) fn decode_submit_calvin_txn_req(payload: &[u8]) -> Result<RaftRpc> {
142 Ok(RaftRpc::SubmitCalvinTxnRequest(from_bytes!(
143 payload,
144 SubmitCalvinTxnRequest,
145 "SubmitCalvinTxnRequest"
146 )?))
147}
148pub(super) fn decode_submit_calvin_txn_resp(payload: &[u8]) -> Result<RaftRpc> {
149 Ok(RaftRpc::SubmitCalvinTxnResponse(from_bytes!(
150 payload,
151 SubmitCalvinTxnResponse,
152 "SubmitCalvinTxnResponse"
153 )?))
154}
155
156pub(super) fn encode_submit_calvin_inbox_req(
157 msg: &SubmitCalvinInboxRequest,
158 out: &mut Vec<u8>,
159) -> Result<()> {
160 write_frame(RPC_SUBMIT_CALVIN_INBOX_REQ, &to_bytes!(msg)?, out)
161}
162pub(super) fn encode_submit_calvin_inbox_resp(
163 msg: &SubmitCalvinInboxResponse,
164 out: &mut Vec<u8>,
165) -> Result<()> {
166 write_frame(RPC_SUBMIT_CALVIN_INBOX_RESP, &to_bytes!(msg)?, out)
167}
168
169pub(super) fn decode_submit_calvin_inbox_req(payload: &[u8]) -> Result<RaftRpc> {
170 Ok(RaftRpc::SubmitCalvinInboxRequest(from_bytes!(
171 payload,
172 SubmitCalvinInboxRequest,
173 "SubmitCalvinInboxRequest"
174 )?))
175}
176pub(super) fn decode_submit_calvin_inbox_resp(payload: &[u8]) -> Result<RaftRpc> {
177 Ok(RaftRpc::SubmitCalvinInboxResponse(from_bytes!(
178 payload,
179 SubmitCalvinInboxResponse,
180 "SubmitCalvinInboxResponse"
181 )?))
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 fn roundtrip_req(req: SubmitCalvinTxnRequest) -> SubmitCalvinTxnRequest {
189 let rpc = RaftRpc::SubmitCalvinTxnRequest(req);
190 let encoded = super::super::encode(&rpc).unwrap();
191 match super::super::decode(&encoded).unwrap() {
192 RaftRpc::SubmitCalvinTxnRequest(r) => r,
193 other => panic!("expected SubmitCalvinTxnRequest, got {other:?}"),
194 }
195 }
196
197 fn roundtrip_resp(resp: SubmitCalvinTxnResponse) -> SubmitCalvinTxnResponse {
198 let rpc = RaftRpc::SubmitCalvinTxnResponse(resp);
199 let encoded = super::super::encode(&rpc).unwrap();
200 match super::super::decode(&encoded).unwrap() {
201 RaftRpc::SubmitCalvinTxnResponse(r) => r,
202 other => panic!("expected SubmitCalvinTxnResponse, got {other:?}"),
203 }
204 }
205
206 #[test]
207 fn roundtrip_submit_calvin_txn_request() {
208 let req = SubmitCalvinTxnRequest {
209 tx_class_bytes: vec![0x01, 0x02, 0x03, 0x04],
210 deadline_remaining_ms: 30_000,
211 trace_id: [7u8; 16],
212 };
213 let decoded = roundtrip_req(req);
214 assert_eq!(decoded.tx_class_bytes, vec![0x01, 0x02, 0x03, 0x04]);
215 assert_eq!(decoded.deadline_remaining_ms, 30_000);
216 assert_eq!(decoded.trace_id, [7u8; 16]);
217 }
218
219 #[test]
220 fn roundtrip_submit_calvin_txn_request_empty() {
221 let req = SubmitCalvinTxnRequest {
222 tx_class_bytes: vec![],
223 deadline_remaining_ms: 0,
224 trace_id: [0u8; 16],
225 };
226 let decoded = roundtrip_req(req);
227 assert!(decoded.tx_class_bytes.is_empty());
228 assert_eq!(decoded.deadline_remaining_ms, 0);
229 }
230
231 #[test]
232 fn roundtrip_submit_calvin_txn_response_ok() {
233 let decoded = roundtrip_resp(SubmitCalvinTxnResponse {
234 error: None,
235 payload_bytes: Some(vec![0xAA, 0xBB]),
236 });
237 assert!(decoded.error.is_none());
238 assert_eq!(decoded.payload_bytes, Some(vec![0xAA, 0xBB]));
239 }
240
241 #[test]
242 fn roundtrip_submit_calvin_txn_response_error() {
243 let decoded = roundtrip_resp(SubmitCalvinTxnResponse {
244 error: Some(TypedClusterError::Internal {
245 code: 0,
246 message: "calvin-submit not configured".into(),
247 }),
248 payload_bytes: None,
249 });
250 match decoded.error {
251 Some(TypedClusterError::Internal { code, message }) => {
252 assert_eq!(code, 0);
253 assert!(message.contains("calvin-submit"));
254 }
255 other => panic!("expected Internal, got {other:?}"),
256 }
257 }
258
259 fn roundtrip_inbox_req(req: SubmitCalvinInboxRequest) -> SubmitCalvinInboxRequest {
260 let rpc = RaftRpc::SubmitCalvinInboxRequest(req);
261 let encoded = super::super::encode(&rpc).unwrap();
262 match super::super::decode(&encoded).unwrap() {
263 RaftRpc::SubmitCalvinInboxRequest(r) => r,
264 other => panic!("expected SubmitCalvinInboxRequest, got {other:?}"),
265 }
266 }
267
268 fn roundtrip_inbox_resp(resp: SubmitCalvinInboxResponse) -> SubmitCalvinInboxResponse {
269 let rpc = RaftRpc::SubmitCalvinInboxResponse(resp);
270 let encoded = super::super::encode(&rpc).unwrap();
271 match super::super::decode(&encoded).unwrap() {
272 RaftRpc::SubmitCalvinInboxResponse(r) => r,
273 other => panic!("expected SubmitCalvinInboxResponse, got {other:?}"),
274 }
275 }
276
277 #[test]
278 fn roundtrip_submit_calvin_inbox_request() {
279 let req = SubmitCalvinInboxRequest {
280 tx_class_bytes: vec![0x0a, 0x0b, 0x0c],
281 deadline_remaining_ms: 15_000,
282 trace_id: [3u8; 16],
283 };
284 let decoded = roundtrip_inbox_req(req);
285 assert_eq!(decoded.tx_class_bytes, vec![0x0a, 0x0b, 0x0c]);
286 assert_eq!(decoded.deadline_remaining_ms, 15_000);
287 assert_eq!(decoded.trace_id, [3u8; 16]);
288 }
289
290 #[test]
291 fn roundtrip_submit_calvin_inbox_response_ok() {
292 let decoded = roundtrip_inbox_resp(SubmitCalvinInboxResponse {
293 inbox_seq: 42,
294 epoch: 7,
295 position: 3,
296 participants: 5,
297 error: None,
298 });
299 assert_eq!(decoded.inbox_seq, 42);
300 assert_eq!(decoded.epoch, 7);
301 assert_eq!(decoded.position, 3);
302 assert_eq!(decoded.participants, 5);
303 assert!(decoded.error.is_none());
304 }
305
306 #[test]
307 fn roundtrip_submit_calvin_inbox_response_error() {
308 let decoded = roundtrip_inbox_resp(SubmitCalvinInboxResponse {
309 inbox_seq: 0,
310 epoch: 0,
311 position: 0,
312 participants: 0,
313 error: Some(TypedClusterError::Internal {
314 code: 0,
315 message: "calvin-inbox not configured".into(),
316 }),
317 });
318 assert_eq!(decoded.inbox_seq, 0);
319 assert_eq!(decoded.epoch, 0);
320 assert_eq!(decoded.position, 0);
321 assert_eq!(decoded.participants, 0);
322 match decoded.error {
323 Some(TypedClusterError::Internal { code, message }) => {
324 assert_eq!(code, 0);
325 assert!(message.contains("calvin-inbox"));
326 }
327 other => panic!("expected Internal, got {other:?}"),
328 }
329 }
330}