1use super::types::{CloudMapping, Resource, Service, TenantPath, WamiArn};
4use crate::error::{AmiError, Result};
5
6#[derive(Debug, Default)]
49pub struct ArnBuilder {
50 service: Option<Service>,
51 tenant_path: Option<TenantPath>,
52 wami_instance_id: Option<String>,
53 cloud_mapping: Option<CloudMapping>,
54 resource: Option<Resource>,
55}
56
57impl ArnBuilder {
58 pub fn new() -> Self {
60 Self::default()
61 }
62
63 pub fn service(mut self, service: Service) -> Self {
73 self.service = Some(service);
74 self
75 }
76
77 pub fn service_str(mut self, service: impl Into<String>) -> Self {
87 self.service = Some(Service::from(service.into().as_str()));
88 self
89 }
90
91 pub fn tenant_path(mut self, path: TenantPath) -> Self {
102 self.tenant_path = Some(path);
103 self
104 }
105
106 pub fn tenant_hierarchy(mut self, segments: Vec<u64>) -> Self {
117 self.tenant_path = Some(TenantPath::new(segments));
118 self
119 }
120
121 pub fn tenant(mut self, tenant_id: u64) -> Self {
131 self.tenant_path = Some(TenantPath::single(tenant_id));
132 self
133 }
134
135 pub fn wami_instance(mut self, instance_id: impl Into<String>) -> Self {
145 self.wami_instance_id = Some(instance_id.into());
146 self
147 }
148
149 pub fn cloud_provider(
160 mut self,
161 provider: impl Into<String>,
162 account_id: impl Into<String>,
163 ) -> Self {
164 self.cloud_mapping = Some(CloudMapping::new(provider, account_id));
165 self
166 }
167
168 pub fn cloud_provider_with_region(
179 mut self,
180 provider: impl Into<String>,
181 account_id: impl Into<String>,
182 region: impl Into<String>,
183 ) -> Self {
184 self.cloud_mapping = Some(CloudMapping::with_region(provider, account_id, region));
185 self
186 }
187
188 pub fn region(mut self, region: impl Into<String>) -> Self {
201 if let Some(ref mut mapping) = self.cloud_mapping {
202 mapping.region = Some(region.into());
203 }
204 self
205 }
206
207 pub fn cloud_mapping(mut self, mapping: CloudMapping) -> Self {
218 self.cloud_mapping = Some(mapping);
219 self
220 }
221
222 pub fn no_cloud_mapping(mut self) -> Self {
234 self.cloud_mapping = None;
235 self
236 }
237
238 pub fn resource_obj(mut self, resource: Resource) -> Self {
249 self.resource = Some(resource);
250 self
251 }
252
253 pub fn resource(
263 mut self,
264 resource_type: impl Into<String>,
265 resource_id: impl Into<String>,
266 ) -> Self {
267 self.resource = Some(Resource::new(resource_type, resource_id));
268 self
269 }
270
271 #[allow(clippy::result_large_err)]
296 pub fn build(self) -> Result<WamiArn> {
297 let service = self.service.ok_or_else(|| AmiError::InvalidParameter {
298 message: "ARN builder: service is required".to_string(),
299 })?;
300
301 let tenant_path = self.tenant_path.ok_or_else(|| AmiError::InvalidParameter {
302 message: "ARN builder: tenant_path is required".to_string(),
303 })?;
304
305 let wami_instance_id = self
306 .wami_instance_id
307 .ok_or_else(|| AmiError::InvalidParameter {
308 message: "ARN builder: wami_instance_id is required".to_string(),
309 })?;
310
311 let resource = self.resource.ok_or_else(|| AmiError::InvalidParameter {
312 message: "ARN builder: resource is required".to_string(),
313 })?;
314
315 if tenant_path.segments.is_empty() {
317 return Err(AmiError::InvalidParameter {
318 message: "ARN builder: tenant_path cannot be empty".to_string(),
319 });
320 }
321
322 if wami_instance_id.is_empty() {
324 return Err(AmiError::InvalidParameter {
325 message: "ARN builder: wami_instance_id cannot be empty".to_string(),
326 });
327 }
328
329 if resource.resource_type.is_empty() {
331 return Err(AmiError::InvalidParameter {
332 message: "ARN builder: resource_type cannot be empty".to_string(),
333 });
334 }
335
336 if resource.resource_id.is_empty() {
337 return Err(AmiError::InvalidParameter {
338 message: "ARN builder: resource_id cannot be empty".to_string(),
339 });
340 }
341
342 Ok(WamiArn {
343 service,
344 tenant_path,
345 wami_instance_id,
346 cloud_mapping: self.cloud_mapping,
347 resource,
348 })
349 }
350}
351
352impl WamiArn {
353 pub fn builder() -> ArnBuilder {
369 ArnBuilder::new()
370 }
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376
377 #[test]
378 fn test_builder_wami_native() {
379 let arn = WamiArn::builder()
380 .service(Service::Iam)
381 .tenant_hierarchy(vec![12345678, 87654321, 99999999])
382 .wami_instance("999888777")
383 .resource("user", "77557755")
384 .build()
385 .unwrap();
386
387 assert_eq!(arn.service, Service::Iam);
388 assert_eq!(arn.tenant_path.segments, vec![12345678, 87654321, 99999999]);
389 assert_eq!(arn.wami_instance_id, "999888777");
390 assert_eq!(arn.cloud_mapping, None);
391 assert_eq!(arn.resource.resource_type, "user");
392 assert_eq!(arn.resource.resource_id, "77557755");
393 assert_eq!(
394 arn.to_string(),
395 "arn:wami:iam:12345678/87654321/99999999:wami:999888777:user/77557755"
396 );
397 }
398
399 #[test]
400 fn test_builder_cloud_synced() {
401 let arn = WamiArn::builder()
402 .service(Service::Iam)
403 .tenant_hierarchy(vec![12345678, 87654321, 99999999])
404 .wami_instance("999888777")
405 .cloud_provider("aws", "223344556677")
406 .resource("user", "77557755")
407 .build()
408 .unwrap();
409
410 assert_eq!(arn.service, Service::Iam);
411 assert_eq!(arn.cloud_mapping.as_ref().unwrap().provider, "aws");
412 assert_eq!(
413 arn.cloud_mapping.as_ref().unwrap().account_id,
414 "223344556677"
415 );
416 assert_eq!(arn.cloud_mapping.as_ref().unwrap().region, None);
417 assert_eq!(
418 arn.to_string(),
419 "arn:wami:iam:12345678/87654321/99999999:wami:999888777:aws:223344556677:global:user/77557755"
420 );
421 }
422
423 #[test]
424 fn test_builder_cloud_synced_with_region() {
425 let arn = WamiArn::builder()
426 .service(Service::Iam)
427 .tenant_hierarchy(vec![12345678, 87654321, 99999999])
428 .wami_instance("999888777")
429 .cloud_provider_with_region("aws", "223344556677", "us-east-1")
430 .resource("user", "77557755")
431 .build()
432 .unwrap();
433
434 assert_eq!(
435 arn.cloud_mapping.as_ref().unwrap().region,
436 Some("us-east-1".to_string())
437 );
438 assert_eq!(
439 arn.to_string(),
440 "arn:wami:iam:12345678/87654321/99999999:wami:999888777:aws:223344556677:us-east-1:user/77557755"
441 );
442 }
443
444 #[test]
445 fn test_builder_region_method() {
446 let arn = WamiArn::builder()
447 .service(Service::Iam)
448 .tenant(12345678)
449 .wami_instance("999888777")
450 .cloud_provider("aws", "223344556677")
451 .region("eu-west-1")
452 .resource("user", "77557755")
453 .build()
454 .unwrap();
455
456 assert_eq!(
457 arn.cloud_mapping.as_ref().unwrap().region,
458 Some("eu-west-1".to_string())
459 );
460 }
461
462 #[test]
463 fn test_builder_single_tenant() {
464 let arn = WamiArn::builder()
465 .service(Service::Sts)
466 .tenant(12345678)
467 .wami_instance("111222333")
468 .resource("session", "sess123")
469 .build()
470 .unwrap();
471
472 assert_eq!(arn.tenant_path.segments, vec![12345678]);
473 assert_eq!(
474 arn.to_string(),
475 "arn:wami:sts:12345678:wami:111222333:session/sess123"
476 );
477 }
478
479 #[test]
480 fn test_builder_service_str() {
481 let arn = WamiArn::builder()
482 .service_str("iam")
483 .tenant(12345678)
484 .wami_instance("999888777")
485 .resource("policy", "pol123")
486 .build()
487 .unwrap();
488
489 assert_eq!(arn.service, Service::Iam);
490 }
491
492 #[test]
493 fn test_builder_custom_service() {
494 let arn = WamiArn::builder()
495 .service_str("custom-service")
496 .tenant(12345678)
497 .wami_instance("999888777")
498 .resource("resource", "res123")
499 .build()
500 .unwrap();
501
502 assert_eq!(arn.service, Service::Custom("custom-service".to_string()));
503 assert_eq!(
504 arn.to_string(),
505 "arn:wami:custom-service:12345678:wami:999888777:resource/res123"
506 );
507 }
508
509 #[test]
510 fn test_builder_no_cloud_mapping() {
511 let arn = WamiArn::builder()
512 .service(Service::Iam)
513 .tenant(12345678)
514 .wami_instance("999888777")
515 .cloud_provider("aws", "123456")
516 .no_cloud_mapping()
517 .resource("user", "77557755")
518 .build()
519 .unwrap();
520
521 assert_eq!(arn.cloud_mapping, None);
522 }
523
524 #[test]
525 fn test_builder_missing_service() {
526 let result = WamiArn::builder()
527 .tenant(12345678)
528 .wami_instance("999888777")
529 .resource("user", "77557755")
530 .build();
531
532 assert!(result.is_err());
533 assert!(result
534 .unwrap_err()
535 .to_string()
536 .contains("service is required"));
537 }
538
539 #[test]
540 fn test_builder_missing_tenant() {
541 let result = WamiArn::builder()
542 .service(Service::Iam)
543 .wami_instance("999888777")
544 .resource("user", "77557755")
545 .build();
546
547 assert!(result.is_err());
548 assert!(result
549 .unwrap_err()
550 .to_string()
551 .contains("tenant_path is required"));
552 }
553
554 #[test]
555 fn test_builder_missing_instance() {
556 let result = WamiArn::builder()
557 .service(Service::Iam)
558 .tenant(12345678)
559 .resource("user", "77557755")
560 .build();
561
562 assert!(result.is_err());
563 assert!(result
564 .unwrap_err()
565 .to_string()
566 .contains("wami_instance_id is required"));
567 }
568
569 #[test]
570 fn test_builder_missing_resource() {
571 let result = WamiArn::builder()
572 .service(Service::Iam)
573 .tenant(12345678)
574 .wami_instance("999888777")
575 .build();
576
577 assert!(result.is_err());
578 assert!(result
579 .unwrap_err()
580 .to_string()
581 .contains("resource is required"));
582 }
583
584 #[test]
585 fn test_builder_empty_tenant_path() {
586 let result = WamiArn::builder()
587 .service(Service::Iam)
588 .tenant_path(TenantPath::new(vec![]))
589 .wami_instance("999888777")
590 .resource("user", "77557755")
591 .build();
592
593 assert!(result.is_err());
594 assert!(result
595 .unwrap_err()
596 .to_string()
597 .contains("tenant_path cannot be empty"));
598 }
599
600 #[test]
601 fn test_builder_resource_obj() {
602 let resource = Resource::new("role", "role123");
603 let arn = WamiArn::builder()
604 .service(Service::Iam)
605 .tenant(12345678)
606 .wami_instance("999888777")
607 .resource_obj(resource)
608 .build()
609 .unwrap();
610
611 assert_eq!(arn.resource.resource_type, "role");
612 assert_eq!(arn.resource.resource_id, "role123");
613 }
614
615 #[test]
616 fn test_builder_cloud_mapping_obj() {
617 let mapping = CloudMapping::new("gcp", "554433221");
618 let arn = WamiArn::builder()
619 .service(Service::Iam)
620 .tenant(12345678)
621 .wami_instance("999888777")
622 .cloud_mapping(mapping)
623 .resource("user", "77557755")
624 .build()
625 .unwrap();
626
627 assert_eq!(arn.cloud_mapping.as_ref().unwrap().provider, "gcp");
628 assert_eq!(arn.cloud_mapping.as_ref().unwrap().account_id, "554433221");
629 }
630}