From 60cf7d406a572552bc26abf62c2aaf7dcac47ca9 Mon Sep 17 00:00:00 2001 From: projectmoon Date: Sun, 6 Jun 2021 22:32:48 +0000 Subject: [PATCH] Add proper error handling to web UI. Organize API client in web UI. --- Cargo.lock | 1 + web-ui/crate/Cargo.toml | 1 + .../crate/src/{graphql.rs => api/dicebot.rs} | 22 +++++------- web-ui/crate/src/api/mod.rs | 36 +++++++++++++++++++ web-ui/crate/src/error.rs | 12 +++++++ web-ui/crate/src/lib.rs | 3 +- web-ui/crate/src/rooms.rs | 21 ++++++----- 7 files changed, 74 insertions(+), 22 deletions(-) rename web-ui/crate/src/{graphql.rs => api/dicebot.rs} (69%) create mode 100644 web-ui/crate/src/api/mod.rs create mode 100644 web-ui/crate/src/error.rs diff --git a/Cargo.lock b/Cargo.lock index 8fdd2fa..875e4b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3481,6 +3481,7 @@ dependencies = [ "js-sys", "serde", "tenebrous-api", + "thiserror", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", diff --git a/web-ui/crate/Cargo.toml b/web-ui/crate/Cargo.toml index 54af717..3d8a413 100644 --- a/web-ui/crate/Cargo.toml +++ b/web-ui/crate/Cargo.toml @@ -25,6 +25,7 @@ web-sys = "0.3" graphql_client = { git = "https://github.com/graphql-rust/graphql-client", branch = "master" } graphql_client_web = { git = "https://github.com/graphql-rust/graphql-client", branch = "master" } serde = { version = "1.0.67", features = ["derive"] } +thiserror = "1.0" # hopefully we can add grpc-web later instead of graphql. # prost = { version = "0.7.0", default-features = false } diff --git a/web-ui/crate/src/graphql.rs b/web-ui/crate/src/api/dicebot.rs similarity index 69% rename from web-ui/crate/src/graphql.rs rename to web-ui/crate/src/api/dicebot.rs index 22dce0c..5e8359a 100644 --- a/web-ui/crate/src/graphql.rs +++ b/web-ui/crate/src/api/dicebot.rs @@ -1,6 +1,9 @@ use graphql_client::web::Client; -use graphql_client::web::ClientError; use graphql_client::GraphQLQuery; +use graphql_client_web::Response; + +use super::ResponseExt; +use crate::error::UiError; #[derive(GraphQLQuery)] #[graphql( @@ -22,7 +25,7 @@ pub async fn get_user_variable( room_id: &str, user_id: &str, variable_name: &str, -) -> Result { +) -> Result { let client = Client::new("http://localhost:10000/graphql"); let variables = get_user_variable::Variables { room_id: room_id.to_owned(), @@ -30,27 +33,20 @@ pub async fn get_user_variable( variable: variable_name.to_owned(), }; - //TODO don't unwrap() option. map to err instead. let response = client.call(GetUserVariable, variables).await?; let response: graphql_client_web::Response = response; - let value = response.data.map(|d| d.variable.value).unwrap(); - Ok(value) + Ok(response.data()?.variable.value) } pub async fn rooms_for_user( user_id: &str, -) -> Result, ClientError> { +) -> Result, UiError> { let client = Client::new("http://localhost:10000/graphql"); let variables = rooms_for_user::Variables { user_id: user_id.to_owned(), }; - //TODO don't unwrap() option. map to err instead. let response = client.call(RoomsForUser, variables).await?; - let response: graphql_client_web::Response = response; - let rooms = response - .data - .map(|d| d.user_rooms.rooms) - .unwrap_or_default(); - Ok(rooms) + let response: Response = response; + Ok(response.data()?.user_rooms.rooms) } diff --git a/web-ui/crate/src/api/mod.rs b/web-ui/crate/src/api/mod.rs new file mode 100644 index 0000000..a73ca1a --- /dev/null +++ b/web-ui/crate/src/api/mod.rs @@ -0,0 +1,36 @@ +use graphql_client_web::Response; + +use crate::error::UiError; + +/// Extensions to the GraphQL web response type to add convenience, +/// particularly when working with errors. +trait ResponseExt { + /// Get the data from the response, or gather all server-side + /// errors into a UiError variant. + fn data(self) -> Result; +} + +impl ResponseExt for Response { + fn data(self) -> Result { + let data = self.data; + let errors = self.errors; + + let data = data.ok_or_else(|| { + UiError::ApiError( + errors + .map(|errors| { + errors + .into_iter() + .map(|e| e.to_string()) + .collect::>() + .join(",") + }) + .unwrap_or("unknown error".into()), + ) + })?; + + Ok(data) + } +} + +pub mod dicebot; diff --git a/web-ui/crate/src/error.rs b/web-ui/crate/src/error.rs new file mode 100644 index 0000000..1d52213 --- /dev/null +++ b/web-ui/crate/src/error.rs @@ -0,0 +1,12 @@ +use graphql_client_web::ClientError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum UiError { + #[error("api client error: {0}")] + ApiClientError(#[from] ClientError), + + /// General API error, collecting errors from graphql server. + #[error("error: {0}")] + ApiError(String), +} diff --git a/web-ui/crate/src/lib.rs b/web-ui/crate/src/lib.rs index 317cd8c..e3dee5a 100644 --- a/web-ui/crate/src/lib.rs +++ b/web-ui/crate/src/lib.rs @@ -3,7 +3,8 @@ use wasm_bindgen::prelude::*; use yew::prelude::*; use yew_router::{components::RouterAnchor, prelude::*}; -pub mod graphql; +pub mod api; +pub mod error; pub mod grpc; pub mod rooms; diff --git a/web-ui/crate/src/rooms.rs b/web-ui/crate/src/rooms.rs index 2ec2716..37652d9 100644 --- a/web-ui/crate/src/rooms.rs +++ b/web-ui/crate/src/rooms.rs @@ -1,7 +1,7 @@ +use crate::api; +use crate::error::UiError; use std::sync::Arc; -use wasm_bindgen::prelude::*; -use wasm_bindgen::JsValue; -use wasm_bindgen_futures::{future_to_promise, spawn_local}; +use wasm_bindgen_futures::spawn_local; use web_sys::console; use yew::prelude::*; use yewdux::prelude::*; @@ -59,10 +59,8 @@ fn view_room(room: &Room) -> Html { } } -async fn do_things(dispatch: &RoomListDispatch) { - let rooms = crate::graphql::rooms_for_user("@projectmoon:agnos.is") - .await - .unwrap(); +async fn load_rooms(dispatch: &RoomListDispatch) -> Result<(), UiError> { + let rooms = api::dicebot::rooms_for_user("@projectmoon:agnos.is").await?; for room in rooms { dispatch.send(Action::AddRoom(Room { @@ -70,6 +68,8 @@ async fn do_things(dispatch: &RoomListDispatch) { display_name: room.display_name, })); } + + Ok(()) } impl Component for YewduxRoomList { @@ -95,7 +95,12 @@ impl Component for YewduxRoomList { let dispatch = dispatch.clone(); spawn_local(async move { - do_things(&*dispatch).await; + //TODO make macro to report errors in some common way: + //handle_errors!(do_things(&*dispatch).await) + match load_rooms(&*dispatch).await { + Err(e) => console::log_1(&format!("Error: {:?}", e).into()), + _ => (), + } }); });