1use crate::prelude::*;
3use crate::util::{Constant, Searchable};
4use bon::Builder;
5use serde::{Deserialize, Serialize};
6use serde_with::skip_serializing_none;
7
8pub trait GeonamesParser {
10 fn country_codes(format: CodeFormat) -> Vec<String>;
14 fn country_data() -> Countries;
21 fn languages(format: CodeFormat) -> Vec<String>;
25}
26#[derive(Clone, Debug, Default, Deserialize, Serialize)]
28#[serde(transparent)]
29pub struct Countries(pub Vec<Country>);
30#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
32pub enum CodeFormat {
33 Alpha2,
35 Alpha3,
37}
38#[skip_serializing_none]
42#[derive(Builder, Clone, Debug, Deserialize, Serialize)]
43#[builder(start_fn = init, on(String, into))]
44pub struct Country {
45 pub iso: String,
47 pub iso3: String,
49 pub iso_numeric: String,
51 pub fips: String,
53 pub name: String,
55 pub capital: String,
57 pub area: Option<u64>,
59 pub population: Option<u64>,
61 pub continent: String,
63 pub top_level_domain: String,
65 pub currency_code: String,
67 pub currency_name: String,
69 pub country_code: String,
71 pub postal_code_format: String,
73 pub postal_code_regex: String,
75 pub languages: Vec<String>,
77 pub identifier: Option<u64>,
79 pub neighbours: Vec<String>,
81 pub equivalent_fips_code: Option<String>,
83}
84impl Countries {
85 pub fn len(&self) -> usize {
87 self.0.len()
88 }
89 pub fn is_empty(&self) -> bool {
91 self.0.is_empty()
92 }
93}
94impl Searchable<Country> for Countries {
95 fn contains(&self, value: &str) -> bool {
96 let trimmed = value.trim();
97 !trimmed.is_empty()
98 && self.0.iter().any(|country| {
99 let Country { iso, iso3, .. } = country;
100 iso.eq_ignore_ascii_case(trimmed) || iso3.eq_ignore_ascii_case(trimmed)
101 })
102 }
103 fn find_by_iso(&self, value: impl Into<String>) -> Option<Country> {
104 let trimmed = value.into().trim().to_string();
105 self.0
106 .iter()
107 .find(|country| {
108 let Country { iso, iso3, .. } = country;
109 iso.eq_ignore_ascii_case(&trimmed) || iso3.eq_ignore_ascii_case(&trimmed)
110 })
111 .cloned()
112 }
113 fn find_by_name(&self, value: impl Into<String>) -> Option<Country> {
114 let trimmed = value.into().trim().to_string();
115 self.0
116 .iter()
117 .find(|country| {
118 let Country { name, .. } = country;
119 name.eq_ignore_ascii_case(&trimmed)
120 })
121 .cloned()
122 }
123}
124impl From<Vec<Country>> for Countries {
125 fn from(value: Vec<Country>) -> Self {
126 Self(value)
127 }
128}
129impl From<&str> for Countries {
130 fn from(value: &str) -> Self {
131 parse(value)
132 }
133}
134impl From<String> for Countries {
135 fn from(value: String) -> Self {
136 parse(&value)
137 }
138}
139impl GeonamesParser for Constant {
140 fn country_codes(format: CodeFormat) -> Vec<String> {
141 let data = Self::country_data();
142 country_codes(data, format)
143 }
144 fn country_data() -> Countries {
145 let text = Self::from_asset("geonames.tsv").unwrap_or_default();
146 parse(&text)
147 }
148 fn languages(format: CodeFormat) -> Vec<String> {
149 let data = Self::country_data();
150 languages(data, format)
151 }
152}
153fn country_codes(data: Countries, format: CodeFormat) -> Vec<String> {
154 let mut codes = data
155 .0
156 .into_iter()
157 .map(|country: Country| {
158 let code = match format {
159 | CodeFormat::Alpha2 => country.iso,
160 | CodeFormat::Alpha3 => country.iso3,
161 };
162 code.trim().to_lowercase()
163 })
164 .filter(|value| !value.is_empty())
165 .collect::<Vec<String>>();
166 codes.sort();
167 codes.dedup();
168 codes
169}
170fn languages(data: Countries, format: CodeFormat) -> Vec<String> {
171 let mut languages = data
172 .0
173 .into_iter()
174 .flat_map(|country: Country| {
175 country
176 .languages
177 .into_iter()
178 .map(|value| value.trim().to_lowercase())
179 .filter(|value| !value.is_empty())
180 .collect::<Vec<String>>()
181 })
182 .filter(|value| match format {
183 | CodeFormat::Alpha2 => value.len() == 2,
184 | CodeFormat::Alpha3 => value.contains('-'),
185 })
186 .collect::<Vec<String>>();
187 languages.sort();
188 languages.dedup();
189 languages
190}
191fn parse(data: &str) -> Countries {
192 fn parse_optional_string_field(value: &str) -> Option<String> {
193 let trimmed = value.trim();
194 if trimmed.is_empty() {
195 None
196 } else {
197 Some(trimmed.to_string())
198 }
199 }
200 fn parse_u64_field(value: &str) -> Option<u64> {
201 let trimmed = value.trim();
202 if trimmed.is_empty() {
203 None
204 } else {
205 trimmed.parse::<u64>().ok()
206 }
207 }
208 fn list_field(value: &str) -> Vec<String> {
209 value
210 .split(',')
211 .map(|item| item.trim())
212 .filter(|item| !item.is_empty())
213 .map(|item| item.to_string())
214 .collect()
215 }
216 fn string_field(columns: &[&str], index: usize) -> String {
217 columns.get(index).map(|s| s.trim().to_string()).unwrap_or_default()
218 }
219 data.lines()
220 .filter(|line| {
221 let line = line.trim();
222 !line.is_empty() && !line.starts_with('#')
223 })
224 .filter(|line| !line.starts_with("ISO\t"))
225 .map(|line| {
226 let columns: Vec<&str> = line.split('\t').collect();
227 Country {
228 iso: string_field(&columns, 0),
229 iso3: string_field(&columns, 1),
230 iso_numeric: string_field(&columns, 2),
231 fips: string_field(&columns, 3),
232 name: string_field(&columns, 4),
233 capital: string_field(&columns, 5),
234 area: columns.get(6).and_then(|s| parse_u64_field(s)),
235 population: columns.get(7).and_then(|s| parse_u64_field(s)),
236 continent: string_field(&columns, 8),
237 top_level_domain: string_field(&columns, 9),
238 currency_code: string_field(&columns, 10),
239 currency_name: string_field(&columns, 11),
240 country_code: string_field(&columns, 12),
241 postal_code_format: string_field(&columns, 13),
242 postal_code_regex: string_field(&columns, 14),
243 languages: columns.get(15).map(|s| list_field(s)).unwrap_or_default(),
244 identifier: columns.get(16).and_then(|s| parse_u64_field(s)),
245 neighbours: columns.get(17).map(|s| list_field(s)).unwrap_or_default(),
246 equivalent_fips_code: columns.get(18).and_then(|s| parse_optional_string_field(s)),
247 }
248 })
249 .collect::<Vec<Country>>()
250 .into()
251}