tc_transact/
id.rs

1//! A transaction ID
2
3use std::cmp::Ordering;
4use std::fmt;
5use std::str::FromStr;
6
7use async_hash::{Digest, Hash, Output};
8use async_trait::async_trait;
9use get_size::GetSize;
10use get_size_derive::*;
11use rand::Rng;
12use safecast::TryCastFrom;
13
14use destream::IntoStream;
15
16use tc_error::*;
17use tcgeneric::{Id, NetworkTime};
18
19/// A zero-values [`TxnId`].
20pub const MIN_ID: TxnId = TxnId {
21    timestamp: 0,
22    nonce: 0,
23};
24
25/// The unique ID of a transaction, used for identity and ordering.
26#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, GetSize)]
27pub struct TxnId {
28    timestamp: u64, // nanoseconds since Unix epoch
29    nonce: u16,
30}
31
32impl TxnId {
33    /// Construct a new `TxnId`.
34    pub fn new(time: NetworkTime) -> Self {
35        let mut rng = rand::thread_rng();
36        let nonce = loop {
37            let nonce = rng.gen();
38            if nonce > 0 && nonce < (u16::MAX - 1) {
39                break nonce;
40            }
41        };
42
43        Self {
44            timestamp: time.as_nanos(),
45            nonce,
46        }
47    }
48
49    /// Return the last valid TxnId before this one (e.g. to construct a range).
50    pub fn prev(&self) -> Self {
51        Self {
52            timestamp: self.timestamp,
53            nonce: self.nonce - 1,
54        }
55    }
56
57    /// Return the timestamp of this `TxnId`.
58    pub fn time(&self) -> NetworkTime {
59        NetworkTime::from_nanos(self.timestamp)
60    }
61
62    /// Convert this `TxnId` into an [`Id`].
63    pub fn to_id(&self) -> Id {
64        Id::try_cast_from(self.to_string(), |_| {
65            unreachable!("number failed ID validation")
66        })
67        .unwrap()
68    }
69}
70
71impl FromStr for TxnId {
72    type Err = TCError;
73
74    fn from_str(s: &str) -> TCResult<TxnId> {
75        let i = s
76            .find('-')
77            .ok_or_else(|| TCError::unexpected(s, "a transaction ID"))?;
78
79        if i == s.len() - 1 {
80            return Err(TCError::unexpected(s, "a transaction ID"));
81        }
82
83        let timestamp = &s[..i];
84        let nonce = &s[i + 1..];
85
86        let timestamp = timestamp
87            .parse()
88            .map_err(|cause| TCError::unexpected(timestamp, "a timestamp").consume(cause))?;
89
90        let nonce = nonce
91            .parse()
92            .map_err(|cause| TCError::unexpected(nonce, "a nonce").consume(cause))?;
93
94        Ok(TxnId { timestamp, nonce })
95    }
96}
97
98impl Ord for TxnId {
99    fn cmp(&self, other: &TxnId) -> std::cmp::Ordering {
100        if self.timestamp == other.timestamp {
101            self.nonce.cmp(&other.nonce)
102        } else {
103            self.timestamp.cmp(&other.timestamp)
104        }
105    }
106}
107
108impl PartialOrd for TxnId {
109    fn partial_cmp(&self, other: &TxnId) -> Option<std::cmp::Ordering> {
110        Some(self.cmp(other))
111    }
112}
113
114impl PartialEq<str> for TxnId {
115    fn eq(&self, other: &str) -> bool {
116        if let Ok(other) = Self::from_str(other) {
117            self == &other
118        } else {
119            false
120        }
121    }
122}
123
124impl PartialEq<Id> for TxnId {
125    fn eq(&self, other: &Id) -> bool {
126        self == other.as_str()
127    }
128}
129
130impl PartialOrd<str> for TxnId {
131    fn partial_cmp(&self, other: &str) -> Option<Ordering> {
132        if let Ok(other) = Self::from_str(other) {
133            self.partial_cmp(&other)
134        } else {
135            None
136        }
137    }
138}
139
140impl freqfs::Name for TxnId {
141    fn partial_cmp(&self, other: &String) -> Option<Ordering> {
142        if let Ok(other) = Self::from_str(other) {
143            Some(self.cmp(&other))
144        } else {
145            None
146        }
147    }
148}
149
150#[async_trait]
151impl destream::de::FromStream for TxnId {
152    type Context = ();
153
154    async fn from_stream<D: destream::de::Decoder>(
155        context: (),
156        d: &mut D,
157    ) -> Result<Self, D::Error> {
158        let s = <String as destream::de::FromStream>::from_stream(context, d).await?;
159        Self::from_str(&s).map_err(destream::de::Error::custom)
160    }
161}
162
163impl<'en> destream::en::IntoStream<'en> for TxnId {
164    fn into_stream<E: destream::en::Encoder<'en>>(self, e: E) -> Result<E::Ok, E::Error> {
165        self.to_string().into_stream(e)
166    }
167}
168
169impl<'en> destream::en::ToStream<'en> for TxnId {
170    fn to_stream<E: destream::en::Encoder<'en>>(&'en self, e: E) -> Result<E::Ok, E::Error> {
171        self.to_string().into_stream(e)
172    }
173}
174
175impl<D: Digest> Hash<D> for TxnId {
176    fn hash(self) -> Output<D> {
177        Hash::<D>::hash(&self)
178    }
179}
180
181impl<'a, D: Digest> Hash<D> for &'a TxnId {
182    fn hash(self) -> Output<D> {
183        let mut bytes = [0u8; 10];
184        bytes[..8].copy_from_slice(&self.timestamp.to_be_bytes());
185        bytes[8..].copy_from_slice(&self.nonce.to_be_bytes());
186        D::digest(&bytes)
187    }
188}
189
190impl fmt::Display for TxnId {
191    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
192        write!(f, "{}-{}", self.timestamp, self.nonce)
193    }
194}