use indexmap::IndexMap;
use sip_header::{
HistoryInfo, HistoryInfoError, SipHeader, SipHeaderLookup, UriInfo, UriInfoError,
};
use crate::lookup::HeaderLookup;
use crate::variables::{EslArray, EslArrayError};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct EslHeaders(IndexMap<String, String>);
impl EslHeaders {
pub fn new() -> Self {
Self(IndexMap::new())
}
pub fn from_map(map: IndexMap<String, String>) -> Self {
Self(map)
}
pub fn as_map(&self) -> &IndexMap<String, String> {
&self.0
}
pub fn into_map(self) -> IndexMap<String, String> {
self.0
}
pub fn insert(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.0
.insert(key.into(), value.into());
}
pub fn remove(&mut self, key: &str) -> Option<String> {
self.0
.shift_remove(key)
}
pub fn len(&self) -> usize {
self.0
.len()
}
pub fn is_empty(&self) -> bool {
self.0
.is_empty()
}
}
impl From<IndexMap<String, String>> for EslHeaders {
fn from(map: IndexMap<String, String>) -> Self {
Self(map)
}
}
fn strip_brackets(s: &str) -> &str {
if let Some(inner) = s.strip_prefix('[') {
if let Some(inner) = inner.strip_suffix(']') {
return inner;
}
}
s
}
fn parse_uri_info_value(value: &str) -> Result<UriInfo, UriInfoError> {
let value = strip_brackets(value);
match EslArray::parse(value) {
Ok(array) => UriInfo::from_entries(
array
.items()
.iter()
.map(String::as_str),
),
Err(EslArrayError::MissingPrefix) => UriInfo::parse(value),
Err(other) => Err(UriInfoError::MissingAngleBrackets(format!(
"ARRAY:: parse failed: {other}"
))),
}
}
fn parse_history_info_value(value: &str) -> Result<HistoryInfo, HistoryInfoError> {
let value = strip_brackets(value);
match EslArray::parse(value) {
Ok(array) => HistoryInfo::from_entries(
array
.items()
.iter()
.map(String::as_str),
),
Err(EslArrayError::MissingPrefix) => HistoryInfo::parse(value),
Err(_) => Err(HistoryInfoError::Empty),
}
}
impl SipHeaderLookup for EslHeaders {
fn sip_header_str(&self, name: &str) -> Option<&str> {
self.0
.get(name)
.map(|s| s.as_str())
}
fn call_info(&self) -> Result<Option<UriInfo>, UriInfoError> {
match self.sip_header(SipHeader::CallInfo) {
Some(s) => parse_uri_info_value(s).map(Some),
None => Ok(None),
}
}
fn history_info(&self) -> Result<Option<HistoryInfo>, HistoryInfoError> {
match self.sip_header(SipHeader::HistoryInfo) {
Some(s) => parse_history_info_value(s).map(Some),
None => Ok(None),
}
}
fn alert_info(&self) -> Result<Option<UriInfo>, UriInfoError> {
match self.sip_header(SipHeader::AlertInfo) {
Some(s) => parse_uri_info_value(s).map(Some),
None => Ok(None),
}
}
}
impl HeaderLookup for EslHeaders {
fn header_str(&self, name: &str) -> Option<&str> {
self.0
.get(name)
.map(|s| s.as_str())
}
fn variable_str(&self, name: &str) -> Option<&str> {
self.0
.get(&format!("variable_{name}"))
.map(|s| s.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::headers::EventHeader;
#[test]
fn header_str_passthrough() {
let mut h = EslHeaders::new();
h.insert("Unique-ID", "abc-123");
assert_eq!(h.header_str("Unique-ID"), Some("abc-123"));
}
#[test]
fn variable_str_prepends_variable_prefix() {
let mut h = EslHeaders::new();
h.insert("variable_sip_call_id", "call-1");
assert_eq!(h.variable_str("sip_call_id"), Some("call-1"));
assert_eq!(h.variable_str("missing"), None);
}
#[test]
fn call_info_single_value_rfc() {
let mut h = EslHeaders::new();
h.insert(
"Call-Info",
"<sip:alice@example.com>;purpose=emergency-CallId",
);
let ci = h
.call_info()
.unwrap()
.expect("present");
assert_eq!(
ci.entries()
.len(),
1
);
assert_eq!(ci.entries()[0].purpose(), Some("emergency-CallId"));
}
#[test]
fn call_info_array_encoding() {
let mut h = EslHeaders::new();
h.insert(
"Call-Info",
"ARRAY::<sip:a@example.com>;purpose=icon|:<sip:b@example.com>;purpose=info",
);
let ci = h
.call_info()
.unwrap()
.expect("present");
assert_eq!(
ci.entries()
.len(),
2
);
assert_eq!(ci.entries()[0].purpose(), Some("icon"));
assert_eq!(ci.entries()[1].purpose(), Some("info"));
}
#[test]
fn call_info_bracket_wrapped() {
let mut h = EslHeaders::new();
h.insert(
"Call-Info",
"[<sip:alice@example.com>;purpose=emergency-CallId]",
);
let ci = h
.call_info()
.unwrap()
.expect("present");
assert_eq!(
ci.entries()
.len(),
1
);
}
#[test]
fn call_info_absent_is_ok_none() {
let h = EslHeaders::new();
assert!(h
.call_info()
.unwrap()
.is_none());
}
#[test]
fn history_info_array_encoding() {
let mut h = EslHeaders::new();
h.insert(
"History-Info",
"ARRAY::<sip:a@example.com>;index=1|:<sip:b@example.com>;index=1.1",
);
let hi = h
.history_info()
.unwrap()
.expect("present");
assert_eq!(
hi.entries()
.len(),
2
);
}
#[test]
fn header_lookup_typed_accessors() {
let mut h = EslHeaders::new();
h.insert(EventHeader::UniqueId.as_str(), "uuid-1");
h.insert(EventHeader::ChannelName.as_str(), "sofia/a/b");
assert_eq!(h.unique_id(), Some("uuid-1"));
assert_eq!(h.channel_name(), Some("sofia/a/b"));
}
}