1use serde::{Deserialize, Serialize};
2use uuid::Uuid;
3macro_rules! uuid_id {
4 ($name:ident) => {
5 uuid_id!($name, new_v4);
6 };
7 ($name:ident, $mint:ident) => {
8 #[derive(
9 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
10 )]
11 #[serde(transparent)]
12 #[doc = concat!("Stable UUID identifying a ", stringify!($name), ".")]
13 pub struct $name(#[doc = "Underlying UUID."] pub Uuid);
14
15 impl $name {
16 #[must_use]
17 pub fn new() -> Self {
19 Self(Uuid::$mint())
20 }
21 }
22
23 impl Default for $name {
24 fn default() -> Self {
25 Self::new()
26 }
27 }
28
29 impl std::fmt::Display for $name {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 self.0.fmt(f)
32 }
33 }
34
35 impl std::str::FromStr for $name {
36 type Err = uuid::Error;
37
38 fn from_str(text: &str) -> Result<Self, Self::Err> {
39 Uuid::parse_str(text).map(Self)
40 }
41 }
42 };
43}
44
45uuid_id!(WorkspaceId);
46uuid_id!(ProjectId);
47uuid_id!(WorkItemId);
48uuid_id!(RequirementId);
49uuid_id!(ArtifactId);
50uuid_id!(DecisionId);
51uuid_id!(RiskId);
52uuid_id!(OperationId, now_v7);
54uuid_id!(LineageId, now_v7);
55uuid_id!(AssetId);
56uuid_id!(ExternalReferenceId);
57uuid_id!(DependencyId);
58uuid_id!(RunId, now_v7);
60uuid_id!(RunEventId, now_v7);
61
62macro_rules! time_ordered {
63 ($($name:ident),*) => {$(
64 impl $name {
65 #[must_use]
68 pub fn is_time_ordered(&self) -> bool {
69 self.0.get_version() == Some(uuid::Version::SortRand)
70 }
71 }
72 )*};
73}
74
75time_ordered!(OperationId, RunId, RunEventId);
76
77#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
78pub struct Key(#[doc = "Underlying key text."] pub String);
80
81impl Key {
82 pub fn new(value: impl Into<String>) -> Self {
84 Self(value.into())
85 }
86
87 #[must_use]
93 pub fn natural_cmp(&self, other: &Self) -> std::cmp::Ordering {
94 let (mut left, mut right) = (self.0.as_str(), other.0.as_str());
95 loop {
96 let (a, rest_a) = next_run(left);
97 let (b, rest_b) = next_run(right);
98 let order = match (a, b) {
99 (None, None) => return self.0.cmp(&other.0),
100 (None, Some(_)) => std::cmp::Ordering::Less,
101 (Some(_), None) => std::cmp::Ordering::Greater,
102 (Some(a), Some(b)) => compare_runs(a, b),
103 };
104 if order.is_ne() {
105 return order;
106 }
107 (left, right) = (rest_a, rest_b);
108 }
109 }
110}
111
112fn next_run(text: &str) -> (Option<&str>, &str) {
114 let digits = text.starts_with(|c: char| c.is_ascii_digit());
115 let end = text
116 .find(|c: char| c.is_ascii_digit() != digits)
117 .unwrap_or(text.len());
118 if end == 0 {
119 return (None, text);
120 }
121 let (run, rest) = text.split_at(end);
122 (Some(run), rest)
123}
124
125fn compare_runs(a: &str, b: &str) -> std::cmp::Ordering {
126 let numeric = |run: &str| run.starts_with(|c: char| c.is_ascii_digit());
127 if numeric(a) && numeric(b) {
128 let (a, b) = (a.trim_start_matches('0'), b.trim_start_matches('0'));
129 a.len().cmp(&b.len()).then_with(|| a.cmp(b))
130 } else {
131 a.cmp(b)
132 }
133}
134
135impl std::fmt::Display for Key {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 self.0.fmt(f)
138 }
139}
140
141#[cfg(test)]
142mod tests;