Skip to main content

plexor_core/
namespace.rs

1// Copyright 2025 Alecks Gates
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7use std::fmt;
8use std::sync::Arc;
9
10pub trait Namespace: fmt::Display + Send + Sync + 'static {
11    fn path(&self) -> String;
12    fn with_suffix(&self, suffix: Vec<&str>) -> String;
13}
14
15/// A static implementation of Namespace.
16///
17/// # Examples
18///
19/// ```rust
20/// # use plexor_core::namespace::{Namespace, NamespaceImpl};
21/// #
22/// let ns = NamespaceImpl { delimiter: ".", parts: vec!["dev", "plexo"] };
23/// assert_eq!(ns.path(), "dev.plexo");
24/// assert_eq!(ns.with_suffix(vec!["test"]), "dev.plexo.test");
25/// ```
26#[derive(Debug, Clone)]
27pub struct NamespaceImpl {
28    pub delimiter: &'static str,
29    pub parts: Vec<&'static str>,
30}
31
32impl NamespaceImpl {
33    /// Create an Arc<NamespaceImpl> from this instance
34    pub fn into_arc(self) -> Arc<NamespaceImpl> {
35        Arc::new(self)
36    }
37}
38
39impl Namespace for NamespaceImpl {
40    fn path(&self) -> String {
41        self.parts.join(self.delimiter)
42    }
43
44    fn with_suffix(&self, suffix: Vec<&str>) -> String {
45        let parts_with_suffix: Vec<&str> = self.parts.clone().into_iter().chain(suffix).collect();
46
47        parts_with_suffix.join(self.delimiter)
48    }
49}
50
51impl fmt::Display for NamespaceImpl {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        write!(f, "{}", self.path())
54    }
55}
56
57/// A dynamic version of Namespace for cases where parts are only known at runtime (e.g. discovery).
58#[derive(Debug, Clone)]
59pub struct DynamicNamespace {
60    pub delimiter: String,
61    pub parts: Vec<String>,
62}
63
64impl DynamicNamespace {
65    /// Create a DynamicNamespace from a string with a delimiter.
66    ///
67    /// # Examples
68    ///
69    /// ```rust
70    /// # use plexor_core::namespace::{Namespace, DynamicNamespace};
71    /// #
72    /// let ns = DynamicNamespace::from_str("dev.plexo", ".");
73    /// assert_eq!(ns.path(), "dev.plexo");
74    /// assert_eq!(ns.with_suffix(vec!["test"]), "dev.plexo.test");
75    /// ```
76    pub fn from_str(path: &str, delimiter: &str) -> Self {
77        Self {
78            delimiter: delimiter.to_string(),
79            parts: path.split(delimiter).map(|s| s.to_string()).collect(),
80        }
81    }
82
83    /// Helper to create an Arc<DynamicNamespace>
84    pub fn new_arc(path: &str, delimiter: &str) -> Arc<Self> {
85        Arc::new(Self::from_str(path, delimiter))
86    }
87}
88
89impl Namespace for DynamicNamespace {
90    fn path(&self) -> String {
91        self.parts.join(&self.delimiter)
92    }
93
94    fn with_suffix(&self, suffix: Vec<&str>) -> String {
95        let mut p = self.parts.clone();
96        p.extend(suffix.iter().map(|s| s.to_string()));
97        p.join(&self.delimiter)
98    }
99}
100
101impl fmt::Display for DynamicNamespace {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        write!(f, "{}", self.path())
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110    use crate::test_utils::test_namespace;
111
112    #[test]
113    fn test_namespace_path() {
114        let ns = test_namespace();
115        assert_eq!("dev.plexo", ns.path());
116    }
117
118    #[test]
119    fn test_namespace_with_suffix() {
120        let ns = test_namespace();
121        assert_eq!(
122            "dev.plexo.test.foobar",
123            ns.with_suffix(vec!["test", "foobar"])
124        );
125    }
126
127    #[test]
128    fn test_namespace_display() {
129        let ns = NamespaceImpl {
130            delimiter: ".",
131            parts: vec!["dev", "plexo"],
132        };
133        assert_eq!("dev.plexo", format!("{ns}"));
134    }
135}