use crate::error::{Error, Result};
use crate::proto::{packet::Body, CallData, CallStart, Packet};
use crate::transport::encode_optional_data;
use bytes::Bytes;
pub fn new_call_start(
service: impl Into<String>,
method: impl Into<String>,
data: Option<Bytes>,
) -> Packet {
let (data_bytes, data_is_zero) = encode_optional_data(data);
Packet {
body: Some(Body::CallStart(CallStart {
rpc_service: service.into(),
rpc_method: method.into(),
data: data_bytes,
data_is_zero,
})),
}
}
pub fn new_call_data(data: Vec<u8>) -> Packet {
let data_is_zero = data.is_empty();
Packet {
body: Some(Body::CallData(CallData {
data,
data_is_zero,
complete: false,
error: String::new(),
})),
}
}
pub fn new_call_data_full(
data: Option<Bytes>,
complete: bool,
error: Option<String>,
) -> Packet {
let (data_bytes, data_is_zero) = encode_optional_data(data);
Packet {
body: Some(Body::CallData(CallData {
data: data_bytes,
data_is_zero,
complete: complete || error.is_some(),
error: error.unwrap_or_default(),
})),
}
}
pub fn new_call_complete() -> Packet {
Packet {
body: Some(Body::CallData(CallData {
data: vec![],
data_is_zero: false,
complete: true,
error: String::new(),
})),
}
}
pub fn new_call_error(error: impl Into<String>) -> Packet {
Packet {
body: Some(Body::CallData(CallData {
data: vec![],
data_is_zero: false,
complete: true,
error: error.into(),
})),
}
}
pub fn new_call_cancel() -> Packet {
Packet {
body: Some(Body::CallCancel(true)),
}
}
pub fn body_type_name(packet: &Packet) -> &'static str {
match &packet.body {
Some(Body::CallStart(_)) => "CallStart",
Some(Body::CallData(_)) => "CallData",
Some(Body::CallCancel(_)) => "CallCancel",
None => "Empty",
}
}
pub trait Validate {
fn validate(&self) -> Result<()>;
}
impl Validate for Packet {
fn validate(&self) -> Result<()> {
match &self.body {
Some(Body::CallStart(cs)) => cs.validate(),
Some(Body::CallData(cd)) => cd.validate(),
Some(Body::CallCancel(_)) => Ok(()),
None => Err(Error::EmptyPacket),
}
}
}
impl Validate for CallStart {
fn validate(&self) -> Result<()> {
if self.rpc_method.is_empty() {
return Err(Error::EmptyMethodId);
}
if self.rpc_service.is_empty() {
return Err(Error::EmptyServiceId);
}
Ok(())
}
}
impl Validate for CallData {
fn validate(&self) -> Result<()> {
if self.data.is_empty()
&& !self.data_is_zero
&& !self.complete
&& self.error.is_empty()
{
return Err(Error::EmptyPacket);
}
Ok(())
}
}
impl Packet {
pub fn is_call_start(&self) -> bool {
matches!(&self.body, Some(Body::CallStart(_)))
}
pub fn is_call_data(&self) -> bool {
matches!(&self.body, Some(Body::CallData(_)))
}
pub fn is_call_cancel(&self) -> bool {
matches!(&self.body, Some(Body::CallCancel(true)))
}
pub fn into_call_start(self) -> Option<CallStart> {
match self.body {
Some(Body::CallStart(cs)) => Some(cs),
_ => None,
}
}
pub fn into_call_data(self) -> Option<CallData> {
match self.body {
Some(Body::CallData(cd)) => Some(cd),
_ => None,
}
}
pub fn is_complete(&self) -> bool {
match &self.body {
Some(Body::CallData(cd)) => cd.complete || !cd.error.is_empty(),
Some(Body::CallCancel(true)) => true,
_ => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_call_start() {
let pkt = new_call_start("test.Service", "Method", None);
let cs = pkt.into_call_start().unwrap();
assert_eq!(cs.rpc_service, "test.Service");
assert_eq!(cs.rpc_method, "Method");
assert!(cs.data.is_empty());
assert!(!cs.data_is_zero);
}
#[test]
fn test_new_call_start_with_empty_data() {
let pkt = new_call_start("svc", "method", Some(Bytes::new()));
let cs = pkt.into_call_start().unwrap();
assert!(cs.data.is_empty());
assert!(cs.data_is_zero);
}
#[test]
fn test_new_call_start_with_data() {
let pkt = new_call_start("svc", "method", Some(Bytes::from(vec![1, 2, 3])));
let cs = pkt.into_call_start().unwrap();
assert_eq!(cs.data, vec![1, 2, 3]);
assert!(!cs.data_is_zero);
}
#[test]
fn test_validate_call_start_valid() {
let cs = CallStart {
rpc_service: "svc".into(),
rpc_method: "method".into(),
data: vec![],
data_is_zero: false,
};
assert!(cs.validate().is_ok());
}
#[test]
fn test_validate_call_start_empty_method() {
let cs = CallStart {
rpc_service: "svc".into(),
rpc_method: String::new(),
data: vec![],
data_is_zero: false,
};
assert!(matches!(cs.validate(), Err(Error::EmptyMethodId)));
}
#[test]
fn test_validate_call_start_empty_service() {
let cs = CallStart {
rpc_service: String::new(),
rpc_method: "method".into(),
data: vec![],
data_is_zero: false,
};
assert!(matches!(cs.validate(), Err(Error::EmptyServiceId)));
}
#[test]
fn test_validate_call_data_valid_with_data() {
let cd = CallData {
data: vec![1, 2, 3],
data_is_zero: false,
complete: false,
error: String::new(),
};
assert!(cd.validate().is_ok());
}
#[test]
fn test_validate_call_data_valid_with_complete() {
let cd = CallData {
data: vec![],
data_is_zero: false,
complete: true,
error: String::new(),
};
assert!(cd.validate().is_ok());
}
#[test]
fn test_validate_call_data_valid_with_error() {
let cd = CallData {
data: vec![],
data_is_zero: false,
complete: false,
error: "some error".into(),
};
assert!(cd.validate().is_ok());
}
#[test]
fn test_validate_call_data_valid_with_zero_data() {
let cd = CallData {
data: vec![],
data_is_zero: true,
complete: false,
error: String::new(),
};
assert!(cd.validate().is_ok());
}
#[test]
fn test_validate_call_data_invalid_empty() {
let cd = CallData {
data: vec![],
data_is_zero: false,
complete: false,
error: String::new(),
};
assert!(matches!(cd.validate(), Err(Error::EmptyPacket)));
}
#[test]
fn test_validate_packet_empty() {
let pkt = Packet { body: None };
assert!(matches!(pkt.validate(), Err(Error::EmptyPacket)));
}
}