use super::discriminants::*;
use super::execute::TypedClusterError;
use super::header::write_frame;
use super::raft_rpc::RaftRpc;
use crate::error::{ClusterError, Result};
#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
pub struct SubmitCalvinTxnRequest {
pub tx_class_bytes: Vec<u8>,
pub deadline_remaining_ms: u64,
pub trace_id: [u8; 16],
}
#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
pub struct SubmitCalvinTxnResponse {
pub error: Option<TypedClusterError>,
pub payload_bytes: Option<Vec<u8>>,
}
#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
pub struct SubmitCalvinInboxRequest {
pub tx_class_bytes: Vec<u8>,
pub deadline_remaining_ms: u64,
pub trace_id: [u8; 16],
}
#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
pub struct SubmitCalvinInboxResponse {
pub inbox_seq: u64,
pub epoch: u64,
pub position: u32,
pub participants: u64,
pub error: Option<TypedClusterError>,
}
macro_rules! to_bytes {
($msg:expr) => {
rkyv::to_bytes::<rkyv::rancor::Error>($msg)
.map(|b| b.to_vec())
.map_err(|e| ClusterError::Codec {
detail: format!("rkyv serialize: {e}"),
})
};
}
macro_rules! from_bytes {
($payload:expr, $T:ty, $name:expr) => {{
let mut aligned = rkyv::util::AlignedVec::<16>::with_capacity($payload.len());
aligned.extend_from_slice($payload);
rkyv::from_bytes::<$T, rkyv::rancor::Error>(&aligned).map_err(|e| ClusterError::Codec {
detail: format!("rkyv deserialize {}: {e}", $name),
})
}};
}
pub(super) fn encode_submit_calvin_txn_req(
msg: &SubmitCalvinTxnRequest,
out: &mut Vec<u8>,
) -> Result<()> {
write_frame(RPC_SUBMIT_CALVIN_TXN_REQ, &to_bytes!(msg)?, out)
}
pub(super) fn encode_submit_calvin_txn_resp(
msg: &SubmitCalvinTxnResponse,
out: &mut Vec<u8>,
) -> Result<()> {
write_frame(RPC_SUBMIT_CALVIN_TXN_RESP, &to_bytes!(msg)?, out)
}
pub(super) fn decode_submit_calvin_txn_req(payload: &[u8]) -> Result<RaftRpc> {
Ok(RaftRpc::SubmitCalvinTxnRequest(from_bytes!(
payload,
SubmitCalvinTxnRequest,
"SubmitCalvinTxnRequest"
)?))
}
pub(super) fn decode_submit_calvin_txn_resp(payload: &[u8]) -> Result<RaftRpc> {
Ok(RaftRpc::SubmitCalvinTxnResponse(from_bytes!(
payload,
SubmitCalvinTxnResponse,
"SubmitCalvinTxnResponse"
)?))
}
pub(super) fn encode_submit_calvin_inbox_req(
msg: &SubmitCalvinInboxRequest,
out: &mut Vec<u8>,
) -> Result<()> {
write_frame(RPC_SUBMIT_CALVIN_INBOX_REQ, &to_bytes!(msg)?, out)
}
pub(super) fn encode_submit_calvin_inbox_resp(
msg: &SubmitCalvinInboxResponse,
out: &mut Vec<u8>,
) -> Result<()> {
write_frame(RPC_SUBMIT_CALVIN_INBOX_RESP, &to_bytes!(msg)?, out)
}
pub(super) fn decode_submit_calvin_inbox_req(payload: &[u8]) -> Result<RaftRpc> {
Ok(RaftRpc::SubmitCalvinInboxRequest(from_bytes!(
payload,
SubmitCalvinInboxRequest,
"SubmitCalvinInboxRequest"
)?))
}
pub(super) fn decode_submit_calvin_inbox_resp(payload: &[u8]) -> Result<RaftRpc> {
Ok(RaftRpc::SubmitCalvinInboxResponse(from_bytes!(
payload,
SubmitCalvinInboxResponse,
"SubmitCalvinInboxResponse"
)?))
}
#[cfg(test)]
mod tests {
use super::*;
fn roundtrip_req(req: SubmitCalvinTxnRequest) -> SubmitCalvinTxnRequest {
let rpc = RaftRpc::SubmitCalvinTxnRequest(req);
let encoded = super::super::encode(&rpc).unwrap();
match super::super::decode(&encoded).unwrap() {
RaftRpc::SubmitCalvinTxnRequest(r) => r,
other => panic!("expected SubmitCalvinTxnRequest, got {other:?}"),
}
}
fn roundtrip_resp(resp: SubmitCalvinTxnResponse) -> SubmitCalvinTxnResponse {
let rpc = RaftRpc::SubmitCalvinTxnResponse(resp);
let encoded = super::super::encode(&rpc).unwrap();
match super::super::decode(&encoded).unwrap() {
RaftRpc::SubmitCalvinTxnResponse(r) => r,
other => panic!("expected SubmitCalvinTxnResponse, got {other:?}"),
}
}
#[test]
fn roundtrip_submit_calvin_txn_request() {
let req = SubmitCalvinTxnRequest {
tx_class_bytes: vec![0x01, 0x02, 0x03, 0x04],
deadline_remaining_ms: 30_000,
trace_id: [7u8; 16],
};
let decoded = roundtrip_req(req);
assert_eq!(decoded.tx_class_bytes, vec![0x01, 0x02, 0x03, 0x04]);
assert_eq!(decoded.deadline_remaining_ms, 30_000);
assert_eq!(decoded.trace_id, [7u8; 16]);
}
#[test]
fn roundtrip_submit_calvin_txn_request_empty() {
let req = SubmitCalvinTxnRequest {
tx_class_bytes: vec![],
deadline_remaining_ms: 0,
trace_id: [0u8; 16],
};
let decoded = roundtrip_req(req);
assert!(decoded.tx_class_bytes.is_empty());
assert_eq!(decoded.deadline_remaining_ms, 0);
}
#[test]
fn roundtrip_submit_calvin_txn_response_ok() {
let decoded = roundtrip_resp(SubmitCalvinTxnResponse {
error: None,
payload_bytes: Some(vec![0xAA, 0xBB]),
});
assert!(decoded.error.is_none());
assert_eq!(decoded.payload_bytes, Some(vec![0xAA, 0xBB]));
}
#[test]
fn roundtrip_submit_calvin_txn_response_error() {
let decoded = roundtrip_resp(SubmitCalvinTxnResponse {
error: Some(TypedClusterError::Internal {
code: 0,
message: "calvin-submit not configured".into(),
}),
payload_bytes: None,
});
match decoded.error {
Some(TypedClusterError::Internal { code, message }) => {
assert_eq!(code, 0);
assert!(message.contains("calvin-submit"));
}
other => panic!("expected Internal, got {other:?}"),
}
}
fn roundtrip_inbox_req(req: SubmitCalvinInboxRequest) -> SubmitCalvinInboxRequest {
let rpc = RaftRpc::SubmitCalvinInboxRequest(req);
let encoded = super::super::encode(&rpc).unwrap();
match super::super::decode(&encoded).unwrap() {
RaftRpc::SubmitCalvinInboxRequest(r) => r,
other => panic!("expected SubmitCalvinInboxRequest, got {other:?}"),
}
}
fn roundtrip_inbox_resp(resp: SubmitCalvinInboxResponse) -> SubmitCalvinInboxResponse {
let rpc = RaftRpc::SubmitCalvinInboxResponse(resp);
let encoded = super::super::encode(&rpc).unwrap();
match super::super::decode(&encoded).unwrap() {
RaftRpc::SubmitCalvinInboxResponse(r) => r,
other => panic!("expected SubmitCalvinInboxResponse, got {other:?}"),
}
}
#[test]
fn roundtrip_submit_calvin_inbox_request() {
let req = SubmitCalvinInboxRequest {
tx_class_bytes: vec![0x0a, 0x0b, 0x0c],
deadline_remaining_ms: 15_000,
trace_id: [3u8; 16],
};
let decoded = roundtrip_inbox_req(req);
assert_eq!(decoded.tx_class_bytes, vec![0x0a, 0x0b, 0x0c]);
assert_eq!(decoded.deadline_remaining_ms, 15_000);
assert_eq!(decoded.trace_id, [3u8; 16]);
}
#[test]
fn roundtrip_submit_calvin_inbox_response_ok() {
let decoded = roundtrip_inbox_resp(SubmitCalvinInboxResponse {
inbox_seq: 42,
epoch: 7,
position: 3,
participants: 5,
error: None,
});
assert_eq!(decoded.inbox_seq, 42);
assert_eq!(decoded.epoch, 7);
assert_eq!(decoded.position, 3);
assert_eq!(decoded.participants, 5);
assert!(decoded.error.is_none());
}
#[test]
fn roundtrip_submit_calvin_inbox_response_error() {
let decoded = roundtrip_inbox_resp(SubmitCalvinInboxResponse {
inbox_seq: 0,
epoch: 0,
position: 0,
participants: 0,
error: Some(TypedClusterError::Internal {
code: 0,
message: "calvin-inbox not configured".into(),
}),
});
assert_eq!(decoded.inbox_seq, 0);
assert_eq!(decoded.epoch, 0);
assert_eq!(decoded.position, 0);
assert_eq!(decoded.participants, 0);
match decoded.error {
Some(TypedClusterError::Internal { code, message }) => {
assert_eq!(code, 0);
assert!(message.contains("calvin-inbox"));
}
other => panic!("expected Internal, got {other:?}"),
}
}
}