Skip to main content

ringo_core/
event.rs

1use std::collections::HashMap;
2
3/// Headers of each INVITE seen in a trace, keyed by SIP `Call-ID`. First
4/// INVITE per Call-ID wins (the call-establishing one).
5pub type InviteHeaders = HashMap<String, Vec<(String, String)>>;
6
7#[derive(Debug, Clone)]
8pub enum AppEvent {
9    Registering {
10        account: String,
11    },
12    RegisterOk {
13        account: String,
14    },
15    RegisterFailed {
16        reason: String,
17    },
18    Unregistered {
19        account: String,
20    },
21    CallIncoming {
22        call_id: String,
23        number: String,
24        display_name: Option<String>,
25    },
26    CallOutgoing {
27        call_id: String,
28        number: String,
29    },
30    CallRinging {
31        call_id: String,
32    },
33    CallEstablished {
34        call_id: String,
35    },
36    CallClosed {
37        call_id: String,
38        reason: String,
39        error: bool,
40    },
41    VoicemailStatus {
42        waiting: bool,
43        new_count: u32,
44    },
45    Response {
46        ok: bool,
47        data: String,
48    },
49    Unknown {
50        class: String,
51        type_: String,
52    },
53    BackendConnectFailed {
54        reason: String,
55    },
56}
57
58/// Whether a call-closed reason indicates an error (not a normal close).
59/// Backend-neutral: shared by all backends that produce SIP reason strings.
60pub fn is_error_reason(reason: &str) -> bool {
61    if reason.is_empty() {
62        return false;
63    }
64    const NORMAL: &[&str] = &[
65        "Connection reset by peer",
66        "Connection closed",
67        "Rejected by user",
68        "Call transfered",
69    ];
70    !NORMAL
71        .iter()
72        .any(|n| reason.to_lowercase().starts_with(&n.to_lowercase()))
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    // ── is_error_reason ────────────────────────────────────────────────────────
80
81    #[test]
82    fn empty_reason_is_not_error() {
83        assert!(!is_error_reason(""));
84    }
85
86    #[test]
87    fn connection_reset_is_not_error() {
88        assert!(!is_error_reason("Connection reset by peer"));
89    }
90
91    #[test]
92    fn connection_reset_with_errno_is_not_error() {
93        assert!(!is_error_reason("Connection reset by peer [104]"));
94    }
95
96    #[test]
97    fn connection_closed_is_not_error() {
98        assert!(!is_error_reason("Connection closed"));
99    }
100
101    #[test]
102    fn rejected_by_user_is_not_error() {
103        assert!(!is_error_reason("Rejected by user"));
104    }
105
106    #[test]
107    fn sip_busy_is_error() {
108        assert!(is_error_reason("486 Busy Here"));
109    }
110
111    #[test]
112    fn sip_not_found_is_error() {
113        assert!(is_error_reason("404 Not Found"));
114    }
115}