1use crate::v1::{Client, error, BASE_URL, standard};
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Deserialize, Debug)]
8pub struct ListWebhooksResponse {
9    pub data: Vec<WebhookResource>,
11    pub links: ResponseLinks,
12}
13
14#[derive(Deserialize, Debug)]
15pub struct GetWebhookResponse {
16    pub data: WebhookResource,
18}
19
20#[derive(Deserialize, Debug)]
21pub struct CreateWebhookResponse {
22    pub data: WebhookResource,
24}
25
26#[derive(Deserialize, Debug)]
27pub struct WebhookResource {
28    pub r#type: String,
30    pub id: String,
32    pub attributes: Attributes,
33    pub relationships: Relationships,
34    pub links: WebhookResourceLinks,
35}
36
37#[derive(Deserialize, Debug)]
38#[serde(rename_all = "camelCase")]
39pub struct Attributes {
40    pub url: String,
42    pub description: Option<String>,
45    pub secret_key: Option<String>,
56    pub created_at: String,
58}
59
60#[derive(Deserialize, Debug)]
61pub struct Relationships {
62    pub logs: Logs,
63}
64
65#[derive(Deserialize, Debug)]
66pub struct Logs {
67    pub links: Option<LogsLinks>,
68}
69
70#[derive(Deserialize, Debug)]
71pub struct LogsLinks {
72    pub related: String,
74}
75
76#[derive(Deserialize, Debug)]
77pub struct WebhookResourceLinks {
78    #[serde(rename = "self")]
80    pub this: String,
81}
82
83#[derive(Deserialize, Debug)]
84pub struct ResponseLinks {
85    pub prev: Option<String>,
88    pub next: Option<String>,
91}
92
93#[derive(Deserialize, Debug)]
94pub struct PingWebhookResponse {
95    pub data: WebhookEventResource,
97}
98
99#[derive(Deserialize, Debug)]
100pub struct WebhookEventResource {
101    pub r#type: String,
103    pub id: String,
106    pub attributes: EventAttributes,
107    pub relationships: EventRelationships,
108}
109
110#[derive(Deserialize, Debug)]
111pub struct EventRelationships {
112    pub webhook: Webhook,
113    pub transaction: Option<Transaction>,
114}
115
116#[derive(Deserialize, Debug)]
117pub struct Transaction {
118    pub data: TransactionData,
119    pub links: Option<TransactionLinks>,
120}
121
122#[derive(Deserialize, Debug)]
123pub struct Webhook {
124    pub data: WebhookData,
125    pub links: Option<WebhookLinks>,
126}
127
128#[derive(Deserialize, Debug)]
129pub struct WebhookData {
130    pub r#type: String,
132    pub id: String,
134}
135
136#[derive(Deserialize, Debug)]
137pub struct WebhookLinks {
138    pub related: String,
140}
141
142#[derive(Deserialize, Debug)]
143pub struct TransactionData {
144    pub r#type: String,
146    pub id: String,
148}
149
150#[derive(Deserialize, Debug)]
151pub struct TransactionLinks {
152    pub related: String,
154}
155
156#[derive(Deserialize, Debug)]
157#[serde(rename_all = "camelCase")]
158pub struct EventAttributes {
159    pub event_type: standard::WebhookEventTypeEnum,
162    pub created_at: String,
164}
165
166
167#[derive(Deserialize, Debug)]
168pub struct ListWebhookLogsResponse {
169    pub data: Vec<WebhookDeliveryLogResource>,
171    pub links: LogsResponseLinks,
172}
173
174#[derive(Deserialize, Debug)]
175pub struct WebhookDeliveryLogResource {
176    pub r#type: String,
178    pub id: String,
180    pub attributes: DeliveryLogAttributes,
181    pub relationships: DeliveryLogRelationships,
182}
183
184#[derive(Deserialize, Debug)]
185#[serde(rename_all = "camelCase")]
186pub struct DeliveryLogRelationships {
187    pub webhook_event: WebhookEvent,
188}
189
190#[derive(Deserialize, Debug)]
191pub struct WebhookEvent {
192    pub data: WebhookEventData
193}
194
195#[derive(Deserialize, Debug)]
196pub struct WebhookEventData {
197    pub r#type: String,
199    pub id: String,
201}
202
203#[derive(Deserialize, Debug)]
204#[serde(rename_all = "camelCase")]
205pub struct DeliveryLogAttributes {
206    pub request: Request,
208    pub response: Option<Response>,
210    pub delivery_status: standard::WebhookDeliveryStatusEnum,
212    pub created_at: String,
214}
215
216#[derive(Deserialize, Debug)]
217pub struct Request {
218    pub body: String,
220}
221
222#[derive(Deserialize, Debug)]
223#[serde(rename_all = "camelCase")]
224pub struct Response {
225    pub status_code: i64,
227    pub body: String,
229}
230
231#[derive(Deserialize, Debug)]
232pub struct LogsResponseLinks {
233    pub prev: Option<String>,
236    pub next: Option<String>,
239}
240
241
242#[derive(Default)]
245pub struct ListWebhooksOptions {
246    page_size: Option<u8>,
248}
249
250impl ListWebhooksOptions {
251    pub fn page_size(&mut self, value: u8) {
253        self.page_size = Some(value);
254    }
255
256    fn add_params(&self, url: &mut reqwest::Url) {
257        let mut query = String::new();
258
259        if let Some(value) = &self.page_size {
260            if !query.is_empty() {
261                query.push('&');
262            }
263            query.push_str(&format!("page[size]={}", value));
264        }
265
266        if !query.is_empty() {
267            url.set_query(Some(&query));
268        }
269    }
270}
271
272#[derive(Default)]
273pub struct ListWebhookLogsOptions {
274    page_size: Option<u8>,
276}
277
278impl ListWebhookLogsOptions {
279    pub fn page_size(&mut self, value: u8) {
281        self.page_size = Some(value);
282    }
283
284    fn add_params(&self, url: &mut reqwest::Url) {
285        let mut query = String::new();
286
287        if let Some(value) = &self.page_size {
288            if !query.is_empty() {
289                query.push('&');
290            }
291            query.push_str(&format!("page[size]={}", value));
292        }
293
294        if !query.is_empty() {
295            url.set_query(Some(&query));
296        }
297    }
298}
299
300#[derive(Serialize)]
303pub struct CreateWebhookRequest {
304    pub data: WebhookInputResource,
306}
307
308#[derive(Serialize)]
309pub struct WebhookInputResource {
310    pub attributes: InputAttributes,
311}
312
313#[derive(Serialize)]
314pub struct InputAttributes {
315    pub url: String,
318    pub description: Option<String>,
320}
321
322impl Client {
323    pub async fn list_webhooks(
327        &self,
328        options: &ListWebhooksOptions,
329    ) -> Result<ListWebhooksResponse, error::Error> {
330        let mut url = reqwest::Url::parse(
331            &format!("{}/webhooks", BASE_URL)
332        ).map_err(error::Error::UrlParse)?;
333        options.add_params(&mut url);
334
335        let res = reqwest::Client::new()
336            .get(url)
337            .header("Authorization", self.auth_header())
338            .send()
339            .await
340            .map_err(error::Error::Request)?;
341
342        match res.status() {
343            reqwest::StatusCode::OK => {
344                let body =
345                    res.text()
346                    .await
347                    .map_err(error::Error::BodyRead)?;
348                let webhook_response: ListWebhooksResponse =
349                    serde_json::from_str(&body)
350                    .map_err(error::Error::Json)?;
351
352                Ok(webhook_response)
353            },
354            _ => {
355                let body =
356                    res.text()
357                    .await
358                    .map_err(error::Error::BodyRead)?;
359                let error: error::ErrorResponse =
360                    serde_json::from_str(&body)
361                    .map_err(error::Error::Json)?;
362
363                Err(error::Error::Api(error))
364            }
365        }
366    }
367
368    pub async fn get_webhook(
370        &self,
371        id: &str,
372    ) -> Result<GetWebhookResponse, error::Error> {
373        if id.is_empty() {
377            panic!("The provided webhook ID must not be empty.");
378        }
379
380        let url = reqwest::Url::parse(
381            &format!("{}/webhooks/{}", BASE_URL, id)
382        ).map_err(error::Error::UrlParse)?;
383
384        let res = reqwest::Client::new()
385            .get(url)
386            .header("Authorization", self.auth_header())
387            .send()
388            .await
389            .map_err(error::Error::Request)?;
390
391        match res.status() {
392            reqwest::StatusCode::OK => {
393                let body =
394                    res.text()
395                    .await
396                    .map_err(error::Error::BodyRead)?;
397                let webhook_response: GetWebhookResponse =
398                    serde_json::from_str(&body)
399                    .map_err(error::Error::Json)?;
400
401                Ok(webhook_response)
402            },
403            _ => {
404                let body =
405                    res.text()
406                    .await
407                    .map_err(error::Error::BodyRead)?;
408                let error: error::ErrorResponse =
409                    serde_json::from_str(&body)
410                    .map_err(error::Error::Json)?;
411
412                Err(error::Error::Api(error))
413            }
414        }
415    }
416
417    pub async fn create_webhook(
437        &self,
438        webhook_url: &str,
439        description: Option<String>,
440    ) -> Result<CreateWebhookResponse, error::Error> {
441        let url = reqwest::Url::parse(
442            &format!("{}/webhooks", BASE_URL)
443        ).map_err(error::Error::UrlParse)?;
444
445        let body = CreateWebhookRequest {
446            data: WebhookInputResource {
447                attributes: InputAttributes {
448                    url: String::from(webhook_url),
449                    description,
450                }
451            }
452        };
453
454        let body =
455            serde_json::to_string(&body)
456            .map_err(error::Error::Serialize)?;
457
458        let res = reqwest::Client::new()
459            .post(url)
460            .header("Authorization", self.auth_header())
461            .header("Content-Type", "application/json")
462            .body(body)
463            .send()
464            .await
465            .map_err(error::Error::Request)?;
466
467        match res.status() {
468            reqwest::StatusCode::CREATED => {
469                let body =
470                    res.text()
471                    .await
472                    .map_err(error::Error::BodyRead)?;
473                let webhook_response: CreateWebhookResponse =
474                    serde_json::from_str(&body)
475                    .map_err(error::Error::Json)?;
476
477                Ok(webhook_response)
478            },
479            _ => {
480                let body =
481                    res.text()
482                    .await
483                    .map_err(error::Error::BodyRead)?;
484                let error: error::ErrorResponse =
485                    serde_json::from_str(&body)
486                    .map_err(error::Error::Json)?;
487
488                Err(error::Error::Api(error))
489            }
490        }
491    }
492
493    pub async fn delete_webhook(&self, id: &str) -> Result<(), error::Error> {
496        let url = reqwest::Url::parse(
497            &format!("{}/webhooks/{}", BASE_URL, id)
498        ).map_err(error::Error::UrlParse)?;
499
500        let res = reqwest::Client::new()
501            .delete(url)
502            .header("Authorization", self.auth_header())
503            .send()
504            .await
505            .map_err(error::Error::Request)?;
506
507        match res.status() {
508            reqwest::StatusCode::NO_CONTENT => {
509                Ok(())
510            },
511            _ => {
512                let body =
513                    res.text()
514                    .await
515                    .map_err(error::Error::BodyRead)?;
516                let error: error::ErrorResponse =
517                    serde_json::from_str(&body)
518                    .map_err(error::Error::Json)?;
519
520                Err(error::Error::Api(error))
521            }
522        }
523    }
524
525    pub async fn ping_webhook(
530        &self,
531        id: &str,
532    ) -> Result<PingWebhookResponse, error::Error> {
533        let url = reqwest::Url::parse(
534            &format!("{}/webhooks/{}/ping", BASE_URL, id)
535        ).map_err(error::Error::UrlParse)?;
536
537        let res = reqwest::Client::new()
538            .post(url)
539            .header("Authorization", self.auth_header())
540            .header("Content-Type", "application/json")
541            .header("Content-Length", "0")
542            .send()
543            .await
544            .map_err(error::Error::Request)?;
545
546        match res.status() {
547            reqwest::StatusCode::CREATED => {
548                let body =
549                    res.text()
550                    .await
551                    .map_err(error::Error::BodyRead)?;
552                let webhook_response: PingWebhookResponse =
553                    serde_json::from_str(&body)
554                    .map_err(error::Error::Json)?;
555
556                Ok(webhook_response)
557            },
558            _ => {
559                let body =
560                    res.text()
561                    .await
562                    .map_err(error::Error::BodyRead)?;
563                let error: error::ErrorResponse =
564                    serde_json::from_str(&body)
565                    .map_err(error::Error::Json)?;
566
567                Err(error::Error::Api(error))
568            }
569        }
570    }
571
572    pub async fn list_webhook_logs(
578        &self,
579        id: &str,
580        options: &ListWebhookLogsOptions,
581    ) -> Result<ListWebhookLogsResponse, error::Error> {
582        let mut url = reqwest::Url::parse(
583            &format!("{}/webhooks/{}/logs", BASE_URL, id)
584        ).map_err(error::Error::UrlParse)?;
585        options.add_params(&mut url);
586
587        let res = reqwest::Client::new()
588            .get(url)
589            .header("Authorization", self.auth_header())
590            .send()
591            .await
592            .map_err(error::Error::Request)?;
593
594        match res.status() {
595            reqwest::StatusCode::OK => {
596                let body =
597                    res.text()
598                    .await
599                    .map_err(error::Error::BodyRead)?;
600                let webhook_response: ListWebhookLogsResponse =
601                    serde_json::from_str(&body)
602                    .map_err(error::Error::Json)?;
603
604                Ok(webhook_response)
605            },
606            _ => {
607                let body =
608                    res.text()
609                    .await
610                    .map_err(error::Error::BodyRead)?;
611                let error: error::ErrorResponse =
612                    serde_json::from_str(&body)
613                    .map_err(error::Error::Json)?;
614
615                Err(error::Error::Api(error))
616            }
617        }
618    }
619}
620
621implement_pagination_v1!(ListWebhooksResponse);