1#[cfg(feature = "serde")]
2use serde::{Deserialize, Serialize};
3use std::str::{self, FromStr};
4use std::{env, fmt};
5
6use crate::error::RegionError;
7
8#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
32#[derive(Clone, Debug, Eq, PartialEq)]
33pub enum Region {
34 UsEast1,
36 UsEast2,
38 UsWest1,
40 UsWest2,
42 CaCentral1,
44 AfSouth1,
46 ApEast1,
48 ApSouth1,
50 ApNortheast1,
52 ApNortheast2,
54 ApNortheast3,
56 ApSoutheast1,
58 ApSoutheast2,
60 CnNorth1,
62 CnNorthwest1,
64 EuNorth1,
66 EuCentral1,
68 EuCentral2,
70 EuWest1,
72 EuWest2,
74 EuWest3,
76 IlCentral1,
78 MeSouth1,
80 MeCentral1,
82 SaEast1,
84 DoNyc3,
86 DoAms3,
88 DoSgp1,
90 DoFra1,
92 OvhGra,
94 OvhRbx,
96 OvhSbg,
98 OvhDe,
100 OvhUk,
102 OvhWaw,
104 OvhBhs,
106 OvhCaEastTor,
108 OvhSgp,
110 Yandex,
112 WaUsEast1,
114 WaUsEast2,
116 WaUsCentral1,
118 WaUsWest1,
120 WaCaCentral1,
122 WaEuCentral1,
124 WaEuCentral2,
126 WaEuWest1,
128 WaEuWest2,
130 WaApNortheast1,
132 WaApNortheast2,
134 WaApSoutheast1,
136 WaApSoutheast2,
138 R2 { account_id: String },
140 R2Eu { account_id: String },
142 Custom { region: String, endpoint: String },
144}
145
146impl fmt::Display for Region {
147 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
148 use self::Region::*;
149 match *self {
150 UsEast1 => write!(f, "us-east-1"),
151 UsEast2 => write!(f, "us-east-2"),
152 UsWest1 => write!(f, "us-west-1"),
153 UsWest2 => write!(f, "us-west-2"),
154 AfSouth1 => write!(f, "af-south-1"),
155 CaCentral1 => write!(f, "ca-central-1"),
156 ApEast1 => write!(f, "ap-east-1"),
157 ApSouth1 => write!(f, "ap-south-1"),
158 ApNortheast1 => write!(f, "ap-northeast-1"),
159 ApNortheast2 => write!(f, "ap-northeast-2"),
160 ApNortheast3 => write!(f, "ap-northeast-3"),
161 ApSoutheast1 => write!(f, "ap-southeast-1"),
162 ApSoutheast2 => write!(f, "ap-southeast-2"),
163 CnNorth1 => write!(f, "cn-north-1"),
164 CnNorthwest1 => write!(f, "cn-northwest-1"),
165 EuNorth1 => write!(f, "eu-north-1"),
166 EuCentral1 => write!(f, "eu-central-1"),
167 EuCentral2 => write!(f, "eu-central-2"),
168 EuWest1 => write!(f, "eu-west-1"),
169 EuWest2 => write!(f, "eu-west-2"),
170 EuWest3 => write!(f, "eu-west-3"),
171 SaEast1 => write!(f, "sa-east-1"),
172 IlCentral1 => write!(f, "il-central-1"),
173 MeCentral1 => write!(f, "me-central-1"),
174 MeSouth1 => write!(f, "me-south-1"),
175 DoNyc3 => write!(f, "nyc3"),
176 DoAms3 => write!(f, "ams3"),
177 DoSgp1 => write!(f, "sgp1"),
178 DoFra1 => write!(f, "fra1"),
179 Yandex => write!(f, "ru-central1"),
180 WaUsEast1 => write!(f, "us-east-1"),
181 WaUsEast2 => write!(f, "us-east-2"),
182 WaUsCentral1 => write!(f, "us-central-1"),
183 WaUsWest1 => write!(f, "us-west-1"),
184 WaCaCentral1 => write!(f, "ca-central-1"),
185 WaEuCentral1 => write!(f, "eu-central-1"),
186 WaEuCentral2 => write!(f, "eu-central-2"),
187 WaEuWest1 => write!(f, "eu-west-1"),
188 WaEuWest2 => write!(f, "eu-west-2"),
189 WaApNortheast1 => write!(f, "ap-northeast-1"),
190 WaApNortheast2 => write!(f, "ap-northeast-2"),
191 WaApSoutheast1 => write!(f, "ap-southeast-1"),
192 WaApSoutheast2 => write!(f, "ap-southeast-2"),
193 OvhGra => write!(f, "gra"),
194 OvhRbx => write!(f, "rbx"),
195 OvhSbg => write!(f, "sbg"),
196 OvhDe => write!(f, "de"),
197 OvhUk => write!(f, "uk"),
198 OvhWaw => write!(f, "waw"),
199 OvhBhs => write!(f, "bhs"),
200 OvhCaEastTor => write!(f, "ca-east-tor"),
201 OvhSgp => write!(f, "sgp"),
202 R2 { .. } => write!(f, "auto"),
203 R2Eu { .. } => write!(f, "auto"),
204 Custom { ref region, .. } => write!(f, "{}", region),
205 }
206 }
207}
208
209impl FromStr for Region {
210 type Err = std::str::Utf8Error;
211
212 fn from_str(s: &str) -> Result<Self, Self::Err> {
213 use self::Region::*;
214 match s {
215 "us-east-1" => Ok(UsEast1),
216 "us-east-2" => Ok(UsEast2),
217 "us-west-1" => Ok(UsWest1),
218 "us-west-2" => Ok(UsWest2),
219 "ca-central-1" => Ok(CaCentral1),
220 "af-south-1" => Ok(AfSouth1),
221 "ap-east-1" => Ok(ApEast1),
222 "ap-south-1" => Ok(ApSouth1),
223 "ap-northeast-1" => Ok(ApNortheast1),
224 "ap-northeast-2" => Ok(ApNortheast2),
225 "ap-northeast-3" => Ok(ApNortheast3),
226 "ap-southeast-1" => Ok(ApSoutheast1),
227 "ap-southeast-2" => Ok(ApSoutheast2),
228 "cn-north-1" => Ok(CnNorth1),
229 "cn-northwest-1" => Ok(CnNorthwest1),
230 "eu-north-1" => Ok(EuNorth1),
231 "eu-central-1" => Ok(EuCentral1),
232 "eu-central-2" => Ok(EuCentral2),
233 "eu-west-1" => Ok(EuWest1),
234 "eu-west-2" => Ok(EuWest2),
235 "eu-west-3" => Ok(EuWest3),
236 "sa-east-1" => Ok(SaEast1),
237 "il-central-1" => Ok(IlCentral1),
238 "me-central-1" => Ok(MeCentral1),
239 "me-south-1" => Ok(MeSouth1),
240 "nyc3" => Ok(DoNyc3),
241 "ams3" => Ok(DoAms3),
242 "sgp1" => Ok(DoSgp1),
243 "fra1" => Ok(DoFra1),
244 "yandex" => Ok(Yandex),
245 "ru-central1" => Ok(Yandex),
246 "wa-us-east-1" => Ok(WaUsEast1),
247 "wa-us-east-2" => Ok(WaUsEast2),
248 "wa-us-central-1" => Ok(WaUsCentral1),
249 "wa-us-west-1" => Ok(WaUsWest1),
250 "wa-ca-central-1" => Ok(WaCaCentral1),
251 "wa-eu-central-1" => Ok(WaEuCentral1),
252 "wa-eu-central-2" => Ok(WaEuCentral2),
253 "wa-eu-west-1" => Ok(WaEuWest1),
254 "wa-eu-west-2" => Ok(WaEuWest2),
255 "wa-ap-northeast-1" => Ok(WaApNortheast1),
256 "wa-ap-northeast-2" => Ok(WaApNortheast2),
257 "wa-ap-southeast-1" => Ok(WaApSoutheast1),
258 "wa-ap-southeast-2" => Ok(WaApSoutheast2),
259 x => Ok(Custom {
260 region: x.to_string(),
261 endpoint: x.to_string(),
262 }),
263 }
264 }
265}
266
267impl Region {
268 pub fn endpoint(&self) -> String {
269 use self::Region::*;
270 match *self {
271 UsEast1 => String::from("s3.amazonaws.com"),
274 UsEast2 => String::from("s3-us-east-2.amazonaws.com"),
275 UsWest1 => String::from("s3-us-west-1.amazonaws.com"),
276 UsWest2 => String::from("s3-us-west-2.amazonaws.com"),
277 CaCentral1 => String::from("s3-ca-central-1.amazonaws.com"),
278 AfSouth1 => String::from("s3-af-south-1.amazonaws.com"),
279 ApEast1 => String::from("s3-ap-east-1.amazonaws.com"),
280 ApSouth1 => String::from("s3-ap-south-1.amazonaws.com"),
281 ApNortheast1 => String::from("s3-ap-northeast-1.amazonaws.com"),
282 ApNortheast2 => String::from("s3-ap-northeast-2.amazonaws.com"),
283 ApNortheast3 => String::from("s3-ap-northeast-3.amazonaws.com"),
284 ApSoutheast1 => String::from("s3-ap-southeast-1.amazonaws.com"),
285 ApSoutheast2 => String::from("s3-ap-southeast-2.amazonaws.com"),
286 CnNorth1 => String::from("s3.cn-north-1.amazonaws.com.cn"),
287 CnNorthwest1 => String::from("s3.cn-northwest-1.amazonaws.com.cn"),
288 EuNorth1 => String::from("s3-eu-north-1.amazonaws.com"),
289 EuCentral1 => String::from("s3.eu-central-1.amazonaws.com"),
290 EuCentral2 => String::from("s3.eu-central-2.amazonaws.com"),
291 EuWest1 => String::from("s3-eu-west-1.amazonaws.com"),
292 EuWest2 => String::from("s3-eu-west-2.amazonaws.com"),
293 EuWest3 => String::from("s3-eu-west-3.amazonaws.com"),
294 SaEast1 => String::from("s3-sa-east-1.amazonaws.com"),
295 IlCentral1 => String::from("s3.il-central-1.amazonaws.com"),
296 MeCentral1 => String::from("s3.me-central-1.amazonaws.com"),
297 MeSouth1 => String::from("s3-me-south-1.amazonaws.com"),
298 DoNyc3 => String::from("nyc3.digitaloceanspaces.com"),
299 DoAms3 => String::from("ams3.digitaloceanspaces.com"),
300 DoSgp1 => String::from("sgp1.digitaloceanspaces.com"),
301 DoFra1 => String::from("fra1.digitaloceanspaces.com"),
302 Yandex => String::from("storage.yandexcloud.net"),
303 WaUsEast1 => String::from("s3.us-east-1.wasabisys.com"),
304 WaUsEast2 => String::from("s3.us-east-2.wasabisys.com"),
305 WaUsCentral1 => String::from("s3.us-central-1.wasabisys.com"),
306 WaUsWest1 => String::from("s3.us-west-1.wasabisys.com"),
307 WaCaCentral1 => String::from("s3.ca-central-1.wasabisys.com"),
308 WaEuCentral1 => String::from("s3.eu-central-1.wasabisys.com"),
309 WaEuCentral2 => String::from("s3.eu-central-2.wasabisys.com"),
310 WaEuWest1 => String::from("s3.eu-west-1.wasabisys.com"),
311 WaEuWest2 => String::from("s3.eu-west-2.wasabisys.com"),
312 WaApNortheast1 => String::from("s3.ap-northeast-1.wasabisys.com"),
313 WaApNortheast2 => String::from("s3.ap-northeast-2.wasabisys.com"),
314 WaApSoutheast1 => String::from("s3.ap-southeast-1.wasabisys.com"),
315 WaApSoutheast2 => String::from("s3.ap-southeast-2.wasabisys.com"),
316 OvhGra => String::from("s3.gra.io.cloud.ovh.net"),
317 OvhRbx => String::from("s3.rbx.io.cloud.ovh.net"),
318 OvhSbg => String::from("s3.sbg.io.cloud.ovh.net"),
319 OvhDe => String::from("s3.de.io.cloud.ovh.net"),
320 OvhUk => String::from("s3.uk.io.cloud.ovh.net"),
321 OvhWaw => String::from("s3.waw.io.cloud.ovh.net"),
322 OvhBhs => String::from("s3.bhs.io.cloud.ovh.net"),
323 OvhCaEastTor => String::from("s3.ca-east-tor.io.cloud.ovh.net"),
324 OvhSgp => String::from("s3.sgp.io.cloud.ovh.net"),
325 R2 { ref account_id } => format!("{}.r2.cloudflarestorage.com", account_id),
326 R2Eu { ref account_id } => format!("{}.eu.r2.cloudflarestorage.com", account_id),
327 Custom { ref endpoint, .. } => endpoint.to_string(),
328 }
329 }
330
331 pub fn scheme(&self) -> String {
332 match *self {
333 Region::Custom { ref endpoint, .. } => match endpoint.find("://") {
334 Some(pos) => endpoint[..pos].to_string(),
335 None => "https".to_string(),
336 },
337 _ => "https".to_string(),
338 }
339 }
340
341 pub fn host(&self) -> String {
342 match *self {
343 Region::Custom { ref endpoint, .. } => {
344 let host = match endpoint.find("://") {
345 Some(pos) => endpoint[pos + 3..].to_string(),
346 None => endpoint.to_string(),
347 };
348 host.trim_end_matches('/').to_string()
351 }
352 _ => self.endpoint(),
353 }
354 }
355
356 pub fn from_env(region_env: &str, endpoint_env: Option<&str>) -> Result<Region, RegionError> {
357 if let Some(endpoint_env) = endpoint_env {
358 Ok(Region::Custom {
359 region: env::var(region_env)?,
360 endpoint: env::var(endpoint_env)?,
361 })
362 } else {
363 Ok(Region::from_str(&env::var(region_env)?)?)
364 }
365 }
366
367 pub fn from_default_env() -> Result<Region, RegionError> {
369 if let Ok(endpoint) = env::var("AWS_ENDPOINT") {
370 Ok(Region::Custom {
371 region: env::var("AWS_REGION")?,
372 endpoint,
373 })
374 } else {
375 Ok(Region::from_str(&env::var("AWS_REGION")?)?)
376 }
377 }
378}
379
380#[test]
381fn yandex_object_storage() {
382 let yandex = Region::Custom {
383 endpoint: "storage.yandexcloud.net".to_string(),
384 region: "ru-central1".to_string(),
385 };
386
387 let yandex_region = "ru-central1".parse::<Region>().unwrap();
388
389 assert_eq!(yandex.endpoint(), yandex_region.endpoint());
390
391 assert_eq!(yandex.to_string(), yandex_region.to_string());
392}
393
394#[test]
395fn test_region_eu_central_2() {
396 let region = "eu-central-2".parse::<Region>().unwrap();
397 assert_eq!(region.endpoint(), "s3.eu-central-2.amazonaws.com");
398}
399
400#[test]
401fn test_region_me_central_1() {
402 let region = "me-central-1".parse::<Region>().unwrap();
403 assert_eq!(region.endpoint(), "s3.me-central-1.amazonaws.com");
404 assert_eq!(region.to_string(), "me-central-1");
405}
406
407#[test]
408fn test_custom_endpoint_trailing_slash() {
409 let region_with_slash = Region::Custom {
411 region: "eu-central-1".to_owned(),
412 endpoint: "https://s3.gra.io.cloud.ovh.net/".to_owned(),
413 };
414 assert_eq!(region_with_slash.host(), "s3.gra.io.cloud.ovh.net");
415
416 let region_without_slash = Region::Custom {
418 region: "eu-central-1".to_owned(),
419 endpoint: "https://s3.gra.io.cloud.ovh.net".to_owned(),
420 };
421 assert_eq!(region_without_slash.host(), "s3.gra.io.cloud.ovh.net");
422
423 let region_multiple_slashes = Region::Custom {
425 region: "eu-central-1".to_owned(),
426 endpoint: "https://s3.example.com///".to_owned(),
427 };
428 assert_eq!(region_multiple_slashes.host(), "s3.example.com");
429
430 let region_with_port = Region::Custom {
432 region: "eu-central-1".to_owned(),
433 endpoint: "http://localhost:9000/".to_owned(),
434 };
435 assert_eq!(region_with_port.host(), "localhost:9000");
436}