made_core/value_objects/
agent_kind.rs1use std::fmt;
11
12use serde::{Deserialize, Serialize};
13
14use crate::error::DomainError;
15
16const MAX_AGENT_KIND_LEN: usize = 64;
17
18#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
19#[serde(transparent)]
20pub struct AgentKind(String);
21
22impl AgentKind {
23 pub fn new(raw: impl Into<String>) -> Result<Self, DomainError> {
24 let trimmed = raw.into().trim().to_owned();
25 if trimmed.is_empty() {
26 return Err(DomainError::EmptyField {
27 field: "agent.kind",
28 });
29 }
30 if trimmed.len() > MAX_AGENT_KIND_LEN {
31 return Err(DomainError::FieldTooLong {
32 field: "agent.kind",
33 actual: trimmed.len(),
34 max: MAX_AGENT_KIND_LEN,
35 });
36 }
37 if trimmed.chars().any(char::is_control) {
38 return Err(DomainError::InvalidCharacters {
39 field: "agent.kind",
40 });
41 }
42 Ok(Self(trimmed))
43 }
44
45 #[must_use]
46 pub fn as_str(&self) -> &str {
47 &self.0
48 }
49
50 #[must_use]
51 pub fn into_inner(self) -> String {
52 self.0
53 }
54}
55
56impl fmt::Display for AgentKind {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 f.write_str(&self.0)
59 }
60}
61
62impl TryFrom<&str> for AgentKind {
63 type Error = DomainError;
64 fn try_from(value: &str) -> Result<Self, Self::Error> {
65 Self::new(value)
66 }
67}
68
69impl TryFrom<String> for AgentKind {
70 type Error = DomainError;
71 fn try_from(value: String) -> Result<Self, Self::Error> {
72 Self::new(value)
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn arbitrary_label_is_accepted() {
82 let k = AgentKind::new("vllm").unwrap();
83 assert_eq!(k.as_str(), "vllm");
84 }
85
86 #[test]
87 fn label_is_trimmed() {
88 assert_eq!(AgentKind::new(" noop ").unwrap().as_str(), "noop");
89 }
90
91 #[test]
92 fn empty_is_rejected() {
93 assert!(matches!(
94 AgentKind::new(" ").unwrap_err(),
95 DomainError::EmptyField {
96 field: "agent.kind"
97 }
98 ));
99 }
100
101 #[test]
102 fn overlong_is_rejected() {
103 let err = AgentKind::new("a".repeat(MAX_AGENT_KIND_LEN + 1)).unwrap_err();
104 assert!(matches!(err, DomainError::FieldTooLong { .. }));
105 }
106
107 #[test]
108 fn control_characters_are_rejected() {
109 assert!(matches!(
110 AgentKind::new("bad\nkind").unwrap_err(),
111 DomainError::InvalidCharacters {
112 field: "agent.kind"
113 }
114 ));
115 }
116
117 #[test]
118 fn display_matches_inner() {
119 assert_eq!(AgentKind::new("rule").unwrap().to_string(), "rule");
120 }
121
122 #[test]
123 fn serde_is_transparent() {
124 let k = AgentKind::new("anthropic").unwrap();
125 assert_eq!(serde_json::to_string(&k).unwrap(), "\"anthropic\"");
126 }
127}