Implement caching for provider get requests (closes: #2)

* Also cache address geocoding requests to OSM Nomatim!
* Use the `cached` crate for an easy implementation
* Add the `cache_key` helper function to deal with floats being annoying
* Cache Buienradar get request for 5 minutes (per position/metric)
* Cache Luchtmeetnet get request for 5 minutes (per position/metric)
* Note the `Item` structs need to implement `Clone` now because
  the cache will own them and Rocket will want a copy too
This commit is contained in:
Paul van Tilburg 2022-02-14 21:06:31 +01:00
parent 927cb0ad92
commit 8d19dbb517
Signed by: paul
GPG Key ID: C6DE073EDA9EEC4D
5 changed files with 139 additions and 5 deletions

107
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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")]

View File

@ -3,6 +3,7 @@
//! For more information about Buienradar, see: <https://www.buienradar.nl/overbuienradar/contact>
//! and <https://www.buienradar.nl/overbuienradar/gratis-weerdata>.
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<Vec<Item>> {
if metric != Metric::Precipitation {
return None;

View File

@ -2,12 +2,13 @@
//!
//! For more information about Luchtmeetnet, see: <https://www.luchtmeetnet.nl/contact>.
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<Vec<Item>> {
let formula = match metric {
Metric::AQI => "lki",