Skip to main content

dbus_router/
config.rs

1//! Configuration file parsing for routing rules
2
3use anyhow::Result;
4use serde::Deserialize;
5use std::path::Path;
6
7/// Router configuration loaded from TOML file.
8#[derive(Debug, Clone, Default, Deserialize)]
9pub struct Config {
10    /// Routes that should go to the host bus instead of sandbox.
11    #[serde(default)]
12    pub host_routes: Vec<RouteRule>,
13}
14
15/// A routing rule that matches destinations.
16#[derive(Debug, Clone, Deserialize)]
17pub struct RouteRule {
18    /// Destination to match. Supports exact match or "org.foo.*" wildcard.
19    pub destination: String,
20}
21
22impl Config {
23    /// Load configuration from a TOML file.
24    pub fn load(path: &Path) -> Result<Self> {
25        let content = std::fs::read_to_string(path)?;
26        let config: Config = toml::from_str(&content)?;
27        tracing::info!(routes = config.host_routes.len(), "Loaded configuration");
28        for rule in &config.host_routes {
29            tracing::debug!(destination = %rule.destination, "Host route");
30        }
31        Ok(config)
32    }
33
34    /// Check if a destination should be routed to the host bus.
35    pub fn should_route_to_host(&self, destination: &str) -> bool {
36        self.host_routes
37            .iter()
38            .any(|rule| rule.matches(destination))
39    }
40}
41
42impl RouteRule {
43    /// Check if the destination matches this rule.
44    /// Supports exact match and "org.foo.*" wildcard pattern.
45    pub fn matches(&self, destination: &str) -> bool {
46        if let Some(prefix) = self.destination.strip_suffix(".*") {
47            // Wildcard: destination must start with prefix and either equal it
48            // or have a dot after the prefix
49            if destination == prefix {
50                return true;
51            }
52            if let Some(rest) = destination.strip_prefix(prefix) {
53                return rest.starts_with('.');
54            }
55            false
56        } else {
57            // Exact match
58            self.destination == destination
59        }
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn test_exact_match() {
69        let rule = RouteRule {
70            destination: "org.freedesktop.DBus".to_string(),
71        };
72        assert!(rule.matches("org.freedesktop.DBus"));
73        assert!(!rule.matches("org.freedesktop.DBus.Peer"));
74        assert!(!rule.matches("org.freedesktop"));
75    }
76
77    #[test]
78    fn test_wildcard_match() {
79        let rule = RouteRule {
80            destination: "org.freedesktop.portal.*".to_string(),
81        };
82        assert!(rule.matches("org.freedesktop.portal.Desktop"));
83        assert!(rule.matches("org.freedesktop.portal.FileChooser"));
84        assert!(rule.matches("org.freedesktop.portal")); // prefix itself matches
85        assert!(!rule.matches("org.freedesktop.portals")); // no dot separator
86        assert!(!rule.matches("org.freedesktop.DBus"));
87    }
88
89    #[test]
90    fn test_should_route_to_host() {
91        let config = Config {
92            host_routes: vec![
93                RouteRule {
94                    destination: "org.freedesktop.DBus".to_string(),
95                },
96                RouteRule {
97                    destination: "org.freedesktop.portal.*".to_string(),
98                },
99            ],
100        };
101
102        assert!(config.should_route_to_host("org.freedesktop.DBus"));
103        assert!(config.should_route_to_host("org.freedesktop.portal.Desktop"));
104        assert!(!config.should_route_to_host("org.example.Test"));
105    }
106
107    #[test]
108    fn test_empty_config() {
109        let config = Config::default();
110        assert!(!config.should_route_to_host("org.freedesktop.DBus"));
111    }
112
113    #[test]
114    fn test_parse_toml() {
115        let toml_str = r#"
116[[host_routes]]
117destination = "org.freedesktop.DBus"
118
119[[host_routes]]
120destination = "org.freedesktop.portal.*"
121"#;
122        let config: Config = toml::from_str(toml_str).unwrap();
123        assert_eq!(config.host_routes.len(), 2);
124        assert_eq!(config.host_routes[0].destination, "org.freedesktop.DBus");
125        assert_eq!(
126            config.host_routes[1].destination,
127            "org.freedesktop.portal.*"
128        );
129    }
130}