parse datetimes properly

This commit is contained in:
Sebastian Hugentobler 2024-05-11 16:38:29 +02:00
parent adda135743
commit a8c0be2c05
Signed by: shu
GPG Key ID: BB32CF3CA052C2F0
11 changed files with 124 additions and 40 deletions

1
Cargo.lock generated
View File

@ -61,6 +61,7 @@ dependencies = [
"sea-orm", "sea-orm",
"serde", "serde",
"serde_json", "serde_json",
"time",
"tokio", "tokio",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",

View File

@ -11,6 +11,7 @@ rumqttc = "0.24.0"
sea-orm = { version = "0.12", features = [ "sqlx-postgres", "runtime-tokio-rustls", "macros" ] } sea-orm = { version = "0.12", features = [ "sqlx-postgres", "runtime-tokio-rustls", "macros" ] }
serde = "1.0.199" serde = "1.0.199"
serde_json = "1.0.116" serde_json = "1.0.116"
time = { version = "0.3.36", features = ["macros", "serde", "formatting", "parsing" ] }
tokio = { version = "1.37.0", features = ["full"] } tokio = { version = "1.37.0", features = ["full"] }
tracing = "0.1.40" tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }

7
src/alert_swiss.rs Normal file
View File

@ -0,0 +1,7 @@
use crate::json::alerts::Alerts;
const ALERT_SWISS_URL: &str = "https://www.alert.swiss/content/alertswiss-internet/en/home/_jcr_content/polyalert.alertswiss_alerts.actual.json";
pub async fn fetch_alerts() -> Result<Alerts, reqwest::Error> {
reqwest::get(ALERT_SWISS_URL).await?.json::<Alerts>().await
}

12
src/cli.rs Normal file
View File

@ -0,0 +1,12 @@
//! Cli interface.
use clap::Parser;
/// Push Alertswiss notifications to MQTT.
#[derive(Parser)]
#[command(version, about, long_about = None)]
pub struct Cli {
/// Update interval in seconds
#[arg(short, long, default_value_t = 10)]
pub interval: u64,
}

15
src/config.rs Normal file
View File

@ -0,0 +1,15 @@
use crate::cli::Cli;
/// Application configuration.
pub struct Config {
/// Update interval in seconds
pub interval: u64,
}
impl From<Cli> for Config {
fn from(value: Cli) -> Self {
Self {
interval: value.interval,
}
}
}

View File

@ -1,4 +1,5 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;
use super::{ use super::{
area::Area, contact::Contact, description::Description, image::Image, area::Area, contact::Contact, description::Description, image::Image,
@ -11,7 +12,8 @@ use super::{
pub struct Alert { pub struct Alert {
pub identifier: String, pub identifier: String,
pub gtrans_origin: Option<String>, pub gtrans_origin: Option<String>,
pub sent: String, #[serde(deserialize_with = "super::datetime::deserialize_alert")]
pub sent: PrimitiveDateTime,
pub title: Title, pub title: Title,
pub description: Description, pub description: Description,
pub nation_wide: bool, pub nation_wide: bool,
@ -27,7 +29,8 @@ pub struct Alert {
pub severity: String, pub severity: String,
pub test_alert: bool, pub test_alert: bool,
pub technical_test_alert: bool, pub technical_test_alert: bool,
pub publish_date: String, #[serde(deserialize_with = "super::datetime::deserialize_alert")]
pub publish_date: PrimitiveDateTime,
pub areas: Vec<Area>, pub areas: Vec<Area>,
pub images: Vec<Image>, pub images: Vec<Image>,
pub image_titles: Vec<ImageTitle>, pub image_titles: Vec<ImageTitle>,

View File

@ -1,10 +1,13 @@
use super::alert::Alert; use super::alert::Alert;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Alerts { pub struct Alerts {
pub heartbeat_age_in_millis: u64, pub heartbeat_age_in_millis: u64,
pub render_time: String, #[serde(deserialize_with = "super::datetime::deserialize_alerts")]
pub alerts: Vec<Alert>, pub render_time: OffsetDateTime,
#[serde(rename = "alerts")]
pub list: Vec<Alert>,
} }

26
src/json/datetime.rs Normal file
View File

@ -0,0 +1,26 @@
use serde::{Deserialize, Deserializer};
use time::format_description::FormatItem;
use time::macros::format_description;
use time::{OffsetDateTime, PrimitiveDateTime};
const ALERT_FORMAT: &[FormatItem<'_>] =
format_description!("[weekday repr:short], [day].[month].[year], [hour]:[minute]");
const ALERTS_FORMAT: &[FormatItem<'_>] = format_description!(
"[day].[month].[year] [hour]:[minute]:[second].[subsecond] [offset_hour sign:mandatory][offset_minute]"
);
pub fn deserialize_alert<'de, D>(deserializer: D) -> Result<PrimitiveDateTime, D::Error>
where
D: Deserializer<'de>,
{
let s: &str = Deserialize::deserialize(deserializer)?;
PrimitiveDateTime::parse(s, ALERT_FORMAT).map_err(serde::de::Error::custom)
}
pub fn deserialize_alerts<'de, D>(deserializer: D) -> Result<OffsetDateTime, D::Error>
where
D: Deserializer<'de>,
{
let s: &str = Deserialize::deserialize(deserializer)?;
OffsetDateTime::parse(s, ALERTS_FORMAT).map_err(serde::de::Error::custom)
}

42
src/lib.rs Normal file
View File

@ -0,0 +1,42 @@
use std::time::Duration;
use config::Config;
use tokio::time;
use tracing::debug;
use crate::json::alert::Alert;
pub mod alert_swiss;
pub mod cli;
pub mod config;
mod json {
pub mod alert;
pub mod alerts;
pub mod area;
pub mod circle;
pub mod contact;
pub mod coordinate;
pub mod datetime;
pub mod description;
pub mod image;
pub mod image_description;
pub mod image_title;
pub mod link;
pub mod polygon;
pub mod region;
pub mod text;
pub mod title;
}
pub mod logging;
pub async fn run(config: Config) {
let mut interval = time::interval(Duration::from_secs(config.interval));
loop {
interval.tick().await;
let alerts = alert_swiss::fetch_alerts().await.unwrap();
let new_alerts: Vec<Alert> = alerts.list.into_iter().filter(|x| true).collect();
debug!("{new_alerts:#?}");
}
}

View File

@ -1,7 +1,7 @@
use tracing::debug; use tracing::debug;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
pub(crate) fn setup(bin_name: &str) { pub fn setup(bin_name: &str) {
let default_config = format!("{}=info,tower_http=info", bin_name); let default_config = format!("{}=info,tower_http=info", bin_name);
tracing_subscriber::registry() tracing_subscriber::registry()
.with( .with(

View File

@ -1,42 +1,16 @@
use std::time::Duration; use alert_me::{cli::Cli, logging};
use clap::Parser;
use tokio::time;
use tracing::debug;
use crate::json::alerts::Alerts;
mod json {
pub mod alert;
pub mod alerts;
pub mod area;
pub mod circle;
pub mod contact;
pub mod coordinate;
pub mod description;
pub mod image;
pub mod image_description;
pub mod image_title;
pub mod link;
pub mod polygon;
pub mod region;
pub mod text;
pub mod title;
}
mod logging;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
if std::env::var_os("RUST_LOG").is_none() {
std::env::set_var("RUST_LOG", "info");
}
logging::setup("alert-me"); logging::setup("alert-me");
let mut interval = time::interval(Duration::from_secs(5)); let args = Cli::parse();
let config = args.into();
loop { alert_me::run(config).await
interval.tick().await;
let resp = reqwest::get("https://www.alert.swiss/content/alertswiss-internet/en/home/_jcr_content/polyalert.alertswiss_alerts.actual.json")
.await
.unwrap()
.json::<Alerts>()
.await.unwrap();
debug!("{resp:#?}");
}
} }