1use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::collections::HashMap;
9
10use crate::meta::Annotations;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct TextResourceContents {
16 pub uri: String,
18 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
20 pub mime_type: Option<String>,
21 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
23 pub meta: Option<HashMap<String, Value>>,
24 pub text: String,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30#[serde(rename_all = "camelCase")]
31pub struct BlobResourceContents {
32 pub uri: String,
34 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
36 pub mime_type: Option<String>,
37 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
39 pub meta: Option<HashMap<String, Value>>,
40 pub blob: String,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46#[serde(untagged)]
47pub enum ResourceContents {
48 Text(TextResourceContents),
50 Blob(BlobResourceContents),
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
56#[serde(rename_all = "camelCase")]
57pub struct ResourceReference {
58 pub uri: String,
60 pub name: String,
62 #[serde(skip_serializing_if = "Option::is_none")]
64 pub title: Option<String>,
65 #[serde(skip_serializing_if = "Option::is_none")]
67 pub description: Option<String>,
68 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
70 pub mime_type: Option<String>,
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub annotations: Option<Annotations>,
74 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
76 pub meta: Option<HashMap<String, Value>>,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81#[serde(tag = "type")]
82pub enum ContentBlock {
83 #[serde(rename = "text")]
85 Text {
86 text: String,
87 #[serde(skip_serializing_if = "Option::is_none")]
88 annotations: Option<Annotations>,
89 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
90 meta: Option<HashMap<String, Value>>,
91 },
92 #[serde(rename = "image")]
94 Image {
95 data: String,
97 #[serde(rename = "mimeType")]
99 mime_type: String,
100 #[serde(skip_serializing_if = "Option::is_none")]
101 annotations: Option<Annotations>,
102 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
103 meta: Option<HashMap<String, Value>>,
104 },
105 #[serde(rename = "audio")]
107 Audio {
108 data: String,
110 #[serde(rename = "mimeType")]
112 mime_type: String,
113 #[serde(skip_serializing_if = "Option::is_none")]
114 annotations: Option<Annotations>,
115 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
116 meta: Option<HashMap<String, Value>>,
117 },
118 #[serde(rename = "resource_link")]
120 ResourceLink {
121 #[serde(flatten)]
122 resource: ResourceReference,
123 #[serde(skip_serializing_if = "Option::is_none")]
124 annotations: Option<Annotations>,
125 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
126 meta: Option<HashMap<String, Value>>,
127 },
128 #[serde(rename = "resource")]
130 Resource {
131 resource: ResourceContents,
132 #[serde(skip_serializing_if = "Option::is_none")]
133 annotations: Option<Annotations>,
134 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
135 meta: Option<HashMap<String, Value>>,
136 },
137}
138
139impl ContentBlock {
140 pub fn text(text: impl Into<String>) -> Self {
142 Self::Text {
143 text: text.into(),
144 annotations: None,
145 meta: None,
146 }
147 }
148
149 pub fn text_with_annotations(text: impl Into<String>, annotations: Annotations) -> Self {
151 Self::Text {
152 text: text.into(),
153 annotations: Some(annotations),
154 meta: None,
155 }
156 }
157
158 pub fn image(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
160 Self::Image {
161 data: data.into(),
162 mime_type: mime_type.into(),
163 annotations: None,
164 meta: None,
165 }
166 }
167
168 pub fn audio(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
170 Self::Audio {
171 data: data.into(),
172 mime_type: mime_type.into(),
173 annotations: None,
174 meta: None,
175 }
176 }
177
178 pub fn resource_link(resource: ResourceReference) -> Self {
180 Self::ResourceLink {
181 resource,
182 annotations: None,
183 meta: None,
184 }
185 }
186
187 pub fn resource(resource: ResourceContents) -> Self {
189 Self::Resource {
190 resource,
191 annotations: None,
192 meta: None,
193 }
194 }
195
196 pub fn with_annotations(mut self, annotations: Annotations) -> Self {
198 match &mut self {
199 ContentBlock::Text { annotations: a, .. }
200 | ContentBlock::Image { annotations: a, .. }
201 | ContentBlock::Audio { annotations: a, .. }
202 | ContentBlock::ResourceLink { annotations: a, .. }
203 | ContentBlock::Resource { annotations: a, .. } => {
204 *a = Some(annotations);
205 }
206 }
207 self
208 }
209
210 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
212 match &mut self {
213 ContentBlock::Text { meta: m, .. }
214 | ContentBlock::Image { meta: m, .. }
215 | ContentBlock::Audio { meta: m, .. }
216 | ContentBlock::ResourceLink { meta: m, .. }
217 | ContentBlock::Resource { meta: m, .. } => {
218 *m = Some(meta);
219 }
220 }
221 self
222 }
223}
224
225impl ResourceContents {
226 pub fn text(uri: impl Into<String>, text: impl Into<String>) -> Self {
228 Self::Text(TextResourceContents {
229 uri: uri.into(),
230 mime_type: None,
231 meta: None,
232 text: text.into(),
233 })
234 }
235
236 pub fn text_with_mime(
238 uri: impl Into<String>,
239 text: impl Into<String>,
240 mime_type: impl Into<String>,
241 ) -> Self {
242 Self::Text(TextResourceContents {
243 uri: uri.into(),
244 mime_type: Some(mime_type.into()),
245 meta: None,
246 text: text.into(),
247 })
248 }
249
250 pub fn blob(
252 uri: impl Into<String>,
253 blob: impl Into<String>,
254 mime_type: impl Into<String>,
255 ) -> Self {
256 Self::Blob(BlobResourceContents {
257 uri: uri.into(),
258 mime_type: Some(mime_type.into()),
259 meta: None,
260 blob: blob.into(),
261 })
262 }
263}
264
265impl ResourceReference {
266 pub fn new(uri: impl Into<String>, name: impl Into<String>) -> Self {
268 Self {
269 uri: uri.into(),
270 name: name.into(),
271 title: None,
272 description: None,
273 mime_type: None,
274 annotations: None,
275 meta: None,
276 }
277 }
278
279 pub fn with_title(mut self, title: impl Into<String>) -> Self {
281 self.title = Some(title.into());
282 self
283 }
284
285 pub fn with_description(mut self, description: impl Into<String>) -> Self {
287 self.description = Some(description.into());
288 self
289 }
290
291 pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
293 self.mime_type = Some(mime_type.into());
294 self
295 }
296
297 pub fn with_annotations(mut self, annotations: Annotations) -> Self {
299 self.annotations = Some(annotations);
300 self
301 }
302
303 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
305 self.meta = Some(meta);
306 self
307 }
308}
309
310impl TextResourceContents {
311 pub fn new(uri: impl Into<String>, text: impl Into<String>) -> Self {
313 Self {
314 uri: uri.into(),
315 mime_type: None,
316 meta: None,
317 text: text.into(),
318 }
319 }
320
321 pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
323 self.mime_type = Some(mime_type.into());
324 self
325 }
326
327 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
329 self.meta = Some(meta);
330 self
331 }
332}
333
334impl BlobResourceContents {
335 pub fn new(
337 uri: impl Into<String>,
338 blob: impl Into<String>,
339 mime_type: impl Into<String>,
340 ) -> Self {
341 Self {
342 uri: uri.into(),
343 mime_type: Some(mime_type.into()),
344 meta: None,
345 blob: blob.into(),
346 }
347 }
348
349 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
351 self.meta = Some(meta);
352 self
353 }
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359 use serde_json::json;
360
361 #[test]
362 fn test_resource_reference_serialization_with_annotations_and_meta() {
363 let mut meta = HashMap::new();
364 meta.insert("version".to_string(), json!("1.0"));
365 meta.insert("created_by".to_string(), json!("test"));
366
367 let resource_ref = ResourceReference::new("file:///test/data.json", "test_data")
368 .with_title("Test Data")
369 .with_description("Sample data for testing")
370 .with_mime_type("application/json")
371 .with_annotations(Annotations::new().with_title("Test Resource"))
372 .with_meta(meta);
373
374 let resource_link = ContentBlock::resource_link(resource_ref);
375
376 let json_str = serde_json::to_string(&resource_link).unwrap();
378 let deserialized: ContentBlock = serde_json::from_str(&json_str).unwrap();
379
380 if let ContentBlock::ResourceLink {
382 resource,
383 annotations,
384 meta,
385 } = deserialized
386 {
387 assert_eq!(resource.uri, "file:///test/data.json");
388 assert_eq!(resource.name, "test_data");
389 assert_eq!(resource.title, Some("Test Data".to_string()));
390 assert_eq!(
391 resource.description,
392 Some("Sample data for testing".to_string())
393 );
394 assert_eq!(resource.mime_type, Some("application/json".to_string()));
395
396 assert!(resource.annotations.is_none());
402 assert!(resource.meta.is_none());
403
404 assert!(annotations.is_some());
406 assert_eq!(
407 annotations.unwrap().title,
408 Some("Test Resource".to_string())
409 );
410
411 assert!(meta.is_some());
412 let cb_meta = meta.unwrap();
413 assert_eq!(cb_meta.get("version"), Some(&json!("1.0")));
414 assert_eq!(cb_meta.get("created_by"), Some(&json!("test")));
415 } else {
416 panic!("Expected ResourceLink variant");
417 }
418 }
419
420 #[test]
421 fn test_resource_reference_minimal() {
422 let resource_ref = ResourceReference::new("file:///minimal.txt", "minimal");
423 let resource_link = ContentBlock::resource_link(resource_ref);
424
425 let json_str = serde_json::to_string(&resource_link).unwrap();
426 let deserialized: ContentBlock = serde_json::from_str(&json_str).unwrap();
427
428 if let ContentBlock::ResourceLink { resource, .. } = deserialized {
429 assert_eq!(resource.uri, "file:///minimal.txt");
430 assert_eq!(resource.name, "minimal");
431 assert!(resource.title.is_none());
432 assert!(resource.description.is_none());
433 assert!(resource.mime_type.is_none());
434 assert!(resource.annotations.is_none());
435 assert!(resource.meta.is_none());
436 } else {
437 panic!("Expected ResourceLink variant");
438 }
439 }
440}