diff --git a/Cargo.lock b/Cargo.lock index 6044aee4..e3d53d37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2168,15 +2168,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" -[[package]] -name = "localtime" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016a009e0bb8ba6e3229fb74bf11a8fe6ef24542cc6ef35ef38863ac13f96d87" -dependencies = [ - "serde", -] - [[package]] name = "lock_api" version = "0.4.14" @@ -2796,7 +2787,6 @@ dependencies = [ "indexmap", "jsonschema", "libc", - "localtime", "log", "multibase", "nonempty", @@ -2807,6 +2797,7 @@ dependencies = [ "radicle-crypto", "radicle-git-metadata", "radicle-git-ref-format", + "radicle-localtime", "radicle-oid", "radicle-ssh", "schemars", @@ -2832,7 +2823,6 @@ dependencies = [ "dunce", "human-panic", "itertools", - "localtime", "log", "nonempty", "pretty_assertions", @@ -2841,6 +2831,7 @@ dependencies = [ "radicle-cob", "radicle-crypto", "radicle-git-ref-format", + "radicle-localtime", "radicle-node", "radicle-surf", "radicle-term", @@ -2981,6 +2972,15 @@ dependencies = [ "git-ref-format-core", ] +[[package]] +name = "radicle-localtime" +version = "0.1.0" +dependencies = [ + "schemars", + "serde", + "serde_json", +] + [[package]] name = "radicle-node" version = "0.16.0" @@ -2994,7 +2994,6 @@ dependencies = [ "cyphernet", "fastrand", "lexopt", - "localtime", "log", "mio 1.0.4", "nonempty", @@ -3003,6 +3002,7 @@ dependencies = [ "radicle", "radicle-crypto", "radicle-fetch", + "radicle-localtime", "radicle-protocol", "radicle-signals", "radicle-systemd", @@ -3041,7 +3041,6 @@ dependencies = [ "crossbeam-channel", "cyphernet", "fastrand", - "localtime", "log", "nonempty", "paste", @@ -3050,6 +3049,7 @@ dependencies = [ "radicle", "radicle-crypto", "radicle-fetch", + "radicle-localtime", "scrypt", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 4f934e86..db55c718 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,6 @@ human-panic = "2" itertools = "0.14" lexopt = "0.3.0" libc = "0.2.137" -localtime = "1.2.0" log = "0.4.17" multibase = "0.9.1" nonempty = "0.9.0" @@ -50,6 +49,7 @@ radicle-dag = { version = "0.10", path = "crates/radicle-dag" } radicle-fetch = { version = "0.16", path = "crates/radicle-fetch" } radicle-git-metadata = { version = "0.1.0", path = "crates/radicle-git-metadata", default-features = false } radicle-git-ref-format = { version = "0.1.0", path = "crates/radicle-git-ref-format", default-features = false } +radicle-localtime = { version = "0.1", path = "crates/radicle-localtime" } radicle-node = { version = "0.16", path = "crates/radicle-node" } radicle-oid = { version = "0.1.0", path = "crates/radicle-oid", default-features = false } radicle-protocol = { version = "0.4", path = "crates/radicle-protocol" } diff --git a/crates/radicle-cli/Cargo.toml b/crates/radicle-cli/Cargo.toml index 58f0f83f..402afdfc 100644 --- a/crates/radicle-cli/Cargo.toml +++ b/crates/radicle-cli/Cargo.toml @@ -21,13 +21,13 @@ clap_complete = "4.5" dunce = { workspace = true } human-panic.workspace = true itertools.workspace = true -localtime = { workspace = true } log = { workspace = true, features = ["std"] } nonempty = { workspace = true } radicle = { workspace = true, features = ["logger", "schemars"] } radicle-cob = { workspace = true } radicle-crypto = { workspace = true } radicle-git-ref-format = { workspace = true, features = ["macro"] } +radicle-localtime = { workspace = true } radicle-surf = { workspace = true } radicle-term = { workspace = true } schemars = { workspace = true } @@ -57,6 +57,7 @@ zeroize = { workspace = true } pretty_assertions = { workspace = true } radicle = { workspace = true, features = ["test"] } radicle-cli-test = { workspace = true } +radicle-localtime = { workspace = true } radicle-node = { workspace = true, features = ["test"] } [lints] diff --git a/crates/radicle-cli/src/lib.rs b/crates/radicle-cli/src/lib.rs index 88660339..7b94cef4 100644 --- a/crates/radicle-cli/src/lib.rs +++ b/crates/radicle-cli/src/lib.rs @@ -9,3 +9,5 @@ pub mod project; pub mod terminal; mod warning; + +extern crate radicle_localtime as localtime; diff --git a/crates/radicle-cli/tests/commands.rs b/crates/radicle-cli/tests/commands.rs index d5278763..ce8ca526 100644 --- a/crates/radicle-cli/tests/commands.rs +++ b/crates/radicle-cli/tests/commands.rs @@ -20,6 +20,7 @@ use radicle::profile::Home; use radicle::storage::{ReadStorage, RefUpdate, RemoteRepository}; use radicle::test::fixtures; +use radicle_localtime::LocalTime; #[allow(unused_imports)] use radicle_node::test::logger; use radicle_node::test::node::Node; @@ -806,7 +807,7 @@ fn rad_node_connect_without_address() { &Alias::new("bob"), 0, &UserAgent::default(), - localtime::LocalTime::now().into(), + LocalTime::now().into(), [node::KnownAddress::new( node::Address::from(bob.addr), node::address::Source::Imported, @@ -1351,7 +1352,7 @@ fn rad_clone_partial_fail() { &Alias::new("carol"), 0, &UserAgent::default(), - localtime::LocalTime::now().into(), + LocalTime::now().into(), [node::KnownAddress::new( // Eve will fail to connect to this address. node::Address::from(net::SocketAddr::from(([0, 0, 0, 0], 19873))), @@ -1361,7 +1362,7 @@ fn rad_clone_partial_fail() { .unwrap(); eve.db .routing_mut() - .add_inventory([&acme], carol, localtime::LocalTime::now().into()) + .add_inventory([&acme], carol, LocalTime::now().into()) .unwrap(); eve.config.peers = node::config::PeerConfig::Static; @@ -1395,7 +1396,7 @@ fn rad_clone_connect() { let mut eve = environment.node("eve"); let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); let ua = UserAgent::default(); - let now = localtime::LocalTime::now().into(); + let now = LocalTime::now().into(); fixtures::repository(working.join("acme")); diff --git a/crates/radicle-cli/tests/util/environment.rs b/crates/radicle-cli/tests/util/environment.rs index 1f44ddc6..79e547ae 100644 --- a/crates/radicle-cli/tests/util/environment.rs +++ b/crates/radicle-cli/tests/util/environment.rs @@ -1,7 +1,6 @@ use std::path::PathBuf; use std::str::FromStr; -use localtime::LocalTime; use radicle::cob::cache::COBS_DB_FILE; use radicle::crypto::ssh::{keystore::MemorySigner, Keystore}; use radicle::crypto::{KeyPair, Seed}; @@ -13,6 +12,7 @@ use radicle::profile::Home; use radicle::profile::{self}; use radicle::storage::git::transport; use radicle::{Profile, Storage}; +use radicle_localtime::LocalTime; use radicle_node::test::node::{Node, NodeHandle}; diff --git a/crates/radicle-localtime/Cargo.toml b/crates/radicle-localtime/Cargo.toml new file mode 100644 index 00000000..9472f12b --- /dev/null +++ b/crates/radicle-localtime/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "radicle-localtime" +version = "0.1.0" +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[lints] +workspace = true + +[dependencies] +serde = { workspace = true, optional = true, features = ["derive"] } +schemars = { workspace = true, optional = true, features = ["derive", "std"] } + +[dev-dependencies] +serde_json = { workspace = true } \ No newline at end of file diff --git a/crates/radicle-localtime/src/lib.rs b/crates/radicle-localtime/src/lib.rs new file mode 100644 index 00000000..d6144576 --- /dev/null +++ b/crates/radicle-localtime/src/lib.rs @@ -0,0 +1,325 @@ +//! Minimal, zero-dependency, monotonic, unix time library for rust. +//! +//! Taken from + +use std::sync::atomic; +use std::time::{SystemTime, UNIX_EPOCH}; + +/// Local time. +/// +/// This clock is monotonic. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Ord, PartialOrd, Default)] +#[cfg_attr( + feature = "schemars", + derive(schemars::JsonSchema), + schemars(description = "A timestamp measured locally in seconds.") +)] +pub struct LocalTime { + /// Milliseconds since Epoch. + millis: u128, +} + +impl std::fmt::Display for LocalTime { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_secs()) + } +} + +impl LocalTime { + /// Construct a local time from the current system time. + 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); + + // If the current time is in the past, return the last recorded time instead. + if now < last { + Self::from_secs(last) + } else { + LAST.store(now, atomic::Ordering::SeqCst); + LocalTime::from_secs(now) + } + } + + /// Construct a local time from whole seconds since Epoch. + pub const fn from_secs(secs: u64) -> Self { + Self { + millis: secs as u128 * 1000, + } + } + + /// Construct a local time from milliseconds since Epoch. + pub const fn from_millis(millis: u128) -> Self { + Self { millis } + } + + /// Return whole seconds since Epoch. + pub fn as_secs(&self) -> u64 { + (self.millis / 1000).try_into().unwrap() + } + + /// Return milliseconds since Epoch. + pub fn as_millis(&self) -> u64 { + self.millis.try_into().unwrap() + } + + /// Get the duration since the given time. + /// + /// # Panics + /// + /// This function will panic if `earlier` is later than `self`. + pub fn duration_since(&self, earlier: LocalTime) -> LocalDuration { + LocalDuration::from_millis( + self.millis + .checked_sub(earlier.millis) + .expect("supplied time is later than self"), + ) + } + + /// Get the difference between two times. + pub fn diff(&self, other: LocalTime) -> LocalDuration { + if self > &other { + self.duration_since(other) + } else { + other.duration_since(*self) + } + } + + /// Elapse time. + /// + /// Adds the given duration to the time. + pub fn elapse(&mut self, duration: LocalDuration) { + self.millis += duration.as_millis() + } +} + +/// Convert a `SystemTime` into a local time. +impl From 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 for LocalTime { + type Output = LocalDuration; + + fn sub(self, other: LocalTime) -> LocalDuration { + LocalDuration(self.millis.saturating_sub(other.millis)) + } +} + +/// Subtract a duration from a local time. Yields a local time. +impl std::ops::Sub for LocalTime { + type Output = LocalTime; + + fn sub(self, other: LocalDuration) -> LocalTime { + LocalTime { + millis: self.millis - other.0, + } + } +} + +/// Add a duration to a local time. Yields a local time. +impl std::ops::Add for LocalTime { + type Output = LocalTime; + + fn add(self, other: LocalDuration) -> LocalTime { + LocalTime { + millis: self.millis + other.0, + } + } +} + +/// Time duration as measured locally. +#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] +#[cfg_attr( + feature = "schemars", + derive(schemars::JsonSchema), + schemars(description = "A time duration measured locally in seconds.") +)] +pub struct LocalDuration(u128); + +impl LocalDuration { + /// The time interval between blocks. The "block time". + pub const BLOCK_INTERVAL: LocalDuration = Self::from_mins(10); + + /// Maximum duration. + pub const MAX: LocalDuration = LocalDuration(u128::MAX); + + /// Create a new duration from whole seconds. + pub const fn from_secs(secs: u64) -> Self { + Self(secs as u128 * 1000) + } + + /// Create a new duration from whole minutes. + pub const fn from_mins(mins: u64) -> Self { + Self::from_secs(mins * 60) + } + + /// Construct a new duration from milliseconds. + pub const fn from_millis(millis: u128) -> Self { + Self(millis) + } + + /// Return the number of minutes in this duration. + pub const fn as_mins(&self) -> u64 { + self.as_secs() / 60 + } + + /// Return the number of seconds in this duration. + pub const fn as_secs(&self) -> u64 { + (self.0 / 1000) as u64 + } + + /// Return the number of milliseconds in this duration. + pub const fn as_millis(&self) -> u128 { + self.0 + } +} + +impl std::fmt::Display for LocalDuration { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.as_millis() < 1000 { + write!(f, "{} millisecond(s)", self.as_millis()) + } else if self.as_secs() < 60 { + let fraction = self.as_millis() % 1000; + if fraction > 0 { + write!(f, "{}.{} second(s)", self.as_secs(), fraction) + } else { + write!(f, "{} second(s)", self.as_secs()) + } + } else if self.as_mins() < 60 { + let fraction = self.as_secs() % 60; + if fraction > 0 { + write!( + f, + "{:.2} minute(s)", + self.as_mins() as f64 + (fraction as f64 / 60.) + ) + } else { + write!(f, "{} minute(s)", self.as_mins()) + } + } else { + let fraction = self.as_mins() % 60; + if fraction > 0 { + write!(f, "{:.2} hour(s)", self.as_mins() as f64 / 60.) + } else { + write!(f, "{} hour(s)", self.as_mins() / 60) + } + } + } +} + +impl<'a> std::iter::Sum<&'a LocalDuration> for LocalDuration { + fn sum>(iter: I) -> LocalDuration { + let mut total: u128 = 0; + + for entry in iter { + total = total + .checked_add(entry.0) + .expect("iter::sum should not overflow"); + } + Self(total) + } +} + +impl std::ops::Add for LocalDuration { + type Output = LocalDuration; + + fn add(self, other: LocalDuration) -> LocalDuration { + LocalDuration(self.0 + other.0) + } +} + +impl std::ops::Div for LocalDuration { + type Output = LocalDuration; + + fn div(self, other: u32) -> LocalDuration { + LocalDuration(self.0 / other as u128) + } +} + +impl std::ops::Mul for LocalDuration { + type Output = LocalDuration; + + fn mul(self, other: u64) -> LocalDuration { + LocalDuration(self.0 * other as u128) + } +} + +impl From for std::time::Duration { + fn from(other: LocalDuration) -> Self { + std::time::Duration::from_millis(other.0 as u64) + } +} + +#[cfg(feature = "serde")] +mod serde_impls { + use super::{LocalDuration, LocalTime}; + + impl serde::Serialize for LocalTime { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_u64(self.as_secs()) + } + } + + impl<'de> serde::Deserialize<'de> for LocalTime { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + u64::deserialize(deserializer).map(LocalTime::from_secs) + } + } + + impl serde::Serialize for LocalDuration { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_u64(self.as_secs()) + } + } + + impl<'de> serde::Deserialize<'de> for LocalDuration { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + u64::deserialize(deserializer).map(LocalDuration::from_secs) + } + } + + #[cfg(test)] + mod test { + use crate::LocalTime; + + #[test] + fn test_localtime() { + #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Eq)] + struct Test { + time: LocalTime, + } + let value = Test { + time: LocalTime::from_secs(1699636852107), + }; + + assert_eq!( + serde_json::from_str::(r#"{"time":1699636852107}"#).unwrap(), + value + ); + assert_eq!( + serde_json::from_str::(serde_json::to_string(&value).unwrap().as_str()) + .unwrap(), + value + ); + } + } +} diff --git a/crates/radicle-node/Cargo.toml b/crates/radicle-node/Cargo.toml index 910607da..a9b6aa94 100644 --- a/crates/radicle-node/Cargo.toml +++ b/crates/radicle-node/Cargo.toml @@ -25,12 +25,12 @@ cyphernet = { workspace = true, features = ["tor", "dns", "ed25519", "p2p-ed2551 fastrand = { workspace = true } lexopt = { workspace = true } log = { workspace = true, features = ["kv", "std"] } -localtime = { workspace = true } mio = { version = "1", features = ["net", "os-poll"] } nonempty = { workspace = true, features = ["serialize"] } qcheck = { workspace = true, optional = true } radicle = { workspace = true, features = ["logger"] } radicle-fetch = { workspace = true } +radicle-localtime = { workspace = true } radicle-protocol = { workspace = true } radicle-signals = { workspace = true } sqlite = { workspace = true, features = ["bundled"] } diff --git a/crates/radicle-node/src/lib.rs b/crates/radicle-node/src/lib.rs index 88b3a0f2..94ae8d27 100644 --- a/crates/radicle-node/src/lib.rs +++ b/crates/radicle-node/src/lib.rs @@ -17,6 +17,8 @@ pub mod test; #[cfg(test)] pub mod tests; +extern crate radicle_localtime as localtime; + use std::str::FromStr; use std::sync::LazyLock; diff --git a/crates/radicle-protocol/Cargo.toml b/crates/radicle-protocol/Cargo.toml index d8aeff0e..2d085895 100644 --- a/crates/radicle-protocol/Cargo.toml +++ b/crates/radicle-protocol/Cargo.toml @@ -18,11 +18,11 @@ crossbeam-channel = { workspace = true } cyphernet = { workspace = true, features = ["tor"] } fastrand = { workspace = true } log = { workspace = true, features = ["std"] } -localtime = { workspace = true } nonempty = { workspace = true, features = ["serialize"] } qcheck = { workspace = true, optional = true } radicle = { workspace = true, features = ["logger"] } radicle-fetch = { workspace = true } +radicle-localtime = { workspace = true } sqlite = { workspace = true, features = ["bundled"] } scrypt = { version = "0.11.0", default-features = false } serde = { workspace = true, features = ["derive"] } diff --git a/crates/radicle-protocol/src/lib.rs b/crates/radicle-protocol/src/lib.rs index 9c20f918..9619806b 100644 --- a/crates/radicle-protocol/src/lib.rs +++ b/crates/radicle-protocol/src/lib.rs @@ -6,3 +6,5 @@ pub mod worker; /// Peer-to-peer protocol version. pub const PROTOCOL_VERSION: u8 = 1; + +extern crate radicle_localtime as localtime; diff --git a/crates/radicle/Cargo.toml b/crates/radicle/Cargo.toml index ef646bd6..75eeb03f 100644 --- a/crates/radicle/Cargo.toml +++ b/crates/radicle/Cargo.toml @@ -13,7 +13,7 @@ rust-version.workspace = true default = [] test = ["tempfile", "qcheck", "radicle-crypto/test", "radicle-cob/test"] logger = ["colored", "chrono"] -schemars = ["radicle-oid/schemars", "dep:schemars"] +schemars = ["radicle-oid/schemars", "radicle-localtime/schemars", "dep:schemars"] [dependencies] amplify = { workspace = true, features = ["std"] } @@ -28,7 +28,6 @@ fast-glob = { version = "0.3.2" } fastrand = { workspace = true, features = ["std"] } git2 = { workspace = true, features = ["vendored-libgit2"] } indexmap = { version = "2", features = ["serde"] } -localtime = { workspace = true, features = ["serde"] } log = { workspace = true, features = ["std"] } multibase = { workspace = true } nonempty = { workspace = true, features = ["serialize"] } @@ -36,6 +35,7 @@ qcheck = { workspace = true, optional = true } radicle-cob = { workspace = true, features = ["git2"] } radicle-crypto = { workspace = true, features = ["git-ref-format-core", "ssh", "sqlite", "cyphernet"] } radicle-git-ref-format = { workspace = true, features = ["macro", "serde"] } +radicle-localtime = { workspace = true, features = ["serde"] } radicle-oid = { workspace = true, features = ["git2", "serde", "std", "sha1"] } radicle-ssh = { workspace = true } schemars = { workspace = true, optional = true, features = ["derive", "std"] } diff --git a/crates/radicle/src/cob/common.rs b/crates/radicle/src/cob/common.rs index 91df7396..0a053b5c 100644 --- a/crates/radicle/src/cob/common.rs +++ b/crates/radicle/src/cob/common.rs @@ -13,10 +13,29 @@ use crate::git::Oid; use crate::prelude::{Did, PublicKey}; /// Timestamp used for COB operations. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Timestamp(LocalTime); +impl Serialize for Timestamp { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_u64(self.0.as_millis()) + } +} + +impl<'de> Deserialize<'de> for Timestamp { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + u128::deserialize(deserializer) + .map(LocalTime::from_millis) + .map(Self) + } +} + impl Timestamp { pub fn from_secs(secs: u64) -> Self { Self(LocalTime::from_secs(secs)) diff --git a/crates/radicle/src/lib.rs b/crates/radicle/src/lib.rs index a83ba98d..b6698be4 100644 --- a/crates/radicle/src/lib.rs +++ b/crates/radicle/src/lib.rs @@ -8,6 +8,8 @@ pub extern crate radicle_crypto as crypto; #[macro_use] extern crate amplify; +extern crate radicle_localtime as localtime; + mod canonical; pub mod cli; diff --git a/crates/radicle/src/node.rs b/crates/radicle/src/node.rs index 63b66c6c..f179f04a 100644 --- a/crates/radicle/src/node.rs +++ b/crates/radicle/src/node.rs @@ -102,11 +102,6 @@ pub enum State { #[serde(rename_all = "camelCase")] Connected { /// Connected since this time. - #[serde(with = "crate::serde_ext::localtime::time")] - #[cfg_attr( - feature = "schemars", - schemars(with = "crate::schemars_ext::localtime::LocalDurationInSeconds") - )] since: LocalTime, /// Ping state. #[serde(skip)] @@ -124,18 +119,8 @@ pub enum State { #[serde(rename_all = "camelCase")] Disconnected { /// Since when has this peer been disconnected. - #[serde(with = "crate::serde_ext::localtime::time")] - #[cfg_attr( - feature = "schemars", - schemars(with = "crate::schemars_ext::localtime::LocalDurationInSeconds") - )] since: LocalTime, /// When to retry the connection. - #[serde(with = "crate::serde_ext::localtime::time")] - #[cfg_attr( - feature = "schemars", - schemars(with = "crate::schemars_ext::localtime::LocalDurationInSeconds") - )] retry_at: LocalTime, }, } diff --git a/crates/radicle/src/node/address.rs b/crates/radicle/src/node/address.rs index 98b23523..baefd98f 100644 --- a/crates/radicle/src/node/address.rs +++ b/crates/radicle/src/node/address.rs @@ -150,18 +150,8 @@ pub struct KnownAddress { /// Address of the peer who sent us this address. pub source: Source, /// Last time this address was used to successfully connect to a peer. - #[serde(with = "crate::serde_ext::localtime::option::time")] - #[cfg_attr( - feature = "schemars", - schemars(with = "Option") - )] pub last_success: Option, /// Last time this address was tried. - #[serde(with = "crate::serde_ext::localtime::option::time")] - #[cfg_attr( - feature = "schemars", - schemars(with = "Option") - )] pub last_attempt: Option, /// Whether this address has been banned. pub banned: bool, diff --git a/crates/radicle/src/node/config.rs b/crates/radicle/src/node/config.rs index eb886e7a..6d289e78 100644 --- a/crates/radicle/src/node/config.rs +++ b/crates/radicle/src/node/config.rs @@ -548,14 +548,7 @@ impl From for log::Level { #[derive(Clone, Copy, Debug, Deserialize, Serialize, Eq, PartialEq)] #[serde(transparent)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct LimitRoutingMaxAge( - #[serde(with = "crate::serde_ext::localtime::duration")] - #[cfg_attr( - feature = "schemars", - schemars(with = "crate::schemars_ext::localtime::LocalDuration") - )] - localtime::LocalDuration, -); +pub struct LimitRoutingMaxAge(localtime::LocalDuration); impl Default for LimitRoutingMaxAge { fn default() -> Self { @@ -578,14 +571,7 @@ impl From for LimitRoutingMaxAge { #[derive(Clone, Copy, Debug, Deserialize, Serialize, Eq, PartialEq)] #[serde(transparent)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct LimitGossipMaxAge( - #[serde(with = "crate::serde_ext::localtime::duration")] - #[cfg_attr( - feature = "schemars", - schemars(with = "crate::schemars_ext::localtime::LocalDuration") - )] - localtime::LocalDuration, -); +pub struct LimitGossipMaxAge(localtime::LocalDuration); impl Default for LimitGossipMaxAge { fn default() -> Self { diff --git a/crates/radicle/src/node/seed.rs b/crates/radicle/src/node/seed.rs index 642872e8..846d9930 100644 --- a/crates/radicle/src/node/seed.rs +++ b/crates/radicle/src/node/seed.rs @@ -15,11 +15,6 @@ pub struct SyncedAt { /// Head of `rad/sigrefs`. pub oid: crate::git::Oid, /// When these refs were synced. - #[serde(with = "crate::serde_ext::localtime::time")] - #[cfg_attr( - feature = "schemars", - schemars(with = "crate::schemars_ext::localtime::LocalDurationInSeconds") - )] pub timestamp: LocalTime, } diff --git a/crates/radicle/src/schemars_ext.rs b/crates/radicle/src/schemars_ext.rs index bac10a98..5186fd23 100644 --- a/crates/radicle/src/schemars_ext.rs +++ b/crates/radicle/src/schemars_ext.rs @@ -69,26 +69,6 @@ pub(crate) mod bytesize { ); } -pub(crate) mod localtime { - use super::*; - - /// See [`::localtime::LocalDuration`] - #[derive(JsonSchema)] - #[schemars( - remote = "localtime::LocalDuration", - description = "A time duration measured locally in milliseconds." - )] - pub(crate) struct LocalDuration(u64); - - /// See [`crate::serde_ext::localtime::time`] - #[derive(JsonSchema)] - #[schemars( - remote = "localtime::LocalDuration", - description = "A time duration measured locally in seconds." - )] - pub(crate) struct LocalDurationInSeconds(u64); -} - pub(crate) mod git { pub(crate) mod fmt { /// See [`crate::git::fmt::RefString`] diff --git a/crates/radicle/src/serde_ext.rs b/crates/radicle/src/serde_ext.rs index 9a32ddbb..bbcc365c 100644 --- a/crates/radicle/src/serde_ext.rs +++ b/crates/radicle/src/serde_ext.rs @@ -31,80 +31,6 @@ pub mod string { } } -/// Unlike the default `serde` instances from `localtime`, this encodes and decodes using seconds -/// instead of milliseconds. -pub mod localtime { - pub mod time { - use localtime::LocalTime; - use serde::{Deserialize, Deserializer, Serializer}; - - pub fn serialize(value: &LocalTime, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_u64(value.as_secs()) - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let seconds = u64::deserialize(deserializer)?; - - Ok(LocalTime::from_secs(seconds)) - } - } - - pub mod option { - pub mod time { - use localtime::LocalTime; - use serde::{Deserialize, Deserializer, Serializer}; - - pub fn serialize(value: &Option, serializer: S) -> Result - where - S: Serializer, - { - match value { - Some(time) => serializer.serialize_some(&time.as_secs()), - None => serializer.serialize_none(), - } - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - let option = Option::::deserialize(deserializer)?; - match option { - Some(seconds) => Ok(Some(LocalTime::from_secs(seconds))), - None => Ok(None), - } - } - } - } - - pub mod duration { - use localtime::LocalDuration; - use serde::{Deserialize, Deserializer, Serializer}; - - pub fn serialize(value: &LocalDuration, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_u64(value.as_secs()) - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let seconds = u64::deserialize(deserializer)?; - - Ok(LocalDuration::from_secs(seconds)) - } - } -} - /// Return true if the given value is the default for that type. pub fn is_default(t: &T) -> bool { t == &T::default() @@ -119,53 +45,3 @@ where let v: serde_json::Value = serde::Deserialize::deserialize(deserializer)?; Ok(T::deserialize(v).unwrap_or_default()) } - -#[cfg(test)] -#[allow(clippy::unwrap_used)] -mod test { - use super::*; - - use ::localtime::LocalTime; - - #[test] - fn test_localtime() { - #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Eq)] - struct Test { - time: LocalTime, - } - let value = Test { - time: LocalTime::from_millis(1699636852107), - }; - - assert_eq!( - serde_json::from_str::(r#"{"time":1699636852107}"#).unwrap(), - value - ); - assert_eq!( - serde_json::from_str::(serde_json::to_string(&value).unwrap().as_str()).unwrap(), - value - ); - } - - #[test] - // Tests serialization into seconds instead of milliseconds. - fn test_localtime_ext() { - #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Eq)] - struct Test { - #[serde(with = "localtime::time")] - time: LocalTime, - } - let value = Test { - time: LocalTime::from_secs(1699636852107), - }; - - assert_eq!( - serde_json::from_str::(r#"{"time":1699636852107}"#).unwrap(), - value - ); - assert_eq!( - serde_json::from_str::(serde_json::to_string(&value).unwrap().as_str()).unwrap(), - value - ); - } -}