1use std::{convert::TryFrom, fmt, str::FromStr};
3
4#[derive(Debug, thiserror::Error)]
6#[error("Unknown region: {0}")]
7pub struct RegionError(String);
8
9#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub enum AwsRegionId {
12 AfSouth1,
14 ApEast1,
16 ApNortheast1,
18 ApNortheast2,
20 ApNortheast3,
22 ApSouth1,
24 ApSouth2,
26 ApSoutheast1,
28 ApSoutheast2,
30 ApSoutheast3,
32 ApSoutheast4,
34 CaCentral1,
36 CaWest1,
38 EuCentral1,
40 EuCentral2,
42 EuNorth1,
44 EuSouth1,
46 EuSouth2,
48 EuWest1,
50 EuWest2,
52 EuWest3,
54 IlCentral1,
56 MeCentral1,
58 MeSouth1,
60 SaEast1,
62 UsEast1,
64 UsEast2,
66 UsWest1,
68 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(®ion).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}