Skip to main content

cufflink_types/
slug.rs

1//! Identifier rules shared by the platform and the processes that turn
2//! identifiers into database names, NATS subjects and filesystem paths.
3
4pub const TENANT_SLUG_MAX: usize = 40;
5pub const SERVICE_NAME_MAX: usize = 64;
6
7/// `^[a-z0-9-]{1,40}$`
8pub fn is_valid_tenant_slug(s: &str) -> bool {
9    (1..=TENANT_SLUG_MAX).contains(&s.len())
10        && s.bytes()
11            .all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-')
12}
13
14/// `^[A-Za-z0-9_-]{1,64}$`
15pub fn is_valid_service_name(s: &str) -> bool {
16    (1..=SERVICE_NAME_MAX).contains(&s.len())
17        && s.bytes()
18            .all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_')
19}
20
21#[cfg(test)]
22mod tests {
23    use super::*;
24
25    #[test]
26    fn tenant_slugs() {
27        for valid in ["default", "preview-pr-7", "a", "0", &"x".repeat(40)] {
28            assert!(is_valid_tenant_slug(valid), "{valid}");
29        }
30        for invalid in [
31            "",
32            "Default",
33            "preview_pr",
34            "a.b",
35            "../etc",
36            "tenant slug",
37            "ténant",
38            &"x".repeat(41),
39        ] {
40            assert!(!is_valid_tenant_slug(invalid), "{invalid}");
41        }
42    }
43
44    #[test]
45    fn service_names() {
46        for valid in ["asset-service", "Blog_Service", "a", &"s".repeat(64)] {
47            assert!(is_valid_service_name(valid), "{valid}");
48        }
49        for invalid in ["", "a.b", "../x", "a/b", "sérvice", "a b", &"s".repeat(65)] {
50            assert!(!is_valid_service_name(invalid), "{invalid}");
51        }
52    }
53}