use crate::mattermost::BaseSession;
use crate::utils::parse_from_hmstr;
use anyhow::Result;
use chrono::{DateTime, Local};
use derivative::Derivative;
use serde::{Deserialize, Serialize};
use serde_json as json;
use std::fmt;
use thiserror::Error;
use tracing::debug;
#[allow(missing_docs)]
#[derive(Debug, Error)]
pub enum MMSError {
#[error("Bad json data")]
BadJSONData(#[from] serde_json::error::Error),
#[error("HTTP request error")]
HTTPRequestError(#[from] ureq::Error),
#[error("Mattermost login error")]
LoginError(#[from] anyhow::Error),
}
#[derive(Derivative, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
#[derivative(Debug)]
pub struct MMStatus {
pub text: String,
pub emoji: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub duration: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<DateTime<Local>>,
}
impl fmt::Display for MMStatus {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}:{} (duration: {:?}, expire at: {:?})",
self.emoji, self.text, self.duration, self.expires_at
)
}
}
impl MMStatus {
pub fn new(text: String, emoji: String) -> MMStatus {
MMStatus {
text,
emoji,
duration: None,
expires_at: None,
}
}
pub fn expires_at(&mut self, time_str: &Option<String>) {
if let Some(expiry) = parse_from_hmstr(time_str) {
if Local::now() < expiry {
self.expires_at = Some(expiry);
self.duration = Some("date_and_time".to_owned());
} else {
debug!("now {:?} >= expiry {:?}", Local::now(), expiry);
}
}
}
pub fn to_json(&self) -> Result<String, MMSError> {
json::to_string(&self).map_err(MMSError::BadJSONData)
}
#[allow(clippy::borrowed_box)] pub fn _send(&self, session: &Box<dyn BaseSession>) -> Result<ureq::Response, ureq::Error> {
let token = session
.token()
.expect("Internal Error: token is unset in current session");
let uri = session.base_uri().to_owned() + "/api/v4/users/me/status/custom";
ureq::put(&uri)
.set("Authorization", &("Bearer ".to_owned() + token))
.send_json(serde_json::to_value(&self).unwrap_or_else(|e| {
panic!(
"Serialization of MMStatus '{:?}' failed with {:?}",
&self, &e
)
}))
}
pub fn send(&mut self, session: &mut Box<dyn BaseSession>) -> Result<ureq::Response, MMSError> {
debug!("Post status: {}", self.to_owned().to_json()?);
match self._send(session) {
Ok(response) => Ok(response),
Err(ureq::Error::Status(code, response)) => {
if code == 401 {
session.login().map_err(MMSError::LoginError)?;
self._send(session)
} else {
Err(ureq::Error::Status(code, response))
}
}
Err(e) => Err(e),
}
.map_err(MMSError::HTTPRequestError)
}
}
#[cfg(test)]
mod send_should {
use super::*;
use crate::mattermost::{BaseSession, Session};
use httpmock::prelude::*;
use test_log::test; #[test]
fn send_required_json() -> Result<()> {
let server = MockServer::start();
let mut mmstatus = MMStatus::new("text".into(), "emoji".into());
let server_mock = server.mock(|expect, resp_with| {
expect
.method(PUT)
.header("Authorization", "Bearer token")
.path("/api/v4/users/me/status/custom")
.json_body(serde_json::json!({"emoji":"emoji","text":"text"}
));
resp_with
.status(200)
.header("content-type", "text/html")
.body("ok");
});
let mut session: Box<dyn BaseSession> =
Box::new(Session::new(&server.url("")).with_token("token"));
let resp = mmstatus.send(&mut session)?;
server_mock.assert();
assert_eq!(resp.status(), 200);
Ok(())
}
#[test]
fn catch_api_error() -> Result<()> {
let server = MockServer::start();
let mut mmstatus = MMStatus::new("text".into(), "emoji".into());
let server_mock = server.mock(|expect, resp_with| {
expect
.method(PUT)
.header("Authorization", "Bearer token")
.path("/api/v4/users/me/status/custom")
.json_body(serde_json::json!({"emoji":"emoji","text":"text"}
));
resp_with
.status(500)
.header("content-type", "text/html")
.body("Internal error");
});
let mut session: Box<dyn BaseSession> =
Box::new(Session::new(&server.url("")).with_token("token"));
let resp = mmstatus.send(&mut session);
assert!(resp.is_err());
server_mock.assert();
Ok(())
}
}