cli: Hint about how to start node
In some cases, we can hint to the user about how they can start their node. We also remove the `CallError` type that was redundant.
This commit is contained in:
parent
25ca4c8b92
commit
fd38d88e14
|
|
@ -80,3 +80,21 @@ Running the command again gives us an error:
|
||||||
$ rad node stop
|
$ rad node stop
|
||||||
✗ Stopping node... error: node is not running
|
✗ 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`.
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use std::convert::TryFrom;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::{env, io, time};
|
use std::{env, time};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Context as _};
|
use anyhow::{anyhow, bail, Context as _};
|
||||||
use serde_json as json;
|
use serde_json as json;
|
||||||
|
|
@ -403,12 +403,12 @@ fn sync(
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
// Some other irrelevant event received.
|
// Some other irrelevant event received.
|
||||||
}
|
}
|
||||||
Err(e) if e.kind() == io::ErrorKind::TimedOut => {
|
Err(radicle::node::Error::TimedOut) => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
spinner.error(&e);
|
spinner.error(&e);
|
||||||
return Err(e.into());
|
return Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ pub fn connect(
|
||||||
) {
|
) {
|
||||||
Ok(ConnectResult::Connected) => spinner.finish(),
|
Ok(ConnectResult::Connected) => spinner.finish(),
|
||||||
Ok(ConnectResult::Disconnected { reason }) => spinner.error(reason),
|
Ok(ConnectResult::Disconnected { reason }) => spinner.error(reason),
|
||||||
Err(err) => spinner.error(err.to_string()),
|
Err(err) => return Err(err.into()),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,12 @@ pub fn fail(_name: &str, error: &anyhow::Error) {
|
||||||
io::error(line);
|
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>() {
|
if let Some(Error::WithHint { hint, .. }) = error.downcast_ref::<Error>() {
|
||||||
io::hint(hint);
|
io::hint(hint);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -245,7 +245,7 @@ impl radicle::node::Handle for Handle {
|
||||||
fn subscribe(
|
fn subscribe(
|
||||||
&self,
|
&self,
|
||||||
_timeout: time::Duration,
|
_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)))
|
Ok(Box::new(self.events().into_iter().map(Ok)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::{io, time};
|
use std::time;
|
||||||
|
|
||||||
use radicle::git;
|
use radicle::git;
|
||||||
use radicle::storage::refs::RefsAt;
|
use radicle::storage::refs::RefsAt;
|
||||||
|
|
@ -75,7 +75,7 @@ impl radicle::node::Handle for Handle {
|
||||||
fn subscribe(
|
fn subscribe(
|
||||||
&self,
|
&self,
|
||||||
_timeout: time::Duration,
|
_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()))
|
Ok(Box::new(std::iter::empty()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
#![allow(clippy::type_complexity)]
|
||||||
mod features;
|
mod features;
|
||||||
|
|
||||||
pub mod address;
|
pub mod address;
|
||||||
|
|
@ -748,28 +749,14 @@ impl IntoIterator for FetchResults {
|
||||||
/// Error returned by [`Handle`] functions.
|
/// Error returned by [`Handle`] functions.
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("failed to connect to node: {0}")]
|
|
||||||
Connect(#[from] io::Error),
|
|
||||||
#[error("failed to call node: {0}")]
|
|
||||||
Call(#[from] CallError),
|
|
||||||
#[error("node: {0}")]
|
|
||||||
Node(String),
|
|
||||||
#[error("received empty response for command")]
|
|
||||||
EmptyResponse,
|
|
||||||
}
|
|
||||||
|
|
||||||
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(_))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Error returned by [`Node::call`] iterator.
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum CallError {
|
|
||||||
#[error("i/o: {0}")]
|
#[error("i/o: {0}")]
|
||||||
Io(#[from] io::Error),
|
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}")]
|
#[error("command error: {reason}")]
|
||||||
Command { reason: String },
|
Command { reason: String },
|
||||||
#[error("received invalid json `{response}` in response to command: {error}")]
|
#[error("received invalid json `{response}` in response to command: {error}")]
|
||||||
|
|
@ -777,6 +764,15 @@ pub enum CallError {
|
||||||
response: String,
|
response: String,
|
||||||
error: json::Error,
|
error: json::Error,
|
||||||
},
|
},
|
||||||
|
#[error("received empty response for command")]
|
||||||
|
EmptyResponse,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 { .. })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
|
@ -838,7 +834,7 @@ pub trait Handle: Clone + Sync + Send {
|
||||||
fn subscribe(
|
fn subscribe(
|
||||||
&self,
|
&self,
|
||||||
timeout: time::Duration,
|
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.
|
/// Public node & device identifier.
|
||||||
|
|
@ -863,32 +859,27 @@ impl Node {
|
||||||
&self,
|
&self,
|
||||||
cmd: Command,
|
cmd: Command,
|
||||||
timeout: time::Duration,
|
timeout: time::Duration,
|
||||||
) -> Result<impl Iterator<Item = Result<T, CallError>>, io::Error> {
|
) -> Result<impl Iterator<Item = Result<T, Error>>, Error> {
|
||||||
let stream = UnixStream::connect(&self.socket)?;
|
let stream = UnixStream::connect(&self.socket)
|
||||||
|
.map_err(|e| Error::Connect(self.socket.clone(), e.kind()))?;
|
||||||
cmd.to_writer(&stream)?;
|
cmd.to_writer(&stream)?;
|
||||||
|
|
||||||
stream.set_read_timeout(Some(timeout))?;
|
stream.set_read_timeout(Some(timeout))?;
|
||||||
|
|
||||||
Ok(BufReader::new(stream).lines().map(move |l| {
|
Ok(BufReader::new(stream).lines().map(move |l| {
|
||||||
let l = l.map_err(|e| {
|
let l = l.map_err(|e| match e.kind() {
|
||||||
if e.kind() == io::ErrorKind::WouldBlock {
|
io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut => Error::TimedOut,
|
||||||
io::Error::new(
|
_ => Error::Io(e),
|
||||||
io::ErrorKind::TimedOut,
|
})?;
|
||||||
"timed out reading from node control socket",
|
|
||||||
)
|
let result: CommandResult<T> = json::from_str(&l).map_err(|e| Error::InvalidJson {
|
||||||
} else {
|
response: l.clone(),
|
||||||
e
|
error: e,
|
||||||
}
|
|
||||||
})?;
|
})?;
|
||||||
let result: CommandResult<T> =
|
|
||||||
json::from_str(&l).map_err(|e| CallError::InvalidJson {
|
|
||||||
response: l.clone(),
|
|
||||||
error: e,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
CommandResult::Okay(result) => Ok(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(_) => {}
|
Ok(_) => {}
|
||||||
|
|
||||||
Err(e) if e.kind() == io::ErrorKind::TimedOut => {
|
Err(Error::TimedOut) => {
|
||||||
timeout.extend(seeds.iter());
|
timeout.extend(seeds.iter());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e),
|
||||||
}
|
}
|
||||||
if seeds.is_empty() {
|
if seeds.is_empty() {
|
||||||
break;
|
break;
|
||||||
|
|
@ -1077,18 +1068,10 @@ impl Handle for Node {
|
||||||
fn subscribe(
|
fn subscribe(
|
||||||
&self,
|
&self,
|
||||||
timeout: time::Duration,
|
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)?;
|
let events = self.call(Command::Subscribe, timeout)?;
|
||||||
|
|
||||||
Ok(Box::new(events.map(|e| {
|
Ok(Box::new(events))
|
||||||
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),
|
|
||||||
})
|
|
||||||
})))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sessions(&self) -> Result<Self::Sessions, Error> {
|
fn sessions(&self) -> Result<Self::Sessions, Error> {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue