1use serde::{Deserialize, Serialize};
2
3use super::{Annotations, Icon, Meta};
4
5#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
9#[serde(rename_all = "camelCase")]
10#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
11#[non_exhaustive]
12pub struct Resource {
13 pub uri: String,
15 pub name: String,
17 #[serde(skip_serializing_if = "Option::is_none")]
19 pub title: Option<String>,
20 #[serde(skip_serializing_if = "Option::is_none")]
22 pub description: Option<String>,
23 #[serde(skip_serializing_if = "Option::is_none")]
25 pub mime_type: Option<String>,
26 #[serde(skip_serializing_if = "Option::is_none")]
28 pub size: Option<u64>,
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub icons: Option<Vec<Icon>>,
32 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
34 pub meta: Option<Meta>,
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub annotations: Option<Annotations>,
38}
39
40impl Resource {
41 pub fn new(uri: impl Into<String>, name: impl Into<String>) -> Self {
42 Self {
43 uri: uri.into(),
44 name: name.into(),
45 title: None,
46 description: None,
47 mime_type: None,
48 size: None,
49 icons: None,
50 meta: None,
51 annotations: None,
52 }
53 }
54
55 pub fn with_title(mut self, title: impl Into<String>) -> Self {
56 self.title = Some(title.into());
57 self
58 }
59
60 pub fn with_description(mut self, description: impl Into<String>) -> Self {
61 self.description = Some(description.into());
62 self
63 }
64
65 pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
66 self.mime_type = Some(mime_type.into());
67 self
68 }
69
70 pub fn with_size(mut self, size: u64) -> Self {
71 self.size = Some(size);
72 self
73 }
74
75 pub fn with_icons(mut self, icons: Vec<Icon>) -> Self {
76 self.icons = Some(icons);
77 self
78 }
79
80 pub fn with_meta(mut self, meta: Meta) -> Self {
81 self.meta = Some(meta);
82 self
83 }
84
85 pub fn with_annotations(mut self, annotations: Annotations) -> Self {
86 self.annotations = Some(annotations);
87 self
88 }
89}
90
91#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
93#[serde(rename_all = "camelCase")]
94#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
95#[non_exhaustive]
96pub struct ResourceTemplate {
97 pub uri_template: String,
99 pub name: String,
101 #[serde(skip_serializing_if = "Option::is_none")]
103 pub title: Option<String>,
104 #[serde(skip_serializing_if = "Option::is_none")]
106 pub description: Option<String>,
107 #[serde(skip_serializing_if = "Option::is_none")]
109 pub mime_type: Option<String>,
110 #[serde(skip_serializing_if = "Option::is_none")]
112 pub icons: Option<Vec<Icon>>,
113 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
115 pub meta: Option<Meta>,
116 #[serde(skip_serializing_if = "Option::is_none")]
118 pub annotations: Option<Annotations>,
119}
120
121impl ResourceTemplate {
122 pub fn new(uri_template: impl Into<String>, name: impl Into<String>) -> Self {
123 Self {
124 uri_template: uri_template.into(),
125 name: name.into(),
126 title: None,
127 description: None,
128 mime_type: None,
129 icons: None,
130 meta: None,
131 annotations: None,
132 }
133 }
134
135 pub fn with_title(mut self, title: impl Into<String>) -> Self {
136 self.title = Some(title.into());
137 self
138 }
139
140 pub fn with_description(mut self, description: impl Into<String>) -> Self {
141 self.description = Some(description.into());
142 self
143 }
144
145 pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
146 self.mime_type = Some(mime_type.into());
147 self
148 }
149
150 pub fn with_icons(mut self, icons: Vec<Icon>) -> Self {
151 self.icons = Some(icons);
152 self
153 }
154
155 pub fn with_meta(mut self, meta: Meta) -> Self {
156 self.meta = Some(meta);
157 self
158 }
159
160 pub fn with_annotations(mut self, annotations: Annotations) -> Self {
161 self.annotations = Some(annotations);
162 self
163 }
164}
165
166#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
168#[serde(untagged)]
169#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
170#[non_exhaustive]
171pub enum ResourceContents {
172 #[serde(rename_all = "camelCase")]
173 TextResourceContents {
174 uri: String,
175 #[serde(skip_serializing_if = "Option::is_none")]
176 mime_type: Option<String>,
177 text: String,
178 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
179 meta: Option<Meta>,
180 },
181 #[serde(rename_all = "camelCase")]
182 BlobResourceContents {
183 uri: String,
184 #[serde(skip_serializing_if = "Option::is_none")]
185 mime_type: Option<String>,
186 blob: String,
187 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
188 meta: Option<Meta>,
189 },
190}
191
192impl ResourceContents {
193 pub fn text(text: impl Into<String>, uri: impl Into<String>) -> Self {
194 Self::TextResourceContents {
195 uri: uri.into(),
196 mime_type: Some("text".into()),
197 text: text.into(),
198 meta: None,
199 }
200 }
201
202 pub fn blob(blob: impl Into<String>, uri: impl Into<String>) -> Self {
203 Self::BlobResourceContents {
204 uri: uri.into(),
205 mime_type: None,
206 blob: blob.into(),
207 meta: None,
208 }
209 }
210
211 pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
212 match &mut self {
213 Self::TextResourceContents { mime_type: mt, .. } => *mt = Some(mime_type.into()),
214 Self::BlobResourceContents { mime_type: mt, .. } => *mt = Some(mime_type.into()),
215 }
216 self
217 }
218
219 pub fn with_meta(mut self, meta: Meta) -> Self {
220 match &mut self {
221 Self::TextResourceContents { meta: m, .. } => *m = Some(meta),
222 Self::BlobResourceContents { meta: m, .. } => *m = Some(meta),
223 }
224 self
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use serde_json;
231
232 use super::*;
233 use crate::model::IconTheme;
234
235 #[test]
236 fn test_resource_serialization() {
237 let resource = Resource::new("file:///test.txt", "test")
238 .with_description("Test resource")
239 .with_mime_type("text/plain")
240 .with_size(100);
241
242 let json = serde_json::to_string(&resource).unwrap();
243 assert!(json.contains("mimeType"));
244 assert!(!json.contains("mime_type"));
245 }
246
247 #[test]
248 fn test_resource_contents_serialization() {
249 let text_contents = ResourceContents::TextResourceContents {
250 uri: "file:///test.txt".to_string(),
251 mime_type: Some("text/plain".to_string()),
252 text: "Hello world".to_string(),
253 meta: None,
254 };
255
256 let json = serde_json::to_string(&text_contents).unwrap();
257 assert!(json.contains("mimeType"));
258 assert!(!json.contains("mime_type"));
259 }
260
261 #[test]
262 fn test_resource_template_with_icons() {
263 let resource_template = ResourceTemplate::new("file:///{path}", "template")
264 .with_title("Test Template")
265 .with_description("A test resource template")
266 .with_mime_type("text/plain")
267 .with_icons(vec![Icon {
268 src: "https://example.com/icon.png".to_string(),
269 mime_type: Some("image/png".to_string()),
270 sizes: Some(vec!["48x48".to_string()]),
271 theme: Some(IconTheme::Light),
272 }]);
273
274 let json = serde_json::to_value(&resource_template).unwrap();
275 assert!(json["icons"].is_array());
276 assert_eq!(json["icons"][0]["src"], "https://example.com/icon.png");
277 assert_eq!(json["icons"][0]["sizes"][0], "48x48");
278 assert_eq!(json["icons"][0]["theme"], "light");
279 }
280
281 #[test]
282 fn test_resource_template_without_icons() {
283 let resource_template = ResourceTemplate::new("file:///{path}", "template");
284 let json = serde_json::to_value(&resource_template).unwrap();
285 assert!(json.get("icons").is_none());
286 }
287
288 #[test]
289 fn test_resource_size_u64() {
290 let resource = Resource::new("file:///big", "big").with_size(5_000_000_000);
291 let json = serde_json::to_value(&resource).unwrap();
292 assert_eq!(json["size"], 5_000_000_000_u64);
293 }
294
295 #[test]
296 fn test_resource_with_annotations() {
297 let resource = Resource::new("file:///test.txt", "test")
298 .with_annotations(Annotations::default().with_priority(0.9));
299 let json = serde_json::to_value(&resource).unwrap();
300 assert_eq!(json["annotations"]["priority"], 0.9_f32);
301 }
302
303 #[test]
304 fn test_resource_template_with_meta() {
305 let resource_template =
306 ResourceTemplate::new("file:///{path}", "template").with_meta(Meta::default());
307 let json = serde_json::to_value(&resource_template).unwrap();
308 assert!(json.get("_meta").is_some());
309 }
310}