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}