prism_mcp_rs/protocol/
roots_types.rs1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
15pub struct Root {
16 #[serde(rename = "uri")]
20 pub uri: String,
21
22 #[serde(skip_serializing_if = "Option::is_none")]
26 pub name: Option<String>,
27
28 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
30 pub meta: Option<HashMap<String, serde_json::Value>>,
31}
32
33impl Root {
34 pub fn new(uri: String) -> Self {
36 Self {
37 uri,
38 name: None,
39 meta: None,
40 }
41 }
42
43 pub fn with_name(uri: String, name: String) -> Self {
45 Self {
46 uri,
47 name: Some(name),
48 meta: None,
49 }
50 }
51
52 pub fn validate(&self) -> Result<(), String> {
54 if !self.uri.starts_with("file:///") {
55 return Err(format!(
56 "Root URI must start with 'file:///', got: {}",
57 self.uri
58 ));
59 }
60 Ok(())
61 }
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
66pub struct ListRootsRequest {
67 #[serde(default = "default_roots_list_method")]
69 pub method: String,
70
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub params: Option<serde_json::Value>,
74}
75
76fn default_roots_list_method() -> String {
77 "roots/list".to_string()
78}
79
80impl ListRootsRequest {
81 pub fn new() -> Self {
83 Self {
84 method: "roots/list".to_string(),
85 params: None,
86 }
87 }
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
92pub struct ListRootsResult {
93 pub roots: Vec<Root>,
95
96 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
98 pub meta: Option<HashMap<String, serde_json::Value>>,
99}
100
101impl ListRootsResult {
102 pub fn new(roots: Vec<Root>) -> Self {
104 Self { roots, meta: None }
105 }
106
107 pub fn empty() -> Self {
109 Self {
110 roots: Vec::new(),
111 meta: None,
112 }
113 }
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
118pub struct RootsListChangedNotification {
119 #[serde(default = "default_roots_list_changed_method")]
121 pub method: String,
122
123 #[serde(skip_serializing_if = "Option::is_none")]
125 pub params: Option<serde_json::Value>,
126}
127
128fn default_roots_list_changed_method() -> String {
129 "notifications/roots/list_changed".to_string()
130}
131
132impl RootsListChangedNotification {
133 pub fn new() -> Self {
135 Self {
136 method: "notifications/roots/list_changed".to_string(),
137 params: None,
138 }
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn test_root_creation() {
148 let root = Root::new("file:///home/user/projects".to_string());
149 assert_eq!(root.uri, "file:///home/user/projects");
150 assert_eq!(root.name, None);
151 assert_eq!(root.meta, None);
152
153 let root_with_name = Root::with_name(
154 "file:///home/user/documents".to_string(),
155 "Documents".to_string(),
156 );
157 assert_eq!(root_with_name.uri, "file:///home/user/documents");
158 assert_eq!(root_with_name.name, Some("Documents".to_string()));
159 }
160
161 #[test]
162 fn test_root_validation() {
163 let valid_root = Root::new("file:///home/user".to_string());
164 assert!(valid_root.validate().is_ok());
165
166 let invalid_root = Root::new("http://example.com".to_string());
167 assert!(invalid_root.validate().is_err());
168 }
169
170 #[test]
171 fn test_root_serialization() {
172 let root = Root::with_name("file:///workspace".to_string(), "Workspace".to_string());
173
174 let json = serde_json::to_value(&root).unwrap();
175 assert_eq!(json["uri"], "file:///workspace");
176 assert_eq!(json["name"], "Workspace");
177 assert!(json.get("_meta").is_none());
178 }
179
180 #[test]
181 fn test_list_roots_request() {
182 let request = ListRootsRequest::new();
183 assert_eq!(request.method, "roots/list");
184 assert!(request.params.is_none());
185
186 let json = serde_json::to_value(&request).unwrap();
187 assert_eq!(json["method"], "roots/list");
188 }
189
190 #[test]
191 fn test_list_roots_result() {
192 let roots = vec![
193 Root::new("file:///home/user".to_string()),
194 Root::with_name("file:///workspace".to_string(), "Work".to_string()),
195 ];
196
197 let result = ListRootsResult::new(roots.clone());
198 assert_eq!(result.roots.len(), 2);
199 assert_eq!(result.roots[0].uri, "file:///home/user");
200 assert_eq!(result.roots[1].name, Some("Work".to_string()));
201
202 let json = serde_json::to_value(&result).unwrap();
203 assert!(json["roots"].is_array());
204 assert_eq!(json["roots"][0]["uri"], "file:///home/user");
205 }
206
207 #[test]
208 fn test_roots_list_changed_notification() {
209 let notification = RootsListChangedNotification::new();
210 assert_eq!(notification.method, "notifications/roots/list_changed");
211 assert!(notification.params.is_none());
212
213 let json = serde_json::to_value(¬ification).unwrap();
214 assert_eq!(json["method"], "notifications/roots/list_changed");
215 }
216}