cloud_scanner_cli/
usage_location.rs1use csv::ReaderBuilder;
3use log::error;
4use once_cell::sync::Lazy;
5use rocket_okapi::okapi::schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::error::Error;
9use std::sync::Mutex;
10use thiserror::Error;
11
12static REGION_COUNTRY_MAP: Lazy<Mutex<HashMap<String, String>>> = Lazy::new(|| {
14 let map = load_region_country_map().unwrap_or_default();
15 Mutex::new(map)
16});
17
18#[derive(Error, Debug)]
19pub enum RegionError {
20 #[error("Unsupported region ({0})")]
21 UnsupportedRegion(String),
22}
23
24#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
28pub struct UsageLocation {
29 pub aws_region: String,
31 pub iso_country_code: String,
33}
34
35impl TryFrom<&str> for UsageLocation {
36 fn try_from(aws_region: &str) -> Result<Self, RegionError> {
37 let cc = get_country_from_aws_region(aws_region)?;
38 Ok(UsageLocation {
39 aws_region: String::from(aws_region),
40 iso_country_code: cc,
41 })
42 }
43 type Error = RegionError;
44}
45
46fn load_region_country_map() -> Result<HashMap<String, String>, Box<dyn Error>> {
48 let csv_content = include_str!("../csv/cloud_providers_regions.csv");
49
50 let mut reader = ReaderBuilder::new()
51 .has_headers(true)
52 .from_reader(csv_content.as_bytes());
53
54 let mut region_country_map = HashMap::new();
55
56 for result in reader.records() {
57 let record = result?;
58 let region = &record[1]; let country_code = &record[2]; region_country_map.insert(region.to_string(), country_code.to_string());
61 }
62
63 Ok(region_country_map)
64}
65
66fn get_country_from_aws_region(aws_region: &str) -> Result<String, RegionError> {
68 let map = REGION_COUNTRY_MAP.lock().unwrap();
69 match map.get(aws_region) {
70 Some(country_code) => Ok(country_code.to_string()),
71 None => {
72 error!(
73 "Unsupported region: unable to match aws region [{}] to country code",
74 aws_region
75 );
76 Err(RegionError::UnsupportedRegion(String::from(aws_region)))
77 }
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::UsageLocation;
85
86 #[test]
87 fn test_get_country_code_for_supported_aws_region() {
88 let location = UsageLocation::try_from("eu-west-1").unwrap();
89 assert_eq!("IRL", location.iso_country_code);
90
91 let location = UsageLocation::try_from("eu-west-2").unwrap();
92 assert_eq!("GBR", location.iso_country_code);
93
94 let location = UsageLocation::try_from("eu-west-3").unwrap();
95 assert_eq!("FRA", location.iso_country_code);
96 }
97
98 #[test]
99 fn test_get_country_code_of_unsupported_aws_region_returns_error() {
100 let res = UsageLocation::try_from("us-gov-east-1");
102 assert!(res.is_err());
103
104 let res = UsageLocation::try_from("whatever");
106 assert!(res.is_err());
107
108 let res = UsageLocation::try_from("");
109 assert!(res.is_err());
110 }
111}