firestore_path/
database_id.rs

1use crate::{error::ErrorKind, Error};
2
3/// A database id.
4///
5/// # Limit
6///
7/// <https://firebase.google.com/docs/firestore/reference/rest/v1/projects.databases/create#query-parameters>
8///
9/// > This value should be 4-63 characters. Valid characters are /[a-z][0-9]-/ with first character a letter and the last a letter or a number. Must not be UUID-like /[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}/.
10/// >
11/// > "(default)" database id is also valid.
12///
13/// # Examples
14///
15/// ```rust
16/// # fn main() -> anyhow::Result<()> {
17/// use firestore_path::DatabaseId;
18/// use std::str::FromStr;
19///
20/// let database_id = DatabaseId::from_str("my-database")?;
21/// assert_eq!(database_id.as_ref(), "my-database");
22/// assert_eq!(database_id.to_string(), "my-database");
23///
24/// let database_id = DatabaseId::from_str("(default)")?;
25/// assert_eq!(database_id.as_ref(), "(default)");
26/// assert_eq!(database_id.to_string(), "(default)");
27///
28/// let database_id = DatabaseId::default();
29/// assert_eq!(database_id.as_ref(), "(default)");
30/// assert_eq!(database_id.to_string(), "(default)");
31/// #     Ok(())
32/// # }
33/// ```
34///
35#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
36pub struct DatabaseId(String);
37
38impl std::convert::AsRef<str> for DatabaseId {
39    fn as_ref(&self) -> &str {
40        self.0.as_ref()
41    }
42}
43
44impl std::convert::TryFrom<&str> for DatabaseId {
45    type Error = Error;
46
47    fn try_from(s: &str) -> Result<Self, Self::Error> {
48        Self::try_from(s.to_string())
49    }
50}
51
52impl std::convert::TryFrom<String> for DatabaseId {
53    type Error = Error;
54
55    fn try_from(s: String) -> Result<Self, Self::Error> {
56        // <https://firebase.google.com/docs/firestore/reference/rest/v1/projects.databases/create#query-parameters>
57        if s == "(default)" {
58            return Ok(Self(s.to_string()));
59        }
60
61        if !(4..=63).contains(&s.len()) {
62            return Err(Error::from(ErrorKind::LengthOutOfBounds));
63        }
64
65        if !s
66            .chars()
67            .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
68        {
69            return Err(Error::from(ErrorKind::ContainsInvalidCharacter));
70        }
71
72        let first_char = s.chars().next().expect("already length checked");
73        if !first_char.is_ascii_lowercase() {
74            return Err(Error::from(ErrorKind::StartsWithNonLetter));
75        }
76
77        if s.ends_with('-') {
78            return Err(Error::from(ErrorKind::EndsWithHyphen));
79        }
80
81        Ok(Self(s))
82    }
83}
84
85impl std::default::Default for DatabaseId {
86    /// Returns a new instance with the value `"(default)"`.
87    ///
88    /// # Examples
89    ///
90    /// ```rust
91    /// use firestore_path::DatabaseId;
92    /// assert_eq!(DatabaseId::default().to_string(), "(default)");
93    /// ```
94    fn default() -> Self {
95        Self("(default)".to_string())
96    }
97}
98
99impl std::fmt::Display for DatabaseId {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101        self.0.fmt(f)
102    }
103}
104
105impl std::str::FromStr for DatabaseId {
106    type Err = Error;
107
108    fn from_str(s: &str) -> Result<Self, Self::Err> {
109        Self::try_from(s)
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use std::str::FromStr;
116
117    use super::*;
118
119    #[test]
120    fn test() -> anyhow::Result<()> {
121        let s = "my-database";
122        let database_id = DatabaseId::from_str(s)?;
123        assert_eq!(database_id.to_string(), s);
124
125        let s = "(default)";
126        let database_id = DatabaseId::from_str(s)?;
127        assert_eq!(database_id.to_string(), s);
128
129        assert_eq!(database_id.as_ref(), s);
130        Ok(())
131    }
132
133    #[test]
134    fn test_impl_from_str() -> anyhow::Result<()> {
135        for (s, expected) in [
136            ("", false),
137            ("(default)", true),
138            ("(default1)", false),
139            ("x".repeat(3).as_str(), false),
140            ("x".repeat(4).as_str(), true),
141            ("x".repeat(63).as_str(), true),
142            ("x".repeat(64).as_str(), false),
143            ("x1-x", true),
144            ("xAxx", false),
145            ("-xxx", false),
146            ("0xxx", false),
147            ("xxx-", false),
148            ("xxx0", true),
149        ] {
150            assert_eq!(DatabaseId::from_str(s).is_ok(), expected);
151            assert_eq!(DatabaseId::try_from(s).is_ok(), expected);
152            assert_eq!(DatabaseId::try_from(s.to_string()).is_ok(), expected);
153            if expected {
154                assert_eq!(DatabaseId::from_str(s)?, DatabaseId::try_from(s)?);
155                assert_eq!(
156                    DatabaseId::from_str(s)?,
157                    DatabaseId::try_from(s.to_string())?
158                );
159                assert_eq!(DatabaseId::from_str(s)?.to_string(), s);
160            }
161        }
162        Ok(())
163    }
164}