1use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use std::collections::HashMap;
8use crate::meta::Cursor;
9
10pub trait HasResourceMetadata {
16 fn name(&self) -> &str;
18
19 fn title(&self) -> Option<&str> { None }
21}
22
23pub trait HasResourceDescription {
25 fn description(&self) -> Option<&str> { None }
26}
27
28pub trait HasResourceUri {
30 fn uri(&self) -> &str;
31}
32
33pub trait HasResourceMimeType {
35 fn mime_type(&self) -> Option<&str> { None }
36}
37
38pub trait HasResourceSize {
40 fn size(&self) -> Option<u64> { None }
41}
42
43pub trait HasResourceAnnotations {
45 fn annotations(&self) -> Option<&crate::meta::Annotations> { None }
46}
47
48pub trait HasResourceMeta {
50 fn resource_meta(&self) -> Option<&HashMap<String, Value>> { None }
51}
52
53pub trait ResourceDefinition:
55 HasResourceMetadata + HasResourceDescription + HasResourceUri + HasResourceMimeType + HasResourceSize + HasResourceAnnotations + HasResourceMeta + Send +
63 Sync
64{
65 fn display_name(&self) -> &str {
67 self.title().unwrap_or_else(|| self.name())
68 }
69
70 fn to_resource(&self) -> Resource {
72 Resource {
73 uri: self.uri().to_string(),
74 name: self.name().to_string(),
75 title: self.title().map(String::from),
76 description: self.description().map(String::from),
77 mime_type: self.mime_type().map(String::from),
78 size: self.size(),
79 annotations: self.annotations().cloned(),
80 meta: self.resource_meta().cloned(),
81 }
82 }
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
88#[serde(rename_all = "camelCase")]
89pub struct ResourceTemplate {
90 pub name: String,
92 #[serde(skip_serializing_if = "Option::is_none")]
94 pub title: Option<String>,
95 #[serde(rename = "uriTemplate")]
97 pub uri_template: String,
98 #[serde(skip_serializing_if = "Option::is_none")]
100 pub description: Option<String>,
101 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
103 pub mime_type: Option<String>,
104 #[serde(skip_serializing_if = "Option::is_none")]
106 pub annotations: Option<crate::meta::Annotations>,
107 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
109 pub meta: Option<HashMap<String, Value>>,
110}
111
112impl ResourceTemplate {
113 pub fn new(name: impl Into<String>, uri_template: impl Into<String>) -> Self {
114 Self {
115 name: name.into(),
116 title: None,
117 uri_template: uri_template.into(),
118 description: None,
119 mime_type: None,
120 annotations: None,
121 meta: None,
122 }
123 }
124
125 pub fn with_title(mut self, title: impl Into<String>) -> Self {
126 self.title = Some(title.into());
127 self
128 }
129
130 pub fn with_description(mut self, description: impl Into<String>) -> Self {
131 self.description = Some(description.into());
132 self
133 }
134
135 pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
136 self.mime_type = Some(mime_type.into());
137 self
138 }
139
140 pub fn with_annotations(mut self, annotations: crate::meta::Annotations) -> Self {
141 self.annotations = Some(annotations);
142 self
143 }
144
145 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
146 self.meta = Some(meta);
147 self
148 }
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
154#[serde(rename_all = "camelCase")]
155pub struct Resource {
156 pub uri: String,
158 pub name: String,
160 #[serde(skip_serializing_if = "Option::is_none")]
162 pub title: Option<String>,
163 #[serde(skip_serializing_if = "Option::is_none")]
165 pub description: Option<String>,
166 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
168 pub mime_type: Option<String>,
169 #[serde(skip_serializing_if = "Option::is_none")]
171 pub size: Option<u64>,
172 #[serde(skip_serializing_if = "Option::is_none")]
174 pub annotations: Option<crate::meta::Annotations>,
175 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
177 pub meta: Option<HashMap<String, Value>>,
178}
179
180impl Resource {
181 pub fn new(uri: impl Into<String>, name: impl Into<String>) -> Self {
182 Self {
183 uri: uri.into(),
184 name: name.into(),
185 title: None,
186 description: None,
187 mime_type: None,
188 size: None,
189 annotations: None,
190 meta: None,
191 }
192 }
193
194 pub fn with_title(mut self, title: impl Into<String>) -> Self {
195 self.title = Some(title.into());
196 self
197 }
198
199 pub fn with_description(mut self, description: impl Into<String>) -> Self {
200 self.description = Some(description.into());
201 self
202 }
203
204 pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
205 self.mime_type = Some(mime_type.into());
206 self
207 }
208
209 pub fn with_size(mut self, size: u64) -> Self {
210 self.size = Some(size);
211 self
212 }
213
214 pub fn with_annotations(mut self, annotations: crate::meta::Annotations) -> Self {
215 self.annotations = Some(annotations);
216 self
217 }
218
219 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
220 self.meta = Some(meta);
221 self
222 }
223}
224
225impl HasResourceMetadata for Resource {
229 fn name(&self) -> &str { &self.name }
230 fn title(&self) -> Option<&str> { self.title.as_deref() }
231}
232
233impl HasResourceDescription for Resource {
234 fn description(&self) -> Option<&str> { self.description.as_deref() }
235}
236
237impl HasResourceUri for Resource {
238 fn uri(&self) -> &str { &self.uri }
239}
240
241impl HasResourceMimeType for Resource {
242 fn mime_type(&self) -> Option<&str> { self.mime_type.as_deref() }
243}
244
245impl HasResourceSize for Resource {
246 fn size(&self) -> Option<u64> { self.size }
247}
248
249impl HasResourceAnnotations for Resource {
250 fn annotations(&self) -> Option<&crate::meta::Annotations> { self.annotations.as_ref() }
251}
252
253impl HasResourceMeta for Resource {
254 fn resource_meta(&self) -> Option<&HashMap<String, Value>> { self.meta.as_ref() }
255}
256
257impl<T> ResourceDefinition for T
259where
260 T: HasResourceMetadata + HasResourceDescription + HasResourceUri + HasResourceMimeType +
261 HasResourceSize + HasResourceAnnotations + HasResourceMeta + Send + Sync
262{}
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
266#[serde(rename_all = "camelCase")]
267pub struct ListResourcesParams {
268 #[serde(skip_serializing_if = "Option::is_none")]
270 pub cursor: Option<Cursor>,
271 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
273 pub meta: Option<std::collections::HashMap<String, Value>>,
274}
275
276impl ListResourcesParams {
277 pub fn new() -> Self {
278 Self {
279 cursor: None,
280 meta: None,
281 }
282 }
283
284 pub fn with_cursor(mut self, cursor: Cursor) -> Self {
285 self.cursor = Some(cursor);
286 self
287 }
288
289 pub fn with_meta(mut self, meta: std::collections::HashMap<String, Value>) -> Self {
290 self.meta = Some(meta);
291 self
292 }
293}
294
295impl Default for ListResourcesParams {
296 fn default() -> Self {
297 Self::new()
298 }
299}
300
301#[derive(Debug, Clone, Serialize, Deserialize)]
303#[serde(rename_all = "camelCase")]
304pub struct ListResourcesRequest {
305 pub method: String,
307 pub params: ListResourcesParams,
309}
310
311impl ListResourcesRequest {
312 pub fn new() -> Self {
313 Self {
314 method: "resources/list".to_string(),
315 params: ListResourcesParams::new(),
316 }
317 }
318
319 pub fn with_cursor(mut self, cursor: Cursor) -> Self {
320 self.params = self.params.with_cursor(cursor);
321 self
322 }
323
324 pub fn with_meta(mut self, meta: std::collections::HashMap<String, Value>) -> Self {
325 self.params = self.params.with_meta(meta);
326 self
327 }
328}
329
330#[derive(Debug, Clone, Serialize, Deserialize)]
332#[serde(rename_all = "camelCase")]
333pub struct ListResourcesResult {
334 pub resources: Vec<Resource>,
336 #[serde(skip_serializing_if = "Option::is_none")]
338 pub next_cursor: Option<Cursor>,
339 #[serde(
341 default,
342 skip_serializing_if = "Option::is_none",
343 alias = "_meta",
344 rename = "_meta"
345 )]
346 pub meta: Option<std::collections::HashMap<String, Value>>,
347}
348
349impl ListResourcesResult {
350 pub fn new(resources: Vec<Resource>) -> Self {
351 Self {
352 resources,
353 next_cursor: None,
354 meta: None,
355 }
356 }
357
358 pub fn with_next_cursor(mut self, cursor: Cursor) -> Self {
359 self.next_cursor = Some(cursor);
360 self
361 }
362
363 pub fn with_meta(mut self, meta: std::collections::HashMap<String, Value>) -> Self {
364 self.meta = Some(meta);
365 self
366 }
367}
368
369#[derive(Debug, Clone, Serialize, Deserialize)]
371#[serde(rename_all = "camelCase")]
372pub struct ReadResourceParams {
373 pub uri: String,
375 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
377 pub meta: Option<std::collections::HashMap<String, Value>>,
378}
379
380impl ReadResourceParams {
381 pub fn new(uri: impl Into<String>) -> Self {
382 Self {
383 uri: uri.into(),
384 meta: None,
385 }
386 }
387
388 pub fn with_meta(mut self, meta: std::collections::HashMap<String, Value>) -> Self {
389 self.meta = Some(meta);
390 self
391 }
392}
393
394#[derive(Debug, Clone, Serialize, Deserialize)]
396#[serde(rename_all = "camelCase")]
397pub struct ReadResourceRequest {
398 pub method: String,
400 pub params: ReadResourceParams,
402}
403
404impl ReadResourceRequest {
405 pub fn new(uri: impl Into<String>) -> Self {
406 Self {
407 method: "resources/read".to_string(),
408 params: ReadResourceParams::new(uri),
409 }
410 }
411
412 pub fn with_meta(mut self, meta: std::collections::HashMap<String, Value>) -> Self {
413 self.params = self.params.with_meta(meta);
414 self
415 }
416}
417
418#[derive(Debug, Clone, Serialize, Deserialize)]
420#[serde(rename_all = "camelCase")]
421pub struct ResourceContents {
422 pub uri: String,
424 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
426 pub mime_type: Option<String>,
427 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
429 pub meta: Option<HashMap<String, Value>>,
430}
431
432#[derive(Debug, Clone, Serialize, Deserialize)]
434#[serde(rename_all = "camelCase")]
435pub struct TextResourceContents {
436 pub uri: String,
438 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
440 pub mime_type: Option<String>,
441 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
443 pub meta: Option<HashMap<String, Value>>,
444 pub text: String,
446}
447
448#[derive(Debug, Clone, Serialize, Deserialize)]
450#[serde(rename_all = "camelCase")]
451pub struct BlobResourceContents {
452 pub uri: String,
454 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
456 pub mime_type: Option<String>,
457 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
459 pub meta: Option<HashMap<String, Value>>,
460 pub blob: String,
462}
463
464#[derive(Debug, Clone, Serialize, Deserialize)]
466#[serde(untagged)]
467pub enum ResourceContent {
468 Text(TextResourceContents),
469 Blob(BlobResourceContents),
470}
471
472impl ResourceContent {
473 pub fn text(uri: impl Into<String>, text: impl Into<String>) -> Self {
474 Self::Text(TextResourceContents {
475 uri: uri.into(),
476 mime_type: Some("text/plain".to_string()),
477 meta: None,
478 text: text.into(),
479 })
480 }
481
482 pub fn blob(uri: impl Into<String>, blob: impl Into<String>, mime_type: impl Into<String>) -> Self {
483 Self::Blob(BlobResourceContents {
484 uri: uri.into(),
485 mime_type: Some(mime_type.into()),
486 meta: None,
487 blob: blob.into(),
488 })
489 }
490}
491
492#[derive(Debug, Clone, Serialize, Deserialize)]
494#[serde(rename_all = "camelCase")]
495pub struct ListResourceTemplatesParams {
496 #[serde(skip_serializing_if = "Option::is_none")]
498 pub cursor: Option<Cursor>,
499 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
501 pub meta: Option<HashMap<String, Value>>,
502}
503
504impl ListResourceTemplatesParams {
505 pub fn new() -> Self {
506 Self {
507 cursor: None,
508 meta: None,
509 }
510 }
511
512 pub fn with_cursor(mut self, cursor: Cursor) -> Self {
513 self.cursor = Some(cursor);
514 self
515 }
516
517 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
518 self.meta = Some(meta);
519 self
520 }
521}
522
523#[derive(Debug, Clone, Serialize, Deserialize)]
525#[serde(rename_all = "camelCase")]
526pub struct ListResourceTemplatesRequest {
527 pub method: String,
529 pub params: ListResourceTemplatesParams,
531}
532
533impl ListResourceTemplatesRequest {
534 pub fn new() -> Self {
535 Self {
536 method: "resources/templates/list".to_string(),
537 params: ListResourceTemplatesParams::new(),
538 }
539 }
540}
541
542#[derive(Debug, Clone, Serialize, Deserialize)]
544#[serde(rename_all = "camelCase")]
545pub struct ListResourceTemplatesResult {
546 #[serde(rename = "resourceTemplates")]
548 pub resource_templates: Vec<ResourceTemplate>,
549 #[serde(skip_serializing_if = "Option::is_none")]
551 pub next_cursor: Option<Cursor>,
552 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
554 pub meta: Option<HashMap<String, Value>>,
555}
556
557impl ListResourceTemplatesResult {
558 pub fn new(resource_templates: Vec<ResourceTemplate>) -> Self {
559 Self {
560 resource_templates,
561 next_cursor: None,
562 meta: None,
563 }
564 }
565
566 pub fn with_next_cursor(mut self, cursor: Cursor) -> Self {
567 self.next_cursor = Some(cursor);
568 self
569 }
570
571 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
572 self.meta = Some(meta);
573 self
574 }
575}
576
577#[derive(Debug, Clone, Serialize, Deserialize)]
579#[serde(rename_all = "camelCase")]
580pub struct SubscribeParams {
581 pub uri: String,
583 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
585 pub meta: Option<HashMap<String, Value>>,
586}
587
588#[derive(Debug, Clone, Serialize, Deserialize)]
590#[serde(rename_all = "camelCase")]
591pub struct SubscribeRequest {
592 pub method: String,
594 pub params: SubscribeParams,
596}
597
598impl SubscribeRequest {
599 pub fn new(uri: impl Into<String>) -> Self {
600 Self {
601 method: "resources/subscribe".to_string(),
602 params: SubscribeParams {
603 uri: uri.into(),
604 meta: None,
605 },
606 }
607 }
608
609 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
610 self.params.meta = Some(meta);
611 self
612 }
613}
614
615#[derive(Debug, Clone, Serialize, Deserialize)]
617#[serde(rename_all = "camelCase")]
618pub struct UnsubscribeParams {
619 pub uri: String,
621 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
623 pub meta: Option<HashMap<String, Value>>,
624}
625
626#[derive(Debug, Clone, Serialize, Deserialize)]
628#[serde(rename_all = "camelCase")]
629pub struct UnsubscribeRequest {
630 pub method: String,
632 pub params: UnsubscribeParams,
634}
635
636impl UnsubscribeRequest {
637 pub fn new(uri: impl Into<String>) -> Self {
638 Self {
639 method: "resources/unsubscribe".to_string(),
640 params: UnsubscribeParams {
641 uri: uri.into(),
642 meta: None,
643 },
644 }
645 }
646
647 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
648 self.params.meta = Some(meta);
649 self
650 }
651}
652
653#[derive(Debug, Clone, Serialize, Deserialize)]
655#[serde(rename_all = "camelCase")]
656pub struct ReadResourceResult {
657 pub contents: Vec<ResourceContent>,
659 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
661 pub meta: Option<HashMap<String, Value>>,
662}
663
664impl ReadResourceResult {
665 pub fn new(contents: Vec<ResourceContent>) -> Self {
666 Self {
667 contents,
668 meta: None,
669 }
670 }
671
672 pub fn single(content: ResourceContent) -> Self {
673 Self::new(vec![content])
674 }
675
676 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
677 self.meta = Some(meta);
678 self
679 }
680}
681
682impl HasData for ReadResourceResult {
684 fn data(&self) -> HashMap<String, Value> {
685 let mut data = HashMap::new();
686 data.insert("contents".to_string(), serde_json::to_value(&self.contents).unwrap_or(Value::Null));
687 data
688 }
689}
690
691impl HasMeta for ReadResourceResult {
692 fn meta(&self) -> Option<HashMap<String, Value>> {
693 self.meta.clone()
694 }
695}
696
697impl crate::traits::RpcResult for ReadResourceResult {}
698
699#[derive(Debug, Clone, Serialize, Deserialize)]
701#[serde(rename_all = "camelCase")]
702pub struct ResourceSubscription {
703 pub uri: String,
705}
706
707impl ResourceSubscription {
708 pub fn new(uri: impl Into<String>) -> Self {
709 Self { uri: uri.into() }
710 }
711}
712
713use crate::traits::*;
716
717impl Params for ListResourcesParams {}
719impl Params for ReadResourceParams {}
720impl Params for SubscribeParams {}
721impl Params for UnsubscribeParams {}
722impl Params for ListResourceTemplatesParams {}
723
724impl HasListResourcesParams for ListResourcesParams {
725 fn cursor(&self) -> Option<&Cursor> {
726 self.cursor.as_ref()
727 }
728}
729
730impl HasMetaParam for ListResourcesParams {
731 fn meta(&self) -> Option<&std::collections::HashMap<String, Value>> {
732 self.meta.as_ref()
733 }
734}
735
736impl HasMethod for ListResourcesRequest {
738 fn method(&self) -> &str {
739 &self.method
740 }
741}
742
743impl HasParams for ListResourcesRequest {
744 fn params(&self) -> Option<&dyn Params> {
745 Some(&self.params)
746 }
747}
748
749impl HasData for ListResourceTemplatesResult {
751 fn data(&self) -> HashMap<String, Value> {
752 let mut data = HashMap::new();
753 data.insert("resourceTemplates".to_string(), serde_json::to_value(&self.resource_templates).unwrap_or(Value::Null));
754 if let Some(ref next_cursor) = self.next_cursor {
755 data.insert("nextCursor".to_string(), Value::String(next_cursor.as_str().to_string()));
756 }
757 data
758 }
759}
760
761impl HasMeta for ListResourceTemplatesResult {
762 fn meta(&self) -> Option<HashMap<String, Value>> {
763 self.meta.clone()
764 }
765}
766
767impl crate::traits::RpcResult for ListResourceTemplatesResult {}
768
769impl crate::traits::ListResourceTemplatesResult for ListResourceTemplatesResult {
770 fn resource_templates(&self) -> &Vec<ResourceTemplate> {
771 &self.resource_templates
772 }
773
774 fn next_cursor(&self) -> Option<&Cursor> {
775 self.next_cursor.as_ref()
776 }
777}
778
779impl HasData for ListResourcesResult {
781 fn data(&self) -> HashMap<String, Value> {
782 let mut data = HashMap::new();
783 data.insert("resources".to_string(), serde_json::to_value(&self.resources).unwrap_or(Value::Null));
784 if let Some(ref next_cursor) = self.next_cursor {
785 data.insert("nextCursor".to_string(), Value::String(next_cursor.as_str().to_string()));
786 }
787 data
788 }
789}
790
791impl HasMeta for ListResourcesResult {
792 fn meta(&self) -> Option<HashMap<String, Value>> {
793 self.meta.clone()
794 }
795}
796
797impl crate::traits::RpcResult for ListResourcesResult {}
799
800impl crate::traits::ListResourcesResult for ListResourcesResult {
801 fn resources(&self) -> &Vec<Resource> {
802 &self.resources
803 }
804
805 fn next_cursor(&self) -> Option<&Cursor> {
806 self.next_cursor.as_ref()
807 }
808}
809
810#[cfg(test)]
811mod tests {
812 use super::*;
813 use crate::traits::ListResourcesResult;
814
815 #[test]
816 fn test_resource_creation() {
817 let resource = Resource::new("file:///test.txt", "Test File")
818 .with_description("A test file")
819 .with_mime_type("text/plain");
820
821 assert_eq!(resource.uri, "file:///test.txt");
822 assert_eq!(resource.name, "Test File");
823 assert!(resource.description.is_some());
824 assert!(resource.mime_type.is_some());
825 }
826
827 #[test]
828 fn test_resource_content() {
829 let text_content = ResourceContent::text("file:///test.txt", "Hello, world!");
830 let blob_content = ResourceContent::blob("file:///image.png", "base64data", "image/png");
831
832 assert!(matches!(text_content, ResourceContent::Text(_)));
833 assert!(matches!(blob_content, ResourceContent::Blob(_)));
834 }
835
836 #[test]
837 fn test_list_resources_response() {
838 let resources = vec![
839 Resource::new("file:///test1.txt", "Test 1"),
840 Resource::new("file:///test2.txt", "Test 2"),
841 ];
842
843 let response = super::ListResourcesResult::new(resources);
844 assert_eq!(response.resources.len(), 2);
845 assert!(response.next_cursor.is_none());
846 }
847
848 #[test]
849 fn test_read_resource_response() {
850 let content = ResourceContent::text("file:///test.txt", "File contents");
851 let response = ReadResourceResult::single(content);
852
853 assert_eq!(response.contents.len(), 1);
854 }
855
856 #[test]
857 fn test_serialization() {
858 let resource = Resource::new("file:///example.txt", "Example");
859 let json = serde_json::to_string(&resource).unwrap();
860 assert!(json.contains("file:///example.txt"));
861 assert!(json.contains("Example"));
862
863 let parsed: Resource = serde_json::from_str(&json).unwrap();
864 assert_eq!(parsed.uri, "file:///example.txt");
865 }
866
867 #[test]
868 fn test_trait_implementations() {
869 let request = ListResourcesRequest::new();
870 assert!(request.params.cursor.is_none());
871
872 let resources = vec![Resource::new("test://resource", "Test Resource")];
873 let response = super::ListResourcesResult::new(resources);
874 assert_eq!(response.resources().len(), 1);
875 assert!(response.next_cursor().is_none());
876
877 let data = response.data();
878 assert!(data.contains_key("resources"));
879 }
880}