1use crate::Error;
4use serde::{Deserialize, Deserializer, Serialize, de};
5use std::fmt;
6
7macro_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 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 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}