Skip to main content

salvor_replay/
id.rs

1//! Identity types for runs and the positions of events within a run.
2//!
3//! These are newtypes, not aliases: [`RunId`] and [`SequenceNumber`] are
4//! distinct types the compiler will not let you mix, even though one wraps a
5//! `Uuid` and the other a `u64`. A function that wants a run identifier cannot
6//! be handed a sequence number by mistake.
7
8use core::fmt;
9
10use serde::{Deserialize, Serialize};
11use uuid::Uuid;
12
13/// Identifies a single run.
14///
15/// Backed by a version 4 (random) UUID. Serializes as its UUID string, so a
16/// recorded run identity reads the same in the event log as it does anywhere
17/// else the UUID appears.
18///
19/// # Purity
20///
21/// [`RunId::new`] draws randomness to mint a fresh UUID, so it is an explicit
22/// constructor the caller invokes on purpose, and it sits behind the default-on
23/// `rng` feature. Nothing else in this crate draws randomness; the event types
24/// never generate identity on their own. A build with `--no-default-features`
25/// (the wasm32 build) drops `new` entirely, which is how the crate proves it
26/// contains no randomness source.
27#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
28#[serde(transparent)]
29pub struct RunId(Uuid);
30
31#[cfg(feature = "rng")]
32impl RunId {
33    /// Mints a fresh, random run identifier.
34    ///
35    /// This draws from the system random source, so treat it as a deliberate
36    /// side effect at the edge of the runtime, never something the pure event
37    /// or replay paths reach for. It lives behind the `rng` feature because the
38    /// underlying `getrandom` source does not build for
39    /// `wasm32-unknown-unknown`.
40    // `new` here is a randomness-drawing constructor, not a cheap default, so a
41    // `Default` impl would misrepresent it.
42    #[allow(clippy::new_without_default)]
43    #[must_use]
44    pub fn new() -> Self {
45        Self(Uuid::new_v4())
46    }
47}
48
49impl RunId {
50    /// Wraps an existing UUID as a run identifier.
51    ///
52    /// Use this to reconstruct a `RunId` read back from storage, where the
53    /// UUID already exists and must not be regenerated.
54    #[must_use]
55    pub const fn from_uuid(uuid: Uuid) -> Self {
56        Self(uuid)
57    }
58
59    /// Returns the underlying UUID.
60    #[must_use]
61    pub const fn as_uuid(&self) -> Uuid {
62        self.0
63    }
64}
65
66/// A monotonic position within a run's event log.
67///
68/// The envelope carries one to order events; some event payloads carry one to
69/// correlate a completion with the request that opened it. Ordering is the
70/// point of the type, so it derives [`Ord`]: a smaller number happened earlier.
71#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
72#[serde(transparent)]
73pub struct SequenceNumber(u64);
74
75impl SequenceNumber {
76    /// Constructs a sequence number from a raw position.
77    #[must_use]
78    pub const fn new(value: u64) -> Self {
79        Self(value)
80    }
81
82    /// Returns the raw position.
83    #[must_use]
84    pub const fn get(self) -> u64 {
85        self.0
86    }
87
88    /// Returns the next position after this one.
89    #[must_use]
90    pub const fn next(self) -> Self {
91        Self(self.0 + 1)
92    }
93}
94
95impl fmt::Display for SequenceNumber {
96    /// Displays as the bare position (`7`), so error messages and logs can
97    /// name a log position without the newtype wrapper showing through.
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        self.0.fmt(f)
100    }
101}