use core::fmt;
use core::str::FromStr;
use compact_str::format_compact;
use compact_str::CompactString;
#[cfg(feature = "client")]
use influxdb::Client;
#[derive(Debug, Clone)]
pub struct InfluxDbUrl {
scheme: Scheme,
connection: CompactString,
auth: Option<(CompactString, CompactString)>,
database: CompactString
}
#[derive(Debug, Clone, Copy)]
enum Scheme {
Http,
Https
}
impl FromStr for InfluxDbUrl {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let Some((scheme, rest)) = s.split_once("://") else {
return Err(Error::MissingScheme);
};
let scheme = match scheme {
"http" => Scheme::Http,
"https" => Scheme::Https,
s => return Err(Error::InvalidScheme(s.into()))
};
let (username, password, rest) = match rest.split_once('@') {
Some((auth_part, rest)) => {
let (username, password) = match auth_part.split_once(':') {
Some((username, password)) => (username, Some(password)),
None => (auth_part, None)
};
(Some(username), password, rest)
},
None => (None, None, rest)
};
let auth = match (username, password) {
(Some(un), Some(pw)) => Some((un, pw)),
(Some(_), None) => return Err(Error::InvalidAuth),
(None, Some(_)) => return Err(Error::InvalidAuth),
(None, None) => None
};
let Some((rest, database)) = rest.split_once('/') else {
return Err(Error::MissingDatabase);
};
if !database.bytes().all(|b| b.is_ascii_alphanumeric()) {
return Err(Error::InvalidDatabase(database.into()));
}
let connection = match rest.split_once(':') {
Some((host, port)) => {
if port.parse::<u16>().is_err() {
return Err(Error::InvalidPort(port.into()));
};
format_compact!("{host}:{port}")
},
None => rest.into()
};
Ok(Self {
scheme,
connection,
auth: auth.map(|(un, pw)| (un.into(), pw.into())),
database: database.into()
})
}
}
impl InfluxDbUrl {
#[cfg(feature = "client")]
#[inline]
pub fn client(&self) -> Client {
let client = Client::new(format!("{}://{}", self.scheme, self.connection), self.database.as_str());
match self.auth.as_ref() {
None => client,
Some((un, pw)) => client.with_auth(un.as_str(), pw.as_str())
}
}
}
impl fmt::Display for InfluxDbUrl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let scheme = match self.scheme {
Scheme::Http => "http://",
Scheme::Https => "https://"
};
f.write_str(scheme)?;
if let Some((un, pw)) = self.auth.as_ref() {
f.write_str(un.as_str())?;
f.write_str(":")?;
f.write_str(pw.as_str())?;
f.write_str("@")?;
}
f.write_str(self.connection.as_str())?;
f.write_str("/")?;
f.write_str(self.database.as_str())
}
}
impl fmt::Display for Scheme {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Http => f.write_str("http"),
Self::Https => f.write_str("https")
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Scheme (http:// or https://) is required")]
MissingScheme,
#[error("Invalid scheme '{0}'; must be http or https")]
InvalidScheme(CompactString),
#[error("If a username is present, a password must be present, and vice versa")]
InvalidAuth,
#[error("Invalid port '{0}'; must be a number between 1 and 65535")]
InvalidPort(CompactString),
#[error("Missing database")]
MissingDatabase,
#[error("Invalid database '{0}'; must be alphanumeric")]
InvalidDatabase(CompactString)
}