uv_distribution_types/
index_name.rs

1use std::borrow::Cow;
2use std::ops::Deref;
3use std::str::FromStr;
4
5use thiserror::Error;
6
7use uv_small_str::SmallString;
8
9/// The normalized name of an index.
10///
11/// Index names may contain letters, digits, hyphens, underscores, and periods, and must be ASCII.
12#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, serde::Serialize)]
13#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
14pub struct IndexName(SmallString);
15
16impl IndexName {
17    /// Validates the given index name and returns [`IndexName`] if it's valid, or an error
18    /// otherwise.
19    pub fn new(name: &str) -> Result<Self, IndexNameError> {
20        for c in name.chars() {
21            match c {
22                'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' | '.' => {}
23                c if c.is_ascii() => {
24                    return Err(IndexNameError::UnsupportedCharacter(c, name.to_string()));
25                }
26                c => {
27                    return Err(IndexNameError::NonAsciiName(c, name.to_string()));
28                }
29            }
30        }
31        Ok(Self(SmallString::from(name)))
32    }
33
34    /// Converts the index name to an environment variable name.
35    ///
36    /// For example, given `IndexName("foo-bar")`, this will return `"FOO_BAR"`.
37    pub fn to_env_var(&self) -> String {
38        self.0
39            .chars()
40            .map(|c| {
41                if c.is_ascii_alphanumeric() {
42                    c.to_ascii_uppercase()
43                } else {
44                    '_'
45                }
46            })
47            .collect::<String>()
48    }
49}
50
51impl FromStr for IndexName {
52    type Err = IndexNameError;
53
54    fn from_str(s: &str) -> Result<Self, Self::Err> {
55        Self::new(s)
56    }
57}
58
59impl<'de> serde::de::Deserialize<'de> for IndexName {
60    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
61    where
62        D: serde::de::Deserializer<'de>,
63    {
64        let s = Cow::<'_, str>::deserialize(deserializer)?;
65        Self::new(&s).map_err(serde::de::Error::custom)
66    }
67}
68
69impl std::fmt::Display for IndexName {
70    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71        self.0.fmt(f)
72    }
73}
74
75impl AsRef<str> for IndexName {
76    fn as_ref(&self) -> &str {
77        &self.0
78    }
79}
80
81impl Deref for IndexName {
82    type Target = str;
83
84    fn deref(&self) -> &Self::Target {
85        &self.0
86    }
87}
88
89/// An error that can occur when parsing an [`IndexName`].
90#[derive(Error, Debug)]
91pub enum IndexNameError {
92    #[error("Index included a name, but the name was empty")]
93    EmptyName,
94    #[error(
95        "Index names may only contain letters, digits, hyphens, underscores, and periods, but found unsupported character (`{0}`) in: `{1}`"
96    )]
97    UnsupportedCharacter(char, String),
98    #[error("Index names must be ASCII, but found non-ASCII character (`{0}`) in: `{1}`")]
99    NonAsciiName(char, String),
100}