turul_mcp_protocol_2025_06_18/
resources.rs

1//! MCP Resources Protocol Types
2//!
3//! This module defines the types used for the MCP resources functionality.
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use std::collections::HashMap;
8use crate::meta::Cursor;
9
10// ===========================================
11// === Resource Definition Trait Hierarchy ===
12// ===========================================
13
14/// Base metadata trait - matches TypeScript BaseMetadata interface
15pub trait HasResourceMetadata {
16    /// Programmatic identifier (fallback display name)
17    fn name(&self) -> &str;
18    
19    /// Human-readable display name (UI contexts)
20    fn title(&self) -> Option<&str> { None }
21}
22
23/// Resource description trait
24pub trait HasResourceDescription {
25    fn description(&self) -> Option<&str> { None }
26}
27
28/// Resource URI trait
29pub trait HasResourceUri {
30    fn uri(&self) -> &str;
31}
32
33/// Resource MIME type trait  
34pub trait HasResourceMimeType {
35    fn mime_type(&self) -> Option<&str> { None }
36}
37
38/// Resource size trait
39pub trait HasResourceSize {
40    fn size(&self) -> Option<u64> { None }
41}
42
43/// Resource annotations trait
44pub trait HasResourceAnnotations {
45    fn annotations(&self) -> Option<&crate::meta::Annotations> { None }
46}
47
48/// Resource-specific meta trait (separate from RPC _meta)
49pub trait HasResourceMeta {
50    fn resource_meta(&self) -> Option<&HashMap<String, Value>> { None }
51}
52
53/// Complete resource definition - composed from fine-grained traits
54pub trait ResourceDefinition: 
55    HasResourceMetadata +       // name, title (from BaseMetadata)
56    HasResourceDescription +    // description
57    HasResourceUri +           // uri
58    HasResourceMimeType +      // mimeType
59    HasResourceSize +          // size
60    HasResourceAnnotations +   // annotations
61    HasResourceMeta +          // _meta (resource-specific)
62    Send + 
63    Sync 
64{
65    /// Display name precedence: title > name (matches TypeScript spec)
66    fn display_name(&self) -> &str {
67        self.title().unwrap_or_else(|| self.name())
68    }
69    
70    /// Convert to concrete Resource struct for protocol serialization
71    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/// A template description for resources available on the server
86/// ResourceTemplate extends BaseMetadata
87#[derive(Debug, Clone, Serialize, Deserialize)]
88#[serde(rename_all = "camelCase")]
89pub struct ResourceTemplate {
90    /// Programmatic identifier (from BaseMetadata)
91    pub name: String,
92    /// Human-readable display name (from BaseMetadata)
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub title: Option<String>,
95    /// A URI template (according to RFC 6570) that can be used to construct resource URIs (format: uri-template)
96    #[serde(rename = "uriTemplate")]
97    pub uri_template: String,
98    /// A description of what this template is for
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub description: Option<String>,
101    /// The MIME type for all resources that match this template
102    #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
103    pub mime_type: Option<String>,
104    /// Optional annotations for the client
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub annotations: Option<crate::meta::Annotations>,
107    /// See General fields: _meta for notes on _meta usage
108    #[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/// A resource descriptor (matches TypeScript Resource interface)
152/// Resource extends BaseMetadata, so it includes name and title fields
153#[derive(Debug, Clone, Serialize, Deserialize)]
154#[serde(rename_all = "camelCase")]
155pub struct Resource {
156    /// The URI of this resource (format: uri)
157    pub uri: String,
158    /// Programmatic identifier (from BaseMetadata)
159    pub name: String,
160    /// Human-readable display name (from BaseMetadata)
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub title: Option<String>,
163    /// A description of what this resource represents
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub description: Option<String>,
166    /// The MIME type of this resource, if known
167    #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
168    pub mime_type: Option<String>,
169    /// The size of the raw resource content, in bytes
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub size: Option<u64>,
172    /// Optional annotations for the client
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub annotations: Option<crate::meta::Annotations>,
175    /// See General fields: _meta for notes on _meta usage
176    #[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
225// ================== TRAIT IMPLEMENTATIONS ==================
226// Implement fine-grained traits for the concrete Resource struct
227
228impl 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
257// Blanket implementation: any type implementing all fine-grained traits automatically implements ResourceDefinition
258impl<T> ResourceDefinition for T 
259where 
260    T: HasResourceMetadata + HasResourceDescription + HasResourceUri + HasResourceMimeType + 
261       HasResourceSize + HasResourceAnnotations + HasResourceMeta + Send + Sync 
262{}
263
264/// Parameters for resources/list request
265#[derive(Debug, Clone, Serialize, Deserialize)]
266#[serde(rename_all = "camelCase")]
267pub struct ListResourcesParams {
268    /// Optional cursor for pagination
269    #[serde(skip_serializing_if = "Option::is_none")]
270    pub cursor: Option<Cursor>,
271    /// Meta information (optional _meta field inside params)
272    #[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/// Complete resources/list request (matches TypeScript ListResourcesRequest interface)
302#[derive(Debug, Clone, Serialize, Deserialize)]
303#[serde(rename_all = "camelCase")]
304pub struct ListResourcesRequest {
305    /// Method name (always "resources/list")
306    pub method: String,
307    /// Request parameters
308    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/// Result for resources/list (per MCP spec)
331#[derive(Debug, Clone, Serialize, Deserialize)]
332#[serde(rename_all = "camelCase")]
333pub struct ListResourcesResult {
334    /// Available resources
335    pub resources: Vec<Resource>,
336    /// Optional cursor for next page
337    #[serde(skip_serializing_if = "Option::is_none")]
338    pub next_cursor: Option<Cursor>,
339    /// Meta information (follows MCP Result interface)
340    #[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/// Parameters for resources/read request
370#[derive(Debug, Clone, Serialize, Deserialize)]
371#[serde(rename_all = "camelCase")]
372pub struct ReadResourceParams {
373    /// URI of the resource to read
374    pub uri: String,
375    /// Meta information (optional _meta field inside params)
376    #[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/// Complete resources/read request (matches TypeScript ReadResourceRequest interface)
395#[derive(Debug, Clone, Serialize, Deserialize)]
396#[serde(rename_all = "camelCase")]
397pub struct ReadResourceRequest {
398    /// Method name (always "resources/read")
399    pub method: String,
400    /// Request parameters
401    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/// The contents of a specific resource or sub-resource (base interface)
419#[derive(Debug, Clone, Serialize, Deserialize)]
420#[serde(rename_all = "camelCase")]
421pub struct ResourceContents {
422    /// The URI of this resource (format: uri)
423    pub uri: String,
424    /// The MIME type of this resource, if known
425    #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
426    pub mime_type: Option<String>,
427    /// See General fields: _meta for notes on _meta usage
428    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
429    pub meta: Option<HashMap<String, Value>>,
430}
431
432/// Text resource contents
433#[derive(Debug, Clone, Serialize, Deserialize)]
434#[serde(rename_all = "camelCase")]
435pub struct TextResourceContents {
436    /// The URI of this resource (format: uri)
437    pub uri: String,
438    /// The MIME type of this resource, if known
439    #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
440    pub mime_type: Option<String>,
441    /// See General fields: _meta for notes on _meta usage
442    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
443    pub meta: Option<HashMap<String, Value>>,
444    /// The text of the item. This must only be set if the item can actually be represented as text (not binary data)
445    pub text: String,
446}
447
448/// Blob resource contents
449#[derive(Debug, Clone, Serialize, Deserialize)]
450#[serde(rename_all = "camelCase")]
451pub struct BlobResourceContents {
452    /// The URI of this resource (format: uri)
453    pub uri: String,
454    /// The MIME type of this resource, if known
455    #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
456    pub mime_type: Option<String>,
457    /// See General fields: _meta for notes on _meta usage
458    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
459    pub meta: Option<HashMap<String, Value>>,
460    /// A base64-encoded string representing the binary data of the item (format: byte)
461    pub blob: String,
462}
463
464/// Union type for resource contents (matches TypeScript union)
465#[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/// Parameters for resources/templates/list request
493#[derive(Debug, Clone, Serialize, Deserialize)]
494#[serde(rename_all = "camelCase")]
495pub struct ListResourceTemplatesParams {
496    /// Optional cursor for pagination
497    #[serde(skip_serializing_if = "Option::is_none")]
498    pub cursor: Option<Cursor>,
499    /// Meta information (optional _meta field inside params)
500    #[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/// Complete resources/templates/list request
524#[derive(Debug, Clone, Serialize, Deserialize)]
525#[serde(rename_all = "camelCase")]
526pub struct ListResourceTemplatesRequest {
527    /// Method name (always "resources/templates/list")
528    pub method: String,
529    /// Request parameters
530    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/// Result for resources/templates/list
543#[derive(Debug, Clone, Serialize, Deserialize)]
544#[serde(rename_all = "camelCase")]
545pub struct ListResourceTemplatesResult {
546    /// Available resource templates
547    #[serde(rename = "resourceTemplates")]
548    pub resource_templates: Vec<ResourceTemplate>,
549    /// Optional cursor for next page
550    #[serde(skip_serializing_if = "Option::is_none")]
551    pub next_cursor: Option<Cursor>,
552    /// Meta information
553    #[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/// Parameters for resources/subscribe request (per MCP spec)
578#[derive(Debug, Clone, Serialize, Deserialize)]
579#[serde(rename_all = "camelCase")]
580pub struct SubscribeParams {
581    /// Resource URI to subscribe to (format: uri)
582    pub uri: String,
583    /// Meta information (optional _meta field inside params)
584    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
585    pub meta: Option<HashMap<String, Value>>,
586}
587
588/// Complete resources/subscribe request (per MCP spec)
589#[derive(Debug, Clone, Serialize, Deserialize)]
590#[serde(rename_all = "camelCase")]
591pub struct SubscribeRequest {
592    /// Method name (always "resources/subscribe")
593    pub method: String,
594    /// Request parameters
595    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/// Parameters for resources/unsubscribe request (per MCP spec)
616#[derive(Debug, Clone, Serialize, Deserialize)]
617#[serde(rename_all = "camelCase")]
618pub struct UnsubscribeParams {
619    /// Resource URI to unsubscribe from (format: uri)
620    pub uri: String,
621    /// Meta information (optional _meta field inside params)
622    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
623    pub meta: Option<HashMap<String, Value>>,
624}
625
626/// Complete resources/unsubscribe request (per MCP spec)
627#[derive(Debug, Clone, Serialize, Deserialize)]
628#[serde(rename_all = "camelCase")]
629pub struct UnsubscribeRequest {
630    /// Method name (always "resources/unsubscribe")
631    pub method: String,
632    /// Request parameters
633    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/// Result for resources/read (per MCP spec)
654#[derive(Debug, Clone, Serialize, Deserialize)]
655#[serde(rename_all = "camelCase")]
656pub struct ReadResourceResult {
657    /// The resource content (TextResourceContents | BlobResourceContents)[]
658    pub contents: Vec<ResourceContent>,
659    /// Meta information (follows MCP Result interface)
660    #[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
682// Add trait implementations for ReadResourceResult
683impl 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/// Resource subscription parameters
700#[derive(Debug, Clone, Serialize, Deserialize)]
701#[serde(rename_all = "camelCase")]
702pub struct ResourceSubscription {
703    /// URI of the resource to subscribe to
704    pub uri: String,
705}
706
707impl ResourceSubscription {
708    pub fn new(uri: impl Into<String>) -> Self {
709        Self { uri: uri.into() }
710    }
711}
712
713// Trait implementations for resources
714
715use crate::traits::*;
716
717// Trait implementations for all params types
718impl 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
736// Trait implementations for ListResourcesRequest
737impl 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
749// Additional trait implementations for ListResourceTemplatesResult
750impl 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
779// Trait implementations for ListResourcesResult
780impl 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
797// RpcResult automatically implemented via blanket impl (HasMeta + HasData)
798impl 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}