use std::fmt::Display; use serde::{Deserialize, Serialize}; use crate::{ current_weather_output_model::CurrentWeatherOutputModel, formats::data_format::DataFormat, formats::speed_unit::{speed_to_unit_string, SpeedUnit}, formats::temp_unit::{temp_to_unit_string, TempUnit}, }; #[derive(Serialize, Deserialize, Debug)] pub struct CurrentWeatherOutput { pub format: DataFormat, pub temperature_unit: TempUnit, pub speed_unit: SpeedUnit, pub data: CurrentWeatherOutputModel, } impl Display for CurrentWeatherOutput { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if DataFormat::JSON == self.format { write!(f, "{}", serde_json::to_string(&self.data).unwrap()) } else { let mut string_vec: Vec = Vec::new(); if DataFormat::Normal == self.format { string_vec.push(self.create_header()); } else { if let Some(latitude) = self.data.latitude { string_vec.push(latitude.to_string()) } if let Some(longitude) = self.data.longitude { string_vec.push(longitude.to_string()) } if let Some(city) = &self.data.city { string_vec.push(city.to_string()) } } let is_day = self.extract_day(); if let Some(is_day) = is_day { string_vec.push(is_day); } let temperature = self.extract_temperature(); if let Some(temperature) = temperature { string_vec.push(temperature); } let windspeed = self.extract_wind_speed(); if let Some(windspeed) = windspeed { string_vec.push(windspeed); } let winddirection = self.extract_wind_direction(); if let Some(winddirection) = winddirection { string_vec.push(winddirection); } if DataFormat::Normal == self.format && self.data.latitude.is_some() && self.data.longitude.is_some() { string_vec.push(format!( "{}, {}", self.parse_simple_data( &self.data.latitude.unwrap().to_string(), "Latitude", None ), self.parse_simple_data( &self.data.longitude.unwrap().to_string(), "Longitude", None ) )); } if DataFormat::Clean == self.format { write!(f, "{}", string_vec.join(",")) } else { string_vec.push(self.create_footer()); write!(f, "{}", string_vec.join("\n")) } } } } impl CurrentWeatherOutput { pub fn new( format: DataFormat, temperature_unit: TempUnit, speed_unit: SpeedUnit, ) -> CurrentWeatherOutput { CurrentWeatherOutput { format, temperature_unit, speed_unit, data: CurrentWeatherOutputModel::new(), } } fn create_header(&self) -> String { let mut title_header: Vec = Vec::new(); title_header.push(String::from("=== Current weather")); if self.data.city.is_some() { title_header.push(String::from("for")); title_header.push(self.data.city.clone().unwrap()); } title_header.push(String::from("===")); title_header.join(" ") } fn create_footer(&self) -> String { String::from("=== Weather data by Open-Meteo.com ===") } fn extract_day(&self) -> Option { if self.data.is_day.is_some() { let day = self.data.is_day.unwrap(); if day == 1 { Some(self.parse_custom_data(&day.to_string(), "Day")) } else { Some(self.parse_custom_data(&day.to_string(), "Night")) } } else { None } } fn extract_temperature(&self) -> Option { if self.data.temperature.is_some() { let temperature = self.data.temperature.unwrap(); Some(self.parse_simple_data( &temperature.to_string(), "Temperature", Some(temp_to_unit_string(&self.temperature_unit).as_str()), )) } else { None } } fn extract_wind_speed(&self) -> Option { if self.data.windspeed.is_some() { let windspeed = self.data.windspeed.unwrap(); Some(self.parse_simple_data( &windspeed.to_string(), "Wind speed", Some(format!(" {}", speed_to_unit_string(&self.speed_unit)).as_str()), )) } else { None } } fn extract_wind_direction(&self) -> Option { if self.data.winddirection.is_some() { let winddirection = self.data.winddirection.unwrap(); Some(self.parse_simple_data(&winddirection.to_string(), "Wind direction", Some("°"))) } else { None } } fn parse_custom_data(&self, data: &str, custom: &str) -> String { if self.format == DataFormat::Clean { data.to_string() } else { custom.to_string() } } fn parse_simple_data(&self, data: &str, descriptor: &str, end_text: Option<&str>) -> String { if self.format == DataFormat::Clean { data.to_string() } else { let end_text = end_text.unwrap_or(""); format!("{descriptor}: {data}{end_text}") } } } #[cfg(test)] mod tests { use super::*; use crate::formats::speed_unit::SpeedUnit; use crate::formats::temp_unit::TempUnit; #[test] fn clean_all_data() { let data = CurrentWeatherOutputModel { latitude: Some(5.0), longitude: Some(-5.0), city: Some("TestCity".to_string()), is_day: Some(1), temperature: Some(12.5), windspeed: Some(7.0), winddirection: Some(90.0), }; let output = CurrentWeatherOutput { format: DataFormat::Clean, temperature_unit: TempUnit::Celsius, speed_unit: SpeedUnit::Kmh, data, }; assert_eq!(output.to_string(), "5,-5,TestCity,1,12.5,7,90"); } #[test] fn clean_data() { let data = CurrentWeatherOutputModel { latitude: None, longitude: None, city: None, is_day: Some(1), temperature: Some(15.5), windspeed: Some(12.2), winddirection: None, }; let output = CurrentWeatherOutput { format: DataFormat::Clean, temperature_unit: TempUnit::Celsius, speed_unit: SpeedUnit::Kmh, data, }; assert_eq!(output.to_string(), "1,15.5,12.2"); } #[test] fn full_normal_data() { let data = CurrentWeatherOutputModel { latitude: Some(5.0), longitude: Some(-5.0), city: Some("TestCity".to_string()), is_day: Some(0), temperature: Some(22.0), windspeed: Some(15.5), winddirection: Some(118.0), }; let output = CurrentWeatherOutput { format: DataFormat::Normal, temperature_unit: TempUnit::Celsius, speed_unit: SpeedUnit::Kmh, data, }; let result = output.to_string(); assert!(result.contains("Night")); assert!(result.contains("Temperature: 22°C")); assert!(result.contains("Wind speed: 15.5 km/h")); assert!(result.contains("Wind direction: 118°")); assert!(result.contains("Latitude: 5")); assert!(result.contains("Longitude: -5")); assert!(result.contains("TestCity")); assert!(result.contains("Open-Meteo.com")); } #[test] fn normal_data() { let data = CurrentWeatherOutputModel { latitude: None, longitude: None, city: None, is_day: Some(1), temperature: Some(55.0), windspeed: Some(11.5), winddirection: None, }; let output = CurrentWeatherOutput { format: DataFormat::Normal, temperature_unit: TempUnit::Fahrenheit, speed_unit: SpeedUnit::Mph, data, }; let result = output.to_string(); assert!(result.contains("Day")); assert!(result.contains("Temperature: 55°F")); assert!(result.contains("Wind speed: 11.5 mp/h")); assert!(!result.contains("Wind direction: 125°")); assert!(!result.contains("Latitude: 12.15")); assert!(!result.contains("Longitude: 0.235")); assert!(!result.contains("Nocity")); assert!(result.contains("Open-Meteo.com")); } #[test] fn full_json_data() { let data = CurrentWeatherOutputModel { latitude: Some(5.0), longitude: Some(-5.0), city: Some("TestCity".to_string()), is_day: Some(0), temperature: Some(22.0), windspeed: Some(15.5), winddirection: Some(118.0), }; let output = CurrentWeatherOutput { format: DataFormat::JSON, temperature_unit: TempUnit::Celsius, speed_unit: SpeedUnit::Kmh, data, }; let result = output.to_string(); assert!(result.contains("\"latitude\":5")); assert!(result.contains("\"longitude\":-5")); assert!(result.contains("\"city\":\"TestCity\"")); assert!(result.contains("\"is_day\":0")); assert!(result.contains("\"temperature\":22")); assert!(result.contains("\"windspeed\":15.5")); assert!(result.contains("\"winddirection\":118")); } #[test] fn json_data() { let data = CurrentWeatherOutputModel { latitude: None, longitude: None, city: None, is_day: Some(1), temperature: Some(55.0), windspeed: Some(11.5), winddirection: None, }; let output = CurrentWeatherOutput { format: DataFormat::JSON, temperature_unit: TempUnit::Fahrenheit, speed_unit: SpeedUnit::Mph, data, }; let result = output.to_string(); assert!(!result.contains("\"latitude\":12.15")); assert!(!result.contains("\"longitude\":-0.235")); assert!(!result.contains("\"city\":\"NoCity\"")); assert!(result.contains("\"is_day\":1")); assert!(result.contains("\"temperature\":55")); assert!(result.contains("\"windspeed\":11.5")); assert!(!result.contains("\"winddirection\":125")); } }