use std::{
convert::TryFrom,
fmt::{self, Display, Formatter},
};
use derive_more::{From, Into, IntoIterator};
use http::{uri::Authority, Uri};
#[cfg(feature = "gen_chat")]
pub mod chat;
#[cfg(feature = "gen_auth")]
pub mod auth {
pub mod v1 {
tonic::include_proto!("protocol.auth.v1");
}
pub use v1::*;
}
#[cfg(feature = "gen_harmonytypes")]
pub mod harmonytypes {
pub mod v1 {
tonic::include_proto!("protocol.harmonytypes.v1");
}
pub use v1::*;
}
#[cfg(feature = "gen_mediaproxy")]
pub mod mediaproxy {
pub mod v1 {
tonic::include_proto!("protocol.mediaproxy.v1");
}
pub use v1::*;
}
#[cfg(feature = "gen_voice")]
pub mod voice {
pub mod v1 {
tonic::include_proto!("protocol.voice.v1");
}
pub use v1::*;
}
pub mod exports {
pub use http;
pub use prost;
}
#[derive(Debug)]
pub enum HmcParseError {
NoServer,
NoId,
InvalidId,
}
impl Display for HmcParseError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
HmcParseError::NoServer => write!(f, "Missing a server part in URL"),
HmcParseError::NoId => write!(f, "Missing an ID part in URL"),
HmcParseError::InvalidId => write!(f, "Invalid ID in URL"),
}
}
}
#[derive(Debug, Default, Clone)]
pub struct Hmc {
inner: Uri,
}
impl Hmc {
pub fn new(server: Authority, id: impl std::fmt::Display) -> Self {
let hmc_compliant_uri = Uri::builder()
.authority(server)
.path_and_query(format!("/{}", id))
.scheme("hmc")
.build()
.unwrap();
Self {
inner: hmc_compliant_uri,
}
}
pub fn id(&self) -> &str {
self.inner.path().trim_start_matches('/')
}
pub fn server(&self) -> &str {
self.inner.authority().unwrap().as_str()
}
}
impl Display for Hmc {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.inner)
}
}
impl Into<String> for Hmc {
fn into(self) -> String {
self.to_string()
}
}
impl TryFrom<Uri> for Hmc {
type Error = HmcParseError;
fn try_from(value: Uri) -> Result<Self, Self::Error> {
let server = if let Some(authority) = value.authority().cloned() {
authority
} else {
return Err(HmcParseError::NoServer);
};
let path = value.path().trim_start_matches('/');
let id = if !path.is_empty() {
if !path.contains('/') {
path.to_string()
} else {
return Err(HmcParseError::InvalidId);
}
} else {
return Err(HmcParseError::NoId);
};
Ok(Self::new(server, id))
}
}
#[derive(new, Debug, Default, Clone, Into, From, IntoIterator)]
pub struct Hmcs(Vec<Hmc>);
impl Into<Vec<String>> for Hmcs {
fn into(self) -> Vec<String> {
self.into_iter().map(|hmc| hmc.to_string()).collect()
}
}
#[cfg(test)]
mod test {
use super::*;
const VALID_HMC: &str = "hmc://chat.harmonyapp.io:2289/fdeded13-844b-42e1-b813-34f74f9afdbc";
const INVALID_ID_HMC: &str =
"hmc://chat.harmonyapp.io:2289/fdeded13-844b-42e1-b813-34f74f9afdbc/342";
const NO_SERVER_HMC: &str = "/fdeded13-844b-42e1-b813-34f74f9afdbc";
const NO_ID_HMC: &str = "hmc://chat.harmonyapp.io:2289";
#[test]
fn parse_valid_hmc() {
Hmc::try_from(Uri::from_static(VALID_HMC)).unwrap();
}
#[test]
#[should_panic(expected = "InvalidId")]
fn parse_invalid_id_hmc() {
if let Err(e) = Hmc::try_from(Uri::from_static(INVALID_ID_HMC)) {
panic!("{:?}", e)
}
}
#[test]
#[should_panic(expected = "NoServer")]
fn parse_no_server_hmc() {
if let Err(e) = Hmc::try_from(Uri::from_static(NO_SERVER_HMC)) {
panic!("{:?}", e)
}
}
#[test]
#[should_panic(expected = "NoId")]
fn parse_no_id_hmc() {
if let Err(e) = Hmc::try_from(Uri::from_static(NO_ID_HMC)) {
panic!("{:?}", e)
}
}
}