Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
rust/clippy: Lints for Defensive Programming
Merged ade opened 3 months ago

Fixes for rust/clippy: Lints for Defensive Programming - 9584e6f. Added all suggested Clippy lints :).

25 files changed +113 -52 02318f19 d860ec15
modified Cargo.toml
@@ -81,6 +81,12 @@ radicle-surf = "0.26.0"
[workspace.lints]
clippy.type_complexity = "allow"
clippy.enum_variant_names = "allow"
+
clippy.indexing_slicing = "deny"
+
clippy.fallible_impl_from = "deny"
+
clippy.wildcard_enum_match_arm = "deny"
+
clippy.unneeded_field_pattern = "deny"
+
clippy.fn_params_excessive_bools = "deny"
+
clippy.must_use_candidate = "deny"

[profile.container]
inherits = "release"
modified crates/radicle-cli/src/commands/cob.rs
@@ -150,7 +150,7 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
            repo,
            objects,
            type_name,
-
            format: _,
+
            ..
        } => {
            let repo = storage.repository(repo)?;
            if let Err(e) = show(objects, &repo, type_name.into(), &profile) {
@@ -167,7 +167,7 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
            type_name,
            object,
            operation,
-
            format: _,
+
            ..
        }) => {
            let signer = &profile.signer()?;
            let repo = storage.repository_mut(repo)?;
modified crates/radicle-cli/src/commands/cob/args.rs
@@ -210,6 +210,7 @@ where

    chunks.map(|chunk| {
        // Slice accesses will not panic, guaranteed by `chunks_exact(2)`.
+
        #[allow(clippy::indexing_slicing)]
        Embed {
            name: chunk[0].to_string(),
            content: EmbedContent::from(T::from(chunk[1].clone())),
modified crates/radicle-cli/src/commands/follow.rs
@@ -14,12 +14,8 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
    let mut node = radicle::Node::new(profile.socket());

    match Operation::from(args) {
-
        Operation::Follow {
-
            nid,
-
            alias,
-
            verbose: _,
-
        } => follow(nid, alias, &mut node, &profile)?,
-
        Operation::List { alias, verbose: _ } => following(&profile, alias)?,
+
        Operation::Follow { nid, alias, .. } => follow(nid, alias, &mut node, &profile)?,
+
        Operation::List { alias, .. } => following(&profile, alias)?,
    }

    Ok(())
modified crates/radicle-cli/src/commands/id/args.rs
@@ -55,6 +55,7 @@ pub(super) fn parse_many_upserts(

    chunks.map(|chunk| {
        // Slice accesses will not panic, guaranteed by `chunks_exact(3)`.
+
        #[allow(clippy::indexing_slicing)]
        Ok(PayloadUpsert {
            id: PayloadId::from_str(&chunk[0])?,
            key: chunk[1].to_owned(),
modified crates/radicle-cli/src/commands/inbox.rs
@@ -453,7 +453,8 @@ fn show(
                .spawn()?
                .wait()?;
        }
-
        notification => {
+
        notification @ NotificationKind::Cob { .. }
+
        | notification @ NotificationKind::Unknown { .. } => {
            term::json::to_pretty(&notification, Path::new("notification.json"))?.print();
        }
    }
modified crates/radicle-cli/src/commands/issue/args.rs
@@ -196,7 +196,7 @@ impl Command {
            // Special handling for `--edit` will be removed in the future.
            | Command::Edit { .. } => true,
            Command::Comment(args) => !args.is_edit(),
-
            _ => false,
+
            Command::Cache{..} | Command::Show { .. } | Command::List(_) => false,
        }
    }
}
modified crates/radicle-cli/src/commands/patch/args.rs
@@ -415,7 +415,7 @@ impl From<CommentArgs> for CommentAction {
            (None, Some(react), None) => CommentAction::React {
                revision,
                comment: react,
-
                emoji: emoji.unwrap(),
+
                emoji: emoji.expect("emoji must be Some when react is Some"),
                undo,
            },
            (None, None, Some(redact)) => CommentAction::Redact {
modified crates/radicle-cli/src/commands/patch/review/builder.rs
@@ -186,7 +186,10 @@ impl ReviewItem {
            Self::FileAdded { hunk, .. } => hunk.as_ref(),
            Self::FileDeleted { hunk, .. } => hunk.as_ref(),
            Self::FileModified { hunk, .. } => hunk.as_ref(),
-
            _ => None,
+
            Self::FileMoved { .. }
+
            | Self::FileCopied { .. }
+
            | Self::FileEofChanged { .. }
+
            | Self::FileModeChanged { .. } => None,
        }
    }

@@ -277,7 +280,7 @@ impl ReviewItem {
                EofNewLine::OldMissing => {
                    VStack::default().child(term::Label::new("`\\n` added at end-of-file"))
                }
-
                _ => VStack::default(),
+
                EofNewLine::BothMissing | EofNewLine::NoneMissing => VStack::default(),
            },
            Self::FileModeChanged { .. } => VStack::default(),
        }
modified crates/radicle-cli/src/git.rs
@@ -43,6 +43,7 @@ pub struct Rev(String);

impl Rev {
    /// Return the revision as a string.
+
    #[must_use]
    pub fn as_str(&self) -> &str {
        &self.0
    }
@@ -344,14 +345,6 @@ pub fn check_version() -> Result<Version, anyhow::Error> {
    Ok(git_version)
}

-
/// Parse a remote refspec into a peer id and ref.
-
pub fn parse_remote(refspec: &str) -> Option<(NodeId, &str)> {
-
    refspec
-
        .strip_prefix("refs/remotes/")
-
        .and_then(|s| s.split_once('/'))
-
        .and_then(|(peer, r)| NodeId::from_str(peer).ok().map(|p| (p, r)))
-
}
-

pub fn add_tag(
    repo: &Repository,
    message: &str,
modified crates/radicle-cli/src/git/ddiff.rs
@@ -382,6 +382,7 @@ impl DDiff {
    }

    /// Returns owned files in the diff.
+
    #[must_use]
    pub fn into_files(self) -> Vec<FileDDiff> {
        self.files
    }
modified crates/radicle-cli/src/git/pretty_diff.rs
@@ -537,14 +537,14 @@ impl ToPretty for Modification {
        match self {
            Modification::Deletion(diff::Deletion { line, line_no }) => {
                if let Some(lines) = &blobs.old.as_ref() {
-
                    lines[*line_no as usize - 1].clone()
+
                    lines.get(*line_no as usize - 1).unwrap().clone()
                } else {
                    term::Line::new(String::from_utf8_lossy(line.as_bytes()).as_ref())
                }
            }
            Modification::Addition(diff::Addition { line, line_no }) => {
                if let Some(lines) = &blobs.new.as_ref() {
-
                    lines[*line_no as usize - 1].clone()
+
                    lines.get(*line_no as usize - 1).unwrap().clone()
                } else {
                    term::Line::new(String::from_utf8_lossy(line.as_bytes()).as_ref())
                }
@@ -554,7 +554,7 @@ impl ToPretty for Modification {
            } => {
                // Nb. we can check in the old or the new blob, we choose the new.
                if let Some(lines) = &blobs.new.as_ref() {
-
                    lines[*line_no_new as usize - 1].clone()
+
                    lines.get(*line_no_new as usize - 1).unwrap().clone()
                } else {
                    term::Line::new(String::from_utf8_lossy(line.as_bytes()).as_ref())
                }
modified crates/radicle-cli/src/git/unified_diff.rs
@@ -33,11 +33,12 @@ impl Error {
        Self::Syntax(msg.to_string())
    }

+
    #[must_use]
    pub fn is_eof(&self) -> bool {
        match self {
            Self::UnexpectedEof => true,
            Self::Io(e) => e.kind() == io::ErrorKind::UnexpectedEof,
-
            _ => false,
+
            Self::Syntax(_) | Self::ParseInt(_) | Self::Utf8(_) => false,
        }
    }
}
@@ -137,12 +138,14 @@ impl TryFrom<&Hunk<Modification>> for HunkHeader {
}

impl HunkHeader {
+
    #[must_use]
    pub fn old_line_range(&self) -> std::ops::Range<u32> {
        let start: u32 = self.old_line_no;
        let end: u32 = self.old_line_no + self.old_size;
        start..end + 1
    }

+
    #[must_use]
    pub fn new_line_range(&self) -> std::ops::Range<u32> {
        let start: u32 = self.new_line_no;
        let end: u32 = self.new_line_no + self.new_size;
@@ -579,6 +582,7 @@ impl<'a> Writer<'a> {
        Ok(())
    }

+
    #[must_use]
    pub fn styled(mut self, value: bool) -> Self {
        self.styled = value;
        self
modified crates/radicle-cli/src/node.rs
@@ -25,12 +25,14 @@ pub struct SyncSettings {

impl SyncSettings {
    /// Set sync timeout. Defaults to [`DEFAULT_SYNC_TIMEOUT`].
+
    #[must_use]
    pub fn timeout(mut self, timeout: time::Duration) -> Self {
        self.timeout = timeout;
        self
    }

    /// Set replicas.
+
    #[must_use]
    pub fn replicas(mut self, replicas: sync::ReplicationFactor) -> Self {
        self.replicas = replicas;
        self
@@ -44,6 +46,7 @@ impl SyncSettings {

    /// Use profile to populate sync settings, by adding preferred seeds if no seeds are specified,
    /// and removing the local node from the set.
+
    #[must_use]
    pub fn with_profile(mut self, profile: &Profile) -> Self {
        // If no seeds were specified, add the preferred seeds.
        if self.seeds.is_empty() {
@@ -87,7 +90,7 @@ impl SyncError {
    fn is_connection_err(&self) -> bool {
        match self {
            Self::Node(e) => e.is_connection_err(),
-
            _ => false,
+
            Self::Repository(_) | Self::AllSeedsTimedOut | Self::Target(_) => false,
        }
    }
}
modified crates/radicle-cli/src/terminal/cob.rs
@@ -51,6 +51,7 @@ pub mod migrate {
    use super::MigrateSpinner;

    /// Display migration progress via a spinner.
+
    #[must_use]
    pub fn spinner() -> MigrateSpinner {
        MigrateSpinner::default()
    }
@@ -102,6 +103,9 @@ where

/// Adds a hint to the COB out-of-date database error.
fn with_hint(e: profile::Error) -> anyhow::Error {
+
    // There are many types that aren't `profile::Error::CobsCache`; specifying them all in an
+
    // error path seems overly verbose with little value.
+
    #[allow(clippy::wildcard_enum_match_arm)]
    match e {
        profile::Error::CobsCache(cob::cache::Error::OutOfDate) => {
            anyhow::Error::from(terminal::args::Error::WithHint {
modified crates/radicle-cli/src/terminal/format.rs
@@ -17,6 +17,7 @@ use radicle_term::element::Line;
use crate::terminal as term;

/// Format a node id to be more compact.
+
#[must_use]
pub fn node_id_human_compact(node: &NodeId) -> Paint<String> {
    let node = node.to_human();
    let start = node.chars().take(7).collect::<String>();
@@ -26,10 +27,12 @@ pub fn node_id_human_compact(node: &NodeId) -> Paint<String> {
}

/// Format a node id.
+
#[must_use]
pub fn node_id_human(node: &NodeId) -> Paint<String> {
    Paint::new(node.to_human())
}

+
#[must_use]
pub fn addr_compact(address: &Address) -> Paint<String> {
    let host = match address.host() {
        HostName::Ip(ip) => ip.to_string(),
@@ -72,17 +75,20 @@ pub fn command<D: fmt::Display>(cmd: D) -> Paint<String> {
}

/// Format a COB id.
+
#[must_use]
pub fn cob(id: &ObjectId) -> Paint<String> {
    Paint::new(format!("{:.7}", id.to_string()))
}

/// Format a DID.
+
#[must_use]
pub fn did(did: &Did) -> Paint<String> {
    let nid = did.as_key().to_human();
    Paint::new(format!("{}…{}", &nid[..7], &nid[nid.len() - 7..]))
}

/// Format a Visibility.
+
#[must_use]
pub fn visibility(v: &Visibility) -> Paint<&str> {
    match v {
        Visibility::Public => term::format::positive("public"),
@@ -91,6 +97,7 @@ pub fn visibility(v: &Visibility) -> Paint<&str> {
}

/// Format a policy.
+
#[must_use]
pub fn policy(p: &Policy) -> Paint<String> {
    match p {
        Policy::Allow => term::format::positive(p.to_string()),
@@ -108,6 +115,7 @@ pub fn timestamp(time: impl Into<LocalTime>) -> Paint<String> {
    Paint::new(fmt.convert(duration.into()))
}

+
#[must_use]
pub fn bytes(size: usize) -> Paint<String> {
    const KB: usize = 1024;
    const MB: usize = 1024usize.pow(2);
@@ -125,6 +133,7 @@ pub fn bytes(size: usize) -> Paint<String> {
}

/// Format a ref update.
+
#[must_use]
pub fn ref_update(update: &RefUpdate) -> Paint<&'static str> {
    match update {
        RefUpdate::Updated { .. } => term::format::tertiary("updated"),
@@ -134,6 +143,7 @@ pub fn ref_update(update: &RefUpdate) -> Paint<&'static str> {
    }
}

+
#[must_use]
pub fn ref_update_verbose(update: &RefUpdate) -> Paint<String> {
    match update {
        RefUpdate::Created { name, .. } => format!(
@@ -175,6 +185,7 @@ pub struct Identity<'a> {
}

impl<'a> Identity<'a> {
+
    #[must_use]
    pub fn new(profile: &'a Profile) -> Self {
        Self {
            profile,
@@ -183,11 +194,13 @@ impl<'a> Identity<'a> {
        }
    }

+
    #[must_use]
    pub fn short(mut self) -> Self {
        self.short = true;
        self
    }

+
    #[must_use]
    pub fn styled(mut self) -> Self {
        self.styled = true;
        self
@@ -227,6 +240,7 @@ pub struct Author<'a> {
}

impl<'a> Author<'a> {
+
    #[must_use]
    pub fn new(nid: &'a NodeId, profile: &Profile, verbose: bool) -> Author<'a> {
        let alias = profile.alias(nid);

@@ -238,10 +252,12 @@ impl<'a> Author<'a> {
        }
    }

+
    #[must_use]
    pub fn alias(&self) -> Option<term::Label> {
        self.alias.as_ref().map(|a| a.to_string().into())
    }

+
    #[must_use]
    pub fn you(&self) -> Option<term::Label> {
        if self.you {
            Some(term::format::primary("(you)").dim().italic().into())
@@ -256,6 +272,7 @@ impl<'a> Author<'a> {
    ///   * `(<did>, (you))` -- the `Author` is the local peer and has no alias
    ///   * `(<alias>, <did>)` -- the `Author` is another peer and has an alias
    ///   * `(<blank>, <did>)` -- the `Author` is another peer and has no alias
+
    #[must_use]
    pub fn labels(self) -> (term::Label, term::Label) {
        let node_id = if self.verbose {
            term::format::node_id_human(self.nid)
@@ -274,6 +291,7 @@ impl<'a> Author<'a> {
        (alias, author)
    }

+
    #[must_use]
    pub fn line(self) -> Line {
        let (alias, author) = self.labels();
        Line::spaced([alias, author])
@@ -283,6 +301,7 @@ impl<'a> Author<'a> {
/// HTML-related formatting.
pub mod html {
    /// Comment a string with HTML comments.
+
    #[must_use]
    pub fn commented(s: &str) -> String {
        format!("<!--\n{s}\n-->")
    }
@@ -290,6 +309,7 @@ pub mod html {
    /// Remove html style comments from a string.
    ///
    /// The HTML comments must start at the beginning of a line and stop at the end.
+
    #[must_use]
    pub fn strip_comments(s: &str) -> String {
        let ends_with_newline = s.ends_with('\n');
        let mut is_comment = false;
@@ -323,6 +343,7 @@ pub mod issue {
    use radicle::issue::{CloseReason, State};

    /// Format issue state.
+
    #[must_use]
    pub fn state(s: &State) -> term::Paint<String> {
        match s {
            State::Open => term::format::positive(s.to_string()),
@@ -341,6 +362,7 @@ pub mod patch {
    use super::*;
    use radicle::patch::{State, Verdict};

+
    #[must_use]
    pub fn verdict(v: Option<Verdict>) -> term::Paint<String> {
        match v {
            Some(Verdict::Accept) => term::PREFIX_SUCCESS.into(),
@@ -350,6 +372,7 @@ pub mod patch {
    }

    /// Format patch state.
+
    #[must_use]
    pub fn state(s: &State) -> term::Paint<String> {
        match s {
            State::Draft => term::format::dim(s.to_string()),
@@ -366,6 +389,7 @@ pub mod identity {
    use radicle::cob::identity::State;

    /// Format identity revision state.
+
    #[must_use]
    pub fn state(s: &State) -> term::Paint<String> {
        match s {
            State::Active => term::format::tertiary(s.to_string()),
modified crates/radicle-cli/src/terminal/highlight.rs
@@ -59,6 +59,7 @@ impl Default for Theme {

impl Theme {
    /// Get the named color.
+
    #[must_use]
    pub fn color(&self, color: &'static str) -> term::Color {
        if let Some(c) = (self.color)(color) {
            c
@@ -68,6 +69,7 @@ impl Theme {
    }

    /// Return the color of a syntax group.
+
    #[must_use]
    pub fn highlight(&self, group: &'static str) -> Option<term::Color> {
        let color = match group {
            "keyword" => self.color("red"),
@@ -145,9 +147,11 @@ impl Builder {
                    }
                }
                ts::HighlightEvent::HighlightStart(h) => {
-
                    let name = HIGHLIGHTS[h.0];
-
                    let style =
-
                        term::Style::default().fg(theme.highlight(name).unwrap_or_default());
+
                    let color = HIGHLIGHTS
+
                        .get(h.0)
+
                        .and_then(|name| theme.highlight(name))
+
                        .unwrap_or_default();
+
                    let style = term::Style::default().fg(color);

                    self.advance();
                    self.styles.push(style);
modified crates/radicle-cli/src/terminal/io.rs
@@ -21,6 +21,7 @@ pub struct PassphraseValidator {

impl PassphraseValidator {
    /// Create a new validator.
+
    #[must_use]
    pub fn new(keystore: Keystore) -> Self {
        Self { keystore }
    }
@@ -78,7 +79,7 @@ pub fn comment_select(issue: &Issue) -> anyhow::Result<(&CommentId, &Comment)> {
        (0..comments.len()).collect(),
    )
    .with_render_config(*CONFIG)
-
    .with_formatter(&|i| comments[i.index].1.body().to_owned())
+
    .with_formatter(&|i| comments.get(i.index).unwrap().1.body().to_owned())
    .prompt()?;

    comments
modified crates/radicle-cli/src/terminal/patch.rs
@@ -140,6 +140,7 @@ blank is also okay.

/// Combine the title and description fields to display to the user.
#[inline]
+
#[must_use]
pub fn message(title: &str, description: &str) -> String {
    format!("{title}\n\n{description}").trim().to_string()
}
modified crates/radicle-cli/src/terminal/upload_pack.rs
@@ -22,6 +22,7 @@ impl Default for UploadPack {

impl UploadPack {
    /// Construct an empty set of spinners.
+
    #[must_use]
    pub fn new() -> Self {
        Self {
            remotes: BTreeSet::new(),
modified crates/radicle-cli/tests/commands.rs
@@ -338,7 +338,7 @@ fn rad_config() {
    let mut environment = Environment::new();
    let alias = Alias::new("alice");
    let profile = environment.profile_with(profile::Config {
-
        preferred_seeds: vec![RADICLE_NODE_BOOTSTRAP_IRIS.clone()[0].clone()],
+
        preferred_seeds: vec![RADICLE_NODE_BOOTSTRAP_IRIS.clone().first().unwrap().clone()],
        ..profile::Config::new(alias)
    });
    let working = tempfile::tempdir().unwrap();
modified crates/radicle-core/src/repo.rs
@@ -47,6 +47,7 @@ impl RepoId {
    ///
    /// Eg. `rad:z3XncAdkZjeK9mQS5Sdc4qhw98BUX`.
    ///
+
    #[must_use]
    pub fn urn(&self) -> String {
        RAD_PREFIX.to_string() + &self.canonical()
    }
@@ -65,6 +66,7 @@ impl RepoId {
    ///
    /// Eg. `z3XncAdkZjeK9mQS5Sdc4qhw98BUX`.
    ///
+
    #[must_use]
    pub fn canonical(&self) -> String {
        multibase::encode(multibase::Base::Base58Btc, AsRef::<[u8]>::as_ref(&self.0))
    }
@@ -234,10 +236,12 @@ mod sqlite_impls {
                    code: None,
                    message: Some(e.to_string()),
                }),
-
                _ => Err(Error {
-
                    code: None,
-
                    message: Some(format!("sql: invalid type `{:?}` for id", value.kind())),
-
                }),
+
                Value::Binary(_) | Value::Float(_) | Value::Integer(_) | Value::Null => {
+
                    Err(Error {
+
                        code: None,
+
                        message: Some(format!("sql: invalid type `{:?}` for id", value.kind())),
+
                    })
+
                }
            }
        }
    }
modified crates/radicle-localtime/src/lib.rs
@@ -30,19 +30,26 @@ impl LocalTime {
    pub fn now() -> Self {
        static LAST: atomic::AtomicU64 = atomic::AtomicU64::new(0);

-
        let now = Self::from(SystemTime::now()).as_secs();
-
        let last = LAST.load(atomic::Ordering::SeqCst);
+
        let now = SystemTime::now()
+
            .duration_since(UNIX_EPOCH)
+
            .map(|duration| Self {
+
                millis: duration.as_millis(),
+
            })
+
            .unwrap();
+
        let last_in_secs = LAST.load(atomic::Ordering::SeqCst);
+
        let now_in_secs = now.as_secs();

        // If the current time is in the past, return the last recorded time instead.
-
        if now < last {
-
            Self::from_secs(last)
+
        if now_in_secs < last_in_secs {
+
            Self::from_secs(last_in_secs)
        } else {
-
            LAST.store(now, atomic::Ordering::SeqCst);
-
            LocalTime::from_secs(now)
+
            LAST.store(now_in_secs, atomic::Ordering::SeqCst);
+
            now
        }
    }

    /// Construct a local time from whole seconds since Epoch.
+
    #[must_use]
    pub const fn from_secs(secs: u64) -> Self {
        Self {
            millis: secs as u128 * 1000,
@@ -50,16 +57,19 @@ impl LocalTime {
    }

    /// Construct a local time from milliseconds since Epoch.
+
    #[must_use]
    pub const fn from_millis(millis: u128) -> Self {
        Self { millis }
    }

    /// Return whole seconds since Epoch.
+
    #[must_use]
    pub fn as_secs(&self) -> u64 {
        (self.millis / 1000).try_into().unwrap()
    }

    /// Return milliseconds since Epoch.
+
    #[must_use]
    pub fn as_millis(&self) -> u64 {
        self.millis.try_into().unwrap()
    }
@@ -69,6 +79,7 @@ impl LocalTime {
    /// # Panics
    ///
    /// This function will panic if `earlier` is later than `self`.
+
    #[must_use]
    pub fn duration_since(&self, earlier: LocalTime) -> LocalDuration {
        LocalDuration::from_millis(
            self.millis
@@ -78,6 +89,7 @@ impl LocalTime {
    }

    /// Get the difference between two times.
+
    #[must_use]
    pub fn diff(&self, other: LocalTime) -> LocalDuration {
        if self > &other {
            self.duration_since(other)
@@ -94,15 +106,6 @@ impl LocalTime {
    }
}

-
/// Convert a `SystemTime` into a local time.
-
impl From<SystemTime> for LocalTime {
-
    fn from(system: SystemTime) -> Self {
-
        let millis = system.duration_since(UNIX_EPOCH).unwrap().as_millis();
-

-
        Self { millis }
-
    }
-
}
-

/// Subtract two local times. Yields a duration.
impl std::ops::Sub<LocalTime> for LocalTime {
    type Output = LocalDuration;
@@ -151,31 +154,37 @@ impl LocalDuration {
    pub const MAX: LocalDuration = LocalDuration(u128::MAX);

    /// Create a new duration from whole seconds.
+
    #[must_use]
    pub const fn from_secs(secs: u64) -> Self {
        Self(secs as u128 * 1000)
    }

    /// Create a new duration from whole minutes.
+
    #[must_use]
    pub const fn from_mins(mins: u64) -> Self {
        Self::from_secs(mins * 60)
    }

    /// Construct a new duration from milliseconds.
+
    #[must_use]
    pub const fn from_millis(millis: u128) -> Self {
        Self(millis)
    }

    /// Return the number of minutes in this duration.
+
    #[must_use]
    pub const fn as_mins(&self) -> u64 {
        self.as_secs() / 60
    }

    /// Return the number of seconds in this duration.
+
    #[must_use]
    pub const fn as_secs(&self) -> u64 {
        (self.0 / 1000) as u64
    }

    /// Return the number of milliseconds in this duration.
+
    #[must_use]
    pub const fn as_millis(&self) -> u128 {
        self.0
    }
modified crates/radicle-node/src/main.rs
@@ -51,6 +51,9 @@ enum Logger {
    Systemd,
}

+
// Required for Mac and potentially Windows as clippy complains because of the OS specific
+
// guard below.
+
#[allow(clippy::derivable_impls)]
impl Default for Logger {
    fn default() -> Self {
        #[cfg(all(feature = "systemd", target_os = "linux"))]
modified crates/radicle-node/src/wire.rs
@@ -5,7 +5,7 @@ use std::collections::hash_map::Entry;
use std::collections::VecDeque;
use std::fmt::Debug;
use std::sync::Arc;
-
use std::time::{Instant, SystemTime};
+
use std::time::Instant;
use std::{io, net, time};

use crossbeam_channel as chan;
@@ -13,6 +13,7 @@ use cyphernet::addr::{HostName, InetHost, NetAddr};
use cyphernet::encrypt::noise::{HandshakePattern, Keyset, NoiseState};
use cyphernet::proxy::socks5;
use cyphernet::{Digest, EcSk, Ecdh, Sha256};
+
use localtime::LocalTime;
use mio::net::TcpStream;
use radicle::node::device::Device;

@@ -510,7 +511,7 @@ where
            .sum();
        self.metrics.worker_queue_size = self.worker.len();

-
        self.service.tick(SystemTime::now().into(), &self.metrics);
+
        self.service.tick(LocalTime::now(), &self.metrics);
    }

    fn timer_reacted(&mut self) {