1use jiff::Timestamp;
4
5use serde::{Deserialize, Serialize};
6use ulid::Ulid;
7
8use crate::agent::AgentName;
9
10#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
15#[serde(transparent)]
16pub struct RunId(String);
17
18impl RunId {
19 #[must_use]
21 pub fn generate() -> Self {
22 Self(format!("run_{}", Ulid::new()))
23 }
24
25 #[must_use]
27 pub fn as_str(&self) -> &str {
28 &self.0
29 }
30}
31
32impl From<&str> for RunId {
33 fn from(value: &str) -> Self {
34 Self(value.to_owned())
35 }
36}
37
38impl std::fmt::Display for RunId {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 f.write_str(&self.0)
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
46#[serde(transparent)]
47pub struct FlightId(String);
48
49impl FlightId {
50 #[must_use]
52 pub fn generate() -> Self {
53 Self(format!("flt_{}", Ulid::new()))
54 }
55
56 #[must_use]
58 pub fn as_str(&self) -> &str {
59 &self.0
60 }
61}
62
63impl From<&str> for FlightId {
70 fn from(value: &str) -> Self {
71 Self(value.to_owned())
72 }
73}
74
75impl std::fmt::Display for FlightId {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 f.write_str(&self.0)
78 }
79}
80
81#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
83#[serde(transparent)]
84pub struct ItineraryId(String);
85
86impl ItineraryId {
87 #[must_use]
89 pub fn generate() -> Self {
90 Self(format!("itn_{}", Ulid::new()))
91 }
92
93 #[must_use]
95 pub fn as_str(&self) -> &str {
96 &self.0
97 }
98}
99
100#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
105#[serde(rename_all = "snake_case")]
106pub enum Origin {
107 Human,
109 Agent(AgentName),
111}
112
113impl Origin {
114 #[must_use]
116 pub fn agent(&self) -> Option<&AgentName> {
117 match self {
118 Self::Human => None,
119 Self::Agent(name) => Some(name),
120 }
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
130pub struct Flight {
131 pub id: FlightId,
133 pub itinerary: ItineraryId,
135 pub from: Origin,
137 pub to: AgentName,
139 pub body: String,
141 pub hops_remaining: u32,
143 pub sent_at: Timestamp,
145}
146
147impl Flight {
148 #[must_use]
150 pub fn new(
151 itinerary: ItineraryId,
152 from: Origin,
153 to: AgentName,
154 body: impl Into<String>,
155 hops_remaining: u32,
156 ) -> Self {
157 Self {
158 id: FlightId::generate(),
159 itinerary,
160 from,
161 to,
162 body: body.into(),
163 hops_remaining,
164 sent_at: Timestamp::now(),
165 }
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn identifiers_are_unique_and_prefixed() {
175 let first = FlightId::generate();
176 let second = FlightId::generate();
177
178 assert_ne!(first, second);
179 assert!(first.as_str().starts_with("flt_"));
180 assert!(ItineraryId::generate().as_str().starts_with("itn_"));
181 }
182
183 #[test]
184 fn human_origin_has_no_agent() {
185 assert!(Origin::Human.agent().is_none());
186 assert_eq!(
187 Origin::Agent("planner".into()).agent(),
188 Some(&AgentName::from("planner"))
189 );
190 }
191}