1use std::fmt;
2
3#[derive(Debug, Clone, PartialEq, Eq, Hash)]
7pub struct Arn {
8 pub partition: String,
9 pub service: String,
10 pub region: String,
11 pub account_id: String,
12 pub resource: String,
13}
14
15impl Arn {
16 pub fn new(service: &str, region: &str, account_id: &str, resource: &str) -> Self {
17 Self {
18 partition: "aws".to_string(),
19 service: service.to_string(),
20 region: region.to_string(),
21 account_id: account_id.to_string(),
22 resource: resource.to_string(),
23 }
24 }
25
26 pub fn global(service: &str, account_id: &str, resource: &str) -> Self {
28 Self::new(service, "", account_id, resource)
29 }
30}
31
32impl fmt::Display for Arn {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 write!(
35 f,
36 "arn:{}:{}:{}:{}:{}",
37 self.partition, self.service, self.region, self.account_id, self.resource
38 )
39 }
40}
41
42impl std::str::FromStr for Arn {
43 type Err = ArnParseError;
44
45 fn from_str(s: &str) -> Result<Self, Self::Err> {
46 let parts: Vec<&str> = s.splitn(6, ':').collect();
47 if parts.len() != 6 || parts[0] != "arn" {
48 return Err(ArnParseError(s.to_string()));
49 }
50 Ok(Self {
51 partition: parts[1].to_string(),
52 service: parts[2].to_string(),
53 region: parts[3].to_string(),
54 account_id: parts[4].to_string(),
55 resource: parts[5].to_string(),
56 })
57 }
58}
59
60#[derive(Debug, thiserror::Error)]
61#[error("invalid ARN: {0}")]
62pub struct ArnParseError(String);
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn round_trip() {
70 let arn = Arn::new("sqs", "us-east-1", "123456789012", "my-queue");
71 let s = arn.to_string();
72 assert_eq!(s, "arn:aws:sqs:us-east-1:123456789012:my-queue");
73 assert_eq!(s.parse::<Arn>().unwrap(), arn);
74 }
75
76 #[test]
77 fn global_arn() {
78 let arn = Arn::global("iam", "123456789012", "user/admin");
79 assert_eq!(arn.to_string(), "arn:aws:iam::123456789012:user/admin");
80 }
81}