use csv::ReaderBuilder;
use log::error;
use once_cell::sync::Lazy;
use rocket_okapi::okapi::schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::error::Error;
use std::sync::Mutex;
use thiserror::Error;
static REGION_COUNTRY_MAP: Lazy<Mutex<HashMap<String, String>>> = Lazy::new(|| {
let map = load_region_country_map().unwrap_or_default();
Mutex::new(map)
});
#[derive(Error, Debug)]
pub enum RegionError {
#[error("Unsupported region ({0})")]
UnsupportedRegion(String),
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct UsageLocation {
pub aws_region: String,
pub iso_country_code: String,
}
impl TryFrom<&str> for UsageLocation {
fn try_from(aws_region: &str) -> Result<Self, RegionError> {
let cc = get_country_from_aws_region(aws_region)?;
Ok(UsageLocation {
aws_region: String::from(aws_region),
iso_country_code: cc,
})
}
type Error = RegionError;
}
fn load_region_country_map() -> Result<HashMap<String, String>, Box<dyn Error>> {
let csv_content = include_str!("../csv/cloud_providers_regions.csv");
let mut reader = ReaderBuilder::new()
.has_headers(true)
.from_reader(csv_content.as_bytes());
let mut region_country_map = HashMap::new();
for result in reader.records() {
let record = result?;
let region = &record[1]; let country_code = &record[2]; region_country_map.insert(region.to_string(), country_code.to_string());
}
Ok(region_country_map)
}
fn get_country_from_aws_region(aws_region: &str) -> Result<String, RegionError> {
let map = REGION_COUNTRY_MAP.lock().unwrap();
match map.get(aws_region) {
Some(country_code) => Ok(country_code.to_string()),
None => {
error!(
"Unsupported region: unable to match aws region [{}] to country code",
aws_region
);
Err(RegionError::UnsupportedRegion(String::from(aws_region)))
}
}
}
#[cfg(test)]
mod tests {
use super::UsageLocation;
#[test]
fn test_get_country_code_for_supported_aws_region() {
let location = UsageLocation::try_from("eu-west-1").unwrap();
assert_eq!("IRL", location.iso_country_code);
let location = UsageLocation::try_from("eu-west-2").unwrap();
assert_eq!("GBR", location.iso_country_code);
let location = UsageLocation::try_from("eu-west-3").unwrap();
assert_eq!("FRA", location.iso_country_code);
}
#[test]
fn test_get_country_code_of_unsupported_aws_region_returns_error() {
let res = UsageLocation::try_from("us-gov-east-1");
assert!(res.is_err());
let res = UsageLocation::try_from("whatever");
assert!(res.is_err());
let res = UsageLocation::try_from("");
assert!(res.is_err());
}
}