use super::{CallError, CallPacket, CallRequest, Frame, LinkTransport};
use crate::link_carrier::{CarrierReceive, LinkCarrier, WireCarrier};
use crate::link_dial::DialedLink;
pub struct CallClient<Carrier: LinkCarrier> {
carrier: Carrier,
transport: LinkTransport,
bound: usize,
frames_sent: u64,
}
impl<Carrier: LinkCarrier> std::fmt::Debug for CallClient<Carrier> {
fn fmt(&self, out: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
out.debug_struct("CallClient")
.field("frames_sent", &self.frames_sent)
.finish_non_exhaustive()
}
}
impl CallClient<WireCarrier> {
#[must_use]
pub fn wire(link: DialedLink, bound: usize) -> Self {
Self::new(link.carrier, link.transport, bound)
}
}
impl<Carrier: LinkCarrier> CallClient<Carrier> {
#[must_use]
pub const fn new(carrier: Carrier, transport: LinkTransport, bound: usize) -> Self {
Self {
carrier,
transport,
bound,
frames_sent: 0,
}
}
pub fn call(&mut self, request: CallRequest) -> Result<Frame, CallError> {
let id = request.id();
let correlation = request.correlation()?;
let subject = request.subject().to_owned();
let sealed = CallPacket::Request(request).seal(&mut self.transport, self.bound)?;
self.carrier
.send(&subject, &sealed)
.map_err(|error| self.peer_gone("send", &error))?;
self.frames_sent = self
.frames_sent
.checked_add(1)
.ok_or(CallError::Unavailable)?;
match self
.carrier
.receive()
.map_err(|error| self.peer_gone("receive", &error))?
{
CarrierReceive::Sealed {
subject: answered,
sealed,
} if answered == subject => {
let CallPacket::Reply(reply) =
CallPacket::open(&mut self.transport, &sealed, self.bound)?
else {
return Err(CallError::Correlation);
};
if reply.id() != id {
return Err(CallError::Correlation);
}
let frame = reply.into_frame();
match &frame {
Frame::ConversationMessage {
stream_id,
conversation_id,
..
}
| Frame::ConversationError {
stream_id,
conversation_id,
..
} if (*stream_id, *conversation_id) == correlation => Ok(frame),
_ => Err(CallError::Correlation),
}
}
CarrierReceive::Refused {
reason_code,
reason,
} => Ok(Frame::ConversationError {
flags: 0,
stream_id: correlation.0,
conversation_id: correlation.1,
reason_code,
message: reason,
}),
CarrierReceive::End { reason } => {
tracing::warn!(reason, "link call ended by its peer");
Err(CallError::PeerGone)
}
_ => Err(CallError::Correlation),
}
}
fn peer_gone(&self, step: &str, error: &crate::link_carrier::CarrierError) -> CallError {
tracing::warn!(step, %error, end = ?self.carrier.ended(), "link call lost its carrier");
CallError::PeerGone
}
#[must_use]
pub fn end_reason(&self) -> Option<String> {
self.carrier.ended()
}
#[must_use]
pub const fn frames_sent(&self) -> u64 {
self.frames_sent
}
}