Skip to main content

unifi/api/
internal.rs

1//! Path/URL construction for the UniFi controller's internal (web-UI) API.
2
3/// Builds paths and URLs under a controller's internal, per-site API.
4#[derive(Debug, Clone)]
5pub struct InternalNetworkApi {
6    base_url: String,
7    site: String,
8    legacy: bool,
9}
10
11impl InternalNetworkApi {
12    /// `base_url` is the controller's base URL (trailing slash trimmed);
13    /// `site` is the site slug; `legacy` selects controllers without the
14    /// `/proxy/network` prefix.
15    pub fn new(base_url: impl Into<String>, site: impl Into<String>, legacy: bool) -> Self {
16        Self {
17            base_url: base_url.into().trim_end_matches('/').to_string(),
18            site: site.into(),
19            legacy,
20        }
21    }
22
23    /// Builds a `v1`-style per-site path: `/api/s/{site}/{suffix}`, prefixed
24    /// with `/proxy/network` unless this is a legacy controller.
25    pub fn v1_site_path(&self, suffix: &str) -> String {
26        let suffix = suffix.trim_start_matches('/');
27        let prefix = if self.legacy { "" } else { "/proxy/network" };
28        format!("{prefix}/api/s/{site}/{suffix}", site = self.site)
29    }
30
31    /// Builds a `v2`-style per-site path: `/v2/api/site/{site}/{suffix}`,
32    /// prefixed with `/proxy/network` unless this is a legacy controller.
33    pub fn v2_site_path(&self, suffix: &str) -> String {
34        let suffix = suffix.trim_start_matches('/');
35        if self.legacy {
36            format!("/v2/api/site/{site}/{suffix}", site = self.site)
37        } else {
38            format!(
39                "/proxy/network/v2/api/site/{site}/{suffix}",
40                site = self.site
41            )
42        }
43    }
44
45    /// `path`, joined onto this API's base URL as-is (no prefixing).
46    pub fn url(&self, path: &str) -> String {
47        format!("{}{}", self.base_url, path)
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn v1_site_path_adds_the_proxy_prefix_for_non_legacy_controllers() {
57        let api = InternalNetworkApi::new("https://unifi.local", "default", false);
58
59        assert_eq!(
60            api.v1_site_path("stat/sta"),
61            "/proxy/network/api/s/default/stat/sta"
62        );
63    }
64
65    #[test]
66    fn v1_site_path_omits_the_proxy_prefix_for_legacy_controllers() {
67        let api = InternalNetworkApi::new("https://unifi.local:8443", "default", true);
68
69        assert_eq!(api.v1_site_path("/stat/sta"), "/api/s/default/stat/sta");
70    }
71
72    #[test]
73    fn v2_site_path_adds_the_proxy_prefix_for_non_legacy_controllers() {
74        let api = InternalNetworkApi::new("https://unifi.local", "default", false);
75
76        assert_eq!(
77            api.v2_site_path("system-log/all"),
78            "/proxy/network/v2/api/site/default/system-log/all"
79        );
80    }
81
82    #[test]
83    fn v2_site_path_omits_the_proxy_prefix_for_legacy_controllers() {
84        let api = InternalNetworkApi::new("https://unifi.local:8443", "default", true);
85
86        assert_eq!(
87            api.v2_site_path("system-log/all"),
88            "/v2/api/site/default/system-log/all"
89        );
90    }
91}