Skip to main content

platonic_core/
ids.rs

1//! Compact identifier newtypes used across the harness event ledger.
2
3use crate::Error;
4use serde::{Deserialize, Deserializer, Serialize, de};
5use std::fmt;
6
7/// Defines a compact string-backed identifier newtype.
8macro_rules! id_type {
9    ($name:ident, $doc:literal) => {
10        #[doc = $doc]
11        #[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize)]
12        pub struct $name(String);
13
14        impl $name {
15            /// Rejects empty or all-whitespace input and otherwise stores it verbatim.
16            pub fn new(value: impl Into<String>) -> Result<Self, Error> {
17                let value = value.into();
18                if value.trim().is_empty() {
19                    return Err(Error::EmptyIdentifier(stringify!($name)));
20                }
21                Ok(Self(value))
22            }
23
24            /// Borrows the exact stored identifier value.
25            pub fn as_str(&self) -> &str {
26                &self.0
27            }
28        }
29
30        impl fmt::Display for $name {
31            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32                f.write_str(&self.0)
33            }
34        }
35
36        impl<'de> Deserialize<'de> for $name {
37            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
38            where
39                D: Deserializer<'de>,
40            {
41                let value = String::deserialize(deserializer)?;
42                Self::new(value).map_err(de::Error::custom)
43            }
44        }
45    };
46}
47
48id_type!(RunId, "Identifier for one durable harness run.");
49id_type!(TurnId, "Identifier for one model/tool turn inside a run.");
50id_type!(AgentId, "Identifier for one bounded agent unit.");
51id_type!(
52    ToolCallId,
53    "Identifier for one host-validated tool invocation."
54);
55id_type!(
56    ArtifactId,
57    "Identifier for a durable artifact emitted by a run."
58);
59id_type!(ToolName, "Stable registered tool name.");
60id_type!(
61    ModelName,
62    "Stable model identifier selected for a request or reported for a response."
63);
64id_type!(ActorId, "Identifier for a human or host approval actor.");
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn identifiers_reject_empty_values() {
72        assert!(matches!(
73            RunId::new("  "),
74            Err(Error::EmptyIdentifier("RunId"))
75        ));
76    }
77
78    #[test]
79    fn identifiers_display_their_inner_value() {
80        let id = AgentId::new("agent_alpha").unwrap();
81        assert_eq!(id.to_string(), "agent_alpha");
82        assert_eq!(id.as_str(), "agent_alpha");
83    }
84
85    #[test]
86    fn identifiers_reject_empty_json_values() {
87        let err = serde_json::from_str::<RunId>("\"  \"").unwrap_err();
88        assert!(err.to_string().contains("RunId cannot be empty"));
89    }
90}