aws_sdk_s3/endpoint_lib/
arn.rs
1use crate::endpoint_lib::diagnostic::DiagnosticCollector;
8use std::borrow::Cow;
9use std::error::Error;
10use std::fmt::{Display, Formatter};
11
12#[derive(Debug, Eq, PartialEq)]
13pub(crate) struct Arn<'a> {
14 partition: &'a str,
15 service: &'a str,
16 region: &'a str,
17 account_id: &'a str,
18 resource_id: Vec<&'a str>,
19}
20
21#[allow(unused)]
22impl<'a> Arn<'a> {
23 pub(crate) fn partition(&self) -> &'a str {
24 self.partition
25 }
26 pub(crate) fn service(&self) -> &'a str {
27 self.service
28 }
29 pub(crate) fn region(&self) -> &'a str {
30 self.region
31 }
32 pub(crate) fn account_id(&self) -> &'a str {
33 self.account_id
34 }
35 pub(crate) fn resource_id(&self) -> &Vec<&'a str> {
36 &self.resource_id
37 }
38}
39
40#[derive(Debug, PartialEq)]
41pub(crate) struct InvalidArn {
42 message: Cow<'static, str>,
43}
44
45impl InvalidArn {
46 fn from_static(message: &'static str) -> InvalidArn {
47 Self {
48 message: Cow::Borrowed(message),
49 }
50 }
51}
52impl Display for InvalidArn {
53 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
54 write!(f, "{}", self.message)
55 }
56}
57impl Error for InvalidArn {}
58
59impl<'a> Arn<'a> {
60 pub(crate) fn parse(arn: &'a str) -> Result<Self, InvalidArn> {
61 let mut split = arn.splitn(6, ':');
62 let invalid_format = || InvalidArn::from_static("ARN must have 6 components delimited by `:`");
63 let arn = split.next().ok_or_else(invalid_format)?;
64 let partition = split.next().ok_or_else(invalid_format)?;
65 let service = split.next().ok_or_else(invalid_format)?;
66 let region = split.next().ok_or_else(invalid_format)?;
67 let account_id = split.next().ok_or_else(invalid_format)?;
68 let resource_id = split.next().ok_or_else(invalid_format)?;
69
70 if arn != "arn" {
71 return Err(InvalidArn::from_static("first component of the ARN must be `arn`"));
72 }
73 if partition.is_empty() || service.is_empty() || resource_id.is_empty() {
74 return Err(InvalidArn::from_static("partition, service, and resource id must all be non-empty"));
75 }
76
77 let resource_id = resource_id.split([':', '/']).collect::<Vec<_>>();
78 Ok(Self {
79 partition,
80 service,
81 region,
82 account_id,
83 resource_id,
84 })
85 }
86}
87
88pub(crate) fn parse_arn<'a>(input: &'a str, e: &mut DiagnosticCollector) -> Option<Arn<'a>> {
89 e.capture(Arn::parse(input))
90}
91
92#[cfg(test)]
93mod test {
94 use super::Arn;
95
96 #[test]
97 fn arn_parser() {
98 let arn = "arn:aws:s3:us-east-2:012345678:outpost:op-1234";
99 let parsed = Arn::parse(arn).expect("valid ARN");
100 assert_eq!(
101 parsed,
102 Arn {
103 partition: "aws",
104 service: "s3",
105 region: "us-east-2",
106 account_id: "012345678",
107 resource_id: vec!["outpost", "op-1234"]
108 }
109 );
110 }
111
112 #[test]
113 fn allow_slash_arns() {
114 let arn = "arn:aws:s3:us-east-2:012345678:outpost/op-1234";
115 let parsed = Arn::parse(arn).expect("valid ARN");
116 assert_eq!(
117 parsed,
118 Arn {
119 partition: "aws",
120 service: "s3",
121 region: "us-east-2",
122 account_id: "012345678",
123 resource_id: vec!["outpost", "op-1234"]
124 }
125 );
126 }
127
128 #[test]
129 fn resource_id_must_be_nonempty() {
130 let arn = "arn:aws:s3:us-east-2:012345678:";
131 Arn::parse(arn).expect_err("empty resource");
132 }
133
134 #[test]
135 fn arns_with_empty_parts() {
136 let arn = "arn:aws:s3:::my_corporate_bucket/Development/*";
137 assert_eq!(
138 Arn::parse(arn).expect("valid arn"),
139 Arn {
140 partition: "aws",
141 service: "s3",
142 region: "",
143 account_id: "",
144 resource_id: vec!["my_corporate_bucket", "Development", "*"]
145 }
146 );
147 }
148}