use std::sync::Arc;
use futures_util::Stream;
use reqwest::Method;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use crate::client::{CallOptions, Client};
use crate::common::MessageResponse;
use crate::error::{Error, Result};
use crate::pagination::{auto_page, paginate, ItemMapper, ListParams, Page, PageFetcher};
use crate::query::QueryBuilder;
use crate::resources::escape;
use crate::resources::sip::SipWebhookEvent;
const PATH: &str = "/v2/sip/webhooks";
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SipWebhook {
#[serde(default, deserialize_with = "crate::common::null_to_default")]
pub id: String,
pub url: Option<String>,
#[serde(default, deserialize_with = "crate::common::null_to_default")]
pub events: Vec<SipWebhookEvent>,
pub created_at: Option<String>,
pub updated_at: Option<String>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct CreateSipWebhookParams {
pub url: String,
pub events: Vec<SipWebhookEvent>,
}
#[derive(Debug, Clone, Default)]
pub struct SipWebhookListParams {
pub page: Option<u32>,
pub per_page: Option<u32>,
pub cursor: Option<String>,
pub search: Option<String>,
}
impl SipWebhookListParams {
fn pagination(&self) -> ListParams {
ListParams {
page: self.page,
per_page: self.per_page,
cursor: self.cursor.clone(),
}
}
}
fn webhook_item_mapper() -> ItemMapper<SipWebhook> {
Arc::new(|value: Value| {
let mut webhook: SipWebhook =
serde_json::from_value(value).map_err(|e| Error::decode("SIP webhook list item", e))?;
if webhook.id.is_empty() {
if let Some(webhook_id) = webhook.extra.get("webhookId").and_then(Value::as_str) {
webhook.id = webhook_id.to_string();
}
}
Ok(webhook)
})
}
#[derive(Debug, Clone, Copy)]
pub struct SipWebhooksResource<'a> {
client: &'a Client,
}
impl<'a> SipWebhooksResource<'a> {
pub(crate) fn new(client: &'a Client) -> Self {
Self { client }
}
pub async fn create(&self, params: CreateSipWebhookParams) -> Result<SipWebhook> {
self.client
.json(Method::POST, PATH, CallOptions::json(¶ms)?)
.await
}
pub async fn list(&self, params: SipWebhookListParams) -> Result<Page<SipWebhook>> {
paginate(
self.fetcher(¶ms),
¶ms.pagination(),
"data",
Some(webhook_item_mapper()),
)
.await
}
pub fn list_stream(
&self,
params: SipWebhookListParams,
) -> impl Stream<Item = Result<SipWebhook>> + Send {
auto_page(
self.fetcher(¶ms),
params.pagination(),
"data",
Some(webhook_item_mapper()),
)
}
pub async fn get(&self, webhook_id: &str) -> Result<SipWebhook> {
let path = format!("{PATH}/{}", escape(webhook_id));
self.client
.json(Method::GET, &path, CallOptions::new())
.await
}
pub async fn update(
&self,
webhook_id: &str,
params: CreateSipWebhookParams,
) -> Result<SipWebhook> {
let path = format!("{PATH}/{}", escape(webhook_id));
self.client
.json(Method::PATCH, &path, CallOptions::json(¶ms)?)
.await
}
pub async fn delete(&self, webhook_id: &str) -> Result<MessageResponse> {
let path = format!("{PATH}/{}", escape(webhook_id));
self.client
.json(Method::DELETE, &path, CallOptions::new())
.await
}
fn fetcher(&self, params: &SipWebhookListParams) -> PageFetcher {
let client = self.client.clone();
let search = params.search.clone();
Arc::new(move |page, per_page| {
let client = client.clone();
let search = search.clone();
Box::pin(async move {
let query = QueryBuilder::new()
.opt("page", page)
.opt("perPage", per_page)
.opt_str("search", search.as_deref())
.into_pairs();
client
.json::<Value>(Method::GET, PATH, CallOptions::new().query(query))
.await
})
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn the_list_mapper_surfaces_webhook_id_as_id() {
let mapper = webhook_item_mapper();
let webhook = mapper(json!({"webhookId": "w-1", "url": "https://x"})).unwrap();
assert_eq!(webhook.id, "w-1");
let webhook = mapper(json!({"id": "real", "webhookId": "w-1"})).unwrap();
assert_eq!(webhook.id, "real");
}
#[test]
fn create_params_serialize_url_and_events() {
let body = serde_json::to_value(CreateSipWebhookParams {
url: "https://example.com/sip".into(),
events: vec![SipWebhookEvent::CALL_STARTED, SipWebhookEvent::CALL_HANGUP],
})
.unwrap();
assert_eq!(
body,
json!({"url": "https://example.com/sip", "events": ["call-started", "call-hangup"]})
);
}
}