Skip to main content

dynamo_runtime/
namespace.rs

1// SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4pub const GLOBAL_NAMESPACE: &str = "dynamo";
5
6/// Determines how namespaces are filtered during model discovery.
7///
8/// This supports the hierarchical model architecture where multiple WorkerSets
9/// with different namespaces (e.g., during rolling updates) should be discovered
10/// together under the same Model.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum NamespaceFilter {
13    /// Discover models from all namespaces (no filtering)
14    Global,
15    /// Discover models only from an exact namespace match
16    Exact(String),
17    /// Discover models from namespaces starting with the given prefix
18    /// (e.g., prefix "ns" matches "ns", "ns-abc123", "ns-def456")
19    Prefix(String),
20}
21
22impl NamespaceFilter {
23    /// Create a NamespaceFilter from optional namespace and namespace_prefix.
24    /// If prefix is provided, it takes precedence over exact namespace.
25    pub fn from_namespace_and_prefix(
26        namespace: Option<&str>,
27        namespace_prefix: Option<&str>,
28    ) -> Self {
29        // Prefix takes precedence if both are specified
30        if let Some(prefix) = namespace_prefix {
31            if prefix.is_empty() || is_global_namespace(prefix) {
32                return NamespaceFilter::Global;
33            }
34            return NamespaceFilter::Prefix(prefix.to_string());
35        }
36
37        if let Some(ns) = namespace {
38            if ns.is_empty() || is_global_namespace(ns) {
39                return NamespaceFilter::Global;
40            }
41            return NamespaceFilter::Exact(ns.to_string());
42        }
43
44        NamespaceFilter::Global
45    }
46
47    /// Check if a given namespace matches this filter.
48    pub fn matches(&self, namespace: &str) -> bool {
49        match self {
50            NamespaceFilter::Global => true,
51            NamespaceFilter::Exact(target) => namespace == target,
52            NamespaceFilter::Prefix(prefix) => namespace.starts_with(prefix),
53        }
54    }
55
56    /// Returns true if this is global namespace filtering (no filtering).
57    pub fn is_global(&self) -> bool {
58        matches!(self, NamespaceFilter::Global)
59    }
60}
61
62pub fn is_global_namespace(namespace: &str) -> bool {
63    namespace == GLOBAL_NAMESPACE || namespace.is_empty()
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn test_from_namespace_and_prefix_global() {
72        assert_eq!(
73            NamespaceFilter::from_namespace_and_prefix(None, None),
74            NamespaceFilter::Global
75        );
76        assert_eq!(
77            NamespaceFilter::from_namespace_and_prefix(Some(""), None),
78            NamespaceFilter::Global
79        );
80        assert_eq!(
81            NamespaceFilter::from_namespace_and_prefix(Some(GLOBAL_NAMESPACE), None),
82            NamespaceFilter::Global
83        );
84    }
85
86    #[test]
87    fn test_from_namespace_and_prefix_exact() {
88        assert_eq!(
89            NamespaceFilter::from_namespace_and_prefix(Some("my-namespace"), None),
90            NamespaceFilter::Exact("my-namespace".to_string())
91        );
92    }
93
94    #[test]
95    fn test_from_namespace_and_prefix_prefix_takes_precedence() {
96        assert_eq!(
97            NamespaceFilter::from_namespace_and_prefix(Some("exact"), Some("prefix")),
98            NamespaceFilter::Prefix("prefix".to_string())
99        );
100    }
101
102    #[test]
103    fn test_matches_global() {
104        let filter = NamespaceFilter::Global;
105        assert!(filter.matches("anything"));
106        assert!(filter.matches(""));
107        assert!(filter.matches("default"));
108        assert!(filter.matches("ns-abc123"));
109    }
110
111    #[test]
112    fn test_matches_exact() {
113        let filter = NamespaceFilter::Exact("my-namespace".to_string());
114        assert!(filter.matches("my-namespace"));
115        assert!(!filter.matches("my-namespace-abc123"));
116        assert!(!filter.matches("other"));
117        assert!(!filter.matches(""));
118    }
119
120    #[test]
121    fn test_matches_prefix() {
122        let filter = NamespaceFilter::Prefix("ns".to_string());
123        assert!(filter.matches("ns"));
124        assert!(filter.matches("ns-abc123"));
125        assert!(filter.matches("ns-def456"));
126        assert!(!filter.matches("other-ns"));
127        assert!(!filter.matches(""));
128    }
129
130    #[test]
131    fn test_is_global() {
132        assert!(NamespaceFilter::Global.is_global());
133        assert!(!NamespaceFilter::Exact("ns".to_string()).is_global());
134        assert!(!NamespaceFilter::Prefix("ns".to_string()).is_global());
135    }
136}