localtime: localise the localtime dependency
The `localtime` crate was defined by cloudhead, and is a minimal repository with a single `lib.rs`. Instead of using it as an external dependency, copy the code directly into a new workspace crate `radicle-localtime`. The default `serde` implementation goes through the `LocalTime`'s seconds values rather than milliseconds, since this is the more common format. This allows the removal of the `serde_ext` functions. The one place milliseconds was used was for the `radicle::cob::common::Timestamp` type, where the `Serialize` and `Deserialize` implementations are manually written. It also adds a `schemars` feature to remove `schemars_ext` functions in `radicle` as well.
This commit is contained in:
parent
3168107df9
commit
d98033a1ff
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -9,3 +9,5 @@ pub mod project;
|
|||
pub mod terminal;
|
||||
|
||||
mod warning;
|
||||
|
||||
extern crate radicle_localtime as localtime;
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
@ -0,0 +1,325 @@
|
|||
//! Minimal, zero-dependency, monotonic, unix time library for rust.
|
||||
//!
|
||||
//! Taken from <https://github.com/cloudhead/localtime>
|
||||
|
||||
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<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;
|
||||
|
||||
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<LocalDuration> 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<LocalDuration> 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<I: Iterator<Item = &'a LocalDuration>>(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<LocalDuration> for LocalDuration {
|
||||
type Output = LocalDuration;
|
||||
|
||||
fn add(self, other: LocalDuration) -> LocalDuration {
|
||||
LocalDuration(self.0 + other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Div<u32> for LocalDuration {
|
||||
type Output = LocalDuration;
|
||||
|
||||
fn div(self, other: u32) -> LocalDuration {
|
||||
LocalDuration(self.0 / other as u128)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<u64> for LocalDuration {
|
||||
type Output = LocalDuration;
|
||||
|
||||
fn mul(self, other: u64) -> LocalDuration {
|
||||
LocalDuration(self.0 * other as u128)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LocalDuration> 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_u64(self.as_secs())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for LocalTime {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
u64::deserialize(deserializer).map(LocalTime::from_secs)
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for LocalDuration {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_u64(self.as_secs())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for LocalDuration {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
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::<Test>(r#"{"time":1699636852107}"#).unwrap(),
|
||||
value
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Test>(serde_json::to_string(&value).unwrap().as_str())
|
||||
.unwrap(),
|
||||
value
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"] }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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"] }
|
||||
|
|
|
|||
|
|
@ -6,3 +6,5 @@ pub mod worker;
|
|||
|
||||
/// Peer-to-peer protocol version.
|
||||
pub const PROTOCOL_VERSION: u8 = 1;
|
||||
|
||||
extern crate radicle_localtime as localtime;
|
||||
|
|
|
|||
|
|
@ -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"] }
|
||||
|
|
|
|||
|
|
@ -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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_u64(self.0.as_millis())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Timestamp {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
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))
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<crate::schemars_ext::localtime::LocalDurationInSeconds>")
|
||||
)]
|
||||
pub last_success: Option<LocalTime>,
|
||||
/// Last time this address was tried.
|
||||
#[serde(with = "crate::serde_ext::localtime::option::time")]
|
||||
#[cfg_attr(
|
||||
feature = "schemars",
|
||||
schemars(with = "Option<crate::schemars_ext::localtime::LocalDurationInSeconds>")
|
||||
)]
|
||||
pub last_attempt: Option<LocalTime>,
|
||||
/// Whether this address has been banned.
|
||||
pub banned: bool,
|
||||
|
|
|
|||
|
|
@ -548,14 +548,7 @@ impl From<LogLevel> 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<LocalDuration> 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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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`]
|
||||
|
|
|
|||
|
|
@ -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<S>(value: &LocalTime, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_u64(value.as_secs())
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<LocalTime, D::Error>
|
||||
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<S>(value: &Option<LocalTime>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
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<Option<LocalTime>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let option = Option::<u64>::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<S>(value: &LocalDuration, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_u64(value.as_secs())
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<LocalDuration, D::Error>
|
||||
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: Default + PartialEq>(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::<Test>(r#"{"time":1699636852107}"#).unwrap(),
|
||||
value
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Test>(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::<Test>(r#"{"time":1699636852107}"#).unwrap(),
|
||||
value
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Test>(serde_json::to_string(&value).unwrap().as_str()).unwrap(),
|
||||
value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue