1#[cfg(feature = "token")]
2use crate::client::Client;
3#[cfg(feature = "token")]
4use crate::errors::Error;
5#[cfg(feature = "token")]
6use crate::insert_optional;
7#[cfg(feature = "token")]
8use crate::license::{License, LicenseAttributes, PaginationOptions};
9#[cfg(feature = "token")]
10use crate::machine::{Machine, MachineAttributes};
11#[cfg(feature = "token")]
12use crate::user::{User, UserAttributes};
13use crate::KeygenResponseData;
14use chrono::{DateTime, Utc};
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct GroupAttributes {
20 pub name: String,
21 #[serde(rename = "maxUsers")]
22 pub max_users: Option<i32>,
23 #[serde(rename = "maxLicenses")]
24 pub max_licenses: Option<i32>,
25 #[serde(rename = "maxMachines")]
26 pub max_machines: Option<i32>,
27 pub metadata: Option<HashMap<String, serde_json::Value>>,
28 pub created: DateTime<Utc>,
29 pub updated: DateTime<Utc>,
30}
31
32#[cfg(feature = "token")]
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub(crate) struct GroupResponse {
35 pub data: KeygenResponseData<GroupAttributes>,
36}
37
38#[cfg(feature = "token")]
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub(crate) struct GroupsResponse {
41 pub data: Vec<KeygenResponseData<GroupAttributes>>,
42}
43
44#[cfg(feature = "token")]
45#[derive(Debug, Clone, Serialize, Deserialize)]
46struct GroupUsersResponse {
47 data: Vec<KeygenResponseData<UserAttributes>>,
48}
49
50#[cfg(feature = "token")]
51#[derive(Debug, Clone, Serialize, Deserialize)]
52struct GroupLicensesResponse {
53 data: Vec<KeygenResponseData<LicenseAttributes>>,
54}
55
56#[cfg(feature = "token")]
57#[derive(Debug, Clone, Serialize, Deserialize)]
58struct GroupMachinesResponse {
59 data: Vec<KeygenResponseData<MachineAttributes>>,
60}
61
62#[cfg(feature = "token")]
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct CreateGroupRequest {
65 pub name: String,
66 #[serde(rename = "maxUsers")]
67 pub max_users: Option<i32>,
68 #[serde(rename = "maxLicenses")]
69 pub max_licenses: Option<i32>,
70 #[serde(rename = "maxMachines")]
71 pub max_machines: Option<i32>,
72 pub metadata: Option<HashMap<String, serde_json::Value>>,
73}
74
75#[cfg(feature = "token")]
76#[derive(Debug, Clone, Default, Serialize, Deserialize)]
77pub struct ListGroupsOptions {
78 #[serde(skip_serializing_if = "Option::is_none")]
79 pub limit: Option<u32>,
80 #[serde(rename = "page[size]", skip_serializing_if = "Option::is_none")]
81 pub page_size: Option<u32>,
82 #[serde(rename = "page[number]", skip_serializing_if = "Option::is_none")]
83 pub page_number: Option<u32>,
84}
85
86#[cfg(feature = "token")]
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct UpdateGroupRequest {
89 #[serde(skip_serializing_if = "Option::is_none")]
90 pub name: Option<String>,
91 #[serde(rename = "maxUsers", skip_serializing_if = "Option::is_none")]
92 pub max_users: Option<i32>,
93 #[serde(rename = "maxLicenses", skip_serializing_if = "Option::is_none")]
94 pub max_licenses: Option<i32>,
95 #[serde(rename = "maxMachines", skip_serializing_if = "Option::is_none")]
96 pub max_machines: Option<i32>,
97 #[serde(skip_serializing_if = "Option::is_none")]
98 pub metadata: Option<HashMap<String, serde_json::Value>>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct Group {
103 pub id: String,
104 pub name: String,
105 pub max_users: Option<i32>,
106 pub max_licenses: Option<i32>,
107 pub max_machines: Option<i32>,
108 pub metadata: Option<HashMap<String, serde_json::Value>>,
109 pub created: DateTime<Utc>,
110 pub updated: DateTime<Utc>,
111 pub account_id: Option<String>,
112 pub owner_id: Option<String>,
113}
114
115impl Default for Group {
116 fn default() -> Self {
117 Self {
118 id: String::new(),
119 name: String::new(),
120 max_users: None,
121 max_licenses: None,
122 max_machines: None,
123 metadata: None,
124 created: Utc::now(),
125 updated: Utc::now(),
126 account_id: None,
127 owner_id: None,
128 }
129 }
130}
131
132impl Group {
133 #[allow(dead_code)]
134 pub(crate) fn from(data: KeygenResponseData<GroupAttributes>) -> Group {
135 Group {
136 id: data.id,
137 name: data.attributes.name,
138 max_users: data.attributes.max_users,
139 max_licenses: data.attributes.max_licenses,
140 max_machines: data.attributes.max_machines,
141 metadata: data.attributes.metadata,
142 created: data.attributes.created,
143 updated: data.attributes.updated,
144 account_id: data
145 .relationships
146 .account
147 .as_ref()
148 .and_then(|a| a.data.as_ref().map(|d| d.id.clone())),
149 owner_id: data
150 .relationships
151 .owner
152 .as_ref()
153 .and_then(|o| o.data.as_ref().map(|d| d.id.clone())),
154 }
155 }
156
157 #[cfg(feature = "token")]
159 pub async fn create(request: CreateGroupRequest) -> Result<Group, Error> {
160 let client = Client::from_global_config()?;
161
162 let mut attributes = serde_json::Map::new();
163 attributes.insert("name".to_string(), serde_json::Value::String(request.name));
164
165 insert_optional(&mut attributes, "maxUsers", request.max_users)?;
166 insert_optional(&mut attributes, "maxLicenses", request.max_licenses)?;
167 insert_optional(&mut attributes, "maxMachines", request.max_machines)?;
168 insert_optional(&mut attributes, "metadata", request.metadata)?;
169
170 let body = serde_json::json!({
171 "data": {
172 "type": "groups",
173 "attributes": attributes
174 }
175 });
176
177 let response = client.post("groups", Some(&body), None::<&()>).await?;
178 let group_response: GroupResponse = serde_json::from_value(response.body)?;
179 Ok(Group::from(group_response.data))
180 }
181
182 #[cfg(feature = "token")]
184 pub async fn list(options: Option<ListGroupsOptions>) -> Result<Vec<Group>, Error> {
185 let client = Client::from_global_config()?;
186 let response = client.get("groups", options.as_ref()).await?;
187 let groups_response: GroupsResponse = serde_json::from_value(response.body)?;
188 Ok(groups_response.data.into_iter().map(Group::from).collect())
189 }
190
191 #[cfg(feature = "token")]
193 pub async fn get(id: &str) -> Result<Group, Error> {
194 let client = Client::from_global_config()?;
195 let endpoint = format!("groups/{id}");
196 let response = client.get(&endpoint, None::<&()>).await?;
197 let group_response: GroupResponse = serde_json::from_value(response.body)?;
198 Ok(Group::from(group_response.data))
199 }
200
201 #[cfg(feature = "token")]
203 pub async fn update(&self, request: UpdateGroupRequest) -> Result<Group, Error> {
204 let client = Client::from_global_config()?;
205 let endpoint = format!("groups/{}", self.id);
206
207 let mut attributes = serde_json::Map::new();
208 insert_optional(&mut attributes, "name", request.name)?;
209 insert_optional(&mut attributes, "maxUsers", request.max_users)?;
210 insert_optional(&mut attributes, "maxLicenses", request.max_licenses)?;
211 insert_optional(&mut attributes, "maxMachines", request.max_machines)?;
212 insert_optional(&mut attributes, "metadata", request.metadata)?;
213
214 let body = serde_json::json!({
215 "data": {
216 "type": "groups",
217 "attributes": attributes
218 }
219 });
220
221 let response = client.patch(&endpoint, Some(&body), None::<&()>).await?;
222 let group_response: GroupResponse = serde_json::from_value(response.body)?;
223 Ok(Group::from(group_response.data))
224 }
225
226 #[cfg(feature = "token")]
228 pub async fn delete(&self) -> Result<(), Error> {
229 let client = Client::from_global_config()?;
230 let endpoint = format!("groups/{}", self.id);
231 client.delete::<(), ()>(&endpoint, None::<&()>).await?;
232 Ok(())
233 }
234
235 #[cfg(feature = "token")]
236 async fn list_related_users(
237 &self,
238 path: &str,
239 options: Option<&PaginationOptions>,
240 ) -> Result<Vec<User>, Error> {
241 let client = Client::from_global_config()?;
242 let response = client.get(path, options).await?;
243 let users_response: GroupUsersResponse = serde_json::from_value(response.body)?;
244 Ok(users_response.data.into_iter().map(User::from).collect())
245 }
246
247 #[cfg(feature = "token")]
248 async fn list_related_licenses(
249 &self,
250 path: &str,
251 options: Option<&PaginationOptions>,
252 ) -> Result<Vec<License>, Error> {
253 let client = Client::from_global_config()?;
254 let response = client.get(path, options).await?;
255 let licenses_response: GroupLicensesResponse = serde_json::from_value(response.body)?;
256 Ok(licenses_response
257 .data
258 .into_iter()
259 .map(License::from)
260 .collect())
261 }
262
263 #[cfg(feature = "token")]
264 async fn list_related_machines(
265 &self,
266 path: &str,
267 options: Option<&PaginationOptions>,
268 ) -> Result<Vec<Machine>, Error> {
269 let client = Client::from_global_config()?;
270 let response = client.get(path, options).await?;
271 let machines_response: GroupMachinesResponse = serde_json::from_value(response.body)?;
272 Ok(machines_response
273 .data
274 .into_iter()
275 .map(Machine::from)
276 .collect())
277 }
278
279 #[cfg(feature = "token")]
281 pub async fn owners(&self, options: Option<&PaginationOptions>) -> Result<Vec<User>, Error> {
282 self.list_related_users(&format!("groups/{}/owners", self.id), options)
283 .await
284 }
285
286 #[cfg(feature = "token")]
288 pub async fn users(&self, options: Option<&PaginationOptions>) -> Result<Vec<User>, Error> {
289 self.list_related_users(&format!("groups/{}/users", self.id), options)
290 .await
291 }
292
293 #[cfg(feature = "token")]
295 pub async fn licenses(
296 &self,
297 options: Option<&PaginationOptions>,
298 ) -> Result<Vec<License>, Error> {
299 self.list_related_licenses(&format!("groups/{}/licenses", self.id), options)
300 .await
301 }
302
303 #[cfg(feature = "token")]
305 pub async fn machines(
306 &self,
307 options: Option<&PaginationOptions>,
308 ) -> Result<Vec<Machine>, Error> {
309 self.list_related_machines(&format!("groups/{}/machines", self.id), options)
310 .await
311 }
312}
313
314#[cfg(all(test, feature = "token"))]
315mod tests {
316 use super::*;
317 use crate::{
318 KeygenRelationship, KeygenRelationshipData, KeygenRelationships, KeygenResponseData,
319 };
320
321 #[test]
322 fn test_group_relationships() {
323 let group_data = KeygenResponseData {
324 id: "test-group-id".to_string(),
325 r#type: "groups".to_string(),
326 attributes: GroupAttributes {
327 name: "Premium Team".to_string(),
328 max_users: Some(10),
329 max_licenses: Some(50),
330 max_machines: Some(100),
331 metadata: Some({
332 let mut map = HashMap::new();
333 map.insert(
334 "tier".to_string(),
335 serde_json::Value::String("premium".to_string()),
336 );
337 map
338 }),
339 created: "2023-01-01T00:00:00Z".parse().unwrap(),
340 updated: "2023-01-01T00:00:00Z".parse().unwrap(),
341 },
342 relationships: KeygenRelationships {
343 account: Some(KeygenRelationship {
344 data: Some(KeygenRelationshipData {
345 r#type: "accounts".to_string(),
346 id: "test-account-id".to_string(),
347 }),
348 links: None,
349 }),
350 owner: Some(KeygenRelationship {
351 data: Some(KeygenRelationshipData {
352 r#type: "users".to_string(),
353 id: "test-owner-id".to_string(),
354 }),
355 links: None,
356 }),
357 ..Default::default()
358 },
359 };
360
361 let group = Group::from(group_data);
362
363 assert_eq!(group.account_id, Some("test-account-id".to_string()));
364 assert_eq!(group.owner_id, Some("test-owner-id".to_string()));
365 assert_eq!(group.id, "test-group-id");
366 assert_eq!(group.name, "Premium Team");
367 assert_eq!(group.max_users, Some(10));
368 assert_eq!(group.max_licenses, Some(50));
369 assert_eq!(group.max_machines, Some(100));
370 assert!(group.metadata.is_some());
371 }
372
373 #[test]
374 fn test_group_without_relationships() {
375 let group_data = KeygenResponseData {
376 id: "test-group-id".to_string(),
377 r#type: "groups".to_string(),
378 attributes: GroupAttributes {
379 name: "Basic Group".to_string(),
380 max_users: None,
381 max_licenses: None,
382 max_machines: None,
383 metadata: None,
384 created: "2023-01-01T00:00:00Z".parse().unwrap(),
385 updated: "2023-01-01T00:00:00Z".parse().unwrap(),
386 },
387 relationships: KeygenRelationships::default(),
388 };
389
390 let group = Group::from(group_data);
391
392 assert_eq!(group.account_id, None);
393 assert_eq!(group.owner_id, None);
394 assert_eq!(group.name, "Basic Group");
395 assert_eq!(group.max_users, None);
396 assert_eq!(group.max_licenses, None);
397 assert_eq!(group.max_machines, None);
398 assert_eq!(group.metadata, None);
399 }
400
401 #[test]
402 fn test_create_group_request_serialization() {
403 let mut metadata = HashMap::new();
404 metadata.insert(
405 "department".to_string(),
406 serde_json::Value::String("engineering".to_string()),
407 );
408
409 let request = CreateGroupRequest {
410 name: "Engineering Team".to_string(),
411 max_users: Some(25),
412 max_licenses: Some(100),
413 max_machines: Some(200),
414 metadata: Some(metadata),
415 };
416
417 let serialized = serde_json::to_string(&request).unwrap();
418 assert!(serialized.contains("\"name\":\"Engineering Team\""));
419 assert!(serialized.contains("\"maxUsers\":25"));
420 assert!(serialized.contains("\"maxLicenses\":100"));
421 assert!(serialized.contains("\"maxMachines\":200"));
422 assert!(serialized.contains("\"metadata\""));
423 }
424
425 #[test]
426 fn test_update_group_request_serialization() {
427 let request = UpdateGroupRequest {
428 name: Some("Updated Team Name".to_string()),
429 max_users: Some(30),
430 max_licenses: None,
431 max_machines: Some(250),
432 metadata: None,
433 };
434
435 let serialized = serde_json::to_string(&request).unwrap();
436 assert!(serialized.contains("\"name\":\"Updated Team Name\""));
437 assert!(serialized.contains("\"maxUsers\":30"));
438 assert!(serialized.contains("\"maxMachines\":250"));
439 assert!(!serialized.contains("\"maxLicenses\":null"));
441 assert!(!serialized.contains("\"metadata\":null"));
442 }
443
444 #[test]
445 fn test_list_groups_options_serialization() {
446 let options = ListGroupsOptions {
447 limit: Some(20),
448 page_size: Some(10),
449 page_number: Some(3),
450 };
451
452 let serialized = serde_json::to_string(&options).unwrap();
453 assert!(serialized.contains("\"limit\":20"));
454 assert!(serialized.contains("\"page[size]\":10"));
455 assert!(serialized.contains("\"page[number]\":3"));
456 }
457
458 #[test]
459 fn test_group_attributes_serde() {
460 let attributes = GroupAttributes {
462 name: "Test Group".to_string(),
463 max_users: Some(15),
464 max_licenses: Some(75),
465 max_machines: Some(150),
466 metadata: Some({
467 let mut map = HashMap::new();
468 map.insert(
469 "region".to_string(),
470 serde_json::Value::String("us-east".to_string()),
471 );
472 map
473 }),
474 created: "2023-01-01T00:00:00Z".parse().unwrap(),
475 updated: "2023-01-02T00:00:00Z".parse().unwrap(),
476 };
477
478 let serialized = serde_json::to_string(&attributes).unwrap();
479 let deserialized: GroupAttributes = serde_json::from_str(&serialized).unwrap();
480
481 assert_eq!(deserialized.name, attributes.name);
482 assert_eq!(deserialized.max_users, attributes.max_users);
483 assert_eq!(deserialized.max_licenses, attributes.max_licenses);
484 assert_eq!(deserialized.max_machines, attributes.max_machines);
485 assert_eq!(deserialized.metadata, attributes.metadata);
486 }
487
488 #[test]
489 fn test_group_default() {
490 let default_group = Group::default();
491
492 assert_eq!(default_group.id, "");
493 assert_eq!(default_group.name, "");
494 assert_eq!(default_group.max_users, None);
495 assert_eq!(default_group.max_licenses, None);
496 assert_eq!(default_group.max_machines, None);
497 assert_eq!(default_group.metadata, None);
498 assert_eq!(default_group.account_id, None);
499 assert_eq!(default_group.owner_id, None);
500 assert!(default_group.created <= Utc::now());
502 assert!(default_group.updated <= Utc::now());
503 }
504
505 #[test]
506 fn test_group_default_in_struct_syntax() {
507 let group = Group {
509 id: "test-id".to_string(),
510 name: "Test Group".to_string(),
511 max_users: Some(5),
512 ..Default::default()
513 };
514
515 assert_eq!(group.id, "test-id");
516 assert_eq!(group.name, "Test Group");
517 assert_eq!(group.max_users, Some(5));
518 assert_eq!(group.max_licenses, None);
519 assert_eq!(group.max_machines, None);
520 assert_eq!(group.metadata, None);
521 assert_eq!(group.account_id, None);
522 assert_eq!(group.owner_id, None);
523 }
524}