firestore_path/
database_id.rs1use crate::{error::ErrorKind, Error};
2
3#[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 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 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}