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