use std::fmt;
use crate::attached::AttachedLines;
use crate::codec::{CodecMedia, CodecOffer, CodecParseError};
use crate::decode::truncate_at_char_boundary;
use crate::level::LogLevel;
use crate::line::LineKind;
use crate::message::{MessageKind, SdpDirection};
use super::collision::MOD_LOGFILE_BUF_SIZE;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Block {
ChannelData {
fields: Vec<(String, String)>,
variables: Vec<(String, String)>,
},
Sdp {
direction: SdpDirection,
body: Vec<String>,
},
CodecNegotiation {
media: CodecMedia,
comparisons: Vec<(CodecOffer, CodecOffer)>,
matched: Vec<CodecOffer>,
near_matched: Vec<CodecOffer>,
},
}
impl Block {
pub fn field(&self, name: &str) -> Option<&str> {
let Block::ChannelData { fields, .. } = self else {
return None;
};
fields
.iter()
.find(|(n, _)| n == name)
.map(|(_, v)| v.as_str())
}
pub fn variable<V: freeswitch_types::variables::VariableName>(&self, var: V) -> Option<&str> {
let Block::ChannelData { variables, .. } = self else {
return None;
};
let wanted = var.as_str();
variables
.iter()
.find(|(n, _)| n.strip_prefix("variable_").unwrap_or(n) == wanted)
.map(|(_, v)| v.as_str())
}
}
#[cfg(feature = "sdp")]
impl Block {
pub fn sdp_codecs(
&self,
) -> Option<Result<freeswitch_types::sdp::SdpCodecs, freeswitch_types::sdp::SdpCodecError>>
{
let Block::Sdp { body, .. } = self else {
return None;
};
let text = body.join("\n");
if text.trim().is_empty() {
return None;
}
Some(freeswitch_types::sdp::SdpCodecs::parse(&text))
}
}
pub(super) const WARNING_EXCERPT_LEN: usize = 80;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SessionReading {
ChannelState,
CallState,
CallDirection,
HangupCause,
}
impl fmt::Display for SessionReading {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let label = match self {
SessionReading::ChannelState => "channel state",
SessionReading::CallState => "call state",
SessionReading::CallDirection => "call direction",
SessionReading::HangupCause => "hangup cause",
};
f.write_str(label)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ParseWarning {
UnclosedVariable { name: String },
UnparseableChannelData { line: String },
UnrecognizedCodecLine {
line: String,
source: Option<CodecParseError>,
},
UnexpectedCodecContinuation { line: String },
OversizeLine { bytes: usize },
AttachedOverflow { line: String },
UnreadableValue {
reading: SessionReading,
value: String,
},
}
impl ParseWarning {
pub(crate) fn excerpt(msg: &str) -> String {
truncate_at_char_boundary(msg, WARNING_EXCERPT_LEN).to_string()
}
}
impl fmt::Display for ParseWarning {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseWarning::UnclosedVariable { name } => {
write!(f, "unclosed multi-line variable: {name}")
}
ParseWarning::UnparseableChannelData { line } => {
write!(f, "unparseable CHANNEL_DATA line: {line}")
}
ParseWarning::UnrecognizedCodecLine {
line,
source: Some(e),
} => write!(f, "unrecognized codec negotiation line ({e}): {line}"),
ParseWarning::UnrecognizedCodecLine { line, source: None } => {
write!(f, "unrecognized codec negotiation line: {line}")
}
ParseWarning::UnexpectedCodecContinuation { line } => {
write!(f, "unexpected codec negotiation continuation: {line}")
}
ParseWarning::OversizeLine { bytes } => write!(
f,
"line exceeds mod_logfile {MOD_LOGFILE_BUF_SIZE}-byte buffer \
({bytes} bytes), data may be truncated"
),
ParseWarning::UnreadableValue { reading, value } => {
write!(f, "unreadable {reading}: {value}")
}
ParseWarning::AttachedOverflow { line } => {
write!(f, "entry's attached lines full, dropped: {line}")
}
}
}
}
#[derive(Debug)]
pub struct LogEntry {
pub uuid: Option<String>,
pub timestamp: String,
pub level: Option<LogLevel>,
pub idle_pct: Option<String>,
pub source: Option<String>,
pub message: String,
pub kind: LineKind,
pub message_kind: MessageKind,
pub block: Option<Block>,
pub attached: AttachedLines,
pub line_number: u64,
pub warnings: Vec<ParseWarning>,
}
impl LogEntry {
pub fn synthetic(message: impl Into<String>) -> LogEntry {
LogEntry {
uuid: None,
timestamp: String::new(),
level: None,
idle_pct: None,
source: None,
message: message.into(),
kind: LineKind::Full,
message_kind: MessageKind::General,
block: None,
attached: AttachedLines::new(),
line_number: 0,
warnings: Vec::new(),
}
}
}