use std::fmt;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct TenantId(String);
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum TenantError {
#[error("a tenant id must not be empty")]
Empty,
#[error(
"a tenant id may not contain '{0}': it becomes part of composite keys and key-ring \
scopes, where a separator makes two distinct tenants collide into one"
)]
Separator(char),
#[error("a tenant id is limited to {max} characters, and this one is {len}")]
TooLong { len: usize, max: usize },
}
impl TenantId {
pub const MAX_LEN: usize = 64;
pub const DEFAULT: &'static str = "default";
pub fn new(name: impl Into<String>) -> Result<Self, TenantError> {
let name = name.into();
let trimmed = name.trim();
if trimmed.is_empty() {
return Err(TenantError::Empty);
}
if trimmed.len() > Self::MAX_LEN {
return Err(TenantError::TooLong {
len: trimmed.len(),
max: Self::MAX_LEN,
});
}
if let Some(bad) = trimmed
.chars()
.find(|c| matches!(c, '/' | ':' | '\0' | '\n') || c.is_control())
{
return Err(TenantError::Separator(bad));
}
Ok(Self(trimmed.to_owned()))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Default for TenantId {
fn default() -> Self {
Self(Self::DEFAULT.to_owned())
}
}
impl fmt::Display for TenantId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}