summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--mullvad-cli/src/cmds/custom_lists.rs83
-rw-r--r--mullvad-types/src/relay_list.rs12
2 files changed, 89 insertions, 6 deletions
diff --git a/mullvad-cli/src/cmds/custom_lists.rs b/mullvad-cli/src/cmds/custom_lists.rs
index a509dfc5bf..04efd8a967 100644
--- a/mullvad-cli/src/cmds/custom_lists.rs
+++ b/mullvad-cli/src/cmds/custom_lists.rs
@@ -5,6 +5,7 @@ use mullvad_management_interface::MullvadProxyClient;
use mullvad_types::{
custom_list::CustomListLocationUpdate,
relay_constraints::{Constraint, GeographicLocationConstraint},
+ relay_list::RelayList,
};
#[derive(Subcommand, Debug)]
@@ -81,8 +82,9 @@ impl CustomList {
/// Print all custom lists.
async fn list() -> Result<()> {
let mut rpc = MullvadProxyClient::new().await?;
+ let cache = rpc.get_relay_locations().await?;
for custom_list in rpc.list_custom_lists().await? {
- Self::print_custom_list(&custom_list)
+ Self::print_custom_list(&custom_list, &cache)
}
Ok(())
}
@@ -92,7 +94,8 @@ impl CustomList {
async fn get(name: String) -> Result<()> {
let mut rpc = MullvadProxyClient::new().await?;
let custom_list = rpc.get_custom_list(name).await?;
- Self::print_custom_list_content(&custom_list);
+ let cache = rpc.get_relay_locations().await?;
+ Self::print_custom_list_content(&custom_list, &cache);
Ok(())
}
@@ -130,14 +133,82 @@ impl CustomList {
Ok(())
}
- fn print_custom_list(custom_list: &mullvad_types::custom_list::CustomList) {
+ fn print_custom_list(custom_list: &mullvad_types::custom_list::CustomList, cache: &RelayList) {
println!("{}", custom_list.name);
- Self::print_custom_list_content(&custom_list);
+ Self::print_custom_list_content(custom_list, cache);
}
- fn print_custom_list_content(custom_list: &mullvad_types::custom_list::CustomList) {
+ fn print_custom_list_content(
+ custom_list: &mullvad_types::custom_list::CustomList,
+ cache: &RelayList,
+ ) {
for location in &custom_list.locations {
- println!("\t{location}");
+ println!(
+ "\t{}",
+ GeographicLocationConstraintFormatter::from_constraint(location, cache)
+ );
+ }
+ }
+}
+
+/// Struct used for pretty printing [`GeographicLocationConstraint`] with
+/// human-readable names for countries and cities.
+pub struct GeographicLocationConstraintFormatter<'a> {
+ constraint: &'a GeographicLocationConstraint,
+ country: Option<String>,
+ city: Option<String>,
+}
+
+impl<'a> GeographicLocationConstraintFormatter<'a> {
+ fn from_constraint(constraint: &'a GeographicLocationConstraint, cache: &RelayList) -> Self {
+ use GeographicLocationConstraint::*;
+ let (country_code, city_code) = match constraint {
+ Country(country) => (Some(country), None),
+ City(country, city) | Hostname(country, city, _) => (Some(country), Some(city)),
+ };
+
+ let country =
+ country_code.and_then(|country_code| cache.lookup_country(country_code.to_string()));
+ let city = city_code.and_then(|city_code| {
+ country.and_then(|country| country.lookup_city(city_code.to_string()))
+ });
+
+ Self {
+ constraint,
+ country: country.map(|x| x.name.clone()),
+ city: city.map(|x| x.name.clone()),
+ }
+ }
+}
+
+impl<'a> std::fmt::Display for GeographicLocationConstraintFormatter<'a> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+ let unwrap_country = |country: Option<String>, constraint: &str| {
+ country.unwrap_or(format!("{constraint} <invalid country>"))
+ };
+
+ let unwrap_city = |city: Option<String>, constraint: &str| {
+ city.unwrap_or(format!("{constraint} <invalid city>"))
+ };
+
+ match &self.constraint {
+ GeographicLocationConstraint::Country(country) => {
+ let rich_country = unwrap_country(self.country.clone(), country);
+ write!(f, "{rich_country} ({country})")
+ }
+ GeographicLocationConstraint::City(country, city) => {
+ let rich_country = unwrap_country(self.country.clone(), country);
+ let rich_city = unwrap_city(self.city.clone(), city);
+ write!(f, "{rich_city}, {rich_country} ({city}, {country})")
+ }
+ GeographicLocationConstraint::Hostname(country, city, hostname) => {
+ let rich_country = unwrap_country(self.country.clone(), country);
+ let rich_city = unwrap_city(self.city.clone(), city);
+ write!(
+ f,
+ "{hostname} in {rich_city}, {rich_country} ({city}, {country})"
+ )
+ }
}
}
}
diff --git a/mullvad-types/src/relay_list.rs b/mullvad-types/src/relay_list.rs
index e2cd8392f5..3364f383a5 100644
--- a/mullvad-types/src/relay_list.rs
+++ b/mullvad-types/src/relay_list.rs
@@ -29,6 +29,12 @@ impl RelayList {
pub fn empty() -> Self {
Self::default()
}
+
+ pub fn lookup_country(&self, country_code: CountryCode) -> Option<&RelayListCountry> {
+ self.countries
+ .iter()
+ .find(|country| country.code == country_code)
+ }
}
/// A list of [`RelayListCity`]s within a country. Used by [`RelayList`].
@@ -41,6 +47,12 @@ pub struct RelayListCountry {
pub cities: Vec<RelayListCity>,
}
+impl RelayListCountry {
+ pub fn lookup_city(&self, city_code: CityCode) -> Option<&RelayListCity> {
+ self.cities.iter().find(|city| city.code == city_code)
+ }
+}
+
/// A list of [`Relay`]s within a city. Used by [`RelayListCountry`].
#[derive(Debug, Clone, Deserialize, Serialize)]
#[cfg_attr(target_os = "android", derive(IntoJava))]