simulator_api/
session_id.rs1use serde_with::{DeserializeFromStr, SerializeDisplay};
13use uuid::Uuid;
14
15macro_rules! prefixed_uuid_id {
17 ($name:ident, $prefix:literal, $doc:literal) => {
18 #[doc = $doc]
19 #[derive(
20 Debug,
21 Clone,
22 Copy,
23 PartialEq,
24 Eq,
25 PartialOrd,
26 Ord,
27 Hash,
28 SerializeDisplay,
29 DeserializeFromStr,
30 )]
31 pub struct $name(Uuid);
32
33 impl $name {
34 pub fn new() -> Self {
36 Self(Uuid::now_v7())
37 }
38
39 pub const PREFIX: &'static str = $prefix;
41 }
42
43 impl std::fmt::Display for $name {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 write!(f, "{}{}", $prefix, self.0.as_simple())
46 }
47 }
48
49 impl std::str::FromStr for $name {
50 type Err = uuid::Error;
51
52 fn from_str(s: &str) -> Result<Self, Self::Err> {
55 let trimmed = s.strip_prefix($prefix).unwrap_or(s);
56 Ok(Self(Uuid::parse_str(trimmed)?))
57 }
58 }
59
60 impl Default for $name {
61 fn default() -> Self {
62 Self::new()
63 }
64 }
65 };
66}
67
68prefixed_uuid_id!(SessionId, "backtest_", "Identifies one backtest session.");
69prefixed_uuid_id!(
70 ControlSessionId,
71 "parallel_",
72 "Identifies the control session grouping a parallel batch of sub-sessions."
73);
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn each_id_renders_with_its_own_prefix() {
81 assert!(SessionId::new().to_string().starts_with("backtest_"));
82 assert!(ControlSessionId::new().to_string().starts_with("parallel_"));
83 }
84
85 #[test]
86 fn ids_round_trip_through_their_string_form() {
87 let session: SessionId = SessionId::new().to_string().parse().expect("session");
88 assert_eq!(session.to_string(), session.to_string());
89
90 let control: ControlSessionId = ControlSessionId::new()
91 .to_string()
92 .parse()
93 .expect("control session");
94 assert_eq!(control.to_string(), control.to_string());
95 }
96
97 #[test]
98 fn the_wire_form_is_the_prefixed_string() {
99 let id = SessionId::new();
100 let json = serde_json::to_string(&id).expect("serialize");
101 assert_eq!(json, format!("\"{id}\""));
102 assert_eq!(
103 serde_json::from_str::<SessionId>(&json).expect("deserialize"),
104 id
105 );
106 }
107
108 #[test]
111 fn request_and_response_envelopes_keep_their_json_shape() {
112 let id = SessionId::new();
113 let json = serde_json::json!({ "sessionId": id.to_string(), "request": null });
114 let round_tripped: SessionId =
115 serde_json::from_value(json["sessionId"].clone()).expect("session id");
116 assert_eq!(round_tripped, id);
117 assert!(json["sessionId"].as_str().unwrap().starts_with("backtest_"));
118 }
119
120 #[test]
121 fn a_control_id_is_not_accepted_as_a_session_id() {
122 let control = ControlSessionId::new().to_string();
125 assert!(control.parse::<SessionId>().is_err());
126 }
127}