use core::time::Duration;
use embassy_time::{Duration as EmbassyDuration, Timer};
use serde::de::DeserializeOwned;
use std::{
string::{String, ToString},
sync::mpsc::{self, TryRecvError},
thread,
};
#[derive(Debug, Clone)]
pub enum NetError {
Http(String),
Decode(String),
WorkerLost,
}
impl core::fmt::Display for NetError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
NetError::Http(s) => write!(f, "http: {s}"),
NetError::Decode(s) => write!(f, "decode: {s}"),
NetError::WorkerLost => write!(f, "worker thread died"),
}
}
}
pub async fn spawn_blocking<R, F>(f: F) -> Result<R, NetError>
where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{
let (tx, rx) = mpsc::sync_channel::<R>(1);
thread::spawn(move || {
let _ = tx.send(f());
});
loop {
match rx.try_recv() {
Ok(result) => return Ok(result),
Err(TryRecvError::Empty) => {
Timer::after(EmbassyDuration::from_millis(50)).await;
}
Err(TryRecvError::Disconnected) => return Err(NetError::WorkerLost),
}
}
}
pub async fn http_get_json<T>(
url: impl Into<String>,
user_agent: &'static str,
) -> Result<T, NetError>
where
T: DeserializeOwned + Send + 'static,
{
let url = url.into();
spawn_blocking(move || {
let client = reqwest::blocking::Client::builder()
.user_agent(user_agent)
.timeout(Duration::from_secs(15))
.build()
.map_err(|e| NetError::Http(e.to_string()))?;
let resp = client
.get(&url)
.send()
.map_err(|e| NetError::Http(e.to_string()))?
.error_for_status()
.map_err(|e| NetError::Http(e.to_string()))?;
resp.json::<T>()
.map_err(|e| NetError::Decode(e.to_string()))
})
.await?
}