guacamole_client/
user_group.rs1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::client::GuacamoleClient;
6use crate::error::Result;
7use crate::patch::PatchOperation;
8use crate::validation::validate_user_group_id;
9
10#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(rename_all = "camelCase")]
13#[non_exhaustive]
14pub struct UserGroup {
15 #[serde(skip_serializing_if = "Option::is_none")]
17 pub identifier: Option<String>,
18
19 #[serde(skip_serializing_if = "Option::is_none")]
21 pub attributes: Option<HashMap<String, Option<String>>>,
22}
23
24impl GuacamoleClient {
25 pub async fn list_user_groups(
27 &self,
28 data_source: Option<&str>,
29 ) -> Result<HashMap<String, UserGroup>> {
30 let ds = self.resolve_data_source(data_source)?;
31 let url = self.url(&format!("/api/session/data/{ds}/userGroups"))?;
32 let response = self.http.get(&url).send().await?;
33 Self::parse_response(response, "user groups").await
34 }
35
36 pub async fn get_user_group(
38 &self,
39 data_source: Option<&str>,
40 group_id: &str,
41 ) -> Result<UserGroup> {
42 validate_user_group_id(group_id)?;
43 let ds = self.resolve_data_source(data_source)?;
44 let url = self.url(&format!("/api/session/data/{ds}/userGroups/{group_id}"))?;
45 let response = self.http.get(&url).send().await?;
46 Self::parse_response(response, &format!("user group {group_id}")).await
47 }
48
49 pub async fn create_user_group(
51 &self,
52 data_source: Option<&str>,
53 group: &UserGroup,
54 ) -> Result<()> {
55 let ds = self.resolve_data_source(data_source)?;
56 let url = self.url(&format!("/api/session/data/{ds}/userGroups"))?;
57 let response = self.http.post(&url).json(group).send().await?;
58 Self::handle_error(response, "create user group").await?;
59 Ok(())
60 }
61
62 pub async fn update_user_group(
64 &self,
65 data_source: Option<&str>,
66 group_id: &str,
67 group: &UserGroup,
68 ) -> Result<()> {
69 validate_user_group_id(group_id)?;
70 let ds = self.resolve_data_source(data_source)?;
71 let url = self.url(&format!("/api/session/data/{ds}/userGroups/{group_id}"))?;
72 let response = self.http.put(&url).json(group).send().await?;
73 Self::handle_error(response, &format!("user group {group_id}")).await?;
74 Ok(())
75 }
76
77 pub async fn delete_user_group(
79 &self,
80 data_source: Option<&str>,
81 group_id: &str,
82 ) -> Result<()> {
83 validate_user_group_id(group_id)?;
84 let ds = self.resolve_data_source(data_source)?;
85 let url = self.url(&format!("/api/session/data/{ds}/userGroups/{group_id}"))?;
86 let response = self.http.delete(&url).send().await?;
87 Self::handle_error(response, &format!("user group {group_id}")).await?;
88 Ok(())
89 }
90
91 pub async fn update_user_group_member_users(
93 &self,
94 data_source: Option<&str>,
95 group_id: &str,
96 patches: &[PatchOperation],
97 ) -> Result<()> {
98 validate_user_group_id(group_id)?;
99 let ds = self.resolve_data_source(data_source)?;
100 let url = self.url(&format!(
101 "/api/session/data/{ds}/userGroups/{group_id}/memberUsers"
102 ))?;
103 let response = self.http.patch(&url).json(patches).send().await?;
104 Self::handle_error(response, &format!("user group {group_id} member users")).await?;
105 Ok(())
106 }
107
108 pub async fn update_user_group_member_groups(
110 &self,
111 data_source: Option<&str>,
112 group_id: &str,
113 patches: &[PatchOperation],
114 ) -> Result<()> {
115 validate_user_group_id(group_id)?;
116 let ds = self.resolve_data_source(data_source)?;
117 let url = self.url(&format!(
118 "/api/session/data/{ds}/userGroups/{group_id}/memberUserGroups"
119 ))?;
120 let response = self.http.patch(&url).json(patches).send().await?;
121 Self::handle_error(response, &format!("user group {group_id} member groups")).await?;
122 Ok(())
123 }
124
125 pub async fn update_user_group_parent_groups(
127 &self,
128 data_source: Option<&str>,
129 group_id: &str,
130 patches: &[PatchOperation],
131 ) -> Result<()> {
132 validate_user_group_id(group_id)?;
133 let ds = self.resolve_data_source(data_source)?;
134 let url = self.url(&format!(
135 "/api/session/data/{ds}/userGroups/{group_id}/userGroups"
136 ))?;
137 let response = self.http.patch(&url).json(patches).send().await?;
138 Self::handle_error(response, &format!("user group {group_id} parent groups")).await?;
139 Ok(())
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn user_group_serde_roundtrip() {
149 let group = UserGroup {
150 identifier: Some("admins".to_string()),
151 attributes: Some(HashMap::from([(
152 "disabled".to_string(),
153 Some("false".to_string()),
154 )])),
155 };
156 let json = serde_json::to_string(&group).unwrap();
157 let deserialized: UserGroup = serde_json::from_str(&json).unwrap();
158 assert_eq!(group, deserialized);
159 }
160
161 #[test]
162 fn user_group_skip_none_fields() {
163 let group = UserGroup::default();
164 let json = serde_json::to_value(&group).unwrap();
165 let obj = json.as_object().unwrap();
166 assert!(obj.is_empty());
167 }
168
169 #[test]
170 fn deserialize_user_group_from_api_json() {
171 let json = r#"{
172 "identifier": "developers",
173 "attributes": {
174 "disabled": "",
175 "expired": ""
176 }
177 }"#;
178 let group: UserGroup = serde_json::from_str(json).unwrap();
179 assert_eq!(group.identifier.as_deref(), Some("developers"));
180 assert!(group.attributes.is_some());
181 }
182
183 #[test]
184 fn deserialize_user_group_unknown_fields_ignored() {
185 let json = r#"{"identifier": "admins", "unknownField": 42}"#;
186 let group: UserGroup = serde_json::from_str(json).unwrap();
187 assert_eq!(group.identifier.as_deref(), Some("admins"));
188 }
189
190 #[test]
191 fn user_group_null_attribute_values() {
192 let json = r#"{
193 "identifier": "team",
194 "attributes": {
195 "disabled": null,
196 "notes": "hello"
197 }
198 }"#;
199 let group: UserGroup = serde_json::from_str(json).unwrap();
200 let attrs = group.attributes.unwrap();
201 assert_eq!(attrs.get("disabled"), Some(&None));
202 assert_eq!(attrs.get("notes"), Some(&Some("hello".to_string())));
203 }
204}