1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
extern crate reqwest; extern crate serde_json; use futures::executor; use http::StatusCode; use regex::Regex; use serde::Deserialize; use std::sync::mpsc; use std::thread; use std::time::Duration; #[cfg(test)] mod tests; /// Location coordinates #[derive(Deserialize, Debug)] pub struct Coord { /// geo location, longitude pub lon: f64, /// geo location, latitude pub lat: f64, } /// Weather condition description #[derive(Deserialize, Debug)] pub struct Weather { /// Weather condition id pub id: u64, /// Group of weather parameters (Rain, Snow, Extreme etc.) pub main: String, /// Weather condition pub description: String, /// Weather icon id pub icon: String, } /// Detailed weather report #[derive(Deserialize, Debug)] pub struct Main { /// Temperature. Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. pub temp: f64, /// Temperature. This temperature parameter accounts for the human perception of weather. /// Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. pub feels_like: f64, /// Atmospheric pressure (on the sea level, if there is no sea_level or grnd_level data), hPa pub pressure: f64, /// Humidity, % pub humidity: f64, /// Minimum temperature at the moment. /// This is minimal currently observed temperature (within large megalopolises and urban areas). /// Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. pub temp_min: f64, /// Maximum temperature at the moment. /// This is maximal currently observed temperature (within large megalopolises and urban areas). /// Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. pub temp_max: f64, /// Atmospheric pressure on the sea level, hPa pub sea_level: Option<f64>, /// Atmospheric pressure on the ground level, hPa pub grnd_level: Option<f64>, } /// Detailed wind report #[derive(Deserialize, Debug)] pub struct Wind { /// Wind speed. Unit Default: meter/sec, Metric: meter/sec, Imperial: miles/hour. pub speed: f64, /// Wind direction, degrees (meteorological) pub deg: f64, /// Wind gust. Unit Default: meter/sec, Metric: meter/sec, Imperial: miles/hour pub gust: Option<f64>, } /// Detailed clouds report #[derive(Deserialize, Debug)] pub struct Clouds { /// Cloudiness, % pub all: f64, } /// Rain or snow volume report #[derive(Deserialize, Debug)] pub struct Volume { /// Volume for the last 1 hour, mm #[serde(rename = "1h")] pub h1: Option<f64>, /// Volume for the last 3 hours, mm #[serde(rename = "3h")] pub h3: Option<f64>, } /// Additional information #[derive(Deserialize, Debug)] pub struct Sys { /// Internal parameter #[serde(rename = "type")] pub type_: Option<u64>, /// Internal parameter pub id: Option<u64>, /// Internal parameter pub message: Option<f64>, /// Country code (GB, JP etc.) pub country: String, /// Sunrise time, unix, UTC pub sunrise: i64, /// Sunset time, unix, UTC pub sunset: i64, } #[derive(Deserialize, Debug)] /// current weather report in a nested struct pub struct CurrentWeather { /// report origin coordinates pub coord: Coord, /// vector with one item of weather condition descriptions pub weather: Vec<Weather>, /// Internal parameter pub base: String, /// detailed weather report pub main: Main, /// Visibility, meter pub visibility: u64, /// detailed wind report pub wind: Wind, /// detailed clouds report pub clouds: Clouds, /// detailed rain report pub rain: Option<Volume>, /// detailed snow report pub snow: Option<Volume>, /// Time of data calculation, unix, UTC pub dt: i64, /// additional information pub sys: Sys, /// Shift in seconds from UTC pub timezone: i64, /// City ID pub id: u64, /// City name pub name: String, /// Internal parameter pub cod: u64, } /// Receiver object you get from `init()` and have top handle to `update()`. pub type Receiver = mpsc::Receiver<Result<CurrentWeather, String>>; /// Loading error messaage you get at the first call of `update()`. pub const LOADING: &str = "loading..."; /// Spawns a thread which fetches the current weather from /// [openweathermap.org](https://openweathermap.org) periodically. /// #### Parameters /// - `location`: Can be a city name, a city ID or a geographical coordinate: /// - city name: may be followed by comma separated state code and/or country code (e.g. `"Berlin,DE"`). /// - city ID: which can be found at [this](https://openweathermap.org/find) where you will get link that includes the ID /// - e.g. `"2950159"` for Berlin, Germany /// - coordinates: given by comma separated latitude and longitude (e.g. `"52.5244,13.4105"`). | /// - `units`: One of the following: /// - `"metric"`: meters, m/s, °C, etc. /// - `"imperial"`: miles, mi/s, °F, etc. /// - `"standard"`: meters, m/s, K, etc. /// - `lang`: Language code: /// - `"en"`: for English /// - `"de"`: for German /// - see [this list](https://openweathermap.org/current#multi) for all available language codes /// - `api_key`: Your API key which you can get [here](https://openweathermap.org/price) /// - `poll_mins`: Update interval: /// - `> 0`: duration of poll period in minutes (`10` is recommended) /// - `= 0`: thread will terminate after the first successful update. /// #### Return value /// - `openweathermap::Receiver`: Handle this to `openweathermap::update()` to get the latest weather update. /// /// The return value is a `mpsc` *channel receiver*: /// ```rust /// pub type Receiver = std::sync::mpsc::Receiver<Result<openweathermap::CurrentWeather, String>>; /// ``` pub fn init(location: &str, units: &str, lang: &str, api_key: &str, poll_mins: u64) -> Receiver { // generate correct request URL depending on city is id or name let url = match location.parse::<u64>().is_ok() { true => format!( "https://api.openweathermap.org/data/2.5/weather?id={}&units={}&lang={}&appid={}", location, units, lang, api_key ), false => { let re = Regex::new(r"(-?\d+\.\d+)\s*,\s*(-?\d+\.\d+)").unwrap(); match re.captures(&location) { Some(caps) => format!("https://api.openweathermap.org/data/2.5/weather?lat={}&lon={}&units={}&lang={}&appid={}", caps.get(1).unwrap().as_str(), caps.get(2).unwrap().as_str(), units, lang, api_key ), None => format!( "https://api.openweathermap.org/data/2.5/weather?q={}&units={}&lang={}&appid={}", location, units, lang, api_key ), } } }; // fork thread that continuously fetches weather updates every <poll_mins> minutes let period = Duration::from_secs(60 * poll_mins); let (tx, rx) = mpsc::channel(); thread::spawn(move || { tx.send(Err(LOADING.to_string())).unwrap_or(()); loop { match reqwest::blocking::get(&url) { Ok(response) => match response.status() { StatusCode::OK => match serde_json::from_str(&response.text().unwrap()) { Ok(w) => { tx.send(Ok(w)).unwrap_or(()); if period == Duration::new(0, 0) { break; } thread::sleep(period); } Err(e) => tx.send(Err(e.to_string())).unwrap_or(()), }, _ => tx.send(Err(response.status().to_string())).unwrap_or(()), }, Err(_e) => (), } } }); // return receiver that provides the updated weather as json string return rx; } /// Get current weather update that the spawned thread could fetched. /// #### Parameters /// - `receiver`: the *channel receiver* from preceded call to `openweathermap::init()` /// #### Returng value /// - ⇒ `None`: No update available /// - ⇒ `Some(Result)`: Update available /// - ⇒ `Ok(CurrentWeather)`: Weather information in a nested struct called `CurrentWeather` /// (see also [*OpenWeatherMap* documentation](https://openweathermap.org/current#parameter) for details) /// - ⇒ `Err(String)`: Error message about any occured http or json issue /// - e.g. `401 Unauthorized`: if your API key is invalid /// - some json parser error message if response from OpenWeatherMap could not be parsed pub fn update(receiver: &Receiver) -> Option<Result<CurrentWeather, String>> { match receiver.try_recv() { Ok(response) => Some(response), Err(_e) => None, } } /// Fetch current weather update once and stop thread immediately after success. /// Returns the result in a *future*. /// #### Parameters /// - `location`: Can be a city name, a city ID or a geographical coordinate: /// - city name: may be followed by comma separated state code and/or country code (e.g. `"Berlin,DE"`). /// - city ID: which can be found at [this](https://openweathermap.org/find) where you will get link that includes the ID /// - e.g. `"2950159"` for Berlin, Germany /// - coordinates: given by comma separated latitude and longitude (e.g. `"52.5244,13.4105"`). | /// - `units`: One of the following: /// - `"metric"`: meters, m/s, °C, etc. /// - `"imperial"`: miles, mi/s, °F, etc. /// - `"standard"`: meters, m/s, K, etc. /// - `lang`: Language code: /// - `"en"`: for English /// - `"de"`: for German /// - see [this list](https://openweathermap.org/current#multi) for all available language codes /// - `api_key`: Your API key which you can get [here](https://openweathermap.org/price) /// #### Return value /// - ⇒ `Ok(CurrentWeather)`: weather information in a nested struct called `CurrentWeather` /// (see also [*OpenWeatherMap* documentation](https://openweathermap.org/current#parameter) for details) /// - ⇒ `Err(String)`: Error message about any occured http or json issue /// - e.g. `401 Unauthorized` if your API key is invalid /// - some json parser error message if response from OpenWeatherMap could not be parsed pub async fn weather( location: &str, units: &str, lang: &str, api_key: &str, ) -> Result<CurrentWeather, String> { let r = init(location, units, lang, api_key, 0); loop { match update(&r) { Some(response) => match response { Ok(current) => return Ok(current), Err(e) => { if e != LOADING { return Err(e); } } }, None => (), } } } /// synchronous functions pub mod blocking { use super::*; /// Fetches a weather update once and stops the thread immediately after success then returns the update. /// #### Parameters /// - `location`: Can be a city name, a city ID or a geographical coordinate: /// - city name may be followed by comma separated state code and/or country code (e.g. `"Berlin,DE"`). /// - city ID which can be found at [this](https://openweathermap.org/find) where you will get link that includes the ID /// - e.g. `"2950159"` for Berlin, Germany /// - coordinates given by comma separated latitude and longitude (e.g. `"52.5244,13.4105"`). | /// - `units`: One of the following: /// - `"metric"`: meters, m/s, °C, etc. /// - `"imperial"`: miles, mi/s, °F, etc. /// - `"standard"`: meters, m/s, K, etc. /// - `lang`: Language code: /// - `"en"`: for English /// - `"de"`: for German /// - see [this list](https://openweathermap.org/current#multi) for all available language codes /// - `api_key`: Your API key which you can get [here](https://openweathermap.org/price) /// #### Return value /// - ⇒ `Ok(CurrentWeather)`: weather information in a nested struct called `CurrentWeather` /// (see also [*OpenWeatherMap* documentation](https://openweathermap.org/current#parameter) for details) /// - ⇒ `Err(String)`: Error message about any occured http or json issue /// - e.g. `401 Unauthorized` if your API key is invalid /// - some json parser error message if response from OpenWeatherMap could not be parsed pub fn weather( location: &str, units: &str, lang: &str, api_key: &str, ) -> Result<CurrentWeather, String> { // wait for result executor::block_on(super::weather(location, units, lang, api_key)) } }