Просмотр исходного кода

dnetview: custom error handling

we migrate custom errors to DnetViewError and DnetViewResult.

there is one outstanding problem:

terminal.draw(|f| match view.render(f) {
    Ok(()) => {}
    Err(e) => {
        debug!("{}", e);
    }
})?;

errors that may occur inside of View are currently being propagated into
this anonymous closure, where they cannot be properly handled.
lunar-mining 4 лет назад
Родитель
Сommit
0ba3dece3a
6 измененных файлов с 127 добавлено и 47 удалено
  1. 1 0
      Cargo.lock
  2. 1 0
      bin/dnetview/Cargo.toml
  3. 77 0
      bin/dnetview/src/error.rs
  4. 1 0
      bin/dnetview/src/lib.rs
  5. 24 17
      bin/dnetview/src/main.rs
  6. 23 30
      bin/dnetview/src/view.rs

+ 1 - 0
Cargo.lock

@@ -1477,6 +1477,7 @@ dependencies = [
  "simplelog",
  "smol",
  "termion",
+ "thiserror",
  "tui",
  "url",
 ]

+ 1 - 0
bin/dnetview/Cargo.toml

@@ -26,6 +26,7 @@ log = "0.4.16"
 num_cpus = "1.13.1"
 url = "2.2.2"
 fxhash = "0.2.1"
+thiserror = "1.0.30"
 
 # Encoding and parsing
 serde_json = "1.0.79"

+ 77 - 0
bin/dnetview/src/error.rs

@@ -0,0 +1,77 @@
+//use serde_json::Value;
+//use darkfi::rpc::jsonrpc::{error as jsonerr, response as jsonresp, ErrorCode, JsonResult};
+
+#[derive(Debug, thiserror::Error)]
+pub enum DnetViewError {
+    #[error("RPC reply is empty")]
+    EmptyRpcReply,
+    #[error("Json Value is not an object")]
+    ValueIsNotObject,
+    #[error("Failed to find ID at current index")]
+    NoIdAtIndex,
+    #[error("Found unexpected data in View")]
+    UnexpectedData,
+    #[error("Message log does not contain ID")]
+    CannotFindId,
+    #[error("ID does not return a selectable object")]
+    NotSelectableObject,
+    #[error("InternalError")]
+    Darkfi(#[from] darkfi::error::Error),
+    #[error("Json serialization error: `{0}`")]
+    SerdeJsonError(String),
+    #[error("IO error: {0}")]
+    Io(std::io::ErrorKind),
+    #[error("SetLogger (log crate) failed: {0}")]
+    SetLoggerError(String),
+    #[error("URL parse error: {0}")]
+    UrlParse(String),
+}
+
+pub type DnetViewResult<T> = std::result::Result<T, DnetViewError>;
+
+impl From<serde_json::Error> for DnetViewError {
+    fn from(err: serde_json::Error) -> DnetViewError {
+        DnetViewError::SerdeJsonError(err.to_string())
+    }
+}
+
+impl From<std::io::Error> for DnetViewError {
+    fn from(err: std::io::Error) -> Self {
+        Self::Io(err.kind())
+    }
+}
+
+impl From<log::SetLoggerError> for DnetViewError {
+    fn from(err: log::SetLoggerError) -> Self {
+        Self::SetLoggerError(err.to_string())
+    }
+}
+
+impl From<url::ParseError> for DnetViewError {
+    fn from(err: url::ParseError) -> Self {
+        Self::UrlParse(err.to_string())
+    }
+}
+//pub fn to_json_result(res: DnetViewResult<Value>, id: Value) -> JsonResult {
+//    match res {
+//        Ok(v) => JsonResult::Resp(jsonresp(v, id)),
+//        Err(err) => match err {
+//            DnetViewError::InvalidId => JsonResult::Err(jsonerr(
+//                ErrorCode::InvalidParams,
+//                Some("invalid task's id".into()),
+//                id,
+//            )),
+//            DnetViewError::InvalidData(e) | DnetViewError::SerdeJsonError(e) => {
+//                JsonResult::Err(jsonerr(ErrorCode::InvalidParams, Some(e), id))
+//            }
+//            DnetViewError::InvalidDueTime => JsonResult::Err(jsonerr(
+//                ErrorCode::InvalidParams,
+//                Some("invalid due time".into()),
+//                id,
+//            )),
+//            DnetViewError::Darkfi(e) => {
+//                JsonResult::Err(jsonerr(ErrorCode::InternalError, Some(e.to_string()), id))
+//            }
+//        },
+//    }
+//}

+ 1 - 0
bin/dnetview/src/lib.rs

@@ -1,4 +1,5 @@
 pub mod config;
+pub mod error;
 pub mod model;
 pub mod options;
 pub mod util;

+ 24 - 17
bin/dnetview/src/main.rs

@@ -27,6 +27,7 @@ use darkfi::{
 
 use dnetview::{
     config::{DnvConfig, CONFIG_FILE_CONTENTS},
+    error::{DnetViewError, DnetViewResult},
     model::{ConnectInfo, Model, NodeInfo, SelectableObject, Session, SessionInfo},
     options::ProgramOptions,
     util::{is_empty_session, make_connect_id, make_empty_id, make_node_id, make_session_id},
@@ -83,7 +84,7 @@ impl DnetView {
 }
 
 #[async_std::main]
-async fn main() -> Result<()> {
+async fn main() -> DnetViewResult<()> {
     let options = ProgramOptions::load()?;
 
     let verbosity_level = options.app.occurrences_of("verbose");
@@ -125,7 +126,7 @@ async fn main() -> Result<()> {
                 poll_and_update_model(&config, ex2.clone(), model.clone()).await?;
                 render_view(&mut terminal, model.clone()).await?;
                 drop(signal);
-                Ok::<(), darkfi::Error>(())
+                Ok(())
             })
         });
 
@@ -138,7 +139,7 @@ async fn poll_and_update_model(
     config: &DnvConfig,
     ex: Arc<Executor<'_>>,
     model: Arc<Model>,
-) -> Result<()> {
+) -> DnetViewResult<()> {
     for node in &config.nodes {
         let client = DnetView::new(Url::parse(&node.rpc_url)?, node.name.clone());
         ex.spawn(poll(client, model.clone())).detach();
@@ -146,15 +147,14 @@ async fn poll_and_update_model(
     Ok(())
 }
 
-async fn poll(client: DnetView, model: Arc<Model>) -> Result<()> {
+async fn poll(client: DnetView, model: Arc<Model>) -> DnetViewResult<()> {
     loop {
         let reply = client.get_info().await?;
 
         if reply.as_object().is_some() && !reply.as_object().unwrap().is_empty() {
             parse_data(reply.as_object().unwrap(), &client, model.clone()).await?;
         } else {
-            // TODO: error handling
-            //debug!("Reply is empty");
+            return Err(DnetViewError::EmptyRpcReply)
         }
         async_util::sleep(2).await;
     }
@@ -164,7 +164,7 @@ async fn parse_data(
     reply: &serde_json::Map<String, Value>,
     client: &DnetView,
     model: Arc<Model>,
-) -> Result<()> {
+) -> DnetViewResult<()> {
     let _ext_addr = reply.get("external_addr");
     let inbound = &reply["session_inbound"];
     let manual = &reply["session_manual"];
@@ -195,7 +195,7 @@ async fn parse_data(
     Ok(())
 }
 
-async fn update_msgs(model: Arc<Model>, sessions: Vec<SessionInfo>) -> Result<()> {
+async fn update_msgs(model: Arc<Model>, sessions: Vec<SessionInfo>) -> DnetViewResult<()> {
     for session in sessions {
         for connection in session.children {
             if !model.msg_log.lock().await.contains_key(&connection.id) {
@@ -230,7 +230,7 @@ async fn update_selectable_and_ids(
     model: Arc<Model>,
     sessions: Vec<SessionInfo>,
     node: NodeInfo,
-) -> Result<()> {
+) -> DnetViewResult<()> {
     let node_obj = SelectableObject::Node(node.clone());
     model.selectables.lock().await.insert(node.id.clone(), node_obj);
     update_ids(model.clone(), node.id.clone()).await;
@@ -247,7 +247,7 @@ async fn update_selectable_and_ids(
     Ok(())
 }
 
-async fn parse_inbound(inbound: &Value, node_id: &String) -> Result<SessionInfo> {
+async fn parse_inbound(inbound: &Value, node_id: &String) -> DnetViewResult<SessionInfo> {
     let name = "Inbound".to_string();
     let session_type = Session::Inbound;
     let parent = node_id.to_string();
@@ -321,12 +321,12 @@ async fn parse_inbound(inbound: &Value, node_id: &String) -> Result<SessionInfo>
             let session_info = SessionInfo::new(id, name, is_empty, parent, connects);
             Ok(session_info)
         }
-        None => Err(Error::ValueIsNotObject),
+        None => Err(DnetViewError::ValueIsNotObject),
     }
 }
 
 // TODO: placeholder for now
-async fn parse_manual(_manual: &Value, node_id: &String) -> Result<SessionInfo> {
+async fn parse_manual(_manual: &Value, node_id: &String) -> DnetViewResult<SessionInfo> {
     let name = "Manual".to_string();
     let session_type = Session::Manual;
     let mut connects: Vec<ConnectInfo> = Vec::new();
@@ -351,7 +351,7 @@ async fn parse_manual(_manual: &Value, node_id: &String) -> Result<SessionInfo>
     Ok(session_info)
 }
 
-async fn parse_outbound(outbound: &Value, node_id: &String) -> Result<SessionInfo> {
+async fn parse_outbound(outbound: &Value, node_id: &String) -> DnetViewResult<SessionInfo> {
     let name = "Outbound".to_string();
     let session_type = Session::Outbound;
     let parent = node_id.to_string();
@@ -427,11 +427,14 @@ async fn parse_outbound(outbound: &Value, node_id: &String) -> Result<SessionInf
             let session_info = SessionInfo::new(id, name, is_empty, parent, connects);
             Ok(session_info)
         }
-        None => Err(Error::ValueIsNotObject),
+        None => Err(DnetViewError::ValueIsNotObject),
     }
 }
 
-async fn render_view<B: Backend>(terminal: &mut Terminal<B>, model: Arc<Model>) -> Result<()> {
+async fn render_view<B: Backend>(
+    terminal: &mut Terminal<B>,
+    model: Arc<Model>,
+) -> DnetViewResult<()> {
     let mut asi = async_stdin();
 
     terminal.clear()?;
@@ -451,8 +454,12 @@ async fn render_view<B: Backend>(terminal: &mut Terminal<B>, model: Arc<Model>)
             model.selectables.lock().await.clone(),
         );
 
-        terminal.draw(|f| {
-            view.render(f);
+        // TODO: we can't return errors from inside an anonymous closure :(
+        terminal.draw(|f| match view.render(f) {
+            Ok(()) => {}
+            Err(e) => {
+                debug!("{}", e);
+            }
         })?;
         for k in asi.by_ref().keys() {
             match k.unwrap() {

+ 23 - 30
bin/dnetview/src/view.rs

@@ -11,7 +11,10 @@ use tui::{
     Frame,
 };
 
-use crate::model::{NodeInfo, SelectableObject};
+use crate::{
+    error::{DnetViewError, DnetViewResult},
+    model::{NodeInfo, SelectableObject},
+};
 //use log::debug;
 
 #[derive(Debug)]
@@ -76,7 +79,7 @@ impl View {
         }
     }
 
-    pub fn render<B: Backend>(&mut self, f: &mut Frame<'_, B>) {
+    pub fn render<B: Backend>(&mut self, f: &mut Frame<'_, B>) -> DnetViewResult<()> {
         let margin = 2;
         let direction = Direction::Horizontal;
         let cnstrnts = vec![Constraint::Percentage(50), Constraint::Percentage(50)];
@@ -87,26 +90,22 @@ impl View {
             .constraints(cnstrnts)
             .split(f.size());
 
-        let mut id_list = self.render_id_list(f, slice.clone());
+        let mut id_list = self.render_id_list(f, slice.clone())?;
 
         // remove any duplicates
         id_list.dedup();
 
         // get the id at the current index
         match self.active_ids.state.selected() {
-            Some(i) => {
-                match id_list.get(i) {
-                    Some(i) => {
-                        self.render_info(f, slice.clone(), i.to_string());
-                    }
-                    None => {
-                        // TODO: Error
-                    }
+            Some(i) => match id_list.get(i) {
+                Some(i) => {
+                    self.render_info(f, slice.clone(), i.to_string())?;
+                    Ok(())
                 }
-            }
-            None => {
-                // TODO: nothing is selected
-            }
+                None => Err(DnetViewError::NoIdAtIndex),
+            },
+            // nothing is selected right now
+            None => Ok(()),
         }
     }
 
@@ -114,7 +113,7 @@ impl View {
         &mut self,
         f: &mut Frame<'_, B>,
         slice: Vec<Rect>,
-    ) -> Vec<String> {
+    ) -> DnetViewResult<Vec<String>> {
         let style = Style::default();
         let mut nodes = Vec::new();
         let mut ids: Vec<String> = Vec::new();
@@ -151,9 +150,7 @@ impl View {
                                 );
                                 info.push(msg);
                             }
-                            _ => {
-                                // TODO
-                            }
+                            _ => return Err(DnetViewError::UnexpectedData),
                         }
 
                         let lines = vec![Spans::from(info)];
@@ -170,7 +167,7 @@ impl View {
 
         f.render_stateful_widget(nodes, slice[0], &mut self.active_ids.state);
 
-        return ids
+        Ok(ids)
     }
 
     fn render_info<B: Backend>(
@@ -178,7 +175,7 @@ impl View {
         f: &mut Frame<'_, B>,
         slice: Vec<Rect>,
         selected: String,
-    ) {
+    ) -> DnetViewResult<()> {
         let style = Style::default();
         let mut spans = Vec::new();
 
@@ -209,20 +206,14 @@ impl View {
                                         Spans::from(Span::styled(format!("R: {}", v), style));
                                     spans.push(msg_log);
                                 }
-                                _ => {
-                                    // TODO
-                                }
+                                _ => return Err(DnetViewError::UnexpectedData),
                             }
                         }
                     }
-                    None => {
-                        // TODO
-                    }
+                    None => return Err(DnetViewError::CannotFindId),
                 }
             }
-            None => {
-                // TODO
-            }
+            None => return Err(DnetViewError::NotSelectableObject),
         }
 
         let graph = Paragraph::new(spans)
@@ -230,6 +221,8 @@ impl View {
             .style(Style::default());
 
         f.render_widget(graph, slice[1]);
+
+        Ok(())
     }
 }