Skip to main content

khive_types/
namespace.rs

1//! Namespace — string-based scoping for substrate records.
2//!
3//! In khive OSS, namespace is a plain string (e.g., `"local"`, `"research"`,
4//! `"lattice-project"`). It groups records and supports cross-namespace
5//! queries via the entity graph.
6//!
7//! Multi-tenant deployments (e.g., khive.ai hosted) add capability-based
8//! access controls on top in a separate crate — those are not part of the
9//! open-source runtime.
10
11extern crate alloc;
12use alloc::string::String;
13use core::fmt;
14
15#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17#[cfg_attr(feature = "serde", serde(transparent))]
18pub struct Namespace(String);
19
20impl Namespace {
21    /// Create a namespace from any string-like value.
22    #[inline]
23    pub fn new(s: impl Into<String>) -> Self {
24        Self(s.into())
25    }
26
27    /// The default namespace name.
28    pub const DEFAULT: &'static str = "local";
29
30    /// Construct the default namespace.
31    pub fn default_ns() -> Self {
32        Self::new(Self::DEFAULT)
33    }
34
35    #[inline]
36    pub fn as_str(&self) -> &str {
37        &self.0
38    }
39
40    /// True if `self` is a hierarchical child of `parent`
41    /// (e.g., `"research:lattice"` is a child of `"research"`).
42    pub fn is_child_of(&self, parent: &Namespace) -> bool {
43        self.0.len() > parent.0.len()
44            && self.0.starts_with(parent.as_str())
45            && self.0.as_bytes().get(parent.0.len()) == Some(&b':')
46    }
47
48    pub fn into_inner(self) -> String {
49        self.0
50    }
51}
52
53impl Default for Namespace {
54    fn default() -> Self {
55        Self::default_ns()
56    }
57}
58
59impl fmt::Display for Namespace {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        f.write_str(&self.0)
62    }
63}
64
65impl AsRef<str> for Namespace {
66    #[inline]
67    fn as_ref(&self) -> &str {
68        &self.0
69    }
70}
71
72impl From<&str> for Namespace {
73    #[inline]
74    fn from(s: &str) -> Self {
75        Self::new(s)
76    }
77}
78
79impl From<String> for Namespace {
80    #[inline]
81    fn from(s: String) -> Self {
82        Self(s)
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn construction() {
92        let ns = Namespace::new("research");
93        assert_eq!(ns.as_str(), "research");
94    }
95
96    #[test]
97    fn default_is_local() {
98        assert_eq!(Namespace::default().as_str(), "local");
99    }
100
101    #[test]
102    fn is_child_of() {
103        let parent = Namespace::new("research");
104        let child = Namespace::new("research:lattice");
105        let sibling = Namespace::new("other");
106
107        assert!(child.is_child_of(&parent));
108        assert!(!sibling.is_child_of(&parent));
109        assert!(!parent.is_child_of(&parent));
110    }
111}