Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cli: Hint about how to start node
cloudhead committed 2 years ago
commit fd38d88e14027698497dc724b529d4a69f5abdca
parent 25ca4c8b92d49763049fdc9c4ad7a1f02f8b8f21
7 files changed +63 -56
modified radicle-cli/examples/rad-node.md
@@ -80,3 +80,21 @@ Running the command again gives us an error:
$ rad node stop
✗ Stopping node... error: node is not running
```
+

+
Some commands also give us a hint if the node isn't running:
+

+
``` (fail)
+
$ rad node events
+
✗ Error: failed to open node control socket "[..]/.radicle/node/control.sock" (entity not found)
+
✗ Hint: to start your node, run `rad node start`.
+
```
+
``` (fail)
+
$ rad sync status
+
✗ Error: to sync a repository, your node must be running. To start it, run `rad node start`
+
```
+
``` (fail)
+
$ rad node connect z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk@radicle.xyz:8776
+
✗ Connecting to z6Mkt67…v4N1tRk@radicle.xyz:8776... <canceled>
+
✗ Error: failed to open node control socket "[..]/.radicle/node/control.sock" (entity not found)
+
✗ Hint: to start your node, run `rad node start`.
+
```
modified radicle-cli/src/commands/init.rs
@@ -5,7 +5,7 @@ use std::convert::TryFrom;
use std::ffi::OsString;
use std::path::PathBuf;
use std::str::FromStr;
-
use std::{env, io, time};
+
use std::{env, time};

use anyhow::{anyhow, bail, Context as _};
use serde_json as json;
@@ -403,12 +403,12 @@ fn sync(
            Ok(_) => {
                // Some other irrelevant event received.
            }
-
            Err(e) if e.kind() == io::ErrorKind::TimedOut => {
+
            Err(radicle::node::Error::TimedOut) => {
                break;
            }
            Err(e) => {
                spinner.error(&e);
-
                return Err(e.into());
+
                return Err(e);
            }
        }
    }
modified radicle-cli/src/commands/node/control.rs
@@ -181,7 +181,7 @@ pub fn connect(
    ) {
        Ok(ConnectResult::Connected) => spinner.finish(),
        Ok(ConnectResult::Disconnected { reason }) => spinner.error(reason),
-
        Err(err) => spinner.error(err.to_string()),
+
        Err(err) => return Err(err.into()),
    }
    Ok(())
}
modified radicle-cli/src/terminal.rs
@@ -139,6 +139,12 @@ pub fn fail(_name: &str, error: &anyhow::Error) {
        io::error(line);
    }

+
    // Catch common node errors, and offer a hint.
+
    if let Some(e) = error.downcast_ref::<radicle::node::Error>() {
+
        if e.is_connection_err() {
+
            io::hint("to start your node, run `rad node start`.");
+
        }
+
    }
    if let Some(Error::WithHint { hint, .. }) = error.downcast_ref::<Error>() {
        io::hint(hint);
    }
modified radicle-node/src/runtime/handle.rs
@@ -245,7 +245,7 @@ impl radicle::node::Handle for Handle {
    fn subscribe(
        &self,
        _timeout: time::Duration,
-
    ) -> Result<Box<dyn Iterator<Item = Result<Event, io::Error>>>, Error> {
+
    ) -> Result<Box<dyn Iterator<Item = Result<Event, Error>>>, Error> {
        Ok(Box::new(self.events().into_iter().map(Ok)))
    }

modified radicle-node/src/test/handle.rs
@@ -1,7 +1,7 @@
use std::collections::HashSet;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
-
use std::{io, time};
+
use std::time;

use radicle::git;
use radicle::storage::refs::RefsAt;
@@ -75,7 +75,7 @@ impl radicle::node::Handle for Handle {
    fn subscribe(
        &self,
        _timeout: time::Duration,
-
    ) -> Result<Box<dyn Iterator<Item = Result<Event, io::Error>>>, Self::Error> {
+
    ) -> Result<Box<dyn Iterator<Item = Result<Event, Self::Error>>>, Self::Error> {
        Ok(Box::new(std::iter::empty()))
    }

modified radicle/src/node.rs
@@ -1,3 +1,4 @@
+
#![allow(clippy::type_complexity)]
mod features;

pub mod address;
@@ -748,12 +749,21 @@ impl IntoIterator for FetchResults {
/// Error returned by [`Handle`] functions.
#[derive(thiserror::Error, Debug)]
pub enum Error {
-
    #[error("failed to connect to node: {0}")]
-
    Connect(#[from] io::Error),
-
    #[error("failed to call node: {0}")]
-
    Call(#[from] CallError),
+
    #[error("i/o: {0}")]
+
    Io(#[from] io::Error),
    #[error("node: {0}")]
    Node(String),
+
    #[error("timed out reading from control socket")]
+
    TimedOut,
+
    #[error("failed to open node control socket {0:?} ({1})")]
+
    Connect(PathBuf, io::ErrorKind),
+
    #[error("command error: {reason}")]
+
    Command { reason: String },
+
    #[error("received invalid json `{response}` in response to command: {error}")]
+
    InvalidJson {
+
        response: String,
+
        error: json::Error,
+
    },
    #[error("received empty response for command")]
    EmptyResponse,
}
@@ -761,24 +771,10 @@ pub enum Error {
impl Error {
    /// Check if the error is due to the not being able to connect to the local node.
    pub fn is_connection_err(&self) -> bool {
-
        matches!(self, Self::Connect(_))
+
        matches!(self, Self::Connect { .. })
    }
}

-
/// Error returned by [`Node::call`] iterator.
-
#[derive(thiserror::Error, Debug)]
-
pub enum CallError {
-
    #[error("i/o: {0}")]
-
    Io(#[from] io::Error),
-
    #[error("command error: {reason}")]
-
    Command { reason: String },
-
    #[error("received invalid json `{response}` in response to command: {error}")]
-
    InvalidJson {
-
        response: String,
-
        error: json::Error,
-
    },
-
}
-

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "status")]
pub enum ConnectResult {
@@ -838,7 +834,7 @@ pub trait Handle: Clone + Sync + Send {
    fn subscribe(
        &self,
        timeout: time::Duration,
-
    ) -> Result<Box<dyn Iterator<Item = Result<Event, io::Error>>>, Self::Error>;
+
    ) -> Result<Box<dyn Iterator<Item = Result<Event, Self::Error>>>, Self::Error>;
}

/// Public node & device identifier.
@@ -863,32 +859,27 @@ impl Node {
        &self,
        cmd: Command,
        timeout: time::Duration,
-
    ) -> Result<impl Iterator<Item = Result<T, CallError>>, io::Error> {
-
        let stream = UnixStream::connect(&self.socket)?;
+
    ) -> Result<impl Iterator<Item = Result<T, Error>>, Error> {
+
        let stream = UnixStream::connect(&self.socket)
+
            .map_err(|e| Error::Connect(self.socket.clone(), e.kind()))?;
        cmd.to_writer(&stream)?;

        stream.set_read_timeout(Some(timeout))?;

        Ok(BufReader::new(stream).lines().map(move |l| {
-
            let l = l.map_err(|e| {
-
                if e.kind() == io::ErrorKind::WouldBlock {
-
                    io::Error::new(
-
                        io::ErrorKind::TimedOut,
-
                        "timed out reading from node control socket",
-
                    )
-
                } else {
-
                    e
-
                }
+
            let l = l.map_err(|e| match e.kind() {
+
                io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut => Error::TimedOut,
+
                _ => Error::Io(e),
+
            })?;
+

+
            let result: CommandResult<T> = json::from_str(&l).map_err(|e| Error::InvalidJson {
+
                response: l.clone(),
+
                error: e,
            })?;
-
            let result: CommandResult<T> =
-
                json::from_str(&l).map_err(|e| CallError::InvalidJson {
-
                    response: l.clone(),
-
                    error: e,
-
                })?;

            match result {
                CommandResult::Okay(result) => Ok(result),
-
                CommandResult::Error { reason } => Err(CallError::Command { reason }),
+
                CommandResult::Error { reason } => Err(Error::Command { reason }),
            }
        }))
    }
@@ -928,11 +919,11 @@ impl Node {
                }
                Ok(_) => {}

-
                Err(e) if e.kind() == io::ErrorKind::TimedOut => {
+
                Err(Error::TimedOut) => {
                    timeout.extend(seeds.iter());
                    break;
                }
-
                Err(e) => return Err(e.into()),
+
                Err(e) => return Err(e),
            }
            if seeds.is_empty() {
                break;
@@ -1077,18 +1068,10 @@ impl Handle for Node {
    fn subscribe(
        &self,
        timeout: time::Duration,
-
    ) -> Result<Box<dyn Iterator<Item = Result<Event, io::Error>>>, Error> {
+
    ) -> Result<Box<dyn Iterator<Item = Result<Event, Error>>>, Error> {
        let events = self.call(Command::Subscribe, timeout)?;

-
        Ok(Box::new(events.map(|e| {
-
            e.map_err(|err| match err {
-
                CallError::Io(e) => e,
-
                CallError::InvalidJson { .. } => {
-
                    io::Error::new(io::ErrorKind::InvalidInput, err.to_string())
-
                }
-
                CallError::Command { reason } => io::Error::new(io::ErrorKind::Other, reason),
-
            })
-
        })))
+
        Ok(Box::new(events))
    }

    fn sessions(&self) -> Result<Self::Sessions, Error> {