Skip to main content

unifi/api/
official.rs

1//! Path/URL construction for UniFi's official `/proxy/network/integration` REST API.
2
3/// Builds paths and URLs under a controller's official integration API.
4#[derive(Debug, Clone)]
5pub struct OfficialNetworkApi {
6    base_url: String,
7}
8
9impl OfficialNetworkApi {
10    /// `base_url` is the controller's base URL, e.g. `https://unifi.local`; a
11    /// trailing slash is trimmed.
12    pub fn new(base_url: impl Into<String>) -> Self {
13        Self {
14            base_url: base_url.into().trim_end_matches('/').to_string(),
15        }
16    }
17
18    /// Maps a capability-catalog path (e.g. `v1/sites`) onto the official
19    /// integration API's request path.
20    ///
21    /// Connector actions (`ConnectorGet`/`Post`/`Put`/`Patch`/`Delete`) can
22    /// substitute a `*path` wildcard that is already fully qualified under
23    /// `/proxy/network/integration/` or `/proxy/protect/integration/` (see
24    /// [`crate::api::path::validate_connector_path`]) — pass those through
25    /// unchanged rather than prefixing them a second time.
26    pub fn path(&self, path: &str) -> String {
27        if path.starts_with("/proxy/network/integration/")
28            || path.starts_with("/proxy/protect/integration/")
29        {
30            return path.to_string();
31        }
32        let normalized = path.trim_start_matches('/');
33        if let Some(rest) = normalized.strip_prefix("v1/") {
34            format!("/proxy/network/integration/v1/{rest}")
35        } else {
36            format!("/proxy/network/integration/{normalized}")
37        }
38    }
39
40    /// [`Self::path`], joined onto this API's base URL.
41    pub fn url(&self, path: &str) -> String {
42        format!("{}{}", self.base_url, self.path(path))
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[test]
51    fn path_prefixes_v1_routes_with_the_integration_v1_segment() {
52        let api = OfficialNetworkApi::new("https://unifi.local");
53
54        assert_eq!(api.path("v1/sites"), "/proxy/network/integration/v1/sites");
55    }
56
57    #[test]
58    fn path_prefixes_other_routes_without_a_version_segment() {
59        let api = OfficialNetworkApi::new("https://unifi.local");
60
61        assert_eq!(
62            api.path("connectors"),
63            "/proxy/network/integration/connectors"
64        );
65    }
66
67    #[test]
68    fn path_passes_through_an_already_qualified_network_connector_path() {
69        let api = OfficialNetworkApi::new("https://unifi.local");
70
71        assert_eq!(
72            api.path("/proxy/network/integration/v1/sites"),
73            "/proxy/network/integration/v1/sites"
74        );
75    }
76
77    #[test]
78    fn path_passes_through_an_already_qualified_protect_connector_path() {
79        let api = OfficialNetworkApi::new("https://unifi.local");
80
81        assert_eq!(
82            api.path("/proxy/protect/integration/v1/cameras"),
83            "/proxy/protect/integration/v1/cameras"
84        );
85    }
86
87    #[test]
88    fn url_joins_the_base_url_and_path() {
89        let api = OfficialNetworkApi::new("https://unifi.local/");
90
91        assert_eq!(
92            api.url("v1/sites"),
93            "https://unifi.local/proxy/network/integration/v1/sites"
94        );
95    }
96}