add tracing + custom error type
All checks were successful
CI/CD Pipeline / test (push) Successful in 2m18s
CI/CD Pipeline / deploy (push) Successful in 2m38s

This commit is contained in:
Philipp Hofer
2025-10-16 11:06:51 +02:00
parent 14ee4d2767
commit 4702017914
4 changed files with 44 additions and 11 deletions

13
Cargo.lock generated
View File

@@ -726,6 +726,7 @@ dependencies = [
"serde_json", "serde_json",
"thiserror", "thiserror",
"tokio", "tokio",
"tracing",
] ]
[[package]] [[package]]
@@ -1291,9 +1292,21 @@ checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [ dependencies = [
"log", "log",
"pin-project-lite", "pin-project-lite",
"tracing-attributes",
"tracing-core", "tracing-core",
] ]
[[package]]
name = "tracing-attributes"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "tracing-core" name = "tracing-core"
version = "0.1.34" version = "0.1.34"

View File

@@ -11,3 +11,4 @@ serde_json = "1"
chrono = "0.4" chrono = "0.4"
quick-xml = "0.38" quick-xml = "0.38"
thiserror = "2" thiserror = "2"
tracing = "0.1"

View File

@@ -6,6 +6,7 @@ use serde_json::Value;
use std::sync::Arc; use std::sync::Arc;
use thiserror::Error; use thiserror::Error;
use tokio::{net::TcpListener, sync::RwLock}; use tokio::{net::TcpListener, sync::RwLock};
use tracing::warn;
pub async fn start( pub async fn start(
title: String, title: String,
@@ -26,10 +27,15 @@ pub async fn start(
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]
enum FetchError {} enum FetchError {
#[error("error fetching url")]
Fetching(reqwest::Error),
#[error("error parsing json")]
JsonParsing(reqwest::Error),
}
trait Feed { trait Feed {
async fn fetch(&mut self) -> Result<(), Box<dyn std::error::Error>>; async fn fetch(&mut self) -> Result<(), FetchError>;
} }
#[cfg(test)] #[cfg(test)]
@@ -69,7 +75,7 @@ impl rss::ToRss for TestFeed {
#[cfg(test)] #[cfg(test)]
impl Feed for TestFeed { impl Feed for TestFeed {
async fn fetch(&mut self) -> Result<(), Box<dyn std::error::Error>> { async fn fetch(&mut self) -> Result<(), FetchError> {
self.amount_fetch_calls += 1; self.amount_fetch_calls += 1;
Ok(()) Ok(())
} }
@@ -84,7 +90,7 @@ struct LiveFeed {
} }
impl Feed for LiveFeed { impl Feed for LiveFeed {
async fn fetch(&mut self) -> Result<(), Box<dyn std::error::Error>> { async fn fetch(&mut self) -> Result<(), FetchError> {
let broadcasts = self.get_all_broadcasts().await?; let broadcasts = self.get_all_broadcasts().await?;
for broadcast in broadcasts { for broadcast in broadcasts {
@@ -132,12 +138,17 @@ impl LiveFeed {
self.episodes.iter().any(|e| e.url == url) self.episodes.iter().any(|e| e.url == url)
} }
async fn get_all_broadcasts(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> { async fn get_all_broadcasts(&self) -> Result<Vec<String>, FetchError> {
// List of broadcasts: https://audioapi.orf.at/oe1/api/json/current/broadcasts // List of broadcasts: https://audioapi.orf.at/oe1/api/json/current/broadcasts
// //
// ^ contains link, e.g. https://audioapi.orf.at/oe1/api/json/4.0/broadcast/797577/20250611 // ^ contains link, e.g. https://audioapi.orf.at/oe1/api/json/4.0/broadcast/797577/20250611
let url = "https://audioapi.orf.at/oe1/api/json/current/broadcasts"; let url = "https://audioapi.orf.at/oe1/api/json/current/broadcasts";
let data: Value = reqwest::get(url).await?.json().await?; let data: Value = reqwest::get(url)
.await
.map_err(FetchError::Fetching)?
.json()
.await
.map_err(FetchError::JsonParsing)?;
let mut ret: Vec<String> = Vec::new(); let mut ret: Vec<String> = Vec::new();
@@ -145,13 +156,19 @@ impl LiveFeed {
for day in days { for day in days {
if let Some(broadcasts) = day["broadcasts"].as_array() { if let Some(broadcasts) = day["broadcasts"].as_array() {
for broadcast in broadcasts { for broadcast in broadcasts {
let Some(title) = broadcast["title"].as_str() else {
warn!("Broadcast has no 'title' attribute, skipping broadcast");
continue;
};
let Some(href) = broadcast["href"].as_str() else {
warn!("Broadcast has no 'href' attribute, skipping broadcast");
continue;
};
if self.filter_titles.is_empty() if self.filter_titles.is_empty()
|| self || self.filter_titles.contains(&title.into())
.filter_titles
.contains(&broadcast["title"].as_str().unwrap().into())
{ {
{ {
ret.push(broadcast["href"].as_str().unwrap().into()); ret.push(href.into());
} }
} }
} }

View File

@@ -4,7 +4,9 @@ use reqwest::header;
use std::sync::Arc; use std::sync::Arc;
use tokio::{net::TcpListener, sync::RwLock}; use tokio::{net::TcpListener, sync::RwLock};
async fn stream_handler<T: Feed + ToRss>(State(state): State<Arc<RwLock<T>>>) -> impl IntoResponse { async fn stream_handler<T: Feed + ToRss + Send>(
State(state): State<Arc<RwLock<T>>>,
) -> impl IntoResponse {
state.write().await.fetch().await.unwrap(); state.write().await.fetch().await.unwrap();
let content = state.read().await.to_rss(); let content = state.read().await.to_rss();