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