Skip to main content

paramodel_elements/
ids.rs

1// Copyright (c) Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! Machine-generated ULID identifiers.
5//!
6//! Per SRD-0003 D8, paramodel uses ULIDs for identities that need to be
7//! unique, lexicographically sortable by creation time, and portable
8//! across processes. [`TrialId`] is the canonical trial identifier; it
9//! shows up in `TrialContext`, `Sequence` coordinates, and anywhere the
10//! system needs to refer to a single trial run.
11//!
12//! Constructors take a pre-generated `Ulid` rather than calling
13//! `Ulid::new()` internally — generation policy (seeded or
14//! thread-random) belongs at the plan-executor layer so paramodel-core
15//! stays free of hidden mutable state (SRD-0003 R9).
16
17use serde::{Deserialize, Serialize};
18use ulid::Ulid;
19
20/// Unique identifier of one trial run.
21///
22/// Lexicographic ordering on the underlying ULID orders trials by
23/// creation instant, so a sorted list of `TrialId`s is also sorted by
24/// time-of-creation within a single generator stream.
25#[derive(
26    Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
27)]
28#[serde(transparent)]
29pub struct TrialId(Ulid);
30
31impl TrialId {
32    /// Construct from an existing ULID.
33    #[must_use]
34    pub const fn from_ulid(u: Ulid) -> Self {
35        Self(u)
36    }
37
38    /// Borrow the inner ULID.
39    #[must_use]
40    pub const fn as_ulid(&self) -> &Ulid {
41        &self.0
42    }
43
44    /// Consume and return the inner ULID.
45    #[must_use]
46    pub const fn into_ulid(self) -> Ulid {
47        self.0
48    }
49}
50
51impl std::fmt::Display for TrialId {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        self.0.fmt(f)
54    }
55}
56
57impl From<Ulid> for TrialId {
58    fn from(u: Ulid) -> Self {
59        Self(u)
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn trial_id_roundtrips_ulid() {
69        let u = Ulid::from_parts(1_700_000_000_000, 42);
70        let t = TrialId::from_ulid(u);
71        assert_eq!(t.as_ulid(), &u);
72        assert_eq!(t.into_ulid(), u);
73    }
74
75    #[test]
76    fn trial_id_display_matches_ulid_display() {
77        let u = Ulid::from_parts(1_700_000_000_000, 42);
78        let t = TrialId::from_ulid(u);
79        assert_eq!(format!("{t}"), format!("{u}"));
80    }
81
82    #[test]
83    fn trial_id_is_lexicographically_ordered_by_time() {
84        let earlier = TrialId::from_ulid(Ulid::from_parts(1_000, 0));
85        let later = TrialId::from_ulid(Ulid::from_parts(2_000, 0));
86        assert!(earlier < later);
87    }
88
89    #[test]
90    fn trial_id_serde_roundtrip() {
91        let t = TrialId::from_ulid(Ulid::from_parts(1_700_000_000_000, 42));
92        let json = serde_json::to_string(&t).unwrap();
93        let back: TrialId = serde_json::from_str(&json).unwrap();
94        assert_eq!(t, back);
95    }
96}