use crate::errors::{data_sources, load_methods, modules};
use crate::{BgpkitCommons, BgpkitCommonsError, LazyLoadable, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Country {
pub code: String,
pub code3: String,
pub name: String,
pub capital: String,
pub continent: String,
pub ltd: Option<String>,
pub neighbors: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Countries {
countries: HashMap<String, Country>,
}
const DATA_URL: &str = "https://download.geonames.org/export/dump/countryInfo.txt";
impl Countries {
pub fn new() -> Result<Self> {
let mut countries: Vec<Country> = vec![];
for line in oneio::read_lines(DATA_URL)? {
let text = line.ok().ok_or_else(|| {
BgpkitCommonsError::data_source_error(data_sources::GEONAMES, "error reading line")
})?;
if text.trim() == "" || text.starts_with('#') {
continue;
}
let splits: Vec<&str> = text.split('\t').collect();
if splits.len() != 19 {
return Err(BgpkitCommonsError::invalid_format(
"countries data",
text.as_str(),
"row missing fields",
));
}
let code = splits[0].to_string();
let code3 = splits[1].to_string();
let name = splits[4].to_string();
let capital = splits[5].to_string();
let continent = splits[8].to_string();
let ltd = match splits[9] {
"" => None,
d => Some(d.to_string()),
};
let neighbors = splits[17]
.split(',')
.map(|x| x.to_string())
.collect::<Vec<String>>();
countries.push(Country {
code,
code3,
name,
capital,
continent,
ltd,
neighbors,
})
}
let mut countries_map: HashMap<String, Country> = HashMap::new();
for country in countries {
countries_map.insert(country.code.clone(), country);
}
Ok(Countries {
countries: countries_map,
})
}
pub fn lookup_by_code(&self, code: &str) -> Option<Country> {
self.countries.get(code).cloned()
}
pub fn lookup_by_name(&self, name: &str) -> Vec<Country> {
let lower_name = name.to_lowercase();
let mut countries: Vec<Country> = vec![];
for country in self.countries.values() {
if country.name.to_lowercase().contains(&lower_name) {
countries.push(country.clone());
}
}
countries
}
pub fn all_countries(&self) -> Vec<Country> {
self.countries.values().cloned().collect()
}
}
impl LazyLoadable for Countries {
fn reload(&mut self) -> Result<()> {
*self = Countries::new().map_err(|e| {
BgpkitCommonsError::data_source_error(data_sources::GEONAMES, e.to_string())
})?;
Ok(())
}
fn is_loaded(&self) -> bool {
!self.countries.is_empty()
}
fn loading_status(&self) -> &'static str {
if self.is_loaded() {
"Countries data loaded"
} else {
"Countries data not loaded"
}
}
}
impl BgpkitCommons {
pub fn country_all(&self) -> Result<Vec<Country>> {
if self.countries.is_none() {
return Err(BgpkitCommonsError::module_not_loaded(
modules::COUNTRIES,
load_methods::LOAD_COUNTRIES,
));
}
Ok(self.countries.as_ref().unwrap().all_countries())
}
pub fn country_by_code(&self, code: &str) -> Result<Option<Country>> {
if self.countries.is_none() {
return Err(BgpkitCommonsError::module_not_loaded(
modules::COUNTRIES,
load_methods::LOAD_COUNTRIES,
));
}
Ok(self.countries.as_ref().unwrap().lookup_by_code(code))
}
pub fn country_by_name(&self, name: &str) -> Result<Vec<Country>> {
if self.countries.is_none() {
return Err(BgpkitCommonsError::module_not_loaded(
modules::COUNTRIES,
load_methods::LOAD_COUNTRIES,
));
}
Ok(self.countries.as_ref().unwrap().lookup_by_name(name))
}
pub fn country_by_code3(&self, code: &str) -> Result<Option<Country>> {
if self.countries.is_none() {
return Err(BgpkitCommonsError::module_not_loaded(
modules::COUNTRIES,
load_methods::LOAD_COUNTRIES,
));
}
Ok(self.countries.as_ref().unwrap().lookup_by_code(code))
}
}