aws_resource_id/
region.rs

1//! # AWS Region ID
2use std::{convert::TryFrom, fmt, str::FromStr};
3
4/// Error encountered when parsing an AWS region
5#[derive(Debug, thiserror::Error)]
6#[error("Unknown region: {0}")]
7pub struct RegionError(String);
8
9/// AWS Region ID
10#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub enum AwsRegionId {
12    /// Africa (Cape Town)
13    AfSouth1,
14    /// Asia Pacific (Hong Kong)
15    ApEast1,
16    /// Asia Pacific (Tokyo)
17    ApNortheast1,
18    /// Asia Pacific (Seoul)
19    ApNortheast2,
20    /// Asia Pacific (Osaka)
21    ApNortheast3,
22    /// Asia Pacific (Mumbai)
23    ApSouth1,
24    /// Asia Pacific (Hyderabad)
25    ApSouth2,
26    /// Asia Pacific (Singapore)
27    ApSoutheast1,
28    /// Asia Pacific (Sydney)
29    ApSoutheast2,
30    /// Asia Pacific (Jakarta)
31    ApSoutheast3,
32    /// Asia Pacific (Melbourne)
33    ApSoutheast4,
34    /// Canada (Central)
35    CaCentral1,
36    /// Canada West (Calgary)
37    CaWest1,
38    /// Europe (Frankfurt)
39    EuCentral1,
40    /// Europe (Zurich)
41    EuCentral2,
42    /// Europe (Stockholm)
43    EuNorth1,
44    /// Europe (Milan)
45    EuSouth1,
46    /// Europe (Spain)
47    EuSouth2,
48    /// Europe (Ireland)
49    EuWest1,
50    /// Europe (London)
51    EuWest2,
52    /// Europe (Paris)
53    EuWest3,
54    /// Israel (Tel Aviv)
55    IlCentral1,
56    /// Middle East (UAE)
57    MeCentral1,
58    /// Middle East (Bahrain)
59    MeSouth1,
60    /// South America (São Paulo)
61    SaEast1,
62    /// US East (N. Virginia)
63    UsEast1,
64    /// US East (Ohio)
65    UsEast2,
66    /// US West (N. California)
67    UsWest1,
68    /// US West (Oregon)
69    UsWest2,
70}
71
72impl TryFrom<&str> for AwsRegionId {
73    type Error = crate::Error;
74
75    fn try_from(s: &str) -> Result<Self, Self::Error> {
76        match s {
77            "af-south-1" => Ok(AwsRegionId::AfSouth1),
78            "ap-east-1" => Ok(AwsRegionId::ApEast1),
79            "ap-northeast-1" => Ok(AwsRegionId::ApNortheast1),
80            "ap-northeast-2" => Ok(AwsRegionId::ApNortheast2),
81            "ap-northeast-3" => Ok(AwsRegionId::ApNortheast3),
82            "ap-south-1" => Ok(AwsRegionId::ApSouth1),
83            "ap-south-2" => Ok(AwsRegionId::ApSouth2),
84            "ap-southeast-1" => Ok(AwsRegionId::ApSoutheast1),
85            "ap-southeast-2" => Ok(AwsRegionId::ApSoutheast2),
86            "ap-southeast-3" => Ok(AwsRegionId::ApSoutheast3),
87            "ap-southeast-4" => Ok(AwsRegionId::ApSoutheast4),
88            "ca-central-1" => Ok(AwsRegionId::CaCentral1),
89            "ca-west-1" => Ok(AwsRegionId::CaWest1),
90            "eu-central-1" => Ok(AwsRegionId::EuCentral1),
91            "eu-central-2" => Ok(AwsRegionId::EuCentral2),
92            "eu-north-1" => Ok(AwsRegionId::EuNorth1),
93            "eu-south-1" => Ok(AwsRegionId::EuSouth1),
94            "eu-south-2" => Ok(AwsRegionId::EuSouth2),
95            "eu-west-1" => Ok(AwsRegionId::EuWest1),
96            "eu-west-2" => Ok(AwsRegionId::EuWest2),
97            "eu-west-3" => Ok(AwsRegionId::EuWest3),
98            "il-central-1" => Ok(AwsRegionId::IlCentral1),
99            "me-central-1" => Ok(AwsRegionId::MeCentral1),
100            "me-south-1" => Ok(AwsRegionId::MeSouth1),
101            "sa-east-1" => Ok(AwsRegionId::SaEast1),
102            "us-east-1" => Ok(AwsRegionId::UsEast1),
103            "us-east-2" => Ok(AwsRegionId::UsEast2),
104            "us-west-1" => Ok(AwsRegionId::UsWest1),
105            "us-west-2" => Ok(AwsRegionId::UsWest2),
106            _ => Err(RegionError(s.into()).into()),
107        }
108    }
109}
110
111impl From<AwsRegionId> for &'static str {
112    fn from(region: AwsRegionId) -> Self {
113        match region {
114            AwsRegionId::AfSouth1 => "af-south-1",
115            AwsRegionId::ApEast1 => "ap-east-1",
116            AwsRegionId::ApNortheast1 => "ap-northeast-1",
117            AwsRegionId::ApNortheast2 => "ap-northeast-2",
118            AwsRegionId::ApNortheast3 => "ap-northeast-3",
119            AwsRegionId::ApSouth1 => "ap-south-1",
120            AwsRegionId::ApSouth2 => "ap-south-2",
121            AwsRegionId::ApSoutheast1 => "ap-southeast-1",
122            AwsRegionId::ApSoutheast2 => "ap-southeast-2",
123            AwsRegionId::ApSoutheast3 => "ap-southeast-3",
124            AwsRegionId::ApSoutheast4 => "ap-southeast-4",
125            AwsRegionId::CaCentral1 => "ca-central-1",
126            AwsRegionId::CaWest1 => "ca-west-1",
127            AwsRegionId::EuCentral1 => "eu-central-1",
128            AwsRegionId::EuCentral2 => "eu-central-2",
129            AwsRegionId::EuNorth1 => "eu-north-1",
130            AwsRegionId::EuSouth1 => "eu-south-1",
131            AwsRegionId::EuSouth2 => "eu-south-2",
132            AwsRegionId::EuWest1 => "eu-west-1",
133            AwsRegionId::EuWest2 => "eu-west-2",
134            AwsRegionId::EuWest3 => "eu-west-3",
135            AwsRegionId::IlCentral1 => "il-central-1",
136            AwsRegionId::MeCentral1 => "me-central-1",
137            AwsRegionId::MeSouth1 => "me-south-1",
138            AwsRegionId::SaEast1 => "sa-east-1",
139            AwsRegionId::UsEast1 => "us-east-1",
140            AwsRegionId::UsEast2 => "us-east-2",
141            AwsRegionId::UsWest1 => "us-west-1",
142            AwsRegionId::UsWest2 => "us-west-2",
143        }
144    }
145}
146
147impl AsRef<str> for AwsRegionId {
148    fn as_ref(&self) -> &str {
149        (*self).into()
150    }
151}
152
153impl TryFrom<String> for AwsRegionId {
154    type Error = crate::Error;
155
156    fn try_from(s: String) -> Result<Self, Self::Error> {
157        Self::try_from(s.as_str())
158    }
159}
160
161impl TryFrom<&String> for AwsRegionId {
162    type Error = crate::Error;
163
164    fn try_from(s: &String) -> Result<Self, Self::Error> {
165        Self::try_from(s.as_str())
166    }
167}
168
169impl FromStr for AwsRegionId {
170    type Err = crate::Error;
171
172    fn from_str(s: &str) -> Result<Self, Self::Err> {
173        Self::try_from(s)
174    }
175}
176
177impl fmt::Display for AwsRegionId {
178    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179        f.write_str(self.as_ref())
180    }
181}
182
183impl From<AwsRegionId> for String {
184    fn from(value: AwsRegionId) -> Self {
185        value.to_string()
186    }
187}
188
189#[cfg(feature = "serde")]
190impl<'de> serde::Deserialize<'de> for AwsRegionId {
191    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
192    where
193        D: serde::Deserializer<'de>,
194    {
195        let s = String::deserialize(deserializer)?;
196        AwsRegionId::try_from(s.as_str()).map_err(serde::de::Error::custom)
197    }
198}
199
200#[cfg(feature = "serde")]
201impl serde::Serialize for AwsRegionId {
202    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
203    where
204        S: serde::Serializer,
205    {
206        serializer.serialize_str(self.as_ref())
207    }
208}
209
210#[cfg(feature = "sqlx-postgres")]
211mod sqlx_impl {
212    use super::AwsRegionId;
213    use sqlx::{
214        postgres::{PgTypeInfo, PgValueRef},
215        Postgres, Type,
216    };
217
218    impl Type<Postgres> for AwsRegionId {
219        fn type_info() -> PgTypeInfo {
220            <String as Type<Postgres>>::type_info()
221        }
222
223        fn compatible(ty: &PgTypeInfo) -> bool {
224            <String as Type<Postgres>>::compatible(ty)
225        }
226    }
227
228    impl sqlx::Encode<'_, Postgres> for AwsRegionId {
229        fn encode_by_ref(
230            &self,
231            buf: &mut sqlx::postgres::PgArgumentBuffer,
232        ) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> {
233            <&str as sqlx::Encode<Postgres>>::encode(self.as_ref(), buf)
234        }
235    }
236
237    impl<'r> sqlx::Decode<'r, Postgres> for AwsRegionId {
238        fn decode(value: PgValueRef<'r>) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
239            let s = <String as sqlx::Decode<Postgres>>::decode(value)?;
240            Ok(AwsRegionId::try_from(s)?)
241        }
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    #[test]
250    fn test_all_regions_covered() {
251        let all_regions = [
252            "af-south-1",
253            "ap-east-1",
254            "ap-northeast-1",
255            "ap-northeast-2",
256            "ap-northeast-3",
257            "ap-south-1",
258            "ap-south-2",
259            "ap-southeast-1",
260            "ap-southeast-2",
261            "ap-southeast-3",
262            "ap-southeast-4",
263            "ca-central-1",
264            "ca-west-1",
265            "eu-central-1",
266            "eu-central-2",
267            "eu-north-1",
268            "eu-south-1",
269            "eu-south-2",
270            "eu-west-1",
271            "eu-west-2",
272            "eu-west-3",
273            "il-central-1",
274            "me-central-1",
275            "me-south-1",
276            "sa-east-1",
277            "us-east-1",
278            "us-east-2",
279            "us-west-1",
280            "us-west-2",
281        ];
282        assert_eq!(all_regions.len(), 29);
283
284        for region_str in all_regions {
285            let region = AwsRegionId::try_from(region_str).unwrap();
286            assert_eq!(region.as_ref(), region_str);
287        }
288    }
289
290    #[test]
291    fn test_eq() {
292        assert_eq!(
293            AwsRegionId::try_from("us-east-1").unwrap(),
294            AwsRegionId::try_from("us-east-1").unwrap(),
295        );
296        assert_ne!(
297            AwsRegionId::try_from("us-east-1").unwrap(),
298            AwsRegionId::try_from("eu-west-2").unwrap(),
299        );
300    }
301
302    #[test]
303    fn test_valid_regions() {
304        assert_eq!(
305            AwsRegionId::try_from("us-east-1").unwrap(),
306            AwsRegionId::UsEast1
307        );
308        assert_eq!(
309            AwsRegionId::try_from("eu-west-2").unwrap(),
310            AwsRegionId::EuWest2
311        );
312    }
313
314    #[test]
315    fn test_invalid_region() {
316        assert!(AwsRegionId::try_from("invalid-region").is_err());
317    }
318
319    #[test]
320    fn test_display() {
321        assert_eq!(AwsRegionId::UsWest2.to_string(), "us-west-2");
322        assert_eq!(AwsRegionId::EuCentral1.to_string(), "eu-central-1");
323    }
324
325    #[test]
326    fn test_into_str() {
327        let s: &str = AwsRegionId::EuCentral1.into();
328        assert_eq!(s, "eu-central-1");
329    }
330
331    #[test]
332    fn test_into_string() {
333        let s: String = AwsRegionId::EuCentral1.into();
334        assert_eq!(s, "eu-central-1");
335    }
336
337    #[test]
338    fn test_asref_str() {
339        let s: &str = AwsRegionId::EuCentral1.as_ref();
340        assert_eq!(s, "eu-central-1");
341    }
342
343    #[test]
344    fn test_tryfrom_str() {
345        assert_eq!(
346            AwsRegionId::try_from("eu-central-1").unwrap(),
347            AwsRegionId::EuCentral1
348        );
349    }
350
351    #[test]
352    fn test_tryfrom_string() {
353        assert_eq!(
354            AwsRegionId::try_from("eu-central-1".to_string()).unwrap(),
355            AwsRegionId::EuCentral1
356        );
357    }
358
359    #[test]
360    fn test_tryfrom_refstring() {
361        assert_eq!(
362            AwsRegionId::try_from(&"eu-central-1".to_string()).unwrap(),
363            AwsRegionId::EuCentral1
364        );
365    }
366
367    #[test]
368    fn test_fromstr() {
369        assert_eq!(
370            "eu-central-1".parse::<AwsRegionId>().unwrap(),
371            AwsRegionId::EuCentral1
372        );
373        assert_eq!(
374            "eu-central-1".to_string().parse::<AwsRegionId>().unwrap(),
375            AwsRegionId::EuCentral1
376        );
377    }
378}
379
380#[cfg(feature = "serde")]
381#[cfg(test)]
382mod serde_tests {
383    use super::*;
384
385    #[test]
386    fn test_serialize() {
387        let region = AwsRegionId::UsEast1;
388        let serialized = serde_json::to_string(&region).unwrap();
389        assert_eq!(serialized, "\"us-east-1\"");
390    }
391
392    #[test]
393    fn test_deserialize() {
394        let deserialized: AwsRegionId = serde_json::from_str("\"eu-west-1\"").unwrap();
395        assert_eq!(deserialized, AwsRegionId::EuWest1);
396    }
397}
398
399#[cfg(feature = "sqlx-postgres")]
400#[cfg(test)]
401mod sqlx_tests {
402    use super::*;
403    use sqlx::PgPool;
404
405    #[sqlx::test]
406    async fn serialize_varchar(pool: PgPool) -> sqlx::Result<()> {
407        let region_str = "eu-central-1";
408        let region: AwsRegionId = region_str.parse().unwrap();
409        let serialized = sqlx::query_scalar!("SELECT $1::varchar", region as _)
410            .fetch_one(&pool)
411            .await?
412            .unwrap();
413        assert_eq!(serialized, region_str);
414        Ok(())
415    }
416
417    #[sqlx::test]
418    async fn serialize_text(pool: PgPool) -> sqlx::Result<()> {
419        let region_str = "eu-central-1";
420        let region: AwsRegionId = region_str.parse().unwrap();
421        let serialized = sqlx::query_scalar!("SELECT $1::text", region as _)
422            .fetch_one(&pool)
423            .await?
424            .unwrap();
425        assert_eq!(serialized, region_str);
426        Ok(())
427    }
428
429    #[sqlx::test]
430    async fn deserialize_varchar(pool: PgPool) -> sqlx::Result<()> {
431        let region: AwsRegionId = "eu-central-1".parse().unwrap();
432        let deserialized =
433            sqlx::query_scalar!(r#"SELECT 'eu-central-1'::varchar as "val: AwsRegionId""#)
434                .fetch_one(&pool)
435                .await?
436                .unwrap();
437        assert_eq!(deserialized, region);
438        Ok(())
439    }
440
441    #[sqlx::test]
442    async fn deserialize_text(pool: PgPool) -> sqlx::Result<()> {
443        let region: AwsRegionId = "eu-central-1".parse().unwrap();
444        let deserialized =
445            sqlx::query_scalar!(r#"SELECT 'eu-central-1'::text as "val: AwsRegionId""#)
446                .fetch_one(&pool)
447                .await?
448                .unwrap();
449        assert_eq!(deserialized, region);
450        Ok(())
451    }
452}