1use std::{borrow::Cow, collections::BTreeMap, sync::Arc};
10
11use derive_more::{Display, From};
12#[cfg(feature = "schemars")]
13use schemars::Schema;
14use serde::{Deserialize, Serialize};
15use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none};
16
17use super::Meta;
18use crate::IntoOption;
19
20#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
22#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
23#[serde(transparent)]
24#[from(Arc<str>, String, &str, &mut str, Box<str>, Cow<'_, str>)]
25#[non_exhaustive]
26pub struct MediaType(pub Arc<str>);
27
28impl MediaType {
29 #[must_use]
31 pub fn new(media_type: impl Into<Self>) -> Self {
32 media_type.into()
33 }
34}
35
36impl AsRef<str> for MediaType {
37 fn as_ref(&self) -> &str {
38 &self.0
39 }
40}
41
42impl From<&String> for MediaType {
43 fn from(media_type: &String) -> Self {
44 Self(media_type.as_str().into())
45 }
46}
47
48macro_rules! impl_media_type_option_conversion {
49 ($source:ty) => {
50 impl IntoOption<MediaType> for $source {
51 fn into_option(self) -> Option<MediaType> {
52 Some(self.into())
53 }
54 }
55 };
56}
57
58impl_media_type_option_conversion!(Arc<str>);
59impl_media_type_option_conversion!(String);
60impl_media_type_option_conversion!(&str);
61impl_media_type_option_conversion!(&mut str);
62impl_media_type_option_conversion!(&String);
63impl_media_type_option_conversion!(Box<str>);
64impl_media_type_option_conversion!(Cow<'_, str>);
65
66#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
79#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
80#[serde(tag = "type", rename_all = "snake_case")]
81#[non_exhaustive]
82pub enum ContentBlock {
83 Text(TextContent),
89 Image(ImageContent),
93 Audio(AudioContent),
97 ResourceLink(ResourceLink),
101 Resource(EmbeddedResource),
107 #[serde(untagged)]
119 Other(OtherContentBlock),
120}
121
122#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
124#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
125#[cfg_attr(feature = "schemars", schemars(inline))]
126#[cfg_attr(feature = "schemars", schemars(transform = other_content_block_schema))]
127#[serde(rename_all = "camelCase")]
128#[non_exhaustive]
129pub struct OtherContentBlock {
130 #[serde(rename = "type")]
136 pub type_: String,
137 #[serde(flatten)]
139 pub fields: BTreeMap<String, serde_json::Value>,
140}
141
142impl OtherContentBlock {
143 #[must_use]
145 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
146 fields.remove("type");
147 Self {
148 type_: type_.into(),
149 fields,
150 }
151 }
152}
153
154impl<'de> Deserialize<'de> for OtherContentBlock {
155 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
156 where
157 D: serde::Deserializer<'de>,
158 {
159 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
160 let type_ = fields
161 .remove("type")
162 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
163 let serde_json::Value::String(type_) = type_ else {
164 return Err(serde::de::Error::custom("`type` must be a string"));
165 };
166
167 if is_known_content_block_type(&type_) {
168 return Err(serde::de::Error::custom(format!(
169 "known content block `{type_}` did not match its schema"
170 )));
171 }
172
173 Ok(Self { type_, fields })
174 }
175}
176
177fn is_known_content_block_type(type_: &str) -> bool {
178 matches!(
179 type_,
180 "text" | "image" | "audio" | "resource_link" | "resource"
181 )
182}
183
184#[cfg(feature = "schemars")]
185fn other_content_block_schema(schema: &mut Schema) {
186 super::schema_util::reject_known_string_discriminators(
187 schema,
188 "type",
189 &["text", "image", "audio", "resource_link", "resource"],
190 );
191}
192
193#[serde_as]
195#[skip_serializing_none]
196#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
197#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
198#[non_exhaustive]
199pub struct TextContent {
200 pub text: String,
202 #[serde_as(deserialize_as = "DefaultOnError")]
206 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
207 #[serde(default)]
208 pub annotations: Option<Annotations>,
209 #[serde_as(deserialize_as = "DefaultOnError")]
215 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
216 #[serde(default)]
217 #[serde(rename = "_meta")]
218 pub meta: Option<Meta>,
219}
220
221impl TextContent {
222 #[must_use]
224 pub fn new(text: impl Into<String>) -> Self {
225 Self {
226 annotations: None,
227 text: text.into(),
228 meta: None,
229 }
230 }
231
232 #[must_use]
234 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
235 self.annotations = annotations.into_option();
236 self
237 }
238
239 #[must_use]
245 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
246 self.meta = meta.into_option();
247 self
248 }
249}
250
251impl<T: Into<String>> From<T> for ContentBlock {
252 fn from(value: T) -> Self {
253 Self::Text(TextContent::new(value))
254 }
255}
256
257#[serde_as]
259#[skip_serializing_none]
260#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
261#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
262#[serde(rename_all = "camelCase")]
263#[non_exhaustive]
264pub struct ImageContent {
265 #[cfg_attr(feature = "schemars", schemars(extend("contentEncoding" = "base64")))]
267 pub data: String,
268 pub mime_type: MediaType,
270 #[serde_as(deserialize_as = "DefaultOnError")]
274 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
275 #[cfg_attr(feature = "schemars", schemars(url))]
276 #[serde(default)]
277 pub uri: Option<String>,
278 #[serde_as(deserialize_as = "DefaultOnError")]
282 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
283 #[serde(default)]
284 pub annotations: Option<Annotations>,
285 #[serde_as(deserialize_as = "DefaultOnError")]
291 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
292 #[serde(default)]
293 #[serde(rename = "_meta")]
294 pub meta: Option<Meta>,
295}
296
297impl ImageContent {
298 #[must_use]
300 pub fn new(data: impl Into<String>, mime_type: impl Into<MediaType>) -> Self {
301 Self {
302 annotations: None,
303 data: data.into(),
304 mime_type: mime_type.into(),
305 uri: None,
306 meta: None,
307 }
308 }
309
310 #[must_use]
312 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
313 self.annotations = annotations.into_option();
314 self
315 }
316
317 #[must_use]
319 pub fn uri(mut self, uri: impl IntoOption<String>) -> Self {
320 self.uri = uri.into_option();
321 self
322 }
323
324 #[must_use]
330 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
331 self.meta = meta.into_option();
332 self
333 }
334}
335
336#[serde_as]
338#[skip_serializing_none]
339#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
340#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
341#[serde(rename_all = "camelCase")]
342#[non_exhaustive]
343pub struct AudioContent {
344 #[cfg_attr(feature = "schemars", schemars(extend("contentEncoding" = "base64")))]
346 pub data: String,
347 pub mime_type: MediaType,
349 #[serde_as(deserialize_as = "DefaultOnError")]
353 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
354 #[serde(default)]
355 pub annotations: Option<Annotations>,
356 #[serde_as(deserialize_as = "DefaultOnError")]
362 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
363 #[serde(default)]
364 #[serde(rename = "_meta")]
365 pub meta: Option<Meta>,
366}
367
368impl AudioContent {
369 #[must_use]
371 pub fn new(data: impl Into<String>, mime_type: impl Into<MediaType>) -> Self {
372 Self {
373 annotations: None,
374 data: data.into(),
375 mime_type: mime_type.into(),
376 meta: None,
377 }
378 }
379
380 #[must_use]
382 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
383 self.annotations = annotations.into_option();
384 self
385 }
386
387 #[must_use]
393 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
394 self.meta = meta.into_option();
395 self
396 }
397}
398
399#[serde_as]
401#[skip_serializing_none]
402#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
403#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
404#[non_exhaustive]
405pub struct EmbeddedResource {
406 pub resource: EmbeddedResourceResource,
408 #[serde_as(deserialize_as = "DefaultOnError")]
412 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
413 #[serde(default)]
414 pub annotations: Option<Annotations>,
415 #[serde_as(deserialize_as = "DefaultOnError")]
421 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
422 #[serde(default)]
423 #[serde(rename = "_meta")]
424 pub meta: Option<Meta>,
425}
426
427impl EmbeddedResource {
428 #[must_use]
430 pub fn new(resource: EmbeddedResourceResource) -> Self {
431 Self {
432 annotations: None,
433 resource,
434 meta: None,
435 }
436 }
437
438 #[must_use]
440 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
441 self.annotations = annotations.into_option();
442 self
443 }
444
445 #[must_use]
451 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
452 self.meta = meta.into_option();
453 self
454 }
455}
456
457#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
459#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
460#[serde(untagged)]
461#[non_exhaustive]
462pub enum EmbeddedResourceResource {
463 TextResourceContents(TextResourceContents),
465 BlobResourceContents(BlobResourceContents),
467}
468
469#[serde_as]
471#[skip_serializing_none]
472#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
473#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
474#[serde(rename_all = "camelCase")]
475#[non_exhaustive]
476pub struct TextResourceContents {
477 pub text: String,
479 #[cfg_attr(feature = "schemars", schemars(url))]
481 pub uri: String,
482 #[serde_as(deserialize_as = "DefaultOnError")]
486 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
487 #[serde(default)]
488 pub mime_type: Option<MediaType>,
489 #[serde_as(deserialize_as = "DefaultOnError")]
495 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
496 #[serde(default)]
497 #[serde(rename = "_meta")]
498 pub meta: Option<Meta>,
499}
500
501impl TextResourceContents {
502 #[must_use]
504 pub fn new(text: impl Into<String>, uri: impl Into<String>) -> Self {
505 Self {
506 mime_type: None,
507 text: text.into(),
508 uri: uri.into(),
509 meta: None,
510 }
511 }
512
513 #[must_use]
515 pub fn mime_type(mut self, mime_type: impl IntoOption<MediaType>) -> Self {
516 self.mime_type = mime_type.into_option();
517 self
518 }
519
520 #[must_use]
526 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
527 self.meta = meta.into_option();
528 self
529 }
530}
531
532#[serde_as]
534#[skip_serializing_none]
535#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
536#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
537#[serde(rename_all = "camelCase")]
538#[non_exhaustive]
539pub struct BlobResourceContents {
540 #[cfg_attr(feature = "schemars", schemars(extend("contentEncoding" = "base64")))]
542 pub blob: String,
543 #[cfg_attr(feature = "schemars", schemars(url))]
545 pub uri: String,
546 #[serde_as(deserialize_as = "DefaultOnError")]
550 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
551 #[serde(default)]
552 pub mime_type: Option<MediaType>,
553 #[serde_as(deserialize_as = "DefaultOnError")]
559 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
560 #[serde(default)]
561 #[serde(rename = "_meta")]
562 pub meta: Option<Meta>,
563}
564
565impl BlobResourceContents {
566 #[must_use]
568 pub fn new(blob: impl Into<String>, uri: impl Into<String>) -> Self {
569 Self {
570 blob: blob.into(),
571 mime_type: None,
572 uri: uri.into(),
573 meta: None,
574 }
575 }
576
577 #[must_use]
579 pub fn mime_type(mut self, mime_type: impl IntoOption<MediaType>) -> Self {
580 self.mime_type = mime_type.into_option();
581 self
582 }
583
584 #[must_use]
590 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
591 self.meta = meta.into_option();
592 self
593 }
594}
595
596#[serde_as]
598#[skip_serializing_none]
599#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
600#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
601#[serde(rename_all = "camelCase")]
602#[non_exhaustive]
603pub struct ResourceLink {
604 pub name: String,
606 #[cfg_attr(feature = "schemars", schemars(url))]
608 pub uri: String,
609 #[serde_as(deserialize_as = "DefaultOnError")]
613 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
614 #[serde(default)]
615 pub title: Option<String>,
616 #[serde_as(deserialize_as = "DefaultOnError")]
620 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
621 #[serde(default)]
622 pub description: Option<String>,
623 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_>>>")]
627 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
628 #[serde(default)]
629 pub icons: Option<Vec<Icon>>,
630 #[serde_as(deserialize_as = "DefaultOnError")]
634 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
635 #[serde(default)]
636 pub mime_type: Option<MediaType>,
637 #[serde_as(deserialize_as = "DefaultOnError")]
641 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
642 #[serde(default)]
643 pub size: Option<u64>,
644 #[serde_as(deserialize_as = "DefaultOnError")]
648 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
649 #[serde(default)]
650 pub annotations: Option<Annotations>,
651 #[serde_as(deserialize_as = "DefaultOnError")]
657 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
658 #[serde(default)]
659 #[serde(rename = "_meta")]
660 pub meta: Option<Meta>,
661}
662
663impl ResourceLink {
664 #[must_use]
666 pub fn new(name: impl Into<String>, uri: impl Into<String>) -> Self {
667 Self {
668 annotations: None,
669 description: None,
670 icons: None,
671 mime_type: None,
672 name: name.into(),
673 size: None,
674 title: None,
675 uri: uri.into(),
676 meta: None,
677 }
678 }
679
680 #[must_use]
682 pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
683 self.annotations = annotations.into_option();
684 self
685 }
686
687 #[must_use]
689 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
690 self.description = description.into_option();
691 self
692 }
693
694 #[must_use]
696 pub fn icons(mut self, icons: impl IntoOption<Vec<Icon>>) -> Self {
697 self.icons = icons.into_option();
698 self
699 }
700
701 #[must_use]
703 pub fn mime_type(mut self, mime_type: impl IntoOption<MediaType>) -> Self {
704 self.mime_type = mime_type.into_option();
705 self
706 }
707
708 #[must_use]
710 pub fn size(mut self, size: impl IntoOption<u64>) -> Self {
711 self.size = size.into_option();
712 self
713 }
714
715 #[must_use]
717 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
718 self.title = title.into_option();
719 self
720 }
721
722 #[must_use]
728 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
729 self.meta = meta.into_option();
730 self
731 }
732}
733
734#[serde_as]
736#[skip_serializing_none]
737#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
738#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
739#[serde(rename_all = "camelCase")]
740#[non_exhaustive]
741pub struct Icon {
742 #[cfg_attr(feature = "schemars", schemars(url))]
746 pub src: String,
747 #[serde_as(deserialize_as = "DefaultOnError")]
751 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
752 #[serde(default)]
753 pub mime_type: Option<MediaType>,
754 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_>>>")]
762 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
763 #[serde(default)]
764 pub sizes: Option<Vec<String>>,
765 #[serde_as(deserialize_as = "DefaultOnError")]
769 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
770 #[serde(default)]
771 pub theme: Option<IconTheme>,
772 #[serde_as(deserialize_as = "DefaultOnError")]
778 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
779 #[serde(default)]
780 #[serde(rename = "_meta")]
781 pub meta: Option<Meta>,
782}
783
784impl Icon {
785 #[must_use]
787 pub fn new(src: impl Into<String>) -> Self {
788 Self {
789 src: src.into(),
790 mime_type: None,
791 sizes: None,
792 theme: None,
793 meta: None,
794 }
795 }
796
797 #[must_use]
803 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
804 self.meta = meta.into_option();
805 self
806 }
807
808 #[must_use]
810 pub fn mime_type(mut self, mime_type: impl IntoOption<MediaType>) -> Self {
811 self.mime_type = mime_type.into_option();
812 self
813 }
814
815 #[must_use]
817 pub fn sizes(mut self, sizes: impl IntoOption<Vec<String>>) -> Self {
818 self.sizes = sizes.into_option();
819 self
820 }
821
822 #[must_use]
824 pub fn theme(mut self, theme: impl IntoOption<IconTheme>) -> Self {
825 self.theme = theme.into_option();
826 self
827 }
828}
829
830#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
832#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
833#[serde(rename_all = "camelCase")]
834#[non_exhaustive]
835pub enum IconTheme {
836 Light,
838 Dark,
840 #[serde(untagged)]
846 Other(String),
847}
848
849#[serde_as]
851#[skip_serializing_none]
852#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
853#[derive(Debug, Clone, Deserialize, Serialize, Default)]
854#[serde(rename_all = "camelCase")]
855#[non_exhaustive]
856pub struct Annotations {
857 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_>>>")]
861 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
862 #[serde(default)]
863 pub audience: Option<Vec<Role>>,
864 #[serde_as(deserialize_as = "DefaultOnError")]
870 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
871 #[serde(default)]
872 pub last_modified: Option<String>,
873 #[serde_as(deserialize_as = "DefaultOnError")]
878 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
879 #[cfg_attr(feature = "schemars", schemars(range(min = 0, max = 1)))]
880 #[serde(default)]
881 pub priority: Option<f64>,
882 #[serde_as(deserialize_as = "DefaultOnError")]
888 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
889 #[serde(default)]
890 #[serde(rename = "_meta")]
891 pub meta: Option<Meta>,
892}
893
894impl PartialEq for Annotations {
899 fn eq(&self, other: &Self) -> bool {
900 let Self {
901 audience,
902 last_modified,
903 priority,
904 meta,
905 } = self;
906 *audience == other.audience
907 && *last_modified == other.last_modified
908 && priority_eq(*priority, other.priority)
909 && *meta == other.meta
910 }
911}
912
913impl Eq for Annotations {}
914
915fn priority_eq(a: Option<f64>, b: Option<f64>) -> bool {
916 match (a, b) {
917 (Some(a), Some(b)) => a.total_cmp(&b).is_eq(),
918 (None, None) => true,
919 _ => false,
920 }
921}
922
923impl Annotations {
924 #[must_use]
926 pub fn new() -> Self {
927 Self::default()
928 }
929
930 #[must_use]
932 pub fn audience(mut self, audience: impl IntoOption<Vec<Role>>) -> Self {
933 self.audience = audience.into_option();
934 self
935 }
936
937 #[must_use]
939 pub fn last_modified(mut self, last_modified: impl IntoOption<String>) -> Self {
940 self.last_modified = last_modified.into_option();
941 self
942 }
943
944 #[must_use]
946 pub fn priority(mut self, priority: impl IntoOption<f64>) -> Self {
947 self.priority = priority.into_option();
948 self
949 }
950
951 #[must_use]
957 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
958 self.meta = meta.into_option();
959 self
960 }
961}
962
963#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
965#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
966#[serde(rename_all = "camelCase")]
967#[non_exhaustive]
968pub enum Role {
969 Assistant,
971 User,
973 #[serde(untagged)]
979 Other(String),
980}
981
982#[cfg(test)]
983mod tests {
984 use super::*;
985
986 #[test]
987 fn test_text_content_roundtrip() {
988 let content = TextContent::new("hello world");
989 let json = serde_json::to_value(&content).unwrap();
990 let parsed: TextContent = serde_json::from_value(json).unwrap();
991 assert_eq!(content, parsed);
992 }
993
994 #[test]
995 fn test_annotations_equality_is_reflexive() {
996 let nan = Annotations::new().priority(f64::NAN);
997 assert_eq!(nan, nan.clone());
998 assert_eq!(
999 Annotations::new().priority(0.5),
1000 Annotations::new().priority(0.5)
1001 );
1002 assert_ne!(
1003 Annotations::new().priority(0.0),
1004 Annotations::new().priority(-0.0)
1005 );
1006 assert_ne!(
1007 serde_json::to_string(&Annotations::new().priority(0.0)).unwrap(),
1008 serde_json::to_string(&Annotations::new().priority(-0.0)).unwrap()
1009 );
1010 assert_ne!(
1011 Annotations::new().priority(0.5),
1012 Annotations::new().priority(1.0)
1013 );
1014 assert_ne!(nan, Annotations::new().priority(0.5));
1015 assert_ne!(Annotations::new().priority(0.5), Annotations::new());
1016 assert_ne!(
1017 Annotations::new().priority(0.5).audience(vec![Role::User]),
1018 Annotations::new().priority(0.5)
1019 );
1020 }
1021
1022 #[test]
1023 fn test_text_content_omits_optional_fields() {
1024 let content = TextContent::new("hello");
1025 let json = serde_json::to_value(&content).unwrap();
1026 assert!(!json.as_object().unwrap().contains_key("annotations"));
1027 assert!(!json.as_object().unwrap().contains_key("meta"));
1028 }
1029
1030 #[test]
1031 fn test_text_content_meta_defaults_on_missing_or_malformed_value() {
1032 let missing: TextContent = serde_json::from_value(serde_json::json!({
1033 "text": "hello"
1034 }))
1035 .unwrap();
1036 assert_eq!(missing.meta, None);
1037
1038 let malformed: TextContent = serde_json::from_value(serde_json::json!({
1039 "text": "hello",
1040 "_meta": false
1041 }))
1042 .unwrap();
1043 assert_eq!(malformed.meta, None);
1044 }
1045
1046 #[test]
1047 fn test_text_content_from_string() {
1048 let block: ContentBlock = "hello".into();
1049 match block {
1050 ContentBlock::Text(c) => assert_eq!(c.text, "hello"),
1051 _ => panic!("Expected Text variant"),
1052 }
1053 }
1054
1055 #[test]
1056 fn role_preserves_unknown_variant() {
1057 let role: Role = serde_json::from_str("\"critic\"").unwrap();
1058 assert_eq!(role, Role::Other("critic".to_string()));
1059 assert_eq!(serde_json::to_value(&role).unwrap(), "critic");
1060 }
1061
1062 #[test]
1063 fn icon_theme_preserves_unknown_variant() {
1064 let theme: IconTheme = serde_json::from_str("\"contrast\"").unwrap();
1065 assert_eq!(theme, IconTheme::Other("contrast".to_string()));
1066 assert_eq!(serde_json::to_value(&theme).unwrap(), "contrast");
1067 }
1068
1069 #[test]
1070 fn content_block_preserves_unknown_variant() {
1071 let block: ContentBlock = serde_json::from_value(serde_json::json!({
1072 "type": "_widget",
1073 "title": "Status",
1074 "state": {"ok": true}
1075 }))
1076 .unwrap();
1077
1078 let ContentBlock::Other(unknown) = block else {
1079 panic!("expected unknown content block");
1080 };
1081
1082 assert_eq!(unknown.type_, "_widget");
1083 assert_eq!(
1084 unknown.fields.get("title"),
1085 Some(&serde_json::json!("Status"))
1086 );
1087 assert_eq!(
1088 serde_json::to_value(ContentBlock::Other(unknown)).unwrap(),
1089 serde_json::json!({
1090 "type": "_widget",
1091 "title": "Status",
1092 "state": {"ok": true}
1093 })
1094 );
1095 }
1096
1097 #[test]
1098 fn content_block_does_not_hide_malformed_known_variant() {
1099 assert!(
1100 serde_json::from_value::<ContentBlock>(serde_json::json!({
1101 "type": "text"
1102 }))
1103 .is_err()
1104 );
1105 }
1106
1107 #[test]
1108 fn test_image_content_roundtrip() {
1109 let content = ImageContent::new("base64data", "image/png");
1110 let json = serde_json::to_value(&content).unwrap();
1111 let parsed: ImageContent = serde_json::from_value(json).unwrap();
1112 assert_eq!(content, parsed);
1113 }
1114
1115 #[test]
1116 fn test_image_content_omits_optional_fields() {
1117 let content = ImageContent::new("data", "image/png");
1118 let json = serde_json::to_value(&content).unwrap();
1119 assert!(!json.as_object().unwrap().contains_key("uri"));
1120 assert!(!json.as_object().unwrap().contains_key("annotations"));
1121 assert!(!json.as_object().unwrap().contains_key("meta"));
1122 }
1123
1124 #[test]
1125 fn test_image_content_with_uri() {
1126 let content = ImageContent::new("data", "image/png").uri("https://example.com/image.png");
1127 let json = serde_json::to_value(&content).unwrap();
1128 assert_eq!(json["uri"], "https://example.com/image.png");
1129 }
1130
1131 #[test]
1132 fn test_audio_content_roundtrip() {
1133 let content = AudioContent::new("base64audio", "audio/mp3");
1134 let json = serde_json::to_value(&content).unwrap();
1135 let parsed: AudioContent = serde_json::from_value(json).unwrap();
1136 assert_eq!(content, parsed);
1137 }
1138
1139 #[test]
1140 fn test_audio_content_omits_optional_fields() {
1141 let content = AudioContent::new("data", "audio/mp3");
1142 let json = serde_json::to_value(&content).unwrap();
1143 assert!(!json.as_object().unwrap().contains_key("annotations"));
1144 assert!(!json.as_object().unwrap().contains_key("meta"));
1145 }
1146
1147 #[test]
1148 fn resource_link_icons_roundtrip() {
1149 let icon = Icon::new("https://example.com/icon.png")
1150 .mime_type("image/png")
1151 .sizes(vec!["48x48".to_string(), "any".to_string()])
1152 .theme(IconTheme::Dark);
1153 let link = ResourceLink::new("Example", "file:///example.txt").icons(vec![icon]);
1154
1155 let json = serde_json::to_value(&link).unwrap();
1156 assert_eq!(json["icons"][0]["src"], "https://example.com/icon.png");
1157 assert_eq!(json["icons"][0]["mimeType"], "image/png");
1158 assert_eq!(json["icons"][0]["sizes"][0], "48x48");
1159 assert_eq!(json["icons"][0]["theme"], "dark");
1160
1161 let parsed: ResourceLink = serde_json::from_value(json).unwrap();
1162 assert_eq!(link, parsed);
1163 }
1164
1165 #[cfg(feature = "schemars")]
1166 #[test]
1167 fn annotations_priority_schema_matches_mcp_bounds() {
1168 let schema = schemars::schema_for!(Annotations);
1169 let json = serde_json::to_value(schema).unwrap();
1170
1171 assert_eq!(json["properties"]["priority"]["minimum"], 0);
1172 assert_eq!(json["properties"]["priority"]["maximum"], 1);
1173 assert!(json["properties"]["lastModified"].get("format").is_none());
1175 }
1176
1177 #[cfg(feature = "schemars")]
1178 #[test]
1179 fn content_schema_uses_standard_string_annotations() {
1180 let image = serde_json::to_value(schemars::schema_for!(ImageContent)).unwrap();
1181 assert_eq!(image["properties"]["data"]["contentEncoding"], "base64");
1182 assert!(image["properties"]["data"].get("format").is_none());
1183 assert_eq!(image["properties"]["uri"]["format"], "uri");
1184
1185 let audio = serde_json::to_value(schemars::schema_for!(AudioContent)).unwrap();
1186 assert_eq!(audio["properties"]["data"]["contentEncoding"], "base64");
1187 assert!(audio["properties"]["data"].get("format").is_none());
1188
1189 let text_resource =
1190 serde_json::to_value(schemars::schema_for!(TextResourceContents)).unwrap();
1191 assert_eq!(text_resource["properties"]["uri"]["format"], "uri");
1192
1193 let blob_resource =
1194 serde_json::to_value(schemars::schema_for!(BlobResourceContents)).unwrap();
1195 assert_eq!(
1196 blob_resource["properties"]["blob"]["contentEncoding"],
1197 "base64"
1198 );
1199 assert!(blob_resource["properties"]["blob"].get("format").is_none());
1200 assert_eq!(blob_resource["properties"]["uri"]["format"], "uri");
1201
1202 let resource_link = serde_json::to_value(schemars::schema_for!(ResourceLink)).unwrap();
1203 assert_eq!(resource_link["properties"]["uri"]["format"], "uri");
1204
1205 let icon = serde_json::to_value(schemars::schema_for!(Icon)).unwrap();
1206 assert_eq!(icon["properties"]["src"]["format"], "uri");
1207 assert_eq!(icon["properties"]["sizes"]["items"]["type"], "string");
1208 assert!(
1209 icon["properties"]["sizes"]["items"]
1210 .get("pattern")
1211 .is_none()
1212 );
1213 }
1214}