use std::borrow::Cow;
use std::ops::Deref;
use std::str::FromStr;
use thiserror::Error;
use uv_small_str::SmallString;
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, serde::Serialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct IndexName(SmallString);
impl IndexName {
pub fn new(name: &str) -> Result<Self, IndexNameError> {
for c in name.chars() {
match c {
'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' | '.' => {}
c if c.is_ascii() => {
return Err(IndexNameError::UnsupportedCharacter(c, name.to_string()));
}
c => {
return Err(IndexNameError::NonAsciiName(c, name.to_string()));
}
}
}
Ok(Self(SmallString::from(name)))
}
pub fn to_env_var(&self) -> String {
self.0
.chars()
.map(|c| {
if c.is_ascii_alphanumeric() {
c.to_ascii_uppercase()
} else {
'_'
}
})
.collect::<String>()
}
}
impl FromStr for IndexName {
type Err = IndexNameError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}
impl<'de> serde::de::Deserialize<'de> for IndexName {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let s = Cow::<'_, str>::deserialize(deserializer)?;
Self::new(&s).map_err(serde::de::Error::custom)
}
}
impl std::fmt::Display for IndexName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl AsRef<str> for IndexName {
fn as_ref(&self) -> &str {
&self.0
}
}
impl Deref for IndexName {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Error, Debug)]
pub enum IndexNameError {
#[error("Index included a name, but the name was empty")]
EmptyName,
#[error(
"Index names may only contain letters, digits, hyphens, underscores, and periods, but found unsupported character (`{0}`) in: `{1}`"
)]
UnsupportedCharacter(char, String),
#[error("Index names must be ASCII, but found non-ASCII character (`{0}`) in: `{1}`")]
NonAsciiName(char, String),
}