use std::fmt::{
Display,
Formatter,
Result as FmtResult,
};
use std::str::FromStr;
use crate::ProviderRegistryError;
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ProviderName(String);
impl ProviderName {
#[inline]
pub fn new(name: &str) -> Result<Self, ProviderRegistryError> {
let trimmed = name.trim();
if trimmed.is_empty() {
return Err(ProviderRegistryError::EmptyProviderName);
}
if !trimmed.is_ascii() {
return Err(invalid_provider_name(
trimmed,
"provider names must be ASCII",
));
}
if !trimmed.bytes().all(is_allowed_provider_name_byte) {
return Err(invalid_provider_name(
trimmed,
"provider names may contain only ASCII letters, digits, '_' or '-'",
));
}
let bytes = trimmed.as_bytes();
let first = bytes[0];
let last = bytes[bytes.len() - 1];
if !first.is_ascii_alphanumeric() {
return Err(invalid_provider_name(
trimmed,
"provider names must start with an ASCII letter or digit",
));
}
if !last.is_ascii_alphanumeric() {
return Err(invalid_provider_name(
trimmed,
"provider names must end with an ASCII letter or digit",
));
}
if has_consecutive_separators(trimmed) {
return Err(invalid_provider_name(
trimmed,
"provider names must not contain consecutive separators",
));
}
Ok(Self(trimmed.to_ascii_lowercase()))
}
#[inline]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl AsRef<str> for ProviderName {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Display for ProviderName {
#[inline]
fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
formatter.write_str(self.as_str())
}
}
impl FromStr for ProviderName {
type Err = ProviderRegistryError;
#[inline]
fn from_str(name: &str) -> Result<Self, Self::Err> {
Self::new(name)
}
}
fn is_allowed_provider_name_byte(byte: u8) -> bool {
byte.is_ascii_alphanumeric() || is_separator_provider_name_byte(byte)
}
fn is_separator_provider_name_byte(byte: u8) -> bool {
matches!(byte, b'_' | b'-')
}
fn has_consecutive_separators(name: &str) -> bool {
name.as_bytes().windows(2).any(|bytes| {
is_separator_provider_name_byte(bytes[0]) && is_separator_provider_name_byte(bytes[1])
})
}
fn invalid_provider_name(name: &str, reason: &str) -> ProviderRegistryError {
ProviderRegistryError::InvalidProviderName {
name: name.to_owned(),
reason: reason.to_owned(),
}
}