Skip to main content

freeswitch_log_parser/session/
parse.rs

1//! Message shapes the tracker reads out of dialplan, originate and bridge lines.
2
3use std::str::FromStr;
4
5use freeswitch_types::{BridgeDialString, DialString};
6
7use crate::fields::processing_parts;
8use crate::message::new_channel_name;
9
10pub(super) struct DialplanContext {
11    pub(super) from: String,
12    pub(super) to: String,
13    pub(super) context: String,
14}
15
16/// The context of a `parsing [<context>-><extension>]` line.
17///
18/// The extension is deliberately dropped: it names a dialplan entry point, not
19/// the dialed number `dialplan_to` carries, and folding the two into one field
20/// left it meaning whichever shape was logged last.
21pub(super) fn parse_dialplan_context(detail: &str) -> Option<&str> {
22    let rest = detail.strip_prefix("parsing [")?;
23    let bracket_end = rest.find(']')?;
24    let inner = &rest[..bracket_end];
25    let arrow = inner.find("->")?;
26    Some(&inner[..arrow])
27}
28
29pub(super) fn parse_processing_line(msg: &str) -> Option<DialplanContext> {
30    let parts = processing_parts(msg)?;
31    Some(DialplanContext {
32        from: msg[parts.head].to_string(),
33        to: msg[parts.dest].to_string(),
34        context: msg[parts.context].to_string(),
35    })
36}
37
38pub(super) fn parse_new_channel(detail: &str) -> Option<&str> {
39    new_channel_name(detail)
40}
41
42/// Which of the two state vocabularies a `... Change <old> -> <new>` line speaks.
43pub(super) enum StateChange<'a> {
44    Channel(&'a str),
45    Call(&'a str),
46}
47
48/// The new state a change line moves to. `Callstate Change` and `State Change`
49/// are distinct markers, so the two vocabularies never have to share a slot.
50pub(super) fn parse_state_change(detail: &str) -> Option<StateChange<'_>> {
51    let arrow = detail.find(" -> ")?;
52    let to = detail[arrow + 4..].trim();
53    if detail.contains("Callstate Change") {
54        Some(StateChange::Call(to))
55    } else {
56        Some(StateChange::Channel(to))
57    }
58}
59
60/// The cause in the last bracket of a `Hangup <channel> [<state>] [<cause>]`
61/// detail. The line's shape is the classifier's finding, not this one's.
62pub(super) fn parse_hangup(detail: &str) -> Option<&str> {
63    let start = detail.rfind('[')?;
64    let end = detail[start..].find(']')?;
65    Some(&detail[start + 1..start + end])
66}
67
68/// Extract `origination_uuid` and the bridge target channel from bridge() arguments.
69/// Uses `BridgeDialString` from freeswitch-types for correct parsing of `[]`, `{}`,
70/// `|` failover, and `,` simultaneous ring syntax.
71///
72/// Returns `None` when the arguments do not parse as a dial string or name no
73/// endpoint at all.
74pub fn parse_bridge_args(arguments: &str) -> Option<BridgeInfo> {
75    let dial = BridgeDialString::from_str(arguments).ok()?;
76    let first_ep = dial.groups().first()?.first()?;
77    // `{}` variables apply to every endpoint, so a single-leg bridge can name
78    // the new leg's UUID there instead of in the endpoint's own `[]`.
79    let origination_uuid = first_ep
80        .variables()
81        .and_then(|v| v.get("origination_uuid"))
82        .or_else(|| dial.variables().and_then(|v| v.get("origination_uuid")))
83        .map(|s| s.to_string());
84    let mut bare = first_ep.clone();
85    bare.set_variables(None);
86    let target_channel = bare.to_string();
87    Some(BridgeInfo {
88        origination_uuid,
89        target_channel,
90    })
91}
92
93/// What a `bridge()` argument list says about the leg it is about to create.
94#[derive(Debug, Clone, PartialEq, Eq)]
95#[non_exhaustive]
96pub struct BridgeInfo {
97    /// The UUID the new leg is being forced to take, when the dial string sets one.
98    pub origination_uuid: Option<String>,
99    /// The first endpoint with its `{}`/`[]` variables removed, matching the form
100    /// the new channel will report as its channel name.
101    pub target_channel: String,
102}