agentmux_srv\backend/
oref.rs1use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
9use std::fmt;
10use uuid::Uuid;
11
12use super::obj::VALID_OTYPES;
13
14#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
17pub struct ORef {
18 pub otype: String,
19 pub oid: String,
20}
21
22impl ORef {
23 #[allow(dead_code)]
24 pub fn new(otype: impl Into<String>, oid: impl Into<String>) -> Self {
25 Self {
26 otype: otype.into(),
27 oid: oid.into(),
28 }
29 }
30
31 pub fn is_empty(&self) -> bool {
32 self.otype.is_empty() || self.oid.is_empty()
33 }
34
35 pub fn parse(s: &str) -> Result<Self, ORefParseError> {
37 if s.is_empty() {
38 return Ok(Self {
39 otype: String::new(),
40 oid: String::new(),
41 });
42 }
43
44 let parts: Vec<&str> = s.splitn(2, ':').collect();
45 if parts.len() != 2 {
46 return Err(ORefParseError::InvalidFormat(s.to_string()));
47 }
48
49 let otype = parts[0];
50 let oid = parts[1];
51
52 if otype.is_empty() || !otype.chars().all(|c| c.is_ascii_lowercase()) {
54 return Err(ORefParseError::InvalidOType(otype.to_string()));
55 }
56
57 if !VALID_OTYPES.contains(&otype) {
58 return Err(ORefParseError::UnknownOType(otype.to_string()));
59 }
60
61 Uuid::parse_str(oid).map_err(|_| ORefParseError::InvalidOID(oid.to_string()))?;
63
64 Ok(Self {
65 otype: otype.to_string(),
66 oid: oid.to_string(),
67 })
68 }
69}
70
71impl fmt::Display for ORef {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 if self.is_empty() {
74 return write!(f, "");
75 }
76 write!(f, "{}:{}", self.otype, self.oid)
77 }
78}
79
80#[derive(Debug, Clone, thiserror::Error)]
82pub enum ORefParseError {
83 #[error("invalid object reference: {0:?}")]
84 InvalidFormat(String),
85 #[error("invalid object type: {0:?}")]
86 InvalidOType(String),
87 #[error("unknown object type: {0:?}")]
88 UnknownOType(String),
89 #[error("invalid object id: {0:?}")]
90 InvalidOID(String),
91}
92
93impl Serialize for ORef {
96 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
97 serializer.serialize_str(&self.to_string())
98 }
99}
100
101impl<'de> Deserialize<'de> for ORef {
102 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
103 let s = String::deserialize(deserializer)?;
104 ORef::parse(&s).map_err(de::Error::custom)
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn test_oref_roundtrip() {
114 let oref = ORef::new("block", "550e8400-e29b-41d4-a716-446655440000");
115 let json = serde_json::to_string(&oref).unwrap();
116 assert_eq!(json, r#""block:550e8400-e29b-41d4-a716-446655440000""#);
117
118 let parsed: ORef = serde_json::from_str(&json).unwrap();
119 assert_eq!(parsed, oref);
120 }
121
122 #[test]
123 fn test_oref_empty() {
124 let oref = ORef::parse("").unwrap();
125 assert!(oref.is_empty());
126 let json = serde_json::to_string(&oref).unwrap();
127 assert_eq!(json, r#""""#);
128 }
129
130 #[test]
131 fn test_oref_all_valid_types() {
132 let uuid = "550e8400-e29b-41d4-a716-446655440000";
133 for otype in VALID_OTYPES {
134 let s = format!("{otype}:{uuid}");
135 let oref = ORef::parse(&s).unwrap();
136 assert_eq!(oref.otype, *otype);
137 assert_eq!(oref.oid, uuid);
138 }
139 }
140
141 #[test]
142 fn test_oref_invalid_otype() {
143 let result = ORef::parse("BLOCK:550e8400-e29b-41d4-a716-446655440000");
144 assert!(result.is_err());
145 assert!(matches!(result, Err(ORefParseError::InvalidOType(_))));
146 }
147
148 #[test]
149 fn test_oref_unknown_otype() {
150 let result = ORef::parse("foobar:550e8400-e29b-41d4-a716-446655440000");
151 assert!(result.is_err());
152 assert!(matches!(result, Err(ORefParseError::UnknownOType(_))));
153 }
154
155 #[test]
156 fn test_oref_invalid_uuid() {
157 let result = ORef::parse("block:not-a-uuid");
158 assert!(result.is_err());
159 assert!(matches!(result, Err(ORefParseError::InvalidOID(_))));
160 }
161
162 #[test]
163 fn test_oref_no_colon() {
164 let result = ORef::parse("blockuuid");
165 assert!(result.is_err());
166 assert!(matches!(result, Err(ORefParseError::InvalidFormat(_))));
167 }
168
169 #[test]
170 fn test_oref_display() {
171 let oref = ORef::new("tab", "550e8400-e29b-41d4-a716-446655440000");
172 assert_eq!(oref.to_string(), "tab:550e8400-e29b-41d4-a716-446655440000");
173 }
174}