use std::error::Error;
use std::fmt;
pub const MAX_DATABASES_PER_PROCESS: usize = 512;
pub const MAX_DATABASE_ALIAS_BYTES: usize = 64;
pub const DEFAULT_DATABASE_ALIAS: &str = "default";
#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct DatabaseAlias(String);
impl DatabaseAlias {
pub fn new(value: impl Into<String>) -> Result<Self, DatabaseAliasError> {
let value = value.into();
let bytes = value.as_bytes();
if bytes.is_empty() {
return Err(DatabaseAliasError::Empty);
}
if bytes.len() > MAX_DATABASE_ALIAS_BYTES {
return Err(DatabaseAliasError::TooLong {
actual: bytes.len(),
maximum: MAX_DATABASE_ALIAS_BYTES,
});
}
if !bytes[0].is_ascii_lowercase() {
return Err(DatabaseAliasError::InvalidCharacter { index: 0 });
}
if let Some(index) = bytes.iter().position(|byte| {
!byte.is_ascii_lowercase() && !byte.is_ascii_digit() && !matches!(byte, b'_' | b'-')
}) {
return Err(DatabaseAliasError::InvalidCharacter { index });
}
Ok(Self(value))
}
#[must_use]
pub fn default_alias() -> Self {
Self(DEFAULT_DATABASE_ALIAS.to_owned())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
}
impl fmt::Debug for DatabaseAlias {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_tuple("DatabaseAlias")
.field(&self.0)
.finish()
}
}
impl fmt::Display for DatabaseAlias {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(formatter)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DatabaseAliasError {
Empty,
TooLong {
actual: usize,
maximum: usize,
},
InvalidCharacter {
index: usize,
},
}
impl fmt::Display for DatabaseAliasError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("invalid database alias")
}
}
impl Error for DatabaseAliasError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn canonical_aliases_preserve_exact_text_and_sort_by_bytes() {
let mut aliases = [
DatabaseAlias::new("zeta-2").expect("valid alias"),
DatabaseAlias::new("ea_local").expect("valid alias"),
DatabaseAlias::default_alias(),
];
aliases.sort();
assert_eq!(
aliases.map(|alias| alias.as_str().to_owned()),
["default", "ea_local", "zeta-2"]
);
}
#[test]
fn grammar_and_bounds_fail_closed() {
assert_eq!(DatabaseAlias::new(""), Err(DatabaseAliasError::Empty));
assert_eq!(
DatabaseAlias::new("A"),
Err(DatabaseAliasError::InvalidCharacter { index: 0 })
);
assert_eq!(
DatabaseAlias::new("1database"),
Err(DatabaseAliasError::InvalidCharacter { index: 0 })
);
assert_eq!(
DatabaseAlias::new("data.base"),
Err(DatabaseAliasError::InvalidCharacter { index: 4 })
);
assert_eq!(
DatabaseAlias::new("a".repeat(MAX_DATABASE_ALIAS_BYTES + 1)),
Err(DatabaseAliasError::TooLong {
actual: MAX_DATABASE_ALIAS_BYTES + 1,
maximum: MAX_DATABASE_ALIAS_BYTES,
})
);
assert!(DatabaseAlias::new("a".repeat(MAX_DATABASE_ALIAS_BYTES)).is_ok());
}
}