From 3808d09976d39180cb5570fb76eadff9274e77e2 Mon Sep 17 00:00:00 2001 From: alemi Date: Mon, 15 Apr 2024 03:03:01 +0200 Subject: [PATCH] feat: error boundary for fetching --- web/src/lib.rs | 150 +++++++++++++++++++++++++++---------------------- 1 file changed, 82 insertions(+), 68 deletions(-) diff --git a/web/src/lib.rs b/web/src/lib.rs index 12a2c93..4e3d3e9 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -180,6 +180,10 @@ pub fn Activity(activity: serde_json::Value) -> impl IntoView { } } +#[derive(Debug, thiserror::Error)] +#[error("{0}")] +struct OmgReqwestErrorIsNotClonable(String); + #[component] pub fn Timeline( token: Signal>, @@ -187,79 +191,89 @@ pub fn Timeline( let (timeline, set_timeline) = create_signal(format!("{BASE_URL}/inbox/page")); let users : Arc> = Arc::new(DashMap::new()); let _users = users.clone(); // TODO i think there is syntactic sugar i forgot? - let items = create_resource(move || timeline.get(), move |feed_url| { + let items = create_local_resource(move || timeline.get(), move |feed_url| { let __users = _users.clone(); // TODO lmao this is meme tier - async move { - let mut req = reqwest::Client::new().get(feed_url); - - if let Some(token) = token.get() { - req = req.header("Authorization", format!("Bearer {token}")); - } - - let activities : Vec = req - .send() - .await.unwrap() - .json::() - .await.unwrap() - .ordered_items() - .collect(); - - // i could make this fancier with iterators and futures::join_all but they would run - // concurrently and make a ton of parallel request, we actually want these sequential because - // first one may fetch same user as second one - // some fancier logic may make a set of all actors and fetch uniques concurrently... - let mut out = Vec::new(); - for x in activities { - if let Some(uid) = x.actor().id() { - if let Some(actor) = __users.get(&uid) { - out.push(x.set_actor(apb::Node::object(actor.clone()))) - } else { - let mut req = reqwest::Client::new() - .get(format!("https://feditest.alemi.dev/users/+?id={uid}")); - - if let Some(token) = token.get() { - req = req.header("Authorization", format!("Bearer {token}")); - } - - let actor = req.send().await.unwrap().json::().await.unwrap(); - __users.insert(uid, actor.clone()); - - out.push(x.set_actor(apb::Node::object(actor))) - } - } else { - out.push(x) - } - } - - out - } + async move { fetch_activities_with_users(&feed_url, token, __users).await } }); view! {
- {move || match items.get() { - None => view! {

loading...

}.into_view(), - Some(data) => { - view! { - - - -
-
- } - } - /> - }.into_view() - }, - }} + {format!("{:?}", err.get())}

} > + {move || items.with(|x| match x { + None => Ok(view! {

loading...

}.into_view()), + Some(data) => match data { + Err(e) => Err(OmgReqwestErrorIsNotClonable(e.to_string())), + Ok(values) => Ok( + values + .iter() + .map(|object| { + let actor = object.actor().extract().unwrap_or_else(|| + serde_json::Value::String(object.actor().id().unwrap_or_default()) + ); + view! { +
+ + +
+
+ } + }) + .collect::>() + .into_view() + ), + } + })} +
} } + +async fn fetch_activities_with_users( + feed_url: &str, + token: Signal>, + users: Arc>, +) -> reqwest::Result> { + let mut req = reqwest::Client::new().get(feed_url); + + if let Some(token) = token.get() { + req = req.header("Authorization", format!("Bearer {token}")); + } + + let activities : Vec = req + .send() + .await? + .json::() + .await? + .ordered_items() + .collect(); + + // i could make this fancier with iterators and futures::join_all but they would run + // concurrently and make a ton of parallel request, we actually want these sequential because + // first one may fetch same user as second one + // some fancier logic may make a set of all actors and fetch uniques concurrently... + let mut out = Vec::new(); + for x in activities { + if let Some(uid) = x.actor().id() { + if let Some(actor) = users.get(&uid) { + out.push(x.set_actor(apb::Node::object(actor.clone()))) + } else { + let mut req = reqwest::Client::new() + .get(format!("https://feditest.alemi.dev/users/+?id={uid}")); + + if let Some(token) = token.get() { + req = req.header("Authorization", format!("Bearer {token}")); + } + + // TODO don't fail whole timeline fetch when one user fails fetching... + let actor = req.send().await?.json::().await?; + users.insert(uid, actor.clone()); + + out.push(x.set_actor(apb::Node::object(actor))) + } + } else { + out.push(x) + } + } + + Ok(out) +}