use std::{
convert::TryFrom,
error::Error as StdError,
fmt::{self, Display, Formatter},
str::FromStr,
};
use http::{
uri::{InvalidUri as UrlParseError, Parts, Scheme},
Uri,
};
#[cfg(feature = "rest")]
pub mod rest;
#[cfg(feature = "gen_chat")]
pub mod chat;
#[cfg(feature = "gen_auth")]
pub mod auth {
pub mod v1 {
#![allow(clippy::unit_arg)]
hrpc::include_proto!("protocol.auth.v1");
}
pub use v1::*;
}
#[cfg(feature = "gen_harmonytypes")]
pub mod harmonytypes;
#[cfg(feature = "gen_mediaproxy")]
pub mod mediaproxy {
pub mod v1 {
hrpc::include_proto!("protocol.mediaproxy.v1");
}
pub use v1::*;
}
#[cfg(feature = "gen_voice")]
pub mod voice {
pub mod v1 {
hrpc::include_proto!("protocol.voice.v1");
}
pub use v1::*;
}
#[cfg(feature = "gen_sync")]
pub mod sync {
pub mod v1 {
hrpc::include_proto!("protocol.sync.v1");
}
pub use v1::*;
}
#[cfg(feature = "gen_profile")]
pub mod profile;
#[cfg(feature = "gen_emote")]
pub mod emote {
pub mod v1 {
hrpc::include_proto!("protocol.emote.v1");
}
pub use v1::*;
}
#[cfg(feature = "gen_batch")]
pub mod batch {
pub mod v1 {
hrpc::include_proto!("protocol.batch.v1");
}
pub use v1::*;
}
pub mod exports {
pub use hrpc;
pub use prost;
}
#[derive(Debug, Clone)]
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"),
}
}
}
impl StdError for HmcParseError {}
#[derive(Debug)]
pub enum HmcFromStrError {
UrlParse(UrlParseError),
HmcParse(HmcParseError),
}
impl Display for HmcFromStrError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
HmcFromStrError::HmcParse(err) => write!(f, "{}", err),
HmcFromStrError::UrlParse(err) => write!(f, "{}", err),
}
}
}
impl StdError for HmcFromStrError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
Some(match self {
HmcFromStrError::HmcParse(err) => err,
HmcFromStrError::UrlParse(err) => err,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Hmc {
inner: Uri,
}
impl Hmc {
pub fn new(
server: impl std::fmt::Display,
id: impl std::fmt::Display,
) -> Result<Self, HmcFromStrError> {
Self::from_str(&format!("hmc://{}/{}", server, id))
}
pub fn id(&self) -> &str {
self.inner.path().trim_start_matches('/')
}
pub fn server(&self) -> &str {
self.inner.host().unwrap()
}
pub fn port(&self) -> u16 {
self.inner.port_u16().unwrap_or(2289)
}
}
impl Display for Hmc {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.inner)
}
}
impl From<Hmc> for String {
fn from(hmc: Hmc) -> String {
hmc.inner.to_string()
}
}
impl FromStr for Hmc {
type Err = HmcFromStrError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let url = value.parse::<Uri>().map_err(HmcFromStrError::UrlParse)?;
Self::try_from(url).map_err(HmcFromStrError::HmcParse)
}
}
impl TryFrom<Uri> for Hmc {
type Error = HmcParseError;
fn try_from(value: Uri) -> Result<Self, Self::Error> {
if value.host().is_none() {
return Err(HmcParseError::NoServer);
};
let path = value.path().trim_start_matches('/');
if path.is_empty() {
return Err(HmcParseError::NoId);
} else if path.contains('/') {
return Err(HmcParseError::InvalidId);
}
Ok(Self { inner: value })
}
}
#[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(VALID_HMC.parse::<Uri>().unwrap()).unwrap();
}
#[test]
#[should_panic(expected = "InvalidId")]
fn parse_invalid_id_hmc() {
if let Err(e) = Hmc::try_from(INVALID_ID_HMC.parse::<Uri>().unwrap()) {
panic!("{:?}", e)
}
}
#[test]
#[should_panic(expected = "NoServer")]
fn parse_no_server_hmc() {
if let Err(e) = Hmc::try_from(NO_SERVER_HMC.parse::<Uri>().unwrap()) {
panic!("{:?}", e)
}
}
#[test]
#[should_panic(expected = "NoId")]
fn parse_no_id_hmc() {
if let Err(e) = Hmc::try_from(NO_ID_HMC.parse::<Uri>().unwrap()) {
panic!("{:?}", e)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HomeserverIdentifier {
domain: String,
port: u16,
}
impl HomeserverIdentifier {
pub fn to_url(&self) -> Uri {
let mut parts = Parts::default();
parts.scheme = Some(Scheme::from_str("https").unwrap());
parts.authority = Some(format!("{}:{}", self.domain, self.port).parse().unwrap());
Uri::from_parts(parts).unwrap()
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum HomeserverIdParseError {
InvalidPort,
MissingPort,
MissingDomain,
Malformed,
}
impl Display for HomeserverIdParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let msg = match self {
Self::InvalidPort => "port is invalid (not an u16)",
Self::MissingPort => "port is missing",
Self::MissingDomain => "domain is missing",
Self::Malformed => "id is malformed",
};
f.write_str(msg)
}
}
impl StdError for HomeserverIdParseError {}
impl FromStr for HomeserverIdentifier {
type Err = HomeserverIdParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut split = s.split(':');
let domain = split.next().ok_or(HomeserverIdParseError::MissingDomain)?;
if domain.parse::<http::uri::Authority>().is_err() {
return Err(HomeserverIdParseError::Malformed);
}
let port_raw = split.next().ok_or(HomeserverIdParseError::MissingPort)?;
let port: u16 = port_raw
.parse()
.map_err(|_| HomeserverIdParseError::InvalidPort)?;
Ok(Self {
domain: domain.to_string(),
port,
})
}
}
pub trait Endpoint {
type Response;
const ENDPOINT_PATH: &'static str;
#[cfg(feature = "_client_common")]
fn call_with(
self,
client: &crate::client::Client,
) -> hrpc::exports::futures_util::future::BoxFuture<
'static,
crate::client::error::ClientResult<hrpc::Response<Self::Response>>,
>;
}