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    /// Display name of the caller side, `None` unless the line brackets a number.
13    pub(super) from_name: Option<String>,
14    /// Number inside the caller side's `<>`, brackets excluded.
15    pub(super) from_number: Option<String>,
16    pub(super) to: String,
17    pub(super) context: String,
18}
19
20/// The context of a `parsing [<context>-><extension>]` line.
21///
22/// The extension is deliberately dropped: it names a dialplan entry point, not
23/// the dialed number `dialplan_to` carries, and folding the two into one field
24/// left it meaning whichever shape was logged last.
25pub(super) fn parse_dialplan_context(detail: &str) -> Option<&str> {
26    let rest = detail.strip_prefix("parsing [")?;
27    let bracket_end = rest.find(']')?;
28    let inner = &rest[..bracket_end];
29    let arrow = inner.find("->")?;
30    Some(&inner[..arrow])
31}
32
33pub(super) fn parse_processing_line(msg: &str) -> Option<DialplanContext> {
34    let parts = processing_parts(msg)?;
35    Some(DialplanContext {
36        from: msg[parts.head].to_string(),
37        from_name: parts.name.map(|r| msg[r].to_string()),
38        from_number: parts.number.map(|r| msg[r].to_string()),
39        to: msg[parts.dest].to_string(),
40        context: msg[parts.context].to_string(),
41    })
42}
43
44pub(super) fn parse_new_channel(detail: &str) -> Option<&str> {
45    new_channel_name(detail)
46}
47
48/// The cause in the last bracket of a `Hangup <channel> [<state>] [<cause>]`
49/// detail. The line's shape is the classifier's finding, not this one's.
50pub(super) fn parse_hangup(detail: &str) -> Option<&str> {
51    let start = detail.rfind('[')?;
52    let end = detail[start..].find(']')?;
53    Some(&detail[start + 1..start + end])
54}
55
56/// Extract `origination_uuid` and the bridge target channel from bridge() arguments.
57/// Uses `BridgeDialString` from freeswitch-types for correct parsing of `[]`, `{}`,
58/// `|` failover, and `,` simultaneous ring syntax.
59///
60/// Returns `None` when the arguments do not parse as a dial string or name no
61/// endpoint at all.
62pub fn parse_bridge_args(arguments: &str) -> Option<BridgeInfo> {
63    let dial = BridgeDialString::from_str(arguments).ok()?;
64    let first_ep = dial.groups().first()?.first()?;
65    // `{}` variables apply to every endpoint, so a single-leg bridge can name
66    // the new leg's UUID there instead of in the endpoint's own `[]`.
67    let origination_uuid = first_ep
68        .variables()
69        .and_then(|v| v.get("origination_uuid"))
70        .or_else(|| dial.variables().and_then(|v| v.get("origination_uuid")))
71        .map(|s| s.to_string());
72    let mut bare = first_ep.clone();
73    bare.set_variables(None);
74    let target_channel = bare.to_string();
75    Some(BridgeInfo {
76        origination_uuid,
77        target_channel,
78    })
79}
80
81/// What a `bridge()` argument list says about the leg it is about to create.
82#[derive(Debug, Clone, PartialEq, Eq)]
83#[non_exhaustive]
84pub struct BridgeInfo {
85    /// The UUID the new leg is being forced to take, when the dial string sets one.
86    pub origination_uuid: Option<String>,
87    /// The first endpoint with its `{}`/`[]` variables removed, matching the form
88    /// the new channel will report as its channel name.
89    pub target_channel: String,
90}