use serde_with::{DeserializeFromStr, SerializeDisplay};
use uuid::Uuid;
macro_rules! prefixed_uuid_id {
($name:ident, $prefix:literal, $doc:literal) => {
#[doc = $doc]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
SerializeDisplay,
DeserializeFromStr,
)]
pub struct $name(Uuid);
impl $name {
pub fn new() -> Self {
Self(Uuid::now_v7())
}
pub const PREFIX: &'static str = $prefix;
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}{}", $prefix, self.0.as_simple())
}
}
impl std::str::FromStr for $name {
type Err = uuid::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let trimmed = s.strip_prefix($prefix).unwrap_or(s);
Ok(Self(Uuid::parse_str(trimmed)?))
}
}
impl Default for $name {
fn default() -> Self {
Self::new()
}
}
};
}
prefixed_uuid_id!(SessionId, "backtest_", "Identifies one backtest session.");
prefixed_uuid_id!(
ControlSessionId,
"parallel_",
"Identifies the control session grouping a parallel batch of sub-sessions."
);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn each_id_renders_with_its_own_prefix() {
assert!(SessionId::new().to_string().starts_with("backtest_"));
assert!(ControlSessionId::new().to_string().starts_with("parallel_"));
}
#[test]
fn ids_round_trip_through_their_string_form() {
let session: SessionId = SessionId::new().to_string().parse().expect("session");
assert_eq!(session.to_string(), session.to_string());
let control: ControlSessionId = ControlSessionId::new()
.to_string()
.parse()
.expect("control session");
assert_eq!(control.to_string(), control.to_string());
}
#[test]
fn the_wire_form_is_the_prefixed_string() {
let id = SessionId::new();
let json = serde_json::to_string(&id).expect("serialize");
assert_eq!(json, format!("\"{id}\""));
assert_eq!(
serde_json::from_str::<SessionId>(&json).expect("deserialize"),
id
);
}
#[test]
fn request_and_response_envelopes_keep_their_json_shape() {
let id = SessionId::new();
let json = serde_json::json!({ "sessionId": id.to_string(), "request": null });
let round_tripped: SessionId =
serde_json::from_value(json["sessionId"].clone()).expect("session id");
assert_eq!(round_tripped, id);
assert!(json["sessionId"].as_str().unwrap().starts_with("backtest_"));
}
#[test]
fn a_control_id_is_not_accepted_as_a_session_id() {
let control = ControlSessionId::new().to_string();
assert!(control.parse::<SessionId>().is_err());
}
}