use async_trait::async_trait;
use serde::Deserialize;
use serde_json::json;
use std::error::Error;
use std::fmt;
#[async_trait]
pub trait HttpClient {
async fn get(&self, url: &str) -> Result<String, ApiError>;
async fn post(&self, url: &str, json_body: &str) -> Result<String, ApiError>;
async fn post_form(&self, url: &str, form_data: &[(&str, &str)]) -> Result<String, ApiError>;
}
#[derive(Debug)]
pub enum ApiError {
AuthenticationError,
RequestError(String),
ParsingError(String),
}
impl fmt::Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ApiError::AuthenticationError => write!(f, "Authentication error"),
ApiError::RequestError(msg) => write!(f, "Request error: {}", msg),
ApiError::ParsingError(msg) => write!(f, "Parsing error: {}", msg),
}
}
}
impl Error for ApiError {}
pub struct IamRespondingAPI<C> {
client: C,
token_for_api: Option<String>, member_id: Option<i64>, }
impl<C> IamRespondingAPI<C>
where
C: HttpClient + Send + Sync,
{
pub fn new(client: C) -> Self {
Self {
client,
token_for_api: None,
member_id: None,
}
}
pub async fn login(
&mut self,
agency: &str,
user: &str,
pass: &str,
) -> Result<(), ApiError> {
let login_params = json!({
"memberLogin": true,
"agencyName": agency,
"memberfname": user,
"memberpwd": pass,
"rememberPwd": false,
"urlTo": "",
"overrideSession": true,
});
let serialized_params = serde_json::to_string(&login_params)
.map_err(|e| ApiError::ParsingError(e.to_string()))?;
let response = self
.client
.post(
"https://iamresponding.com/v3/Pages/memberlogin.aspx/ValidateLoginInfo",
&serialized_params,
)
.await?;
if response.contains("The log-in information that you have entered is incorrect.") {
return Err(ApiError::AuthenticationError);
}
let login_data: LoginResponse = serde_json::from_str(&response)
.map_err(|e| ApiError::ParsingError(e.to_string()))?;
self.token_for_api = Some(login_data.token_for_api);
self.member_id = Some(login_data.member_id);
Ok(())
}
pub async fn get_now_responding(&self) -> Result<Vec<NowResponding>, ApiError> {
let response = self
.client
.post_form("https://iamresponding.com/v3/AgencyServices.asmx/GetNowRespondingWithSort", &[])
.await?;
let data: NowRespondingResponse = serde_json::from_str(&response)
.map_err(|e| ApiError::ParsingError(e.to_string()))?;
Ok(data.now_responding)
}
pub async fn get_on_schedule(&self) -> Result<Vec<OnSchedule>, ApiError> {
let response = self
.client
.post_form("https://iamresponding.com/v3/AgencyServices.asmx/GetOnScheduleWithSort", &[])
.await?;
let data: OnScheduleResponse = serde_json::from_str(&response)
.map_err(|e| ApiError::ParsingError(e.to_string()))?;
Ok(data.on_schedule)
}
pub async fn list_dispatch_messages(&self) -> Result<Vec<DispatchMessage>, ApiError> {
let response = self
.client
.post_form("https://iamresponding.com/v3/DispatchMessages.asmx/ListWithParser", &[])
.await?;
let data: DispatchMessageResponse = serde_json::from_str(&response)
.map_err(|e| ApiError::ParsingError(e.to_string()))?;
Ok(data.dispatch_messages)
}
pub async fn get_incident_info(&self, incident_id: i64) -> Result<IncidentInfoData, ApiError> {
let params = json!({
"messageID": incident_id,
"token": self.token_for_api,
});
let serialized_params = serde_json::to_string(¶ms)
.map_err(|e| ApiError::ParsingError(e.to_string()))?;
let response = self
.client
.post("https://iamresponding.com/v3/agency/IncidentsDashboard.aspx/GetIncidentInfo", &serialized_params)
.await?;
let data: IncidentInfoResponse = serde_json::from_str(&response)
.map_err(|e| ApiError::ParsingError(e.to_string()))?;
data.data.into_iter().next().ok_or(ApiError::ParsingError("No data for incident".to_string()))
}
pub async fn get_latest_incidents(&self) -> Result<Vec<IncidentInfoData>, ApiError> {
let token = self.token_for_api.as_deref().ok_or(ApiError::AuthenticationError)?;
let member_id = self.member_id.ok_or(ApiError::AuthenticationError)?;
let params = json!({
"memberID": member_id,
"token": token,
});
let serialized_params = serde_json::to_string(¶ms)
.map_err(|e| ApiError::ParsingError(e.to_string()))?;
let response = self
.client
.post("https://iamresponding.com/v3/agency/IncidentsDashboard.aspx/GetLatestIncidents", &serialized_params)
.await?;
let data: IncidentInfoResponse = serde_json::from_str(&response)
.map_err(|e| ApiError::ParsingError(e.to_string()))?;
Ok(data.data)
}
pub async fn get_reminders_by_member(&self) -> Result<Vec<EventReminder>, ApiError> {
let token = self.token_for_api.as_deref().ok_or(ApiError::AuthenticationError)?;
let member_id = self.member_id.ok_or(ApiError::AuthenticationError)?;
let member_id_str = member_id.to_string();
let days_str = "7".to_string();
let form_data = [
("subsString", &member_id_str as &str),
("days", &days_str as &str), ];
let response = self
.client
.post_form("https://iamresponding.com/v3/AgencyServices.asmx/GetRemindersByMember", &form_data)
.await?;
let data: RemindersResponse = serde_json::from_str(&response)
.map_err(|e| ApiError::ParsingError(e.to_string()))?;
Ok(data.reminders)
}
}
#[derive(Debug, Deserialize)]
struct LoginResponse {
token_for_api: String,
member_id: i64,
}
#[derive(Debug, Deserialize)]
struct NowRespondingResponse {
now_responding: Vec<NowResponding>,
}
#[derive(Debug, Deserialize)]
pub struct NowResponding {
pub member_name: String,
}
#[derive(Debug, Deserialize)]
struct OnScheduleResponse {
on_schedule: Vec<OnSchedule>,
}
#[derive(Debug, Deserialize)]
pub struct OnSchedule {
pub member_name: String,
}
#[derive(Debug, Deserialize)]
struct DispatchMessageResponse {
dispatch_messages: Vec<DispatchMessage>,
}
#[derive(Debug, Deserialize)]
pub struct DispatchMessage {
pub message_body: String,
pub address: Option<String>,
}
#[derive(Debug, Deserialize)]
struct IncidentInfoResponse {
data: Vec<IncidentInfoData>,
}
#[derive(Debug, Deserialize)]
pub struct IncidentInfoData {
pub id: i64,
pub incident_type: String,
}
#[derive(Debug, Deserialize)]
struct RemindersResponse {
reminders: Vec<EventReminder>,
}
#[derive(Debug, Deserialize)]
pub struct EventReminder {
pub event_name: String,
pub date: String,
pub time: String,
}