use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::client::GuacamoleClient;
use crate::error::Result;
use crate::validation::{validate_connection_group_id, validate_query_param};
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct ConnectionGroup {
#[serde(skip_serializing_if = "Option::is_none")]
pub identifier: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_identifier: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub type_: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attributes: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub active_connections: Option<i32>,
}
impl GuacamoleClient {
pub async fn list_connection_groups(
&self,
data_source: Option<&str>,
) -> Result<HashMap<String, ConnectionGroup>> {
let ds = self.resolve_data_source(data_source)?;
let url = self.url(&format!("/api/session/data/{ds}/connectionGroups"))?;
let response = self.http.get(&url).send().await?;
Self::parse_response(response, "connection groups").await
}
pub async fn get_connection_group(
&self,
data_source: Option<&str>,
group_id: &str,
) -> Result<ConnectionGroup> {
validate_connection_group_id(group_id)?;
let ds = self.resolve_data_source(data_source)?;
let url = self.url(&format!(
"/api/session/data/{ds}/connectionGroups/{group_id}"
))?;
let response = self.http.get(&url).send().await?;
Self::parse_response(response, &format!("connection group {group_id}")).await
}
pub async fn get_connection_group_tree(
&self,
data_source: Option<&str>,
group_id: &str,
permission: Option<&str>,
) -> Result<Value> {
validate_connection_group_id(group_id)?;
let ds = self.resolve_data_source(data_source)?;
let url = self.url(&format!(
"/api/session/data/{ds}/connectionGroups/{group_id}/tree"
))?;
let mut request = self.http.get(&url);
if let Some(p) = permission {
validate_query_param("permission", p)?;
request = request.query(&[("permission", p)]);
}
let response = request.send().await?;
Self::parse_response(response, &format!("connection group {group_id} tree")).await
}
pub async fn create_connection_group(
&self,
data_source: Option<&str>,
group: &ConnectionGroup,
) -> Result<ConnectionGroup> {
let ds = self.resolve_data_source(data_source)?;
let url = self.url(&format!("/api/session/data/{ds}/connectionGroups"))?;
let response = self.http.post(&url).json(group).send().await?;
Self::parse_response(response, "create connection group").await
}
pub async fn update_connection_group(
&self,
data_source: Option<&str>,
group_id: &str,
group: &ConnectionGroup,
) -> Result<()> {
validate_connection_group_id(group_id)?;
let ds = self.resolve_data_source(data_source)?;
let url = self.url(&format!(
"/api/session/data/{ds}/connectionGroups/{group_id}"
))?;
let response = self.http.put(&url).json(group).send().await?;
Self::handle_error(response, &format!("connection group {group_id}")).await?;
Ok(())
}
pub async fn delete_connection_group(
&self,
data_source: Option<&str>,
group_id: &str,
) -> Result<()> {
validate_connection_group_id(group_id)?;
let ds = self.resolve_data_source(data_source)?;
let url = self.url(&format!(
"/api/session/data/{ds}/connectionGroups/{group_id}"
))?;
let response = self.http.delete(&url).send().await?;
Self::handle_error(response, &format!("connection group {group_id}")).await?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn connection_group_serde_roundtrip() {
let group = ConnectionGroup {
identifier: Some("1".to_string()),
parent_identifier: Some("ROOT".to_string()),
name: Some("Servers".to_string()),
type_: Some("ORGANIZATIONAL".to_string()),
attributes: Some(HashMap::new()),
active_connections: Some(0),
};
let json = serde_json::to_string(&group).unwrap();
let deserialized: ConnectionGroup = serde_json::from_str(&json).unwrap();
assert_eq!(group, deserialized);
}
#[test]
fn connection_group_camel_case_keys() {
let group = ConnectionGroup {
parent_identifier: Some("ROOT".to_string()),
active_connections: Some(2),
..Default::default()
};
let json = serde_json::to_value(&group).unwrap();
assert!(json.get("parentIdentifier").is_some());
assert!(json.get("activeConnections").is_some());
}
#[test]
fn connection_group_type_field_rename() {
let group = ConnectionGroup {
type_: Some("BALANCING".to_string()),
..Default::default()
};
let json = serde_json::to_value(&group).unwrap();
assert!(json.get("type").is_some());
assert_eq!(json.get("type").unwrap(), "BALANCING");
assert!(json.get("type_").is_none(), "Rust field name must not leak into JSON");
}
#[test]
fn connection_group_skip_none_fields() {
let group = ConnectionGroup::default();
let json = serde_json::to_value(&group).unwrap();
let obj = json.as_object().unwrap();
assert!(obj.is_empty());
}
#[test]
fn deserialize_connection_group_from_api_json() {
let json = r#"{
"identifier": "1",
"parentIdentifier": "ROOT",
"name": "Servers",
"type": "ORGANIZATIONAL",
"attributes": {},
"activeConnections": 3
}"#;
let group: ConnectionGroup = serde_json::from_str(json).unwrap();
assert_eq!(group.identifier.as_deref(), Some("1"));
assert_eq!(group.parent_identifier.as_deref(), Some("ROOT"));
assert_eq!(group.name.as_deref(), Some("Servers"));
assert_eq!(group.type_.as_deref(), Some("ORGANIZATIONAL"));
assert_eq!(group.active_connections, Some(3));
}
#[test]
fn deserialize_connection_group_unknown_fields_ignored() {
let json = r#"{"identifier": "1", "unknownField": true}"#;
let group: ConnectionGroup = serde_json::from_str(json).unwrap();
assert_eq!(group.identifier.as_deref(), Some("1"));
}
}