use super::{ArnTransformer, ProviderArnInfo};
use crate::arn::types::{Service, WamiArn};
use crate::error::{AmiError, Result};
pub struct AwsArnTransformer;
impl ArnTransformer for AwsArnTransformer {
fn to_provider_arn(&self, arn: &WamiArn) -> Result<String> {
let cloud_mapping =
arn.cloud_mapping
.as_ref()
.ok_or_else(|| AmiError::InvalidParameter {
message: "ARN is not cloud-synced".to_string(),
})?;
if cloud_mapping.provider != "aws" {
return Err(AmiError::InvalidParameter {
message: format!(
"ARN provider is '{}', expected 'aws'",
cloud_mapping.provider
),
});
}
let aws_service = match &arn.service {
Service::Iam => "iam",
Service::Sts => "sts",
Service::SsoAdmin => "sso",
Service::Custom(s) => s.as_str(),
};
let region = cloud_mapping.region.as_deref().unwrap_or("");
Ok(format!(
"arn:aws:{}:{}:{}:{}/{}",
aws_service,
region,
cloud_mapping.account_id,
arn.resource.resource_type,
arn.resource.resource_id
))
}
fn from_provider_arn(&self, provider_arn: &str) -> Result<ProviderArnInfo> {
let parts: Vec<&str> = provider_arn.split(':').collect();
if parts.len() < 6 {
return Err(AmiError::InvalidParameter {
message: format!(
"Invalid AWS ARN format: expected at least 6 parts, got {}",
parts.len()
),
});
}
if parts[0] != "arn" || parts[1] != "aws" {
return Err(AmiError::InvalidParameter {
message: format!(
"Invalid AWS ARN prefix: expected 'arn:aws', got '{}:{}'",
parts[0], parts[1]
),
});
}
let service = parts[2].to_string();
let region = parts[3].to_string(); let account_id = parts[4].to_string();
let resource_part = parts[5..].join(":");
let resource_parts: Vec<&str> = resource_part.split('/').collect();
if resource_parts.len() < 2 {
return Err(AmiError::InvalidParameter {
message: format!(
"Invalid AWS ARN resource format: expected 'type/id', got '{}'",
resource_part
),
});
}
let resource_type = resource_parts[0].to_string();
let resource_id = resource_parts[1..].join("/");
Ok(ProviderArnInfo {
provider: "aws".to_string(),
account_id,
service,
resource_type,
resource_id,
region: if region.is_empty() {
None
} else {
Some(region)
},
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::arn::{Service, WamiArn};
#[test]
fn test_aws_transformer() {
let arn = WamiArn::builder()
.service(Service::Iam)
.tenant(12345678)
.wami_instance("999888777")
.cloud_provider("aws", "223344556677")
.resource("user", "77557755")
.build()
.unwrap();
let transformer = AwsArnTransformer;
let aws_arn = transformer.to_provider_arn(&arn).unwrap();
assert_eq!(aws_arn, "arn:aws:iam::223344556677:user/77557755");
let info = transformer.from_provider_arn(&aws_arn).unwrap();
assert_eq!(info.provider, "aws");
assert_eq!(info.account_id, "223344556677");
assert_eq!(info.service, "iam");
assert_eq!(info.resource_type, "user");
assert_eq!(info.resource_id, "77557755");
}
#[test]
fn test_aws_transformer_wrong_provider() {
let arn = WamiArn::builder()
.service(Service::Iam)
.tenant(12345678)
.wami_instance("999888777")
.cloud_provider("gcp", "123456")
.resource("user", "77557755")
.build()
.unwrap();
let transformer = AwsArnTransformer;
let result = transformer.to_provider_arn(&arn);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("expected 'aws'"));
}
#[test]
fn test_aws_transformer_not_cloud_synced() {
let arn = WamiArn::builder()
.service(Service::Iam)
.tenant(12345678)
.wami_instance("999888777")
.resource("user", "77557755")
.build()
.unwrap();
let transformer = AwsArnTransformer;
let result = transformer.to_provider_arn(&arn);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not cloud-synced"));
}
#[test]
fn test_aws_sts_service() {
let arn = WamiArn::builder()
.service(Service::Sts)
.tenant(12345678)
.wami_instance("999888777")
.cloud_provider("aws", "223344556677")
.resource("assumed-role", "role123")
.build()
.unwrap();
let transformer = AwsArnTransformer;
let aws_arn = transformer.to_provider_arn(&arn).unwrap();
assert_eq!(aws_arn, "arn:aws:sts::223344556677:assumed-role/role123");
}
#[test]
fn test_aws_resource_with_slash() {
let arn = WamiArn::builder()
.service(Service::Iam)
.tenant(12345678)
.wami_instance("999888777")
.cloud_provider("aws", "223344556677")
.resource("policy", "path/to/policy")
.build()
.unwrap();
let transformer = AwsArnTransformer;
let aws_arn = transformer.to_provider_arn(&arn).unwrap();
assert_eq!(aws_arn, "arn:aws:iam::223344556677:policy/path/to/policy");
let info = transformer.from_provider_arn(&aws_arn).unwrap();
assert_eq!(info.resource_id, "path/to/policy");
}
#[test]
fn test_aws_transformer_with_region() {
let arn = WamiArn::builder()
.service(Service::Iam)
.tenant(12345678)
.wami_instance("999888777")
.cloud_provider_with_region("aws", "223344556677", "us-east-1")
.resource("user", "alice")
.build()
.unwrap();
let transformer = AwsArnTransformer;
let aws_arn = transformer.to_provider_arn(&arn).unwrap();
assert_eq!(aws_arn, "arn:aws:iam:us-east-1:223344556677:user/alice");
}
#[test]
fn test_aws_transformer_invalid_arn_format() {
let transformer = AwsArnTransformer;
let result = transformer.from_provider_arn("not-an-arn");
assert!(result.is_err());
}
#[test]
fn test_aws_transformer_from_arn_edge_cases() {
let transformer = AwsArnTransformer;
let aws_arn = "arn:aws:sts::123456789012:assumed-role/MyRole/session";
let info = transformer.from_provider_arn(aws_arn).unwrap();
assert_eq!(info.provider, "aws");
assert_eq!(info.account_id, "123456789012");
assert_eq!(info.service, "sts");
assert_eq!(info.resource_type, "assumed-role");
assert_eq!(info.resource_id, "MyRole/session");
assert_eq!(info.region, None);
let aws_arn = "arn:aws:s3:us-east-1::bucket/my-bucket";
let info = transformer.from_provider_arn(aws_arn).unwrap();
assert_eq!(info.service, "s3");
assert_eq!(info.region, Some("us-east-1".to_string()));
}
}