iarapi_rs/
lib.rs

1use async_trait::async_trait;
2use serde::Deserialize;
3use serde_json::json;
4use std::error::Error;
5use std::fmt;
6
7/// A trait for abstracting the HTTP client implementation, allowing flexibility in runtime selection.
8#[async_trait]
9pub trait HttpClient {
10    async fn get(&self, url: &str) -> Result<String, ApiError>;
11    async fn post(&self, url: &str, json_body: &str) -> Result<String, ApiError>;
12    async fn post_form(&self, url: &str, form_data: &[(&str, &str)]) -> Result<String, ApiError>;
13}
14
15/// Error type for API interactions, implemented with `std::error::Error`.
16#[derive(Debug)]
17pub enum ApiError {
18    AuthenticationError,
19    RequestError(String),
20    ParsingError(String),
21}
22
23impl fmt::Display for ApiError {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        match self {
26            ApiError::AuthenticationError => write!(f, "Authentication error"),
27            ApiError::RequestError(msg) => write!(f, "Request error: {}", msg),
28            ApiError::ParsingError(msg) => write!(f, "Parsing error: {}", msg),
29        }
30    }
31}
32
33impl Error for ApiError {}
34
35/// Main struct for interacting with the IAmResponding API.
36pub struct IamRespondingAPI<C> {
37    client: C,
38    token_for_api: Option<String>, // Optional because it may not be populated until after login
39    member_id: Option<i64>,         // Optional for the same reason
40}
41
42impl<C> IamRespondingAPI<C>
43where
44    C: HttpClient + Send + Sync,
45{
46    pub fn new(client: C) -> Self {
47        Self {
48            client,
49            token_for_api: None,
50            member_id: None,
51        }
52    }
53
54    /// Logs in to the IAmResponding API and stores token and member_id for future requests.
55    pub async fn login(
56        &mut self,
57        agency: &str,
58        user: &str,
59        pass: &str,
60    ) -> Result<(), ApiError> {
61        let login_params = json!({
62            "memberLogin": true,
63            "agencyName": agency,
64            "memberfname": user,
65            "memberpwd": pass,
66            "rememberPwd": false,
67            "urlTo": "",
68            "overrideSession": true,
69        });
70
71        let serialized_params = serde_json::to_string(&login_params)
72            .map_err(|e| ApiError::ParsingError(e.to_string()))?;
73
74        let response = self
75            .client
76            .post(
77                "https://iamresponding.com/v3/Pages/memberlogin.aspx/ValidateLoginInfo",
78                &serialized_params,
79            )
80            .await?;
81
82        if response.contains("The log-in information that you have entered is incorrect.") {
83            return Err(ApiError::AuthenticationError);
84        }
85
86        // Example parsing of token and member ID from the response
87        // Assuming `token_for_api` and `member_id` are found in the response body
88        // Adjust based on actual response format
89        let login_data: LoginResponse = serde_json::from_str(&response)
90            .map_err(|e| ApiError::ParsingError(e.to_string()))?;
91        
92        self.token_for_api = Some(login_data.token_for_api);
93        self.member_id = Some(login_data.member_id);
94
95        Ok(())
96    }
97
98    /// Fetches the currently responding members.
99    pub async fn get_now_responding(&self) -> Result<Vec<NowResponding>, ApiError> {
100        let response = self
101            .client
102            .post_form("https://iamresponding.com/v3/AgencyServices.asmx/GetNowRespondingWithSort", &[])
103            .await?;
104
105        let data: NowRespondingResponse = serde_json::from_str(&response)
106            .map_err(|e| ApiError::ParsingError(e.to_string()))?;
107
108        Ok(data.now_responding)
109    }
110
111    /// Fetches scheduled members.
112    pub async fn get_on_schedule(&self) -> Result<Vec<OnSchedule>, ApiError> {
113        let response = self
114            .client
115            .post_form("https://iamresponding.com/v3/AgencyServices.asmx/GetOnScheduleWithSort", &[])
116            .await?;
117
118        let data: OnScheduleResponse = serde_json::from_str(&response)
119            .map_err(|e| ApiError::ParsingError(e.to_string()))?;
120
121        Ok(data.on_schedule)
122    }
123
124    /// Fetches dispatch messages.
125    pub async fn list_dispatch_messages(&self) -> Result<Vec<DispatchMessage>, ApiError> {
126        let response = self
127            .client
128            .post_form("https://iamresponding.com/v3/DispatchMessages.asmx/ListWithParser", &[])
129            .await?;
130
131        let data: DispatchMessageResponse = serde_json::from_str(&response)
132            .map_err(|e| ApiError::ParsingError(e.to_string()))?;
133
134        Ok(data.dispatch_messages)
135    }
136
137    /// Fetches specific incident information.
138    pub async fn get_incident_info(&self, incident_id: i64) -> Result<IncidentInfoData, ApiError> {
139        let params = json!({
140            "messageID": incident_id,
141            "token": self.token_for_api,
142        });
143
144        let serialized_params = serde_json::to_string(&params)
145            .map_err(|e| ApiError::ParsingError(e.to_string()))?;
146
147        let response = self
148            .client
149            .post("https://iamresponding.com/v3/agency/IncidentsDashboard.aspx/GetIncidentInfo", &serialized_params)
150            .await?;
151
152        let data: IncidentInfoResponse = serde_json::from_str(&response)
153            .map_err(|e| ApiError::ParsingError(e.to_string()))?;
154
155        data.data.into_iter().next().ok_or(ApiError::ParsingError("No data for incident".to_string()))
156    }
157
158    // Example of one of the methods using `token_for_api` and `member_id`
159    pub async fn get_latest_incidents(&self) -> Result<Vec<IncidentInfoData>, ApiError> {
160        let token = self.token_for_api.as_deref().ok_or(ApiError::AuthenticationError)?;
161        let member_id = self.member_id.ok_or(ApiError::AuthenticationError)?;
162
163        let params = json!({
164            "memberID": member_id,
165            "token": token,
166        });
167
168        let serialized_params = serde_json::to_string(&params)
169            .map_err(|e| ApiError::ParsingError(e.to_string()))?;
170
171        let response = self
172            .client
173            .post("https://iamresponding.com/v3/agency/IncidentsDashboard.aspx/GetLatestIncidents", &serialized_params)
174            .await?;
175
176        let data: IncidentInfoResponse = serde_json::from_str(&response)
177            .map_err(|e| ApiError::ParsingError(e.to_string()))?;
178
179        Ok(data.data)
180    }
181
182    /// Fetches reminders for scheduled events or meetings for a specific member.
183    pub async fn get_reminders_by_member(&self) -> Result<Vec<EventReminder>, ApiError> {
184        let token = self.token_for_api.as_deref().ok_or(ApiError::AuthenticationError)?;
185        let member_id = self.member_id.ok_or(ApiError::AuthenticationError)?;
186
187        let member_id_str = member_id.to_string();
188        let days_str = "7".to_string();
189
190        let form_data = [
191            ("subsString", &member_id_str as &str),
192            ("days", &days_str as &str),  // Convert to &str to match the expected type
193        ];
194
195        let response = self
196            .client
197            .post_form("https://iamresponding.com/v3/AgencyServices.asmx/GetRemindersByMember", &form_data)
198            .await?;
199
200        let data: RemindersResponse = serde_json::from_str(&response)
201            .map_err(|e| ApiError::ParsingError(e.to_string()))?;
202
203        Ok(data.reminders)
204    }
205}
206
207// Login response struct to extract token and member_id from login response.
208#[derive(Debug, Deserialize)]
209struct LoginResponse {
210    token_for_api: String,
211    member_id: i64,
212}
213
214/// Data structure for deserializing response data.
215#[derive(Debug, Deserialize)]
216struct NowRespondingResponse {
217    now_responding: Vec<NowResponding>,
218}
219
220#[derive(Debug, Deserialize)]
221pub struct NowResponding {
222    pub member_name: String,
223}
224
225#[derive(Debug, Deserialize)]
226struct OnScheduleResponse {
227    on_schedule: Vec<OnSchedule>,
228}
229
230#[derive(Debug, Deserialize)]
231pub struct OnSchedule {
232    pub member_name: String,
233}
234
235#[derive(Debug, Deserialize)]
236struct DispatchMessageResponse {
237    dispatch_messages: Vec<DispatchMessage>,
238}
239
240#[derive(Debug, Deserialize)]
241pub struct DispatchMessage {
242    pub message_body: String,
243    pub address: Option<String>,
244}
245
246#[derive(Debug, Deserialize)]
247struct IncidentInfoResponse {
248    data: Vec<IncidentInfoData>,
249}
250
251#[derive(Debug, Deserialize)]
252pub struct IncidentInfoData {
253    pub id: i64,
254    pub incident_type: String,
255}
256
257/// Data structure for deserializing response data from `get_reminders_by_member`.
258#[derive(Debug, Deserialize)]
259struct RemindersResponse {
260    reminders: Vec<EventReminder>,
261}
262
263#[derive(Debug, Deserialize)]
264pub struct EventReminder {
265    pub event_name: String,
266    pub date: String,
267    pub time: String,
268}