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#[derive(Debug, Clone)]
16pub struct NamespaceImpl {
17    pub delimiter: &'static str,
18    pub parts: Vec<&'static str>,
19}
20
21impl NamespaceImpl {
22    /// Create an Arc<NamespaceImpl> from this instance
23    pub fn into_arc(self) -> Arc<NamespaceImpl> {
24        Arc::new(self)
25    }
26}
27
28impl Namespace for NamespaceImpl {
29    fn path(&self) -> String {
30        self.parts.join(self.delimiter)
31    }
32
33    fn with_suffix(&self, suffix: Vec<&str>) -> String {
34        let parts_with_suffix: Vec<&str> = self.parts.clone().into_iter().chain(suffix).collect();
35
36        parts_with_suffix.join(self.delimiter)
37    }
38}
39
40impl fmt::Display for NamespaceImpl {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        write!(f, "{}", self.path())
43    }
44}
45
46/// A dynamic version of Namespace for cases where parts are only known at runtime (e.g. discovery).
47#[derive(Debug, Clone)]
48pub struct DynamicNamespace {
49    pub delimiter: String,
50    pub parts: Vec<String>,
51}
52
53impl DynamicNamespace {
54    /// Create a DynamicNamespace from a string with a delimiter.
55    pub fn from_str(path: &str, delimiter: &str) -> Self {
56        Self {
57            delimiter: delimiter.to_string(),
58            parts: path.split(delimiter).map(|s| s.to_string()).collect(),
59        }
60    }
61
62    /// Helper to create an Arc<DynamicNamespace>
63    pub fn new_arc(path: &str, delimiter: &str) -> Arc<Self> {
64        Arc::new(Self::from_str(path, delimiter))
65    }
66}
67
68impl Namespace for DynamicNamespace {
69    fn path(&self) -> String {
70        self.parts.join(&self.delimiter)
71    }
72
73    fn with_suffix(&self, suffix: Vec<&str>) -> String {
74        let mut p = self.parts.clone();
75        p.extend(suffix.iter().map(|s| s.to_string()));
76        p.join(&self.delimiter)
77    }
78}
79
80impl fmt::Display for DynamicNamespace {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        write!(f, "{}", self.path())
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89    use crate::test_utils::test_namespace;
90
91    #[test]
92    fn test_namespace_path() {
93        let ns = test_namespace();
94        assert_eq!("dev.plexo", ns.path());
95    }
96
97    #[test]
98    fn test_namespace_with_suffix() {
99        let ns = test_namespace();
100        assert_eq!(
101            "dev.plexo.test.foobar",
102            ns.with_suffix(vec!["test", "foobar"])
103        );
104    }
105
106    #[test]
107    fn test_namespace_display() {
108        let ns = NamespaceImpl {
109            delimiter: ".",
110            parts: vec!["dev", "plexo"],
111        };
112        assert_eq!("dev.plexo", format!("{ns}"));
113    }
114}