freeswitch_types/variables/
esl_headers.rs1use indexmap::IndexMap;
20use sip_header::{
21 HistoryInfo, HistoryInfoError, SipHeader, SipHeaderLookup, UriInfo, UriInfoError,
22};
23
24use crate::lookup::HeaderLookup;
25use crate::variables::{EslArray, EslArrayError};
26
27#[derive(Debug, Clone, Default, PartialEq, Eq)]
54#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55pub struct EslHeaders(IndexMap<String, String>);
56
57impl EslHeaders {
58 pub fn new() -> Self {
60 Self(IndexMap::new())
61 }
62
63 pub fn from_map(map: IndexMap<String, String>) -> Self {
65 Self(map)
66 }
67
68 pub fn as_map(&self) -> &IndexMap<String, String> {
70 &self.0
71 }
72
73 pub fn into_map(self) -> IndexMap<String, String> {
75 self.0
76 }
77
78 pub fn insert(&mut self, key: impl Into<String>, value: impl Into<String>) {
80 self.0
81 .insert(key.into(), value.into());
82 }
83
84 pub fn remove(&mut self, key: &str) -> Option<String> {
86 self.0
87 .shift_remove(key)
88 }
89
90 pub fn len(&self) -> usize {
92 self.0
93 .len()
94 }
95
96 pub fn is_empty(&self) -> bool {
98 self.0
99 .is_empty()
100 }
101}
102
103impl From<IndexMap<String, String>> for EslHeaders {
104 fn from(map: IndexMap<String, String>) -> Self {
105 Self(map)
106 }
107}
108
109fn strip_brackets(s: &str) -> &str {
112 if let Some(inner) = s.strip_prefix('[') {
113 if let Some(inner) = inner.strip_suffix(']') {
114 return inner;
115 }
116 }
117 s
118}
119
120fn parse_uri_info_value(value: &str) -> Result<UriInfo, UriInfoError> {
126 let value = strip_brackets(value);
127 match EslArray::parse(value) {
128 Ok(array) => UriInfo::from_entries(
129 array
130 .items()
131 .iter()
132 .map(String::as_str),
133 ),
134 Err(EslArrayError::MissingPrefix) => UriInfo::parse(value),
135 Err(other) => Err(UriInfoError::MissingAngleBrackets(format!(
139 "ARRAY:: parse failed: {other}"
140 ))),
141 }
142}
143
144fn parse_history_info_value(value: &str) -> Result<HistoryInfo, HistoryInfoError> {
149 let value = strip_brackets(value);
150 match EslArray::parse(value) {
151 Ok(array) => HistoryInfo::from_entries(
152 array
153 .items()
154 .iter()
155 .map(String::as_str),
156 ),
157 Err(EslArrayError::MissingPrefix) => HistoryInfo::parse(value),
158 Err(_) => Err(HistoryInfoError::Empty),
159 }
160}
161
162impl SipHeaderLookup for EslHeaders {
163 fn sip_header_str(&self, name: &str) -> Option<&str> {
164 self.0
165 .get(name)
166 .map(|s| s.as_str())
167 }
168
169 fn call_info(&self) -> Result<Option<UriInfo>, UriInfoError> {
170 match self.sip_header(SipHeader::CallInfo) {
171 Some(s) => parse_uri_info_value(s).map(Some),
172 None => Ok(None),
173 }
174 }
175
176 fn history_info(&self) -> Result<Option<HistoryInfo>, HistoryInfoError> {
177 match self.sip_header(SipHeader::HistoryInfo) {
178 Some(s) => parse_history_info_value(s).map(Some),
179 None => Ok(None),
180 }
181 }
182
183 fn alert_info(&self) -> Result<Option<UriInfo>, UriInfoError> {
184 match self.sip_header(SipHeader::AlertInfo) {
185 Some(s) => parse_uri_info_value(s).map(Some),
186 None => Ok(None),
187 }
188 }
189}
190
191impl HeaderLookup for EslHeaders {
192 fn header_str(&self, name: &str) -> Option<&str> {
193 self.0
194 .get(name)
195 .map(|s| s.as_str())
196 }
197
198 fn variable_str(&self, name: &str) -> Option<&str> {
199 self.0
200 .get(&format!("variable_{name}"))
201 .map(|s| s.as_str())
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use crate::headers::EventHeader;
209
210 #[test]
211 fn header_str_passthrough() {
212 let mut h = EslHeaders::new();
213 h.insert("Unique-ID", "abc-123");
214 assert_eq!(h.header_str("Unique-ID"), Some("abc-123"));
215 }
216
217 #[test]
218 fn variable_str_prepends_variable_prefix() {
219 let mut h = EslHeaders::new();
220 h.insert("variable_sip_call_id", "call-1");
221 assert_eq!(h.variable_str("sip_call_id"), Some("call-1"));
222 assert_eq!(h.variable_str("missing"), None);
223 }
224
225 #[test]
226 fn call_info_single_value_rfc() {
227 let mut h = EslHeaders::new();
228 h.insert(
229 "Call-Info",
230 "<sip:alice@example.com>;purpose=emergency-CallId",
231 );
232 let ci = h
233 .call_info()
234 .unwrap()
235 .expect("present");
236 assert_eq!(
237 ci.entries()
238 .len(),
239 1
240 );
241 assert_eq!(ci.entries()[0].purpose(), Some("emergency-CallId"));
242 }
243
244 #[test]
245 fn call_info_array_encoding() {
246 let mut h = EslHeaders::new();
247 h.insert(
248 "Call-Info",
249 "ARRAY::<sip:a@example.com>;purpose=icon|:<sip:b@example.com>;purpose=info",
250 );
251 let ci = h
252 .call_info()
253 .unwrap()
254 .expect("present");
255 assert_eq!(
256 ci.entries()
257 .len(),
258 2
259 );
260 assert_eq!(ci.entries()[0].purpose(), Some("icon"));
261 assert_eq!(ci.entries()[1].purpose(), Some("info"));
262 }
263
264 #[test]
265 fn call_info_bracket_wrapped() {
266 let mut h = EslHeaders::new();
267 h.insert(
268 "Call-Info",
269 "[<sip:alice@example.com>;purpose=emergency-CallId]",
270 );
271 let ci = h
272 .call_info()
273 .unwrap()
274 .expect("present");
275 assert_eq!(
276 ci.entries()
277 .len(),
278 1
279 );
280 }
281
282 #[test]
283 fn call_info_absent_is_ok_none() {
284 let h = EslHeaders::new();
285 assert!(h
286 .call_info()
287 .unwrap()
288 .is_none());
289 }
290
291 #[test]
292 fn history_info_array_encoding() {
293 let mut h = EslHeaders::new();
294 h.insert(
295 "History-Info",
296 "ARRAY::<sip:a@example.com>;index=1|:<sip:b@example.com>;index=1.1",
297 );
298 let hi = h
299 .history_info()
300 .unwrap()
301 .expect("present");
302 assert_eq!(
303 hi.entries()
304 .len(),
305 2
306 );
307 }
308
309 #[test]
310 fn header_lookup_typed_accessors() {
311 let mut h = EslHeaders::new();
312 h.insert(EventHeader::UniqueId.as_str(), "uuid-1");
313 h.insert(EventHeader::ChannelName.as_str(), "sofia/a/b");
314 assert_eq!(h.unique_id(), Some("uuid-1"));
315 assert_eq!(h.channel_name(), Some("sofia/a/b"));
316 }
317}