1use crate::credential::ProfileProvider;
8use serde::ser::SerializeTuple;
9use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
10use std;
11use std::error::Error;
12use std::fmt::{self, Display, Error as FmtError, Formatter};
13use std::str::FromStr;
14
15#[derive(Clone, Debug, PartialEq, Eq, Hash)]
40pub enum Region {
41 ApEast1,
43
44 ApNortheast1,
46
47 ApNortheast2,
49
50 ApNortheast3,
52
53 ApSouth1,
55
56 ApSoutheast1,
58
59 ApSoutheast2,
61
62 ApSoutheast3,
64
65 CaCentral1,
67
68 EuCentral1,
70
71 EuWest1,
73
74 EuWest2,
76
77 EuWest3,
79
80 EuNorth1,
82
83 EuSouth1,
85
86 MeSouth1,
88
89 SaEast1,
91
92 UsEast1,
94
95 UsEast2,
97
98 UsWest1,
100
101 UsWest2,
103
104 UsGovEast1,
106
107 UsGovWest1,
109
110 CnNorth1,
112
113 CnNorthwest1,
115
116 AfSouth1,
118
119 Custom {
121 name: String,
123
124 endpoint: String,
127 },
128}
129
130impl Region {
131 pub fn name(&self) -> &str {
142 match *self {
143 Region::ApEast1 => "ap-east-1",
144 Region::ApNortheast1 => "ap-northeast-1",
145 Region::ApNortheast2 => "ap-northeast-2",
146 Region::ApNortheast3 => "ap-northeast-3",
147 Region::ApSouth1 => "ap-south-1",
148 Region::ApSoutheast1 => "ap-southeast-1",
149 Region::ApSoutheast2 => "ap-southeast-2",
150 Region::ApSoutheast3 => "ap-southeast-3",
151 Region::CaCentral1 => "ca-central-1",
152 Region::EuCentral1 => "eu-central-1",
153 Region::EuWest1 => "eu-west-1",
154 Region::EuWest2 => "eu-west-2",
155 Region::EuWest3 => "eu-west-3",
156 Region::EuNorth1 => "eu-north-1",
157 Region::EuSouth1 => "eu-south-1",
158 Region::MeSouth1 => "me-south-1",
159 Region::SaEast1 => "sa-east-1",
160 Region::UsEast1 => "us-east-1",
161 Region::UsEast2 => "us-east-2",
162 Region::UsWest1 => "us-west-1",
163 Region::UsWest2 => "us-west-2",
164 Region::UsGovEast1 => "us-gov-east-1",
165 Region::UsGovWest1 => "us-gov-west-1",
166 Region::CnNorth1 => "cn-north-1",
167 Region::CnNorthwest1 => "cn-northwest-1",
168 Region::AfSouth1 => "af-south-1",
169 Region::Custom { ref name, .. } => name,
170 }
171 }
172}
173
174#[derive(Debug, PartialEq)]
176pub struct ParseRegionError {
177 message: String,
178}
179
180impl Serialize for Region {
183 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
184 where
185 S: Serializer,
186 {
187 let mut seq = serializer.serialize_tuple(2)?;
188 if let Region::Custom {
189 ref endpoint,
190 ref name,
191 } = *self
192 {
193 seq.serialize_element(&name)?;
194 seq.serialize_element(&Some(&endpoint))?;
195 } else {
196 seq.serialize_element(self.name())?;
197 seq.serialize_element(&None as &Option<&str>)?;
198 }
199 seq.end()
200 }
201}
202
203struct RegionVisitor;
204
205impl<'de> de::Visitor<'de> for RegionVisitor {
206 type Value = Region;
207
208 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
209 formatter.write_str("sequence of (name, Some(endpoint))")
210 }
211
212 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
213 where
214 A: de::SeqAccess<'de>,
215 {
216 let name: String = seq
217 .next_element::<String>()?
218 .ok_or_else(|| de::Error::custom("region is missing name"))?;
219 let endpoint: Option<String> = seq.next_element::<Option<String>>()?.unwrap_or_default();
220 match (name, endpoint) {
221 (name, Some(endpoint)) => Ok(Region::Custom { name, endpoint }),
222 (name, None) => name.parse().map_err(de::Error::custom),
223 }
224 }
225}
226
227impl<'de> Deserialize<'de> for Region {
230 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
231 where
232 D: Deserializer<'de>,
233 {
234 deserializer.deserialize_tuple(2, RegionVisitor)
235 }
236}
237
238impl FromStr for Region {
239 type Err = ParseRegionError;
240
241 fn from_str(s: &str) -> Result<Region, ParseRegionError> {
242 let v: &str = &s.to_lowercase();
243 match v {
244 "ap-east-1" | "apeast1" => Ok(Region::ApEast1),
245 "ap-northeast-1" | "apnortheast1" => Ok(Region::ApNortheast1),
246 "ap-northeast-2" | "apnortheast2" => Ok(Region::ApNortheast2),
247 "ap-northeast-3" | "apnortheast3" => Ok(Region::ApNortheast3),
248 "ap-south-1" | "apsouth1" => Ok(Region::ApSouth1),
249 "ap-southeast-1" | "apsoutheast1" => Ok(Region::ApSoutheast1),
250 "ap-southeast-2" | "apsoutheast2" => Ok(Region::ApSoutheast2),
251 "ap-southeast-3" | "apsoutheast3" => Ok(Region::ApSoutheast3),
252 "ca-central-1" | "cacentral1" => Ok(Region::CaCentral1),
253 "eu-central-1" | "eucentral1" => Ok(Region::EuCentral1),
254 "eu-west-1" | "euwest1" => Ok(Region::EuWest1),
255 "eu-west-2" | "euwest2" => Ok(Region::EuWest2),
256 "eu-west-3" | "euwest3" => Ok(Region::EuWest3),
257 "eu-north-1" | "eunorth1" => Ok(Region::EuNorth1),
258 "eu-south-1" | "eusouth1" => Ok(Region::EuSouth1),
259 "me-south-1" | "mesouth1" => Ok(Region::MeSouth1),
260 "sa-east-1" | "saeast1" => Ok(Region::SaEast1),
261 "us-east-1" | "useast1" => Ok(Region::UsEast1),
262 "us-east-2" | "useast2" => Ok(Region::UsEast2),
263 "us-west-1" | "uswest1" => Ok(Region::UsWest1),
264 "us-west-2" | "uswest2" => Ok(Region::UsWest2),
265 "us-gov-east-1" | "usgoveast1" => Ok(Region::UsGovEast1),
266 "us-gov-west-1" | "usgovwest1" => Ok(Region::UsGovWest1),
267 "cn-north-1" | "cnnorth1" => Ok(Region::CnNorth1),
268 "cn-northwest-1" | "cnnorthwest1" => Ok(Region::CnNorthwest1),
269 "af-south-1" | "afsouth1" => Ok(Region::AfSouth1),
270 s => Err(ParseRegionError::new(s)),
271 }
272 }
273}
274
275impl ParseRegionError {
276 pub fn new(input: &str) -> Self {
278 ParseRegionError {
279 message: format!("Not a valid AWS region: {}", input),
280 }
281 }
282}
283
284impl Error for ParseRegionError {}
285
286impl Display for ParseRegionError {
287 fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
288 write!(f, "{}", self.message)
289 }
290}
291
292impl Default for Region {
293 fn default() -> Region {
294 match std::env::var("AWS_DEFAULT_REGION").or_else(|_| std::env::var("AWS_REGION")) {
295 Ok(ref v) => Region::from_str(v).unwrap_or(Region::UsEast1),
296 Err(_) => match ProfileProvider::region() {
297 Ok(Some(region)) => Region::from_str(®ion).unwrap_or(Region::UsEast1),
298 _ => Region::UsEast1,
299 },
300 }
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 extern crate serde_test;
307 use self::serde_test::{assert_tokens, Token};
308 use super::*;
309
310 #[test]
311 fn from_str() {
312 assert_eq!(
313 "foo"
314 .parse::<Region>()
315 .err()
316 .expect("Parsing foo as a Region was not an error")
317 .to_string(),
318 "Not a valid AWS region: foo".to_owned()
319 );
320 assert_eq!("ap-east-1".parse(), Ok(Region::ApEast1));
321 assert_eq!("ap-northeast-1".parse(), Ok(Region::ApNortheast1));
322 assert_eq!("ap-northeast-2".parse(), Ok(Region::ApNortheast2));
323 assert_eq!("ap-northeast-3".parse(), Ok(Region::ApNortheast3));
324 assert_eq!("ap-south-1".parse(), Ok(Region::ApSouth1));
325 assert_eq!("ap-southeast-1".parse(), Ok(Region::ApSoutheast1));
326 assert_eq!("ap-southeast-2".parse(), Ok(Region::ApSoutheast2));
327 assert_eq!("ap-southeast-3".parse(), Ok(Region::ApSoutheast3));
328 assert_eq!("ca-central-1".parse(), Ok(Region::CaCentral1));
329 assert_eq!("eu-central-1".parse(), Ok(Region::EuCentral1));
330 assert_eq!("eu-west-1".parse(), Ok(Region::EuWest1));
331 assert_eq!("eu-west-2".parse(), Ok(Region::EuWest2));
332 assert_eq!("eu-west-3".parse(), Ok(Region::EuWest3));
333 assert_eq!("eu-north-1".parse(), Ok(Region::EuNorth1));
334 assert_eq!("eu-south-1".parse(), Ok(Region::EuSouth1));
335 assert_eq!("me-south-1".parse(), Ok(Region::MeSouth1));
336 assert_eq!("sa-east-1".parse(), Ok(Region::SaEast1));
337 assert_eq!("us-east-1".parse(), Ok(Region::UsEast1));
338 assert_eq!("us-east-2".parse(), Ok(Region::UsEast2));
339 assert_eq!("us-west-1".parse(), Ok(Region::UsWest1));
340 assert_eq!("us-west-2".parse(), Ok(Region::UsWest2));
341 assert_eq!("us-gov-east-1".parse(), Ok(Region::UsGovEast1));
342 assert_eq!("us-gov-west-1".parse(), Ok(Region::UsGovWest1));
343 assert_eq!("cn-north-1".parse(), Ok(Region::CnNorth1));
344 assert_eq!("cn-northwest-1".parse(), Ok(Region::CnNorthwest1));
345 assert_eq!("af-south-1".parse(), Ok(Region::AfSouth1));
346 }
347
348 #[test]
349 fn region_serialize_deserialize() {
350 assert_tokens(&Region::ApEast1, &tokens_for_region("ap-east-1"));
351 assert_tokens(&Region::ApNortheast1, &tokens_for_region("ap-northeast-1"));
352 assert_tokens(&Region::ApNortheast2, &tokens_for_region("ap-northeast-2"));
353 assert_tokens(&Region::ApNortheast3, &tokens_for_region("ap-northeast-3"));
354 assert_tokens(&Region::ApSouth1, &tokens_for_region("ap-south-1"));
355 assert_tokens(&Region::ApSoutheast1, &tokens_for_region("ap-southeast-1"));
356 assert_tokens(&Region::ApSoutheast2, &tokens_for_region("ap-southeast-2"));
357 assert_tokens(&Region::ApSoutheast3, &tokens_for_region("ap-southeast-3"));
358 assert_tokens(&Region::CaCentral1, &tokens_for_region("ca-central-1"));
359 assert_tokens(&Region::EuCentral1, &tokens_for_region("eu-central-1"));
360 assert_tokens(&Region::EuWest1, &tokens_for_region("eu-west-1"));
361 assert_tokens(&Region::EuWest2, &tokens_for_region("eu-west-2"));
362 assert_tokens(&Region::EuWest3, &tokens_for_region("eu-west-3"));
363 assert_tokens(&Region::EuNorth1, &tokens_for_region("eu-north-1"));
364 assert_tokens(&Region::EuSouth1, &tokens_for_region("eu-south-1"));
365 assert_tokens(&Region::MeSouth1, &tokens_for_region("me-south-1"));
366 assert_tokens(&Region::SaEast1, &tokens_for_region("sa-east-1"));
367 assert_tokens(&Region::UsEast1, &tokens_for_region("us-east-1"));
368 assert_tokens(&Region::UsEast2, &tokens_for_region("us-east-2"));
369 assert_tokens(&Region::UsWest1, &tokens_for_region("us-west-1"));
370 assert_tokens(&Region::UsWest2, &tokens_for_region("us-west-2"));
371 assert_tokens(&Region::UsGovEast1, &tokens_for_region("us-gov-east-1"));
372 assert_tokens(&Region::UsGovWest1, &tokens_for_region("us-gov-west-1"));
373 assert_tokens(&Region::CnNorth1, &tokens_for_region("cn-north-1"));
374 assert_tokens(&Region::CnNorthwest1, &tokens_for_region("cn-northwest-1"));
375 assert_tokens(&Region::AfSouth1, &tokens_for_region("af-south-1"));
376 }
377
378 fn tokens_for_region(name: &'static str) -> [Token; 4] {
379 [
380 Token::Tuple { len: 2 },
381 Token::String(name),
382 Token::None,
383 Token::TupleEnd,
384 ]
385 }
386
387 #[test]
388 fn region_serialize_deserialize_custom() {
389 let custom_region = Region::Custom {
390 endpoint: "http://localhost:8000".to_owned(),
391 name: "eu-east-1".to_owned(),
392 };
393 assert_tokens(
394 &custom_region,
395 &[
396 Token::Tuple { len: 2 },
397 Token::String("eu-east-1"),
398 Token::Some,
399 Token::String("http://localhost:8000"),
400 Token::TupleEnd,
401 ],
402 );
403 let expected = "[\"eu-east-1\",\"http://localhost:8000\"]";
404 let region_deserialized = serde_json::to_string(&custom_region).unwrap();
405 assert_eq!(region_deserialized, expected);
406
407 let from_json = serde_json::de::from_str(®ion_deserialized).unwrap();
408 assert_eq!(custom_region, from_json);
409 }
410
411 #[test]
412 fn region_serialize_deserialize_standard() {
413 let r = Region::UsWest2;
414 let region_deserialized = serde_json::to_string(&r).unwrap();
415 let expected = "[\"us-west-2\",null]";
416
417 assert_eq!(region_deserialized, expected);
418
419 let from_json = serde_json::de::from_str(®ion_deserialized).unwrap();
420 assert_eq!(r, from_json);
421 }
422
423 #[test]
424 fn region_serialize_deserialize_standard_only_region_name() {
425 let r = Region::UsWest2;
426 let only_region_name = "[\"us-west-2\"]";
427 let from_json = serde_json::de::from_str(&only_region_name).unwrap();
428 assert_eq!(r, from_json);
429 }
430}