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/// Which of the two state vocabularies a `... Change <old> -> <new>` line speaks.
49pub(super) enum StateChange<'a> {
50    Channel(&'a str),
51    Call(&'a str),
52}
53
54/// The new state a change line moves to. `Callstate Change` and `State Change`
55/// are distinct markers, so the two vocabularies never have to share a slot.
56pub(super) fn parse_state_change(detail: &str) -> Option<StateChange<'_>> {
57    let arrow = detail.find(" -> ")?;
58    let to = detail[arrow + 4..].trim();
59    if detail.contains("Callstate Change") {
60        Some(StateChange::Call(to))
61    } else {
62        Some(StateChange::Channel(to))
63    }
64}
65
66/// The cause in the last bracket of a `Hangup <channel> [<state>] [<cause>]`
67/// detail. The line's shape is the classifier's finding, not this one's.
68pub(super) fn parse_hangup(detail: &str) -> Option<&str> {
69    let start = detail.rfind('[')?;
70    let end = detail[start..].find(']')?;
71    Some(&detail[start + 1..start + end])
72}
73
74/// Extract `origination_uuid` and the bridge target channel from bridge() arguments.
75/// Uses `BridgeDialString` from freeswitch-types for correct parsing of `[]`, `{}`,
76/// `|` failover, and `,` simultaneous ring syntax.
77///
78/// Returns `None` when the arguments do not parse as a dial string or name no
79/// endpoint at all.
80pub fn parse_bridge_args(arguments: &str) -> Option<BridgeInfo> {
81    let dial = BridgeDialString::from_str(arguments).ok()?;
82    let first_ep = dial.groups().first()?.first()?;
83    // `{}` variables apply to every endpoint, so a single-leg bridge can name
84    // the new leg's UUID there instead of in the endpoint's own `[]`.
85    let origination_uuid = first_ep
86        .variables()
87        .and_then(|v| v.get("origination_uuid"))
88        .or_else(|| dial.variables().and_then(|v| v.get("origination_uuid")))
89        .map(|s| s.to_string());
90    let mut bare = first_ep.clone();
91    bare.set_variables(None);
92    let target_channel = bare.to_string();
93    Some(BridgeInfo {
94        origination_uuid,
95        target_channel,
96    })
97}
98
99/// What a `bridge()` argument list says about the leg it is about to create.
100#[derive(Debug, Clone, PartialEq, Eq)]
101#[non_exhaustive]
102pub struct BridgeInfo {
103    /// The UUID the new leg is being forced to take, when the dial string sets one.
104    pub origination_uuid: Option<String>,
105    /// The first endpoint with its `{}`/`[]` variables removed, matching the form
106    /// the new channel will report as its channel name.
107    pub target_channel: String,
108}