use crate::codec::RawAmiMessage;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub struct AmiResponse {
pub action_id: String,
pub success: bool,
pub response_type: String,
pub message: Option<String>,
pub headers: HashMap<String, String>,
pub output: Vec<String>,
pub channel_variables: HashMap<String, String>,
}
impl AmiResponse {
pub fn from_raw(raw: &RawAmiMessage) -> Option<Self> {
if raw.get("Event").is_some() {
return None;
}
let response_type = raw.get("Response")?.to_string();
let action_id = raw.get("ActionID").unwrap_or("").to_string();
let success = response_type.eq_ignore_ascii_case("success")
|| response_type.eq_ignore_ascii_case("follows");
let message = raw.get("Message").map(String::from);
let headers = raw.to_map();
Some(Self {
action_id,
success,
response_type,
message,
headers,
output: raw.output.clone(),
channel_variables: raw.channel_variables.clone(),
})
}
pub fn get(&self, key: &str) -> Option<&str> {
self.headers
.iter()
.find(|(k, _)| k.eq_ignore_ascii_case(key))
.map(|(_, v)| v.as_str())
}
pub fn get_variable(&self, name: &str) -> Option<&str> {
self.channel_variables.get(name).map(|s| s.as_str())
}
}
#[derive(Debug, Clone)]
pub struct EventListResponse {
pub response: AmiResponse,
pub events: Vec<crate::event::AmiEvent>,
}
pub const MAX_EVENT_LIST_EVENTS: usize = 10_000;
struct PendingEventList {
response: Option<AmiResponse>,
events: Vec<crate::event::AmiEvent>,
tx: tokio::sync::oneshot::Sender<EventListResponse>,
}
pub struct PendingActions {
pending: HashMap<String, tokio::sync::oneshot::Sender<AmiResponse>>,
pending_event_lists: HashMap<String, PendingEventList>,
}
impl PendingActions {
pub fn new() -> Self {
Self {
pending: HashMap::new(),
pending_event_lists: HashMap::new(),
}
}
pub fn register(&mut self, action_id: String) -> tokio::sync::oneshot::Receiver<AmiResponse> {
let (tx, rx) = tokio::sync::oneshot::channel();
self.pending.insert(action_id, tx);
rx
}
pub fn deliver(&mut self, response: AmiResponse) -> bool {
if let Some(tx) = self.pending.remove(&response.action_id) {
tx.send(response).is_ok()
} else {
false
}
}
pub fn pending_count(&self) -> usize {
self.pending.len() + self.pending_event_lists.len()
}
pub fn cancel_all(&mut self) {
self.pending.clear();
self.pending_event_lists.clear();
}
pub fn register_with_sender(
&mut self,
action_id: String,
tx: tokio::sync::oneshot::Sender<AmiResponse>,
) {
self.pending.insert(action_id, tx);
}
pub fn register_event_list(
&mut self,
action_id: String,
tx: tokio::sync::oneshot::Sender<EventListResponse>,
) {
self.pending_event_lists.insert(
action_id,
PendingEventList {
response: None,
events: Vec::new(),
tx,
},
);
}
pub fn contains_event_list(&self, action_id: &str) -> bool {
self.pending_event_lists.contains_key(action_id)
}
pub fn deliver_event_list_response(&mut self, response: AmiResponse) -> bool {
if let Some(pending) = self.pending_event_lists.get_mut(&response.action_id) {
pending.response = Some(response);
true
} else {
false
}
}
pub fn deliver_event_list_event(
&mut self,
action_id: &str,
event: crate::event::AmiEvent,
) -> bool {
let is_complete = event.is_event_list_complete();
if is_complete {
let Some(mut pending) = self.pending_event_lists.remove(action_id) else {
return false;
};
pending.events.push(event);
let response = match pending.response {
Some(resp) => resp,
None => {
tracing::warn!(action_id, "event list Complete arrived before Response");
AmiResponse {
action_id: action_id.to_string(),
success: false,
response_type: String::new(),
message: Some("event list completed before response received".into()),
headers: HashMap::new(),
output: vec![],
channel_variables: HashMap::new(),
}
}
};
let _ = pending.tx.send(EventListResponse {
response,
events: pending.events,
});
true
} else {
let Some(pending) = self.pending_event_lists.get_mut(action_id) else {
return false;
};
if pending.events.len() >= MAX_EVENT_LIST_EVENTS {
tracing::warn!(
action_id,
count = pending.events.len(),
"event list exceeded {MAX_EVENT_LIST_EVENTS} events, dropping"
);
self.pending_event_lists.remove(action_id);
return true;
}
pending.events.push(event);
true
}
}
}
impl Default for PendingActions {
fn default() -> Self {
Self::new()
}
}