From 2e56ab693223752c5af8503f3ba5939839a6fb96 Mon Sep 17 00:00:00 2001 From: nitro-neal <5314059+nitro-neal@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:59:20 -0500 Subject: [PATCH] Add key sort (#140) * Add key sort * add new error type --- crates/tbdex/src/errors.rs | 3 +++ crates/tbdex/src/messages/mod.rs | 19 +++++++++++++++++-- crates/tbdex/src/messages/rfq.rs | 14 ++++++++++++++ crates/tbdex/src/resources/mod.rs | 20 +++++++++++++++++--- crates/tbdex/src/resources/offering.rs | 15 +++++++++++++++ 5 files changed, 66 insertions(+), 5 deletions(-) diff --git a/crates/tbdex/src/errors.rs b/crates/tbdex/src/errors.rs index ef65b17d..1fbe14e3 100644 --- a/crates/tbdex/src/errors.rs +++ b/crates/tbdex/src/errors.rs @@ -33,6 +33,9 @@ pub enum TbdexError { Web5Error(#[from] Web5Error), #[error(transparent)] ErrorResponseBody(#[from] ErrorResponseBody), + + #[error("{0}")] + Generic(String), } impl From for TbdexError { diff --git a/crates/tbdex/src/messages/mod.rs b/crates/tbdex/src/messages/mod.rs index 187b73d8..6410abbb 100644 --- a/crates/tbdex/src/messages/mod.rs +++ b/crates/tbdex/src/messages/mod.rs @@ -19,8 +19,10 @@ use quote::Quote; use rfq::Rfq; use serde::{de::Visitor, Deserialize, Deserializer, Serialize}; use std::{fmt, str::FromStr, sync::Arc}; +use std::sync::atomic::{AtomicU64, Ordering}; +use chrono::Utc; use type_safe_id::{DynamicType, TypeSafeId}; -use uuid::Uuid; +use uuid::{NoContext, Timestamp, Uuid}; #[derive(Debug, Default, Deserialize, PartialEq, Serialize, Clone)] #[serde(rename_all = "lowercase")] @@ -66,10 +68,23 @@ impl fmt::Display for MessageKind { } } +static COUNTER: AtomicU64 = AtomicU64::new(0); + impl MessageKind { pub fn typesafe_id(&self) -> Result { let dynamic_type = DynamicType::new(&self.to_string())?; - Ok(TypeSafeId::from_type_and_uuid(dynamic_type, Uuid::new_v4()).to_string()) + let timestamp_nanos = Utc::now() + .timestamp_nanos_opt() + .ok_or_else(|| TbdexError::Generic("Failed to get timestamp nanos".to_string()))?; + + let seconds = (timestamp_nanos / 1_000_000_000) as u64; + let subsec_nanos = (timestamp_nanos % 1_000_000_000) as u32; + + let count = COUNTER.fetch_add(1, Ordering::SeqCst); + let unique_seconds = seconds.wrapping_add(count); + + let timestamp = Timestamp::from_unix(NoContext, unique_seconds, subsec_nanos); + Ok(TypeSafeId::from_type_and_uuid(dynamic_type, Uuid::new_v7(timestamp)).to_string()) } } diff --git a/crates/tbdex/src/messages/rfq.rs b/crates/tbdex/src/messages/rfq.rs index e2c1adfd..4d3455dd 100644 --- a/crates/tbdex/src/messages/rfq.rs +++ b/crates/tbdex/src/messages/rfq.rs @@ -633,6 +633,20 @@ mod tests { assert_eq!(rfq, parsed_rfq); } + + #[test] + fn test_typesafe_id_sorting() { + let resource_kind = MessageKind::Rfq; + let ids: Vec = (0..1000).map(|_| resource_kind.typesafe_id().unwrap()).collect(); + + let mut sorted_ids = ids.clone(); + sorted_ids.sort(); + + assert_eq!(ids, sorted_ids, "IDs should be generated in sortable order"); + + let unique_ids: std::collections::HashSet<_> = ids.into_iter().collect(); + assert_eq!(unique_ids.len(), 1000, "All generated IDs should be unique"); + } } #[cfg(test)] diff --git a/crates/tbdex/src/resources/mod.rs b/crates/tbdex/src/resources/mod.rs index 2b70db85..5192ad9b 100644 --- a/crates/tbdex/src/resources/mod.rs +++ b/crates/tbdex/src/resources/mod.rs @@ -2,11 +2,12 @@ pub mod balance; pub mod offering; use std::{fmt, str::FromStr}; - +use std::sync::atomic::{AtomicU64, Ordering}; +use chrono::Utc; use crate::errors::{Result, TbdexError}; use serde::{Deserialize, Serialize}; use type_safe_id::{DynamicType, TypeSafeId}; -use uuid::Uuid; +use uuid::{NoContext, Timestamp, Uuid}; #[derive(Debug, Deserialize, PartialEq, Serialize, Clone)] #[serde(rename_all = "lowercase")] @@ -36,10 +37,23 @@ impl fmt::Display for ResourceKind { } } +static COUNTER: AtomicU64 = AtomicU64::new(0); + impl ResourceKind { pub fn typesafe_id(&self) -> Result { let dynamic_type = DynamicType::new(&self.to_string())?; - Ok(TypeSafeId::from_type_and_uuid(dynamic_type, Uuid::new_v4()).to_string()) + let timestamp_nanos = Utc::now() + .timestamp_nanos_opt() + .ok_or_else(|| TbdexError::Generic("Failed to get timestamp nanos".to_string()))?; + + let seconds = (timestamp_nanos / 1_000_000_000) as u64; + let subsec_nanos = (timestamp_nanos % 1_000_000_000) as u32; + + let count = COUNTER.fetch_add(1, Ordering::SeqCst); + let unique_seconds = seconds.wrapping_add(count); + + let timestamp = Timestamp::from_unix(NoContext, unique_seconds, subsec_nanos); + Ok(TypeSafeId::from_type_and_uuid(dynamic_type, Uuid::new_v7(timestamp)).to_string()) } } diff --git a/crates/tbdex/src/resources/offering.rs b/crates/tbdex/src/resources/offering.rs index 590ed7ba..98800afb 100644 --- a/crates/tbdex/src/resources/offering.rs +++ b/crates/tbdex/src/resources/offering.rs @@ -326,6 +326,21 @@ mod tests { assert_eq!(offering, parsed_offering); } + + #[test] + fn test_typesafe_id_sorting() { + let resource_kind = ResourceKind::Offering; + + let ids: Vec = (0..1000).map(|_| resource_kind.typesafe_id().unwrap()).collect(); + + let mut sorted_ids = ids.clone(); + sorted_ids.sort(); + + assert_eq!(ids, sorted_ids, "IDs should be generated in sortable order"); + + let unique_ids: std::collections::HashSet<_> = ids.into_iter().collect(); + assert_eq!(unique_ids.len(), 1000, "All generated IDs should be unique"); + } } #[cfg(test)]