ift_webhook/
lib.rs

1mod test;
2
3#[cfg(feature = "non-blocking")]
4use std::collections::HashMap;
5#[cfg(feature = "delay")]
6use tokio::task::JoinHandle;
7#[cfg(feature = "delay")]
8use tokio::time::sleep;
9#[cfg(feature = "blocking")]
10use ureq::{SerdeMap, SerdeValue};
11
12#[cfg(feature = "delay")]
13pub type DelayResultHandler = tokio::task::JoinHandle<Result<(), Error>>;
14
15///
16#[cfg(feature = "blocking")]
17#[derive(Debug, Clone)]
18pub struct IftWHClient {
19    client: ureq::Agent,
20    api_key: String,
21}
22
23pub struct WebHookData {
24    value1: Option<String>,
25    value2: Option<String>,
26    value3: Option<String>,
27}
28
29impl WebHookData {
30    pub fn new(value1: Option<&str>, value2: Option<&str>, value3: Option<&str>) -> Option<Self> {
31        Some(Self {
32            value1: value1.map(|s| s.to_string()),
33            value2: value2.map(|s| s.to_string()),
34            value3: value3.map(|s| s.to_string()),
35        })
36    }
37}
38
39///Blocking api client
40#[cfg(feature = "blocking")]
41impl IftWHClient {
42    pub fn new(api_key: &str) -> Self {
43        let client = ureq::Agent::new();
44        Self {
45            client,
46            api_key: api_key.to_string(),
47        }
48    }
49
50    pub fn trigger(&self, event_name: &str, data: Option<WebHookData>) -> Result<(), Error> {
51        let url = format!(
52            "https://maker.ifttt.com/trigger/{event}/with/key/{key}",
53            event = event_name,
54            key = self.api_key
55        );
56        match data {
57            Some(data) => {
58                let value = make_serde_value(data);
59                let res = self
60                    .client
61                    .post(&url)
62                    .set("Content-Type", "application/json")
63                    .send_json(value)?;
64                if res.status() != 200 {
65                    return Err(Error::IftttResponseError);
66                }
67                return Ok(());
68            }
69            None => {
70                let res = self.client.post(&url).call()?;
71                if res.status() != 200 {
72                    return Err(Error::IftttResponseError);
73                }
74                return Ok(());
75            }
76        }
77    }
78}
79
80/// async api client
81#[cfg(feature = "non-blocking")]
82#[derive(Debug, Clone)]
83pub struct AsyncIftWHClient {
84    client: reqwest::Client,
85    api_key: String,
86}
87
88#[cfg(feature = "non-blocking")]
89impl AsyncIftWHClient {
90    pub fn new(api_key: &str) -> Self {
91        let client = reqwest::Client::new();
92        Self {
93            client,
94            api_key: api_key.to_string(),
95        }
96    }
97
98    pub async fn trigger(&self, event_name: &str, data: Option<WebHookData>) -> Result<(), Error> {
99        let url = format!(
100            "https://maker.ifttt.com/trigger/{event}/with/key/{key}",
101            event = event_name,
102            key = self.api_key
103        );
104        match data {
105            Some(data) => {
106                let map = nonblocking_make_serde_value(data);
107                let res = self.client.post(&url).json(&map).send().await?;
108
109                if res.status() != reqwest::StatusCode::OK {
110                    return Err(Error::IftttResponseError);
111                }
112                return Ok(());
113            }
114            None => {
115                let res = self.client.post(&url).send().await?;
116                if res.status() != reqwest::StatusCode::OK {
117                    return Err(Error::IftttResponseError);
118                }
119                return Ok(());
120            }
121        }
122    }
123
124    ///this delay function will take ownership of the client.
125    #[cfg(feature = "delay")]
126    pub fn trigger_with_delay(
127        self,
128        event_name: &str,
129        data: Option<WebHookData>,
130        delay_time: std::time::Duration,
131    ) -> DelayResultHandler {
132        let url = format!(
133            "https://maker.ifttt.com/trigger/{event}/with/key/{key}",
134            event = event_name,
135            key = self.api_key
136        );
137        match data {
138            Some(data) => {
139                let map = nonblocking_make_serde_value(data);
140                let handler: JoinHandle<Result<(), Error>> = tokio::spawn(async move {
141                    sleep(delay_time).await;
142                    let res = self.client.post(&url).json(&map).send().await?;
143                    if res.status() != reqwest::StatusCode::OK {
144                        return Err(Error::IftttResponseError);
145                    };
146                    Ok(())
147                });
148                return handler;
149            }
150            None => {
151                let handler: JoinHandle<Result<(), Error>> = tokio::spawn(async move {
152                    sleep(delay_time).await;
153                    let res = self.client.post(&url).send().await?;
154                    if res.status() != reqwest::StatusCode::OK {
155                        return Err(Error::IftttResponseError);
156                    }
157                    Ok(())
158                });
159                return handler;
160            }
161        }
162    }
163}
164
165#[derive(Debug)]
166pub enum Error {
167    #[cfg(feature = "blocking")]
168    BlockingRequestError(ureq::Error),
169    #[cfg(feature = "non-blocking")]
170    NonBlockingRequestError(reqwest::Error),
171    IftttResponseError,
172}
173#[cfg(feature = "blocking")]
174impl From<ureq::Error> for Error {
175    fn from(e: ureq::Error) -> Self {
176        Self::BlockingRequestError(e)
177    }
178}
179#[cfg(feature = "non-blocking")]
180impl From<reqwest::Error> for Error {
181    fn from(e: reqwest::Error) -> Self {
182        Self::NonBlockingRequestError(e)
183    }
184}
185
186#[inline]
187#[cfg(feature = "blocking")]
188fn make_serde_value(data: WebHookData) -> SerdeValue {
189    let mut map = SerdeMap::new();
190    if let Some(value1) = data.value1 {
191        map.insert("value1".to_string(), SerdeValue::String(value1));
192    }
193    if let Some(value2) = data.value2 {
194        map.insert("value2".to_string(), SerdeValue::String(value2));
195    }
196    if let Some(value3) = data.value3 {
197        map.insert("value3".to_string(), SerdeValue::String(value3));
198    }
199    let value = SerdeValue::Object(map);
200    value
201}
202
203#[inline]
204#[cfg(feature = "non-blocking")]
205fn nonblocking_make_serde_value(data: WebHookData) -> HashMap<String, String> {
206    let mut map: HashMap<String, String> = HashMap::new();
207    if let Some(value1) = data.value1 {
208        map.insert("value1".to_string(), value1);
209    }
210    if let Some(value2) = data.value2 {
211        map.insert("value2".to_string(), value2);
212    }
213    if let Some(value3) = data.value3 {
214        map.insert("value3".to_string(), value3);
215    }
216    map
217}