a2a_protocol_types/
artifact.rs1use serde::{Deserialize, Serialize};
14
15use crate::message::Part;
16
17#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
23pub struct ArtifactId(pub String);
24
25impl ArtifactId {
26 #[must_use]
28 pub fn new(s: impl Into<String>) -> Self {
29 Self(s.into())
30 }
31}
32
33impl std::fmt::Display for ArtifactId {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 f.write_str(&self.0)
36 }
37}
38
39impl From<String> for ArtifactId {
40 fn from(s: String) -> Self {
41 Self(s)
42 }
43}
44
45impl From<&str> for ArtifactId {
46 fn from(s: &str) -> Self {
47 Self(s.to_owned())
48 }
49}
50
51impl AsRef<str> for ArtifactId {
52 fn as_ref(&self) -> &str {
53 &self.0
54 }
55}
56
57#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64#[serde(rename_all = "camelCase")]
65pub struct Artifact {
66 #[serde(rename = "artifactId")]
68 pub id: ArtifactId,
69
70 #[serde(skip_serializing_if = "Option::is_none")]
72 pub name: Option<String>,
73
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub description: Option<String>,
77
78 pub parts: Vec<Part>,
83
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub extensions: Option<Vec<String>>,
87
88 #[serde(skip_serializing_if = "Option::is_none")]
90 pub metadata: Option<serde_json::Value>,
91}
92
93impl Artifact {
94 #[must_use]
96 pub fn new(id: impl Into<ArtifactId>, parts: Vec<Part>) -> Self {
97 Self {
98 id: id.into(),
99 name: None,
100 description: None,
101 parts,
102 extensions: None,
103 metadata: None,
104 }
105 }
106}
107
108#[cfg(test)]
111mod tests {
112 use super::*;
113 use crate::message::Part;
114
115 #[test]
116 fn artifact_roundtrip() {
117 let artifact = Artifact::new("art-1", vec![Part::text("result content")]);
118 let json = serde_json::to_string(&artifact).expect("serialize");
119 assert!(json.contains("\"artifactId\":\"art-1\""));
120 assert!(json.contains("\"text\":\"result content\""));
121
122 let back: Artifact = serde_json::from_str(&json).expect("deserialize");
123 assert_eq!(back.id, ArtifactId::new("art-1"));
124 assert_eq!(back.parts.len(), 1);
125 }
126
127 #[test]
128 fn optional_fields_omitted() {
129 let artifact = Artifact::new("art-2", vec![Part::text("x")]);
130 let json = serde_json::to_string(&artifact).expect("serialize");
131 assert!(!json.contains("\"name\""), "name should be omitted");
132 assert!(
133 !json.contains("\"description\""),
134 "description should be omitted"
135 );
136 assert!(!json.contains("\"metadata\""), "metadata should be omitted");
137 }
138
139 #[test]
140 fn artifact_id_from_string() {
141 let id: ArtifactId = String::from("art-from-string").into();
142 assert_eq!(id, ArtifactId::new("art-from-string"));
143 }
144
145 #[test]
146 fn artifact_id_from_str() {
147 let id: ArtifactId = "art-from-str".into();
148 assert_eq!(id, ArtifactId::new("art-from-str"));
149 }
150
151 #[test]
152 fn artifact_id_as_ref() {
153 let id = ArtifactId::new("ref-test");
154 assert_eq!(id.as_ref(), "ref-test");
155 }
156
157 #[test]
158 fn artifact_new_optional_fields_are_none() {
159 let a = Artifact::new("id", vec![Part::text("x")]);
160 assert!(a.name.is_none());
161 assert!(a.description.is_none());
162 assert!(a.extensions.is_none());
163 assert!(a.metadata.is_none());
164 }
165
166 #[test]
167 fn artifact_id_display() {
168 let id = ArtifactId::new("my-artifact");
169 assert_eq!(id.to_string(), "my-artifact");
170 }
171}