rusoto_signature/
region.rs

1//! AWS Regions and helper functions.
2//!
3//! Mostly used for translating the Region enum to a string AWS accepts.
4//!
5//! For example: `UsEast1` to "us-east-1"
6
7use 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/// An AWS region.
16///
17/// # Default
18///
19/// `Region` implements the `Default` trait. Calling `Region::default()` will attempt to read the
20/// `AWS_DEFAULT_REGION` or `AWS_REGION` environment variable. If it is malformed, it will fall back to `Region::UsEast1`.
21/// If it is not present it will fallback on the value associated with the current profile in `~/.aws/config` or the file
22/// specified by the `AWS_CONFIG_FILE` environment variable. If that is malformed of absent it will fall back on `Region::UsEast1`
23///
24/// # AWS-compatible services
25///
26/// `Region::Custom` can be used to connect to AWS-compatible services such as DynamoDB Local or Ceph.
27///
28/// ```
29///     # use rusoto_signature::Region;
30///     Region::Custom {
31///         name: "eu-east-3".to_owned(),
32///         endpoint: "http://localhost:8000".to_owned(),
33///     };
34/// ```
35///
36/// # Caveats
37///
38/// `CnNorth1` is currently untested due to Rusoto maintainers not having access to AWS China.
39#[derive(Clone, Debug, PartialEq, Eq, Hash)]
40pub enum Region {
41    /// Region that covers the Eastern part of Asia Pacific
42    ApEast1,
43
44    /// Region that covers the North-Eastern part of Asia Pacific
45    ApNortheast1,
46
47    /// Region that covers the North-Eastern part of Asia Pacific
48    ApNortheast2,
49
50    /// Region that covers the North-Eastern part of Asia Pacific
51    ApNortheast3,
52
53    /// Region that covers the Southern part of Asia Pacific
54    ApSouth1,
55
56    /// Region that covers the South-Eastern part of Asia Pacific
57    ApSoutheast1,
58
59    /// Region that covers the South-Eastern part of Asia Pacific
60    ApSoutheast2,
61    
62    /// Region that covers the South-Eastern part of Asia Pacific
63    ApSoutheast3,
64
65    /// Region that covers Canada
66    CaCentral1,
67
68    /// Region that covers Central Europe
69    EuCentral1,
70
71    /// Region that covers Western Europe
72    EuWest1,
73
74    /// Region that covers Western Europe
75    EuWest2,
76
77    /// Region that covers Western Europe
78    EuWest3,
79
80    /// Region that covers Northern Europe
81    EuNorth1,
82
83    /// Region that covers Southern Europe
84    EuSouth1,
85
86    /// Bahrain, Middle East South
87    MeSouth1,
88
89    /// Region that covers South America
90    SaEast1,
91
92    /// Region that covers the Eastern part of the United States
93    UsEast1,
94
95    /// Region that covers the Eastern part of the United States
96    UsEast2,
97
98    /// Region that covers the Western part of the United States
99    UsWest1,
100
101    /// Region that covers the Western part of the United States
102    UsWest2,
103
104    /// Region that covers the Eastern part of the United States for the US Government
105    UsGovEast1,
106
107    /// Region that covers the Western part of the United States for the US Government
108    UsGovWest1,
109
110    /// Region that covers China
111    CnNorth1,
112
113    /// Region that covers North-Western part of China
114    CnNorthwest1,
115
116    /// Region that covers southern part Africa
117    AfSouth1,
118
119    /// Specifies a custom region, such as a local Ceph target
120    Custom {
121        /// Name of the endpoint (e.g. `"eu-east-2"`).
122        name: String,
123
124        /// Endpoint to be used. For instance, `"https://s3.my-provider.net"` or just
125        /// `"s3.my-provider.net"` (default scheme is https).
126        endpoint: String,
127    },
128}
129
130impl Region {
131    /// Name of the region
132    ///
133    /// ```
134    ///     # use rusoto_signature::Region;
135    ///     assert_eq!(Region::CaCentral1.name(), "ca-central-1");
136    ///     assert_eq!(
137    ///         Region::Custom { name: "eu-east-3".to_owned(), endpoint: "s3.net".to_owned() }.name(),
138    ///         "eu-east-3"
139    ///     );
140    /// ```
141    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/// An error produced when attempting to convert a `str` into a `Region` fails.
175#[derive(Debug, PartialEq)]
176pub struct ParseRegionError {
177    message: String,
178}
179
180// Manually created for lack of a way to flatten the `Region::Custom` variant
181// Related: https://github.com/serde-rs/serde/issues/119
182impl 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
227// Manually created for lack of a way to flatten the `Region::Custom` variant
228// Related: https://github.com/serde-rs/serde/issues/119
229impl<'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    /// Parses a region given as a string literal into a type `Region'
277    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(&region).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(&region_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(&region_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}