use super::RECORD_VERSION;
use crate::console::{ConsoleError, wall::WallBase};
use crate::tap::{self, TapIdent, TapOutcome, TapSource};
use serde::Serialize;
use std::num::NonZeroU32;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Outcome {
Delivered,
Answered,
Unanswered,
Nobody,
Unaccepted,
Refused,
Unresolved,
}
impl From<TapOutcome> for Outcome {
fn from(outcome: TapOutcome) -> Self {
match outcome {
TapOutcome::Answered => Self::Answered,
TapOutcome::Unanswered => Self::Unanswered,
TapOutcome::Delivered => Self::Delivered,
TapOutcome::NoSubscriber => Self::Nobody,
TapOutcome::NoneAccepted => Self::Unaccepted,
TapOutcome::Unresolved => Self::Unresolved,
TapOutcome::Refused => Self::Refused,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct Hop {
pub node: String,
pub at_ms: u64,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct TapMessage {
pub id: String,
pub subject: String,
pub size: u64,
pub outcome: Outcome,
pub t: u64,
pub from: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub from_station: Option<String>,
pub to: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub to_stations: Option<Vec<String>>,
pub hops: Vec<Hop>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_to: Option<String>,
pub held: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub diverted: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fields: Option<tap::message::CallDetails>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct TapBatch {
v: u64,
#[serde(rename = "type")]
record_type: &'static str,
pub target: String,
pub seq: u64,
pub sampled: NonZeroU32,
pub dropped: u64,
pub messages: Vec<TapMessage>,
}
impl TapBatch {
#[must_use]
pub const fn new(
target: String,
seq: u64,
sampled: NonZeroU32,
dropped: u64,
messages: Vec<TapMessage>,
) -> Self {
Self {
v: RECORD_VERSION,
record_type: "tap.batch",
target,
seq,
sampled,
dropped,
messages,
}
}
}
pub fn tap_batch_record(
device: &str,
target: &str,
batch: &tap::TapBatch,
wall: &WallBase,
) -> Result<TapBatch, ConsoleError> {
let messages = batch
.messages
.iter()
.map(|message| tap_message(device, message, wall))
.collect::<Result<Vec<_>, _>>()?;
Ok(TapBatch::new(
target.to_owned(),
batch.seq,
batch.sampled,
batch.dropped,
messages,
))
}
fn tap_message(
device: &str,
message: &tap::TapMessage,
wall: &WallBase,
) -> Result<TapMessage, ConsoleError> {
Ok(TapMessage {
id: match message.ident {
TapIdent::Call {
connection,
operation,
} => format!("{device}:call:{connection}:{operation}"),
TapIdent::Message(id) => format!("{device}:{id}"),
TapIdent::Observation(observation) => format!("{device}:x{observation}"),
},
subject: message.subject.clone(),
size: message.size,
outcome: message.outcome.into(),
t: wall.unix_ms_at(message.seen_at)?,
from: match message.source {
TapSource::Connection(connection) => connection.to_string(),
TapSource::Server => message
.station
.as_deref()
.map_or_else(|| format!("liminal.{device}"), str::to_owned),
},
from_station: message.station.as_deref().map(str::to_owned),
to: device.to_owned(),
to_stations: message.to_stations.as_deref().map(|stations| {
stations
.iter()
.map(|reached| reached.station.to_string())
.collect()
}),
hops: message
.hops
.iter()
.map(|hop| Hop {
node: hop.node.to_string(),
at_ms: hop.at_ms,
})
.collect(),
reply_to: message.reply_to.clone(),
held: message.held,
diverted: None,
fields: message.call.clone(),
})
}