1#[cfg(feature = "token")]
2use crate::client::Client;
3#[cfg(feature = "token")]
4use crate::errors::Error;
5use crate::KeygenResponseData;
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use serde_json::{json, Value};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ComponentAttributes {
13 pub fingerprint: String,
14 pub name: String,
15 pub metadata: Option<HashMap<String, Value>>,
16 pub created: DateTime<Utc>,
17 pub updated: DateTime<Utc>,
18}
19
20#[cfg(feature = "token")]
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub(crate) struct ComponentResponse {
23 pub data: KeygenResponseData<ComponentAttributes>,
24}
25
26#[cfg(feature = "token")]
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub(crate) struct ComponentsResponse {
29 pub data: Vec<KeygenResponseData<ComponentAttributes>>,
30}
31
32#[cfg(feature = "token")]
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct CreateComponentRequest {
35 pub fingerprint: String,
36 pub name: String,
37 pub metadata: Option<HashMap<String, Value>>,
38 pub machine_id: String, }
40
41#[cfg(feature = "token")]
42#[derive(Debug, Clone, Default, Serialize, Deserialize)]
43pub struct ListComponentsOptions {
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub limit: Option<u32>,
46 #[serde(rename = "page[size]", skip_serializing_if = "Option::is_none")]
47 pub page_size: Option<u32>,
48 #[serde(rename = "page[number]", skip_serializing_if = "Option::is_none")]
49 pub page_number: Option<u32>,
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub machine: Option<String>,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub license: Option<String>,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub owner: Option<String>,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub user: Option<String>,
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub product: Option<String>,
61}
62
63#[cfg(feature = "token")]
64#[derive(Debug, Clone, Serialize, Deserialize, Default)]
65pub struct UpdateComponentRequest {
66 pub name: Option<String>, pub metadata: Option<HashMap<String, Value>>,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct Component {
72 pub id: String,
73 pub fingerprint: String,
74 pub name: String,
75 pub metadata: Option<HashMap<String, Value>>,
76 pub created: DateTime<Utc>,
77 pub updated: DateTime<Utc>,
78 pub account_id: Option<String>,
80 pub environment_id: Option<String>,
81 pub product_id: Option<String>,
82 pub license_id: Option<String>,
83 pub machine_id: Option<String>,
84}
85
86impl Default for Component {
87 fn default() -> Self {
88 let now = Utc::now();
89 Self {
90 id: String::new(),
91 fingerprint: String::new(),
92 name: String::new(),
93 metadata: None,
94 created: now,
95 updated: now,
96 account_id: None,
97 environment_id: None,
98 product_id: None,
99 license_id: None,
100 machine_id: None,
101 }
102 }
103}
104
105impl Component {
106 #[allow(dead_code)]
107 pub(crate) fn from(data: KeygenResponseData<ComponentAttributes>) -> Component {
108 Component {
109 id: data.id,
110 fingerprint: data.attributes.fingerprint,
111 name: data.attributes.name,
112 metadata: data.attributes.metadata,
113 created: data.attributes.created,
114 updated: data.attributes.updated,
115 account_id: data
116 .relationships
117 .account
118 .as_ref()
119 .and_then(|a| a.data.as_ref().map(|d| d.id.clone())),
120 environment_id: data
121 .relationships
122 .environment
123 .as_ref()
124 .and_then(|e| e.data.as_ref().map(|d| d.id.clone())),
125 product_id: data
126 .relationships
127 .product
128 .as_ref()
129 .and_then(|p| p.data.as_ref().map(|d| d.id.clone())),
130 license_id: data
131 .relationships
132 .license
133 .as_ref()
134 .and_then(|l| l.data.as_ref().map(|d| d.id.clone())),
135 machine_id: data
136 .relationships
137 .machines
138 .as_ref()
139 .and_then(|m| m.data.as_ref().map(|d| d.id.clone())),
140 }
141 }
142
143 pub fn create_object(component: &Component) -> serde_json::Value {
145 json!({
146 "data": {
147 "id": component.id,
148 "type": "components",
149 "attributes": {
150 "fingerprint": component.fingerprint,
151 "name": component.name,
152 "metadata": component.metadata
153 }
154 }
155 })
156 }
157
158 #[cfg(feature = "token")]
160 pub async fn create(request: CreateComponentRequest) -> Result<Component, Error> {
161 let client = Client::from_global_config()?;
162
163 let mut attributes = serde_json::Map::new();
164 attributes.insert(
165 "fingerprint".to_string(),
166 Value::String(request.fingerprint),
167 );
168 attributes.insert("name".to_string(), Value::String(request.name));
169
170 if let Some(metadata) = request.metadata {
171 attributes.insert("metadata".to_string(), serde_json::to_value(metadata)?);
172 }
173
174 let body = json!({
175 "data": {
176 "type": "components",
177 "attributes": attributes,
178 "relationships": {
179 "machine": {
180 "data": {
181 "type": "machines",
182 "id": request.machine_id
183 }
184 }
185 }
186 }
187 });
188
189 let response = client.post("components", Some(&body), None::<&()>).await?;
190 let component_response: ComponentResponse = serde_json::from_value(response.body)?;
191 Ok(Component::from(component_response.data))
192 }
193
194 #[cfg(feature = "token")]
196 pub async fn list(options: Option<ListComponentsOptions>) -> Result<Vec<Component>, Error> {
197 let client = Client::from_global_config()?;
198 let mut query_params = HashMap::new();
199
200 if let Some(opts) = options {
201 if let Some(limit) = opts.limit {
202 query_params.insert("limit".to_string(), limit.to_string());
203 }
204 if let Some(page_size) = opts.page_size {
205 query_params.insert("page[size]".to_string(), page_size.to_string());
206 }
207 if let Some(page_number) = opts.page_number {
208 query_params.insert("page[number]".to_string(), page_number.to_string());
209 }
210 if let Some(machine) = opts.machine {
212 query_params.insert("machine".to_string(), machine);
213 }
214 if let Some(license) = opts.license {
215 query_params.insert("license".to_string(), license);
216 }
217 if let Some(owner) = opts.owner {
218 query_params.insert("owner".to_string(), owner);
219 }
220 if let Some(user) = opts.user {
221 query_params.insert("user".to_string(), user);
222 }
223 if let Some(product) = opts.product {
224 query_params.insert("product".to_string(), product);
225 }
226 }
227
228 let query = if query_params.is_empty() {
229 None
230 } else {
231 Some(query_params)
232 };
233
234 let response = client.get("components", query.as_ref()).await?;
235 let components_response: ComponentsResponse = serde_json::from_value(response.body)?;
236 Ok(components_response
237 .data
238 .into_iter()
239 .map(Component::from)
240 .collect())
241 }
242
243 #[cfg(feature = "token")]
245 pub async fn get(id: &str) -> Result<Component, Error> {
246 let client = Client::from_global_config()?;
247 let endpoint = format!("components/{id}");
248 let response = client.get(&endpoint, None::<&()>).await?;
249 let component_response: ComponentResponse = serde_json::from_value(response.body)?;
250 Ok(Component::from(component_response.data))
251 }
252
253 #[cfg(feature = "token")]
255 pub async fn update(&self, request: UpdateComponentRequest) -> Result<Component, Error> {
256 let client = Client::from_global_config()?;
257 let endpoint = format!("components/{}", self.id);
258
259 let mut attributes = serde_json::Map::new();
260 if let Some(name) = request.name {
261 attributes.insert("name".to_string(), Value::String(name));
262 }
263 if let Some(metadata) = request.metadata {
264 attributes.insert("metadata".to_string(), serde_json::to_value(metadata)?);
265 }
266
267 let body = json!({
268 "data": {
269 "type": "components",
270 "attributes": attributes
271 }
272 });
273
274 let response = client.patch(&endpoint, Some(&body), None::<&()>).await?;
275 let component_response: ComponentResponse = serde_json::from_value(response.body)?;
276 Ok(Component::from(component_response.data))
277 }
278
279 #[cfg(feature = "token")]
281 pub async fn delete(&self) -> Result<(), Error> {
282 let client = Client::from_global_config()?;
283 let endpoint = format!("components/{}", self.id);
284 client.delete::<(), ()>(&endpoint, None::<&()>).await?;
285 Ok(())
286 }
287}
288
289#[cfg(feature = "token")]
291impl CreateComponentRequest {
292 pub fn new(fingerprint: String, name: String, machine_id: String) -> Self {
294 Self {
295 fingerprint,
296 name,
297 metadata: None,
298 machine_id,
299 }
300 }
301
302 pub fn with_metadata(mut self, metadata: HashMap<String, Value>) -> Self {
304 self.metadata = Some(metadata);
305 self
306 }
307}
308
309#[cfg(feature = "token")]
310impl UpdateComponentRequest {
311 pub fn new() -> Self {
313 Self {
314 name: None,
315 metadata: None,
316 }
317 }
318
319 pub fn with_name(mut self, name: String) -> Self {
321 self.name = Some(name);
322 self
323 }
324
325 pub fn with_metadata(mut self, metadata: HashMap<String, Value>) -> Self {
327 self.metadata = Some(metadata);
328 self
329 }
330}
331
332#[cfg(feature = "token")]
333impl ListComponentsOptions {
334 pub fn new() -> Self {
336 Self::default()
337 }
338
339 pub fn with_limit(mut self, limit: u32) -> Self {
341 self.limit = Some(limit);
342 self
343 }
344
345 pub fn with_pagination(mut self, page_number: u32, page_size: u32) -> Self {
347 self.page_number = Some(page_number);
348 self.page_size = Some(page_size);
349 self
350 }
351
352 pub fn with_machine(mut self, machine: String) -> Self {
354 self.machine = Some(machine);
355 self
356 }
357
358 pub fn with_license(mut self, license: String) -> Self {
360 self.license = Some(license);
361 self
362 }
363
364 pub fn with_owner(mut self, owner: String) -> Self {
366 self.owner = Some(owner);
367 self
368 }
369
370 pub fn with_user(mut self, user: String) -> Self {
372 self.user = Some(user);
373 self
374 }
375
376 pub fn with_product(mut self, product: String) -> Self {
378 self.product = Some(product);
379 self
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 use super::*;
386 use crate::{
387 KeygenRelationship, KeygenRelationshipData, KeygenRelationships, KeygenResponseData,
388 };
389 use chrono::Utc;
390
391 #[test]
392 fn test_component_from_data() {
393 let component_data = KeygenResponseData {
394 id: "test-component-id".to_string(),
395 r#type: "components".to_string(),
396 attributes: ComponentAttributes {
397 fingerprint: "test-fingerprint".to_string(),
398 name: "Test Component".to_string(),
399 metadata: Some({
400 let mut map = HashMap::new();
401 map.insert("version".to_string(), Value::String("1.0.0".to_string()));
402 map
403 }),
404 created: Utc::now(),
405 updated: Utc::now(),
406 },
407 relationships: KeygenRelationships {
408 account: Some(KeygenRelationship {
409 data: Some(KeygenRelationshipData {
410 r#type: "accounts".to_string(),
411 id: "test-account-id".to_string(),
412 }),
413 links: None,
414 }),
415 environment: Some(KeygenRelationship {
416 data: Some(KeygenRelationshipData {
417 r#type: "environments".to_string(),
418 id: "test-environment-id".to_string(),
419 }),
420 links: None,
421 }),
422 product: Some(KeygenRelationship {
423 data: Some(KeygenRelationshipData {
424 r#type: "products".to_string(),
425 id: "test-product-id".to_string(),
426 }),
427 links: None,
428 }),
429 license: Some(KeygenRelationship {
430 data: Some(KeygenRelationshipData {
431 r#type: "licenses".to_string(),
432 id: "test-license-id".to_string(),
433 }),
434 links: None,
435 }),
436 machines: Some(KeygenRelationship {
437 data: Some(KeygenRelationshipData {
438 r#type: "machines".to_string(),
439 id: "test-machine-id".to_string(),
440 }),
441 links: None,
442 }),
443 ..Default::default()
444 },
445 };
446
447 let component = Component::from(component_data);
448
449 assert_eq!(component.id, "test-component-id");
450 assert_eq!(component.fingerprint, "test-fingerprint");
451 assert_eq!(component.name, "Test Component");
452 assert_eq!(component.account_id, Some("test-account-id".to_string()));
453 assert_eq!(
454 component.environment_id,
455 Some("test-environment-id".to_string())
456 );
457 assert_eq!(component.product_id, Some("test-product-id".to_string()));
458 assert_eq!(component.license_id, Some("test-license-id".to_string()));
459 assert_eq!(component.machine_id, Some("test-machine-id".to_string()));
460 assert!(component.metadata.is_some());
461 let metadata = component.metadata.unwrap();
462 assert_eq!(metadata.get("version").unwrap().as_str().unwrap(), "1.0.0");
463 }
464
465 #[test]
466 fn test_component_without_relationships() {
467 let component_data = KeygenResponseData {
468 id: "test-component-id".to_string(),
469 r#type: "components".to_string(),
470 attributes: ComponentAttributes {
471 fingerprint: "test-fingerprint".to_string(),
472 name: "Test Component".to_string(),
473 metadata: None,
474 created: Utc::now(),
475 updated: Utc::now(),
476 },
477 relationships: KeygenRelationships::default(),
478 };
479
480 let component = Component::from(component_data);
481
482 assert_eq!(component.account_id, None);
483 assert_eq!(component.environment_id, None);
484 assert_eq!(component.product_id, None);
485 assert_eq!(component.license_id, None);
486 assert_eq!(component.machine_id, None);
487 assert!(component.metadata.is_none());
488 }
489
490 #[cfg(feature = "token")]
491 #[test]
492 fn test_create_component_request_builder() {
493 let mut metadata = HashMap::new();
494 metadata.insert("version".to_string(), Value::String("1.0.0".to_string()));
495
496 let request = CreateComponentRequest::new(
497 "test-fingerprint".to_string(),
498 "Test Component".to_string(),
499 "test-machine-id".to_string(),
500 )
501 .with_metadata(metadata.clone());
502
503 assert_eq!(request.fingerprint, "test-fingerprint");
504 assert_eq!(request.name, "Test Component");
505 assert_eq!(request.machine_id, "test-machine-id");
506 assert_eq!(request.metadata, Some(metadata));
507 }
508
509 #[cfg(feature = "token")]
510 #[test]
511 fn test_update_component_request_builder() {
512 let mut metadata = HashMap::new();
513 metadata.insert("version".to_string(), Value::String("2.0.0".to_string()));
514
515 let request = UpdateComponentRequest::new()
516 .with_name("Updated Component".to_string())
517 .with_metadata(metadata.clone());
518
519 assert_eq!(request.name, Some("Updated Component".to_string()));
520 assert_eq!(request.metadata, Some(metadata));
521 }
522
523 #[cfg(feature = "token")]
524 #[test]
525 fn test_list_components_options_builder() {
526 let options = ListComponentsOptions::new()
527 .with_limit(50)
528 .with_pagination(2, 25)
529 .with_machine("test-machine-id".to_string())
530 .with_license("test-license-id".to_string())
531 .with_owner("test-owner-id".to_string())
532 .with_user("test-user-id".to_string())
533 .with_product("test-product-id".to_string());
534
535 assert_eq!(options.limit, Some(50));
536 assert_eq!(options.page_number, Some(2));
537 assert_eq!(options.page_size, Some(25));
538 assert_eq!(options.machine, Some("test-machine-id".to_string()));
539 assert_eq!(options.license, Some("test-license-id".to_string()));
540 assert_eq!(options.owner, Some("test-owner-id".to_string()));
541 assert_eq!(options.user, Some("test-user-id".to_string()));
542 assert_eq!(options.product, Some("test-product-id".to_string()));
543 }
544
545 #[test]
546 fn test_component_default() {
547 let component = Component::default();
548
549 assert!(component.id.is_empty());
550 assert!(component.fingerprint.is_empty());
551 assert!(component.name.is_empty());
552 assert!(component.metadata.is_none());
553 assert!(component.account_id.is_none());
554 assert!(component.environment_id.is_none());
555 assert!(component.product_id.is_none());
556 assert!(component.license_id.is_none());
557 assert!(component.machine_id.is_none());
558 assert!(component.created <= Utc::now());
560 assert!(component.updated <= Utc::now());
561 }
562
563 #[test]
564 fn test_create_object_legacy_compatibility() {
565 let component = Component {
566 id: "test-id".to_string(),
567 fingerprint: "test-fingerprint".to_string(),
568 name: "Test Component".to_string(),
569 metadata: Some({
570 let mut map = HashMap::new();
571 map.insert("version".to_string(), Value::String("1.0.0".to_string()));
572 map
573 }),
574 ..Default::default()
575 };
576
577 let object = Component::create_object(&component);
578
579 assert!(object["data"]["id"].is_string());
580 assert_eq!(object["data"]["type"], "components");
581 assert_eq!(
582 object["data"]["attributes"]["fingerprint"],
583 "test-fingerprint"
584 );
585 assert_eq!(object["data"]["attributes"]["name"], "Test Component");
586 }
587}