Add proper error handling to web UI. Organize API client in web UI.

This commit is contained in:
projectmoon 2021-06-06 22:32:48 +00:00
parent 57ab6a51da
commit 60cf7d406a
7 changed files with 74 additions and 22 deletions

1
Cargo.lock generated
View File

@ -3481,6 +3481,7 @@ dependencies = [
"js-sys", "js-sys",
"serde", "serde",
"tenebrous-api", "tenebrous-api",
"thiserror",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",

View File

@ -25,6 +25,7 @@ web-sys = "0.3"
graphql_client = { git = "https://github.com/graphql-rust/graphql-client", branch = "master" } 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" } graphql_client_web = { git = "https://github.com/graphql-rust/graphql-client", branch = "master" }
serde = { version = "1.0.67", features = ["derive"] } serde = { version = "1.0.67", features = ["derive"] }
thiserror = "1.0"
# hopefully we can add grpc-web later instead of graphql. # hopefully we can add grpc-web later instead of graphql.
# prost = { version = "0.7.0", default-features = false } # prost = { version = "0.7.0", default-features = false }

View File

@ -1,6 +1,9 @@
use graphql_client::web::Client; use graphql_client::web::Client;
use graphql_client::web::ClientError;
use graphql_client::GraphQLQuery; use graphql_client::GraphQLQuery;
use graphql_client_web::Response;
use super::ResponseExt;
use crate::error::UiError;
#[derive(GraphQLQuery)] #[derive(GraphQLQuery)]
#[graphql( #[graphql(
@ -22,7 +25,7 @@ pub async fn get_user_variable(
room_id: &str, room_id: &str,
user_id: &str, user_id: &str,
variable_name: &str, variable_name: &str,
) -> Result<i64, ClientError> { ) -> Result<i64, UiError> {
let client = Client::new("http://localhost:10000/graphql"); let client = Client::new("http://localhost:10000/graphql");
let variables = get_user_variable::Variables { let variables = get_user_variable::Variables {
room_id: room_id.to_owned(), room_id: room_id.to_owned(),
@ -30,27 +33,20 @@ pub async fn get_user_variable(
variable: variable_name.to_owned(), variable: variable_name.to_owned(),
}; };
//TODO don't unwrap() option. map to err instead.
let response = client.call(GetUserVariable, variables).await?; let response = client.call(GetUserVariable, variables).await?;
let response: graphql_client_web::Response<get_user_variable::ResponseData> = response; let response: graphql_client_web::Response<get_user_variable::ResponseData> = response;
let value = response.data.map(|d| d.variable.value).unwrap(); Ok(response.data()?.variable.value)
Ok(value)
} }
pub async fn rooms_for_user( pub async fn rooms_for_user(
user_id: &str, user_id: &str,
) -> Result<Vec<rooms_for_user::RoomsForUserUserRoomsRooms>, ClientError> { ) -> Result<Vec<rooms_for_user::RoomsForUserUserRoomsRooms>, UiError> {
let client = Client::new("http://localhost:10000/graphql"); let client = Client::new("http://localhost:10000/graphql");
let variables = rooms_for_user::Variables { let variables = rooms_for_user::Variables {
user_id: user_id.to_owned(), user_id: user_id.to_owned(),
}; };
//TODO don't unwrap() option. map to err instead.
let response = client.call(RoomsForUser, variables).await?; let response = client.call(RoomsForUser, variables).await?;
let response: graphql_client_web::Response<rooms_for_user::ResponseData> = response; let response: Response<rooms_for_user::ResponseData> = response;
let rooms = response Ok(response.data()?.user_rooms.rooms)
.data
.map(|d| d.user_rooms.rooms)
.unwrap_or_default();
Ok(rooms)
} }

View File

@ -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<T> {
/// Get the data from the response, or gather all server-side
/// errors into a UiError variant.
fn data(self) -> Result<T, UiError>;
}
impl<T> ResponseExt<T> for Response<T> {
fn data(self) -> Result<T, UiError> {
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::<Vec<_>>()
.join(",")
})
.unwrap_or("unknown error".into()),
)
})?;
Ok(data)
}
}
pub mod dicebot;

12
web-ui/crate/src/error.rs Normal file
View File

@ -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),
}

View File

@ -3,7 +3,8 @@ use wasm_bindgen::prelude::*;
use yew::prelude::*; use yew::prelude::*;
use yew_router::{components::RouterAnchor, prelude::*}; use yew_router::{components::RouterAnchor, prelude::*};
pub mod graphql; pub mod api;
pub mod error;
pub mod grpc; pub mod grpc;
pub mod rooms; pub mod rooms;

View File

@ -1,7 +1,7 @@
use crate::api;
use crate::error::UiError;
use std::sync::Arc; use std::sync::Arc;
use wasm_bindgen::prelude::*; use wasm_bindgen_futures::spawn_local;
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::{future_to_promise, spawn_local};
use web_sys::console; use web_sys::console;
use yew::prelude::*; use yew::prelude::*;
use yewdux::prelude::*; use yewdux::prelude::*;
@ -59,10 +59,8 @@ fn view_room(room: &Room) -> Html {
} }
} }
async fn do_things(dispatch: &RoomListDispatch) { async fn load_rooms(dispatch: &RoomListDispatch) -> Result<(), UiError> {
let rooms = crate::graphql::rooms_for_user("@projectmoon:agnos.is") let rooms = api::dicebot::rooms_for_user("@projectmoon:agnos.is").await?;
.await
.unwrap();
for room in rooms { for room in rooms {
dispatch.send(Action::AddRoom(Room { dispatch.send(Action::AddRoom(Room {
@ -70,6 +68,8 @@ async fn do_things(dispatch: &RoomListDispatch) {
display_name: room.display_name, display_name: room.display_name,
})); }));
} }
Ok(())
} }
impl Component for YewduxRoomList { impl Component for YewduxRoomList {
@ -95,7 +95,12 @@ impl Component for YewduxRoomList {
let dispatch = dispatch.clone(); let dispatch = dispatch.clone();
spawn_local(async move { 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()),
_ => (),
}
}); });
}); });