guacamole_client/
connection_group.rs1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use crate::client::GuacamoleClient;
7use crate::error::Result;
8use crate::validation::{validate_connection_group_id, validate_query_param};
9
10#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(rename_all = "camelCase")]
13#[non_exhaustive]
14pub struct ConnectionGroup {
15 #[serde(skip_serializing_if = "Option::is_none")]
17 pub identifier: Option<String>,
18
19 #[serde(skip_serializing_if = "Option::is_none")]
21 pub parent_identifier: Option<String>,
22
23 #[serde(skip_serializing_if = "Option::is_none")]
25 pub name: Option<String>,
26
27 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
29 pub type_: Option<String>,
30
31 #[serde(skip_serializing_if = "Option::is_none")]
33 pub attributes: Option<HashMap<String, String>>,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub active_connections: Option<i32>,
38}
39
40impl GuacamoleClient {
41 pub async fn list_connection_groups(
43 &self,
44 data_source: Option<&str>,
45 ) -> Result<HashMap<String, ConnectionGroup>> {
46 let ds = self.resolve_data_source(data_source)?;
47 let url = self.url(&format!("/api/session/data/{ds}/connectionGroups"))?;
48 let response = self.http.get(&url).send().await?;
49 Self::parse_response(response, "connection groups").await
50 }
51
52 pub async fn get_connection_group(
54 &self,
55 data_source: Option<&str>,
56 group_id: &str,
57 ) -> Result<ConnectionGroup> {
58 validate_connection_group_id(group_id)?;
59 let ds = self.resolve_data_source(data_source)?;
60 let url = self.url(&format!(
61 "/api/session/data/{ds}/connectionGroups/{group_id}"
62 ))?;
63 let response = self.http.get(&url).send().await?;
64 Self::parse_response(response, &format!("connection group {group_id}")).await
65 }
66
67 pub async fn get_connection_group_tree(
72 &self,
73 data_source: Option<&str>,
74 group_id: &str,
75 permission: Option<&str>,
76 ) -> Result<Value> {
77 validate_connection_group_id(group_id)?;
78 let ds = self.resolve_data_source(data_source)?;
79 let url = self.url(&format!(
80 "/api/session/data/{ds}/connectionGroups/{group_id}/tree"
81 ))?;
82
83 let mut request = self.http.get(&url);
84 if let Some(p) = permission {
85 validate_query_param("permission", p)?;
86 request = request.query(&[("permission", p)]);
87 }
88
89 let response = request.send().await?;
90 Self::parse_response(response, &format!("connection group {group_id} tree")).await
91 }
92
93 pub async fn create_connection_group(
95 &self,
96 data_source: Option<&str>,
97 group: &ConnectionGroup,
98 ) -> Result<ConnectionGroup> {
99 let ds = self.resolve_data_source(data_source)?;
100 let url = self.url(&format!("/api/session/data/{ds}/connectionGroups"))?;
101 let response = self.http.post(&url).json(group).send().await?;
102 Self::parse_response(response, "create connection group").await
103 }
104
105 pub async fn update_connection_group(
107 &self,
108 data_source: Option<&str>,
109 group_id: &str,
110 group: &ConnectionGroup,
111 ) -> Result<()> {
112 validate_connection_group_id(group_id)?;
113 let ds = self.resolve_data_source(data_source)?;
114 let url = self.url(&format!(
115 "/api/session/data/{ds}/connectionGroups/{group_id}"
116 ))?;
117 let response = self.http.put(&url).json(group).send().await?;
118 Self::handle_error(response, &format!("connection group {group_id}")).await?;
119 Ok(())
120 }
121
122 pub async fn delete_connection_group(
124 &self,
125 data_source: Option<&str>,
126 group_id: &str,
127 ) -> Result<()> {
128 validate_connection_group_id(group_id)?;
129 let ds = self.resolve_data_source(data_source)?;
130 let url = self.url(&format!(
131 "/api/session/data/{ds}/connectionGroups/{group_id}"
132 ))?;
133 let response = self.http.delete(&url).send().await?;
134 Self::handle_error(response, &format!("connection group {group_id}")).await?;
135 Ok(())
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn connection_group_serde_roundtrip() {
145 let group = ConnectionGroup {
146 identifier: Some("1".to_string()),
147 parent_identifier: Some("ROOT".to_string()),
148 name: Some("Servers".to_string()),
149 type_: Some("ORGANIZATIONAL".to_string()),
150 attributes: Some(HashMap::new()),
151 active_connections: Some(0),
152 };
153 let json = serde_json::to_string(&group).unwrap();
154 let deserialized: ConnectionGroup = serde_json::from_str(&json).unwrap();
155 assert_eq!(group, deserialized);
156 }
157
158 #[test]
159 fn connection_group_camel_case_keys() {
160 let group = ConnectionGroup {
161 parent_identifier: Some("ROOT".to_string()),
162 active_connections: Some(2),
163 ..Default::default()
164 };
165 let json = serde_json::to_value(&group).unwrap();
166 assert!(json.get("parentIdentifier").is_some());
167 assert!(json.get("activeConnections").is_some());
168 }
169
170 #[test]
171 fn connection_group_type_field_rename() {
172 let group = ConnectionGroup {
173 type_: Some("BALANCING".to_string()),
174 ..Default::default()
175 };
176 let json = serde_json::to_value(&group).unwrap();
177 assert!(json.get("type").is_some());
178 assert_eq!(json.get("type").unwrap(), "BALANCING");
179 assert!(json.get("type_").is_none(), "Rust field name must not leak into JSON");
180 }
181
182 #[test]
183 fn connection_group_skip_none_fields() {
184 let group = ConnectionGroup::default();
185 let json = serde_json::to_value(&group).unwrap();
186 let obj = json.as_object().unwrap();
187 assert!(obj.is_empty());
188 }
189
190 #[test]
191 fn deserialize_connection_group_from_api_json() {
192 let json = r#"{
193 "identifier": "1",
194 "parentIdentifier": "ROOT",
195 "name": "Servers",
196 "type": "ORGANIZATIONAL",
197 "attributes": {},
198 "activeConnections": 3
199 }"#;
200 let group: ConnectionGroup = serde_json::from_str(json).unwrap();
201 assert_eq!(group.identifier.as_deref(), Some("1"));
202 assert_eq!(group.parent_identifier.as_deref(), Some("ROOT"));
203 assert_eq!(group.name.as_deref(), Some("Servers"));
204 assert_eq!(group.type_.as_deref(), Some("ORGANIZATIONAL"));
205 assert_eq!(group.active_connections, Some(3));
206 }
207
208 #[test]
209 fn deserialize_connection_group_unknown_fields_ignored() {
210 let json = r#"{"identifier": "1", "unknownField": true}"#;
211 let group: ConnectionGroup = serde_json::from_str(json).unwrap();
212 assert_eq!(group.identifier.as_deref(), Some("1"));
213 }
214}