diff --git a/Cargo.lock b/Cargo.lock index 450bdc5..0d862dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,6 +41,25 @@ dependencies = [ "num-traits", ] +[[package]] +name = "async-mutex" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-rwlock" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261803dcc39ba9e72760ba6e16d0199b1eef9fc44e81bffabbebb9f5aea3906c" +dependencies = [ + "async-mutex", + "event-listener", +] + [[package]] name = "async-stream" version = "0.3.2" @@ -186,6 +205,40 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +[[package]] +name = "cached" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4dfac631a8e77b2f327f7852bb6172771f5279c4512efe79fad6067b37be3d" +dependencies = [ + "async-mutex", + "async-rwlock", + "async-trait", + "cached_proc_macro", + "cached_proc_macro_types", + "futures", + "hashbrown", + "once_cell", +] + +[[package]] +name = "cached_proc_macro" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "725f434d6da2814b989bd905c62ca28a9383feff7440210dc279665fbbbc9511" +dependencies = [ + "cached_proc_macro_types", + "darling", + "quote", + "syn", +] + +[[package]] +name = "cached_proc_macro_types" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663" + [[package]] name = "cc" version = "1.0.72" @@ -381,6 +434,41 @@ dependencies = [ "memchr", ] +[[package]] +name = "darling" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "deflate" version = "1.0.0" @@ -444,6 +532,12 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "event-listener" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" + [[package]] name = "exr" version = "1.4.1" @@ -912,6 +1006,12 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.2.3" @@ -2042,6 +2142,7 @@ dependencies = [ name = "sinoptik" version = "0.1.0" dependencies = [ + "cached", "chrono", "chrono-tz", "color-eyre", @@ -2176,6 +2277,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "1.0.86" diff --git a/Cargo.toml b/Cargo.toml index d754e44..064ced9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +cached = { version = "0.30.0", features = ["async"] } csv = "1.1.6" chrono = "0.4.19" chrono-tz = "0.6.1" diff --git a/src/main.rs b/src/main.rs index cf6ccef..81eb571 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ use std::sync::{Arc, Mutex}; +use cached::proc_macro::cached; use color_eyre::Result; use geocoding::{Forward, Openstreetmap, Point}; use rocket::serde::json::Json; @@ -27,6 +28,17 @@ use self::providers::luchtmeetnet::Item as LuchtmeetnetItem; pub(crate) mod maps; pub(crate) mod providers; +/// Caching key helper function that can be used by providers. +/// +/// This is necessary because `f64` does not implement `Eq` nor `Hash`, which is required by +/// the caching implementation. +fn cache_key(lat: f64, lon: f64, metric: Metric) -> (i32, i32, Metric) { + let lat_key = (lat * 10_000.0) as i32; + let lon_key = (lon * 10_000.0) as i32; + + (lat_key, lon_key, metric) +} + /// The current for a specific location. /// /// Only the metrics asked for are included as well as the position and current time. @@ -94,7 +106,7 @@ impl Forecast { /// /// This is used for selecting which metrics should be calculated & returned. #[allow(clippy::upper_case_acronyms)] -#[derive(Copy, Clone, Debug, Eq, PartialEq, FromFormField)] +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, FromFormField)] enum Metric { /// All metrics. #[field(value = "all")] diff --git a/src/providers/buienradar.rs b/src/providers/buienradar.rs index 3423d8d..6c22f13 100644 --- a/src/providers/buienradar.rs +++ b/src/providers/buienradar.rs @@ -3,6 +3,7 @@ //! For more information about Buienradar, see: //! and . +use cached::proc_macro::cached; use chrono::offset::TimeZone; use chrono::serde::ts_seconds; use chrono::{DateTime, Local, NaiveTime, ParseError, Utc}; @@ -11,7 +12,7 @@ use csv::ReaderBuilder; use reqwest::Url; use rocket::serde::{Deserialize, Serialize}; -use crate::Metric; +use crate::{cache_key, Metric}; /// The base URL for the Buienradar API. const BUIENRADAR_BASE_URL: &str = "https://gpsgadget.buienradar.nl/data/raintext"; @@ -30,7 +31,7 @@ struct Row { } /// The Buienradar API precipitation data item. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(crate = "rocket::serde", try_from = "Row")] pub(crate) struct Item { /// The time(stamp) of the forecast. @@ -89,6 +90,12 @@ fn convert_value(v: u16) -> f32 { /// /// Returns [`None`] if retrieval or deserialization fails, or if the metric is not supported by /// this provider. +#[cached( + time = 300, + convert = "{ cache_key(lat, lon, metric) }", + key = "(i32, i32, Metric)", + option = true +)] pub(crate) async fn get(lat: f64, lon: f64, metric: Metric) -> Option> { if metric != Metric::Precipitation { return None; diff --git a/src/providers/luchtmeetnet.rs b/src/providers/luchtmeetnet.rs index 26c243d..a5480ff 100644 --- a/src/providers/luchtmeetnet.rs +++ b/src/providers/luchtmeetnet.rs @@ -2,12 +2,13 @@ //! //! For more information about Luchtmeetnet, see: . +use cached::proc_macro::cached; use chrono::serde::ts_seconds; use chrono::{DateTime, Utc}; use reqwest::Url; use rocket::serde::{Deserialize, Serialize}; -use crate::Metric; +use crate::{cache_key, Metric}; /// The base URL for the Luchtmeetnet API. const LUCHTMEETNET_BASE_URL: &str = "https://api.luchtmeetnet.nl/open_api/concentrations"; @@ -22,7 +23,7 @@ struct Container { } /// The Luchtmeetnet API data item. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(crate = "rocket::serde")] pub(crate) struct Item { /// The time(stamp) of the forecast. @@ -48,6 +49,12 @@ pub(crate) struct Item { /// /// Returns [`None`] if retrieval or deserialization fails, or if the metric is not supported by /// this provider. +#[cached( + time = 300, + convert = "{ cache_key(lat, lon, metric) }", + key = "(i32, i32, Metric)", + option = true +)] pub(crate) async fn get(lat: f64, lon: f64, metric: Metric) -> Option> { let formula = match metric { Metric::AQI => "lki",