use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
pub const SYSTEM_TENANT: &str = "__system__";
#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct TenantId(String);
impl TenantId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
pub fn system() -> Self {
Self(SYSTEM_TENANT.to_string())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn is_system(&self) -> bool {
self.0 == SYSTEM_TENANT
}
pub fn is_descendant_of(&self, ancestor: &TenantId) -> bool {
if self.0 == ancestor.0 {
return false; }
self.0.starts_with(&ancestor.0) && self.0[ancestor.0.len()..].starts_with('/')
}
pub fn is_ancestor_of(&self, descendant: &TenantId) -> bool {
descendant.is_descendant_of(self)
}
pub fn parent(&self) -> Option<TenantId> {
self.0.rfind('/').map(|idx| TenantId::new(&self.0[..idx]))
}
pub fn depth(&self) -> usize {
self.0.matches('/').count()
}
pub fn ancestors(&self) -> impl Iterator<Item = TenantId> + '_ {
TenantAncestorIterator {
current: self.clone(),
}
}
pub fn root(&self) -> TenantId {
match self.0.find('/') {
Some(idx) => TenantId::new(&self.0[..idx]),
None => self.clone(),
}
}
pub fn child(&self, segment: &str) -> TenantId {
TenantId::new(format!("{}/{}", self.0, segment))
}
}
impl fmt::Display for TenantId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl fmt::Debug for TenantId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "TenantId({})", self.0)
}
}
impl FromStr for TenantId {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(TenantId::new(s))
}
}
impl From<&str> for TenantId {
fn from(s: &str) -> Self {
TenantId::new(s)
}
}
impl From<String> for TenantId {
fn from(s: String) -> Self {
TenantId::new(s)
}
}
impl AsRef<str> for TenantId {
fn as_ref(&self) -> &str {
&self.0
}
}
struct TenantAncestorIterator {
current: TenantId,
}
impl Iterator for TenantAncestorIterator {
type Item = TenantId;
fn next(&mut self) -> Option<Self::Item> {
let parent = self.current.parent()?;
self.current = parent.clone();
Some(parent)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tenant_id_creation() {
let tenant = TenantId::new("my-tenant");
assert_eq!(tenant.as_str(), "my-tenant");
}
#[test]
fn test_system_tenant() {
let system = TenantId::system();
assert!(system.is_system());
assert_eq!(system.as_str(), SYSTEM_TENANT);
}
#[test]
fn test_hierarchy_descendant() {
let parent = TenantId::new("acme");
let child = TenantId::new("acme/research");
let grandchild = TenantId::new("acme/research/oncology");
let unrelated = TenantId::new("other");
assert!(child.is_descendant_of(&parent));
assert!(grandchild.is_descendant_of(&parent));
assert!(grandchild.is_descendant_of(&child));
assert!(!parent.is_descendant_of(&child));
assert!(!child.is_descendant_of(&unrelated));
assert!(!parent.is_descendant_of(&parent)); }
#[test]
fn test_hierarchy_ancestor() {
let parent = TenantId::new("acme");
let child = TenantId::new("acme/research");
assert!(parent.is_ancestor_of(&child));
assert!(!child.is_ancestor_of(&parent));
}
#[test]
fn test_parent() {
let root = TenantId::new("acme");
let child = TenantId::new("acme/research");
let grandchild = TenantId::new("acme/research/oncology");
assert_eq!(root.parent(), None);
assert_eq!(child.parent(), Some(TenantId::new("acme")));
assert_eq!(grandchild.parent(), Some(TenantId::new("acme/research")));
}
#[test]
fn test_depth() {
assert_eq!(TenantId::new("acme").depth(), 0);
assert_eq!(TenantId::new("acme/research").depth(), 1);
assert_eq!(TenantId::new("acme/research/oncology").depth(), 2);
}
#[test]
fn test_ancestors() {
let tenant = TenantId::new("acme/research/oncology");
let ancestors: Vec<_> = tenant.ancestors().collect();
assert_eq!(ancestors.len(), 2);
assert_eq!(ancestors[0].as_str(), "acme/research");
assert_eq!(ancestors[1].as_str(), "acme");
}
#[test]
fn test_root() {
assert_eq!(TenantId::new("acme").root().as_str(), "acme");
assert_eq!(TenantId::new("acme/research").root().as_str(), "acme");
assert_eq!(
TenantId::new("acme/research/oncology").root().as_str(),
"acme"
);
}
#[test]
fn test_child() {
let parent = TenantId::new("acme");
let child = parent.child("research");
assert_eq!(child.as_str(), "acme/research");
}
#[test]
fn test_serde_roundtrip() {
let tenant = TenantId::new("acme/research");
let json = serde_json::to_string(&tenant).unwrap();
assert_eq!(json, "\"acme/research\"");
let parsed: TenantId = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, tenant);
}
#[test]
fn test_from_string() {
let tenant: TenantId = "my-tenant".into();
assert_eq!(tenant.as_str(), "my-tenant");
let tenant2: TenantId = String::from("my-tenant").into();
assert_eq!(tenant2.as_str(), "my-tenant");
}
}