refactor(domain): extract country utilities into dedicated module

Move COUNTRY_MAP, country_to_iso(), iso_to_flag_emoji() and their tests
from country_stats.rs into a new countries.rs module for reuse by the
detail page view models.
This commit is contained in:
Jon Seager 2026-02-08 16:09:46 +00:00
parent bfe65d23ff
commit 9237a386e0
No known key found for this signature in database
3 changed files with 149 additions and 147 deletions

146
src/domain/countries.rs Normal file
View file

@ -0,0 +1,146 @@
use std::collections::HashMap;
use std::sync::LazyLock;
static COUNTRY_MAP: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
HashMap::from([
// Coffee-producing countries
("ethiopia", "ET"),
("colombia", "CO"),
("kenya", "KE"),
("brazil", "BR"),
("guatemala", "GT"),
("costa rica", "CR"),
("rwanda", "RW"),
("honduras", "HN"),
("panama", "PA"),
("mexico", "MX"),
("el salvador", "SV"),
("bolivia", "BO"),
("peru", "PE"),
("indonesia", "ID"),
("india", "IN"),
("vietnam", "VN"),
("myanmar", "MM"),
("china", "CN"),
("papua new guinea", "PG"),
("tanzania", "TZ"),
("uganda", "UG"),
("burundi", "BI"),
("democratic republic of the congo", "CD"),
("drc", "CD"),
("congo", "CD"),
("republic of the congo", "CG"),
("yemen", "YE"),
("nicaragua", "NI"),
("ecuador", "EC"),
("dominican republic", "DO"),
("haiti", "HT"),
("jamaica", "JM"),
("thailand", "TH"),
("laos", "LA"),
("philippines", "PH"),
("taiwan", "TW"),
// European roaster/cafe countries
("united kingdom", "GB"),
("uk", "GB"),
("england", "GB"),
("scotland", "GB"),
("wales", "GB"),
("northern ireland", "GB"),
("germany", "DE"),
("france", "FR"),
("spain", "ES"),
("italy", "IT"),
("netherlands", "NL"),
("belgium", "BE"),
("denmark", "DK"),
("sweden", "SE"),
("norway", "NO"),
("finland", "FI"),
("switzerland", "CH"),
("austria", "AT"),
("portugal", "PT"),
("ireland", "IE"),
("poland", "PL"),
("czech republic", "CZ"),
("czechia", "CZ"),
("greece", "GR"),
("slovenia", "SI"),
("croatia", "HR"),
("romania", "RO"),
("hungary", "HU"),
// North America
("united states", "US"),
("united states of america", "US"),
("usa", "US"),
("us", "US"),
("canada", "CA"),
// Asia-Pacific
("japan", "JP"),
("south korea", "KR"),
("korea", "KR"),
("australia", "AU"),
("new zealand", "NZ"),
("singapore", "SG"),
// Other
("south africa", "ZA"),
("turkey", "TR"),
("israel", "IL"),
("united arab emirates", "AE"),
("uae", "AE"),
])
});
/// Maps a free-text country name to its ISO-3166-1 alpha-2 code.
pub fn country_to_iso(name: &str) -> Option<&'static str> {
COUNTRY_MAP
.get(name.trim().to_lowercase().as_str())
.copied()
}
/// Converts an ISO-3166-1 alpha-2 code to a flag emoji using regional indicator symbols.
pub fn iso_to_flag_emoji(code: &str) -> String {
code.chars()
.filter_map(|c| {
let upper = c.to_ascii_uppercase();
if upper.is_ascii_uppercase() {
char::from_u32(0x1F1E6 + (upper as u32 - 'A' as u32))
} else {
None
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn country_to_iso_normalises_case() {
assert_eq!(country_to_iso("Ethiopia"), Some("ET"));
assert_eq!(country_to_iso("ETHIOPIA"), Some("ET"));
assert_eq!(country_to_iso(" ethiopia "), Some("ET"));
}
#[test]
fn country_to_iso_handles_aliases() {
assert_eq!(country_to_iso("United Kingdom"), Some("GB"));
assert_eq!(country_to_iso("UK"), Some("GB"));
assert_eq!(country_to_iso("England"), Some("GB"));
}
#[test]
fn country_to_iso_returns_none_for_unknown() {
assert_eq!(country_to_iso("Blend"), None);
assert_eq!(country_to_iso("Multiple Origins"), None);
assert_eq!(country_to_iso(""), None);
}
#[test]
fn iso_to_flag_emoji_produces_correct_flags() {
assert_eq!(iso_to_flag_emoji("GB"), "🇬🇧");
assert_eq!(iso_to_flag_emoji("US"), "🇺🇸");
assert_eq!(iso_to_flag_emoji("ET"), "🇪🇹");
}
}

View file

@ -1,8 +1,7 @@
use std::collections::HashMap;
use std::sync::LazyLock;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::countries::{country_to_iso, iso_to_flag_emoji};
/// A single country's count for geographic statistics. /// A single country's count for geographic statistics.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CountryStat { pub struct CountryStat {
@ -51,147 +50,3 @@ impl GeoStats {
} }
} }
} }
static COUNTRY_MAP: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
HashMap::from([
// Coffee-producing countries
("ethiopia", "ET"),
("colombia", "CO"),
("kenya", "KE"),
("brazil", "BR"),
("guatemala", "GT"),
("costa rica", "CR"),
("rwanda", "RW"),
("honduras", "HN"),
("panama", "PA"),
("mexico", "MX"),
("el salvador", "SV"),
("bolivia", "BO"),
("peru", "PE"),
("indonesia", "ID"),
("india", "IN"),
("vietnam", "VN"),
("myanmar", "MM"),
("china", "CN"),
("papua new guinea", "PG"),
("tanzania", "TZ"),
("uganda", "UG"),
("burundi", "BI"),
("democratic republic of the congo", "CD"),
("drc", "CD"),
("congo", "CD"),
("republic of the congo", "CG"),
("yemen", "YE"),
("nicaragua", "NI"),
("ecuador", "EC"),
("dominican republic", "DO"),
("haiti", "HT"),
("jamaica", "JM"),
("thailand", "TH"),
("laos", "LA"),
("philippines", "PH"),
("taiwan", "TW"),
// European roaster/cafe countries
("united kingdom", "GB"),
("uk", "GB"),
("england", "GB"),
("scotland", "GB"),
("wales", "GB"),
("northern ireland", "GB"),
("germany", "DE"),
("france", "FR"),
("spain", "ES"),
("italy", "IT"),
("netherlands", "NL"),
("belgium", "BE"),
("denmark", "DK"),
("sweden", "SE"),
("norway", "NO"),
("finland", "FI"),
("switzerland", "CH"),
("austria", "AT"),
("portugal", "PT"),
("ireland", "IE"),
("poland", "PL"),
("czech republic", "CZ"),
("czechia", "CZ"),
("greece", "GR"),
("slovenia", "SI"),
("croatia", "HR"),
("romania", "RO"),
("hungary", "HU"),
// North America
("united states", "US"),
("united states of america", "US"),
("usa", "US"),
("us", "US"),
("canada", "CA"),
// Asia-Pacific
("japan", "JP"),
("south korea", "KR"),
("korea", "KR"),
("australia", "AU"),
("new zealand", "NZ"),
("singapore", "SG"),
// Other
("south africa", "ZA"),
("turkey", "TR"),
("israel", "IL"),
("united arab emirates", "AE"),
("uae", "AE"),
])
});
/// Maps a free-text country name to its ISO-3166-1 alpha-2 code.
pub fn country_to_iso(name: &str) -> Option<&'static str> {
COUNTRY_MAP
.get(name.trim().to_lowercase().as_str())
.copied()
}
/// Converts an ISO-3166-1 alpha-2 code to a flag emoji using regional indicator symbols.
pub fn iso_to_flag_emoji(code: &str) -> String {
code.chars()
.filter_map(|c| {
let upper = c.to_ascii_uppercase();
if upper.is_ascii_uppercase() {
char::from_u32(0x1F1E6 + (upper as u32 - 'A' as u32))
} else {
None
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn country_to_iso_normalises_case() {
assert_eq!(country_to_iso("Ethiopia"), Some("ET"));
assert_eq!(country_to_iso("ETHIOPIA"), Some("ET"));
assert_eq!(country_to_iso(" ethiopia "), Some("ET"));
}
#[test]
fn country_to_iso_handles_aliases() {
assert_eq!(country_to_iso("United Kingdom"), Some("GB"));
assert_eq!(country_to_iso("UK"), Some("GB"));
assert_eq!(country_to_iso("England"), Some("GB"));
}
#[test]
fn country_to_iso_returns_none_for_unknown() {
assert_eq!(country_to_iso("Blend"), None);
assert_eq!(country_to_iso("Multiple Origins"), None);
assert_eq!(country_to_iso(""), None);
}
#[test]
fn iso_to_flag_emoji_produces_correct_flags() {
assert_eq!(iso_to_flag_emoji("GB"), "🇬🇧");
assert_eq!(iso_to_flag_emoji("US"), "🇺🇸");
assert_eq!(iso_to_flag_emoji("ET"), "🇪🇹");
}
}

View file

@ -2,6 +2,7 @@ pub mod ai_usage;
pub mod bags; pub mod bags;
pub mod brews; pub mod brews;
pub mod cafes; pub mod cafes;
pub mod countries;
pub mod country_stats; pub mod country_stats;
pub mod cups; pub mod cups;
pub mod errors; pub mod errors;