videosdk/resources/sip/
webhooks.rs1use std::sync::Arc;
7
8use futures_util::Stream;
9use reqwest::Method;
10use serde::{Deserialize, Serialize};
11use serde_json::{Map, Value};
12
13use crate::client::{CallOptions, Client};
14use crate::common::MessageResponse;
15use crate::error::{Error, Result};
16use crate::pagination::{auto_page, paginate, ItemMapper, ListParams, Page, PageFetcher};
17use crate::query::QueryBuilder;
18use crate::resources::escape;
19use crate::resources::sip::SipWebhookEvent;
20
21const PATH: &str = "/v2/sip/webhooks";
22
23#[derive(Debug, Clone, Deserialize)]
25#[serde(rename_all = "camelCase")]
26pub struct SipWebhook {
27 #[serde(default, deserialize_with = "crate::common::null_to_default")]
31 pub id: String,
32 pub url: Option<String>,
34 #[serde(default, deserialize_with = "crate::common::null_to_default")]
36 pub events: Vec<SipWebhookEvent>,
37 pub created_at: Option<String>,
39 pub updated_at: Option<String>,
41 #[serde(flatten)]
43 pub extra: Map<String, Value>,
44}
45
46#[derive(Debug, Clone, Default, Serialize)]
48pub struct CreateSipWebhookParams {
49 pub url: String,
51 pub events: Vec<SipWebhookEvent>,
53}
54
55#[derive(Debug, Clone, Default)]
57pub struct SipWebhookListParams {
58 pub page: Option<u32>,
60 pub per_page: Option<u32>,
62 pub cursor: Option<String>,
64 pub search: Option<String>,
66}
67
68impl SipWebhookListParams {
69 fn pagination(&self) -> ListParams {
70 ListParams {
71 page: self.page,
72 per_page: self.per_page,
73 cursor: self.cursor.clone(),
74 }
75 }
76}
77
78fn webhook_item_mapper() -> ItemMapper<SipWebhook> {
81 Arc::new(|value: Value| {
82 let mut webhook: SipWebhook =
83 serde_json::from_value(value).map_err(|e| Error::decode("SIP webhook list item", e))?;
84 if webhook.id.is_empty() {
85 if let Some(webhook_id) = webhook.extra.get("webhookId").and_then(Value::as_str) {
86 webhook.id = webhook_id.to_string();
87 }
88 }
89 Ok(webhook)
90 })
91}
92
93#[derive(Debug, Clone, Copy)]
95pub struct SipWebhooksResource<'a> {
96 client: &'a Client,
97}
98
99impl<'a> SipWebhooksResource<'a> {
100 pub(crate) fn new(client: &'a Client) -> Self {
101 Self { client }
102 }
103
104 pub async fn create(&self, params: CreateSipWebhookParams) -> Result<SipWebhook> {
106 self.client
107 .json(Method::POST, PATH, CallOptions::json(¶ms)?)
108 .await
109 }
110
111 pub async fn list(&self, params: SipWebhookListParams) -> Result<Page<SipWebhook>> {
113 paginate(
114 self.fetcher(¶ms),
115 ¶ms.pagination(),
116 "data",
117 Some(webhook_item_mapper()),
118 )
119 .await
120 }
121
122 pub fn list_stream(
124 &self,
125 params: SipWebhookListParams,
126 ) -> impl Stream<Item = Result<SipWebhook>> + Send {
127 auto_page(
128 self.fetcher(¶ms),
129 params.pagination(),
130 "data",
131 Some(webhook_item_mapper()),
132 )
133 }
134
135 pub async fn get(&self, webhook_id: &str) -> Result<SipWebhook> {
137 let path = format!("{PATH}/{}", escape(webhook_id));
138 self.client
139 .json(Method::GET, &path, CallOptions::new())
140 .await
141 }
142
143 pub async fn update(
145 &self,
146 webhook_id: &str,
147 params: CreateSipWebhookParams,
148 ) -> Result<SipWebhook> {
149 let path = format!("{PATH}/{}", escape(webhook_id));
150 self.client
151 .json(Method::PATCH, &path, CallOptions::json(¶ms)?)
152 .await
153 }
154
155 pub async fn delete(&self, webhook_id: &str) -> Result<MessageResponse> {
157 let path = format!("{PATH}/{}", escape(webhook_id));
158 self.client
159 .json(Method::DELETE, &path, CallOptions::new())
160 .await
161 }
162
163 fn fetcher(&self, params: &SipWebhookListParams) -> PageFetcher {
164 let client = self.client.clone();
165 let search = params.search.clone();
166 Arc::new(move |page, per_page| {
167 let client = client.clone();
168 let search = search.clone();
169 Box::pin(async move {
170 let query = QueryBuilder::new()
171 .opt("page", page)
172 .opt("perPage", per_page)
173 .opt_str("search", search.as_deref())
174 .into_pairs();
175 client
176 .json::<Value>(Method::GET, PATH, CallOptions::new().query(query))
177 .await
178 })
179 })
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use serde_json::json;
187
188 #[test]
189 fn the_list_mapper_surfaces_webhook_id_as_id() {
190 let mapper = webhook_item_mapper();
191 let webhook = mapper(json!({"webhookId": "w-1", "url": "https://x"})).unwrap();
192 assert_eq!(webhook.id, "w-1");
193
194 let webhook = mapper(json!({"id": "real", "webhookId": "w-1"})).unwrap();
195 assert_eq!(webhook.id, "real");
196 }
197
198 #[test]
199 fn create_params_serialize_url_and_events() {
200 let body = serde_json::to_value(CreateSipWebhookParams {
201 url: "https://example.com/sip".into(),
202 events: vec![SipWebhookEvent::CALL_STARTED, SipWebhookEvent::CALL_HANGUP],
203 })
204 .unwrap();
205 assert_eq!(
206 body,
207 json!({"url": "https://example.com/sip", "events": ["call-started", "call-hangup"]})
208 );
209 }
210}