Skip to main content

ave_common/
namespace.rs

1//! Hierarchical namespace type used by subjects and governance roles.
2
3use borsh::{BorshDeserialize, BorshSerialize};
4
5use serde::{Deserialize, Serialize};
6
7use std::cmp::Ordering;
8use std::fmt::{Error, Formatter};
9
10#[cfg(feature = "openapi")]
11use utoipa::ToSchema;
12
13#[cfg(feature = "typescript")]
14use ts_rs::TS;
15
16/// Dot-separated namespace.
17#[derive(
18    Clone,
19    Hash,
20    Eq,
21    PartialEq,
22    PartialOrd,
23    Ord,
24    Serialize,
25    Deserialize,
26    BorshDeserialize,
27    BorshSerialize,
28)]
29#[cfg_attr(feature = "openapi", derive(ToSchema))]
30#[cfg_attr(feature = "typescript", derive(TS))]
31#[cfg_attr(feature = "typescript", ts(export))]
32pub struct Namespace(Vec<String>);
33
34impl Namespace {
35    /// Creates an empty namespace.
36    pub const fn new() -> Self {
37        Self(Vec::new())
38    }
39
40    /// Returns `true` when all namespace tokens are non-empty, trimmed and short enough.
41    pub fn check(&self) -> bool {
42        !self
43            .0
44            .iter()
45            .any(|x| x.trim().is_empty() || x.len() > 100 || x != x.trim())
46    }
47
48    /// Appends a non-empty token to the namespace.
49    pub fn add(&mut self, name: &str) {
50        let name = name.trim();
51
52        if !name.is_empty() {
53            self.0.push(name.to_owned())
54        }
55    }
56
57    /// Returns the top-level namespace segment.
58    pub fn root(&self) -> Self {
59        if self.0.len() == 1 {
60            self.clone()
61        } else if !self.0.is_empty() {
62            Self(self.0.iter().take(1).cloned().collect())
63        } else {
64            Self(Vec::new())
65        }
66    }
67
68    /// Returns the direct parent namespace.
69    pub fn parent(&self) -> Self {
70        if self.0.len() > 1 {
71            let mut tokens = self.0.clone();
72            tokens.truncate(tokens.len() - 1);
73            Self(tokens)
74        } else {
75            Self(Vec::new())
76        }
77    }
78
79    /// Returns the last namespace segment.
80    pub fn key(&self) -> String {
81        self.0.last().cloned().unwrap_or_else(|| "".to_string())
82    }
83
84    /// Returns the number of namespace segments.
85    pub const fn level(&self) -> usize {
86        self.0.len()
87    }
88
89    /// Returns the namespace truncated to `level` segments.
90    pub fn at_level(&self, level: usize) -> Self {
91        if level == 0 || level > self.level() {
92            self.clone()
93        } else {
94            let mut tokens = self.0.clone();
95            tokens.truncate(level);
96            Self(tokens)
97        }
98    }
99
100    /// Returns `true` when the namespace has no segments.
101    pub const fn is_empty(&self) -> bool {
102        self.0.is_empty()
103    }
104
105    /// Returns `true` when `self` is a strict ancestor of `other`.
106    pub fn is_ancestor_of(&self, other: &Self) -> bool {
107        let me = format!("{}.", self);
108        other.to_string().as_str().starts_with(me.as_str()) || self.is_empty()
109    }
110
111    /// Returns `true` when `self` is an ancestor of `other` or both are equal.
112    pub fn is_ancestor_or_equal_of(&self, other: &Self) -> bool {
113        let me = format!("{}.", self);
114        other.to_string().as_str().starts_with(me.as_str())
115            || self.is_empty()
116            || self == other
117    }
118
119    /// Returns `true` when `self` is a strict descendant of `other`.
120    pub fn is_descendant_of(&self, other: &Self) -> bool {
121        let me = self.to_string();
122        me.as_str().starts_with(format!("{}.", other).as_str())
123    }
124
125    /// Returns `true` when `self` is the direct parent of `other`.
126    pub fn is_parent_of(&self, other: &Self) -> bool {
127        *self == other.parent()
128    }
129
130    /// Returns `true` when `self` is the direct child of `other`.
131    pub fn is_child_of(&self, other: &Self) -> bool {
132        self.parent() == *other
133    }
134
135    /// Returns `true` when the namespace has a single segment.
136    pub const fn is_top_level(&self) -> bool {
137        self.0.len() == 1
138    }
139}
140
141impl std::fmt::Display for Namespace {
142    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
143        match self.level().cmp(&1) {
144            Ordering::Less => write!(f, ""),
145            Ordering::Equal => write!(f, "{}", self.0[0]),
146            Ordering::Greater => write!(f, "{}", self.0.join(".")),
147        }
148    }
149}
150
151impl std::fmt::Debug for Namespace {
152    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
153        match self.level().cmp(&1) {
154            Ordering::Less => {
155                write!(f, "")
156            }
157            Ordering::Equal => write!(f, "{}", self.0[0]),
158            Ordering::Greater => write!(f, "{}", self.0.join(".")),
159        }
160    }
161}
162
163impl Default for Namespace {
164    fn default() -> Self {
165        Self::new()
166    }
167}
168
169impl From<&str> for Namespace {
170    fn from(str: &str) -> Self {
171        let tokens: Vec<String> = str
172            .split('.')
173            .filter(|x| !x.trim().is_empty())
174            .map(|s| s.trim().to_string())
175            .collect();
176
177        Self(tokens)
178    }
179}
180
181impl From<String> for Namespace {
182    fn from(str: String) -> Self {
183        Self::from(str.as_str())
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190
191    #[test]
192    fn test_namespace() {
193        let ns = Namespace::from("a.b.c");
194        assert_eq!(ns.level(), 3);
195        assert_eq!(ns.key(), "c");
196        assert_eq!(ns.root().to_string(), "a");
197        assert_eq!(ns.parent().to_string(), "a.b");
198        assert_eq!(ns.at_level(1).to_string(), "a");
199        assert_eq!(ns.at_level(2).to_string(), "a.b");
200        assert_eq!(ns.at_level(3).to_string(), "a.b.c");
201        assert!(!ns.is_empty());
202        assert!(ns.is_ancestor_of(&Namespace::from("a.b.c.d")));
203        assert!(ns.is_descendant_of(&Namespace::from("a.b")));
204        assert!(ns.is_parent_of(&Namespace::from("a.b.c.d")));
205        assert!(ns.is_child_of(&Namespace::from("a.b")));
206        assert!(!ns.is_top_level());
207        assert!(Namespace::new().is_ancestor_of(&Namespace::from("a.b.c.d")));
208    }
209}