1use anyhow::Result;
4use serde::Deserialize;
5use std::path::Path;
6
7#[derive(Debug, Clone, Default, Deserialize)]
9pub struct Config {
10 #[serde(default)]
12 pub host_routes: Vec<RouteRule>,
13}
14
15#[derive(Debug, Clone, Deserialize)]
17pub struct RouteRule {
18 pub destination: String,
20}
21
22impl Config {
23 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 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 pub fn matches(&self, destination: &str) -> bool {
46 if let Some(prefix) = self.destination.strip_suffix(".*") {
47 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 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")); assert!(!rule.matches("org.freedesktop.portals")); 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}