use std::{convert::TryInto, error::Error as StdError, str::FromStr};
use super::Hmc;
use derive_more::{Display, From, Into, IntoIterator};
use derive_new::new;
use http::{HeaderValue, Uri};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum FileKind {
Attachment,
Inline,
}
#[derive(Debug, Clone, Display, PartialEq, Eq, Hash)]
pub enum FileId {
Hmc(Hmc),
Id(String),
External(Uri),
}
impl From<FileId> for String {
fn from(o: FileId) -> String {
match o {
FileId::Hmc(hmc) => hmc.into(),
FileId::Id(id) => id,
FileId::External(url) => url.to_string(),
}
}
}
#[derive(Debug, Clone, Display)]
#[display(fmt = "Specified string is not a valid FileId.")]
pub struct InvalidFileId;
impl StdError for InvalidFileId {}
impl FromStr for FileId {
type Err = InvalidFileId;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
Err(InvalidFileId)
} else {
match s.parse::<Uri>() {
Ok(url) => {
if let Ok(hmc) = url.clone().try_into() {
Ok(FileId::Hmc(hmc))
} else if !url.path().trim_start_matches('/').is_empty() {
Ok(FileId::External(url))
} else {
Ok(FileId::Id(s.to_owned()))
}
}
_ => Ok(FileId::Id(s.to_owned())),
}
}
}
}
#[derive(new, Debug, Default, Clone, Into, From, IntoIterator)]
pub struct FileIds(Vec<FileId>);
impl From<FileIds> for Vec<String> {
fn from(o: FileIds) -> Vec<String> {
o.into_iter().map(|id| id.to_string()).collect()
}
}
impl From<Hmc> for FileId {
fn from(hmc: Hmc) -> Self {
Self::Hmc(hmc)
}
}
pub fn extract_file_info_from_download_response<'a>(
headers: &'a http::HeaderMap,
) -> Result<(&'a str, &'a HeaderValue, FileKind), &'static str> {
let mimetype = headers
.get(http::header::CONTENT_TYPE)
.ok_or("server did not respond with `Content-Type` header")?;
let mut split = headers
.get(http::header::CONTENT_DISPOSITION)
.ok_or("server did not respond with `Content-Disposition` header")?
.to_str()
.map_err(|_| "server responded with non ASCII content disposition")?
.split(';');
let kind = match split
.next()
.ok_or("server did not respond with file kind")?
{
"attachment" => FileKind::Attachment,
"inline" => FileKind::Inline,
_ => return Err("server responded with invalid file kind"),
};
const NO_NAME: &str = "server did not respond with a file name";
let name = split
.next()
.ok_or(NO_NAME)?
.split('=')
.nth(1)
.ok_or(NO_NAME)?
.trim_matches('"');
Ok((name, mimetype, kind))
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
pub struct About {
#[serde(rename = "serverName")]
pub server_name: String,
pub version: String,
#[serde(rename = "aboutServer")]
pub about_server: String,
#[serde(rename = "messageOfTheDay")]
pub message_of_the_day: String,
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
fn parse_id() {
const ID: &str = "654624512343";
let file_id = FileId::from_str(ID).expect("file id parse");
assert!(matches!(file_id, FileId::Id(_)));
assert_eq!(ID.to_string(), file_id.to_string());
}
#[test]
fn parse_hmc() {
const HMC: &str = "hmc://chat.harmonyapp.io/654624512343";
let file_id = FileId::from_str(HMC).expect("file id parse");
assert!(matches!(file_id, FileId::Hmc(_)));
assert_eq!(HMC.to_string(), file_id.to_string());
}
#[test]
fn parse_uri() {
const URI: &str = "https://media.discordapp.net/attachments/330412938277945345/801119250269339728/unknown.png";
let file_id = FileId::from_str(URI).expect("file id parse");
assert!(matches!(file_id, FileId::External(_)));
assert_eq!(URI.to_string(), file_id.to_string());
}
#[test]
#[should_panic(expected = "InvalidFileId")]
fn parse_empty() {
const EMPTY: &str = "";
FileId::from_str(EMPTY).expect("file id parse");
}
}