1use std::error::Error;
4use std::fmt;
5
6pub const MAX_DATABASES_PER_PROCESS: usize = 512;
8pub const MAX_DATABASE_ALIAS_BYTES: usize = 64;
10pub const DEFAULT_DATABASE_ALIAS: &str = "default";
12
13#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
15pub struct DatabaseAlias(String);
16
17impl DatabaseAlias {
18 pub fn new(value: impl Into<String>) -> Result<Self, DatabaseAliasError> {
20 let value = value.into();
21 let bytes = value.as_bytes();
22 if bytes.is_empty() {
23 return Err(DatabaseAliasError::Empty);
24 }
25 if bytes.len() > MAX_DATABASE_ALIAS_BYTES {
26 return Err(DatabaseAliasError::TooLong {
27 actual: bytes.len(),
28 maximum: MAX_DATABASE_ALIAS_BYTES,
29 });
30 }
31 if !bytes[0].is_ascii_lowercase() {
32 return Err(DatabaseAliasError::InvalidCharacter { index: 0 });
33 }
34 if let Some(index) = bytes.iter().position(|byte| {
35 !byte.is_ascii_lowercase() && !byte.is_ascii_digit() && !matches!(byte, b'_' | b'-')
36 }) {
37 return Err(DatabaseAliasError::InvalidCharacter { index });
38 }
39 Ok(Self(value))
40 }
41
42 #[must_use]
44 pub fn default_alias() -> Self {
45 Self(DEFAULT_DATABASE_ALIAS.to_owned())
46 }
47
48 #[must_use]
50 pub fn as_str(&self) -> &str {
51 &self.0
52 }
53
54 #[must_use]
56 pub fn as_bytes(&self) -> &[u8] {
57 self.0.as_bytes()
58 }
59}
60
61impl fmt::Debug for DatabaseAlias {
62 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
63 formatter
64 .debug_tuple("DatabaseAlias")
65 .field(&self.0)
66 .finish()
67 }
68}
69
70impl fmt::Display for DatabaseAlias {
71 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
72 self.0.fmt(formatter)
73 }
74}
75
76#[derive(Clone, Copy, Debug, Eq, PartialEq)]
78pub enum DatabaseAliasError {
79 Empty,
81 TooLong {
83 actual: usize,
85 maximum: usize,
87 },
88 InvalidCharacter {
90 index: usize,
92 },
93}
94
95impl fmt::Display for DatabaseAliasError {
96 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
97 formatter.write_str("invalid database alias")
98 }
99}
100
101impl Error for DatabaseAliasError {}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn canonical_aliases_preserve_exact_text_and_sort_by_bytes() {
109 let mut aliases = [
110 DatabaseAlias::new("zeta-2").expect("valid alias"),
111 DatabaseAlias::new("ea_local").expect("valid alias"),
112 DatabaseAlias::default_alias(),
113 ];
114 aliases.sort();
115 assert_eq!(
116 aliases.map(|alias| alias.as_str().to_owned()),
117 ["default", "ea_local", "zeta-2"]
118 );
119 }
120
121 #[test]
122 fn grammar_and_bounds_fail_closed() {
123 assert_eq!(DatabaseAlias::new(""), Err(DatabaseAliasError::Empty));
124 assert_eq!(
125 DatabaseAlias::new("A"),
126 Err(DatabaseAliasError::InvalidCharacter { index: 0 })
127 );
128 assert_eq!(
129 DatabaseAlias::new("1database"),
130 Err(DatabaseAliasError::InvalidCharacter { index: 0 })
131 );
132 assert_eq!(
133 DatabaseAlias::new("data.base"),
134 Err(DatabaseAliasError::InvalidCharacter { index: 4 })
135 );
136 assert_eq!(
137 DatabaseAlias::new("a".repeat(MAX_DATABASE_ALIAS_BYTES + 1)),
138 Err(DatabaseAliasError::TooLong {
139 actual: MAX_DATABASE_ALIAS_BYTES + 1,
140 maximum: MAX_DATABASE_ALIAS_BYTES,
141 })
142 );
143 assert!(DatabaseAlias::new("a".repeat(MAX_DATABASE_ALIAS_BYTES)).is_ok());
144 }
145}