machi_protocol/
content.rs1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
7#[serde(transparent)]
8pub struct ImageMime(pub String);
9
10impl ImageMime {
11 #[must_use]
13 pub fn png() -> Self {
14 Self("image/png".into())
15 }
16
17 #[must_use]
19 pub fn jpeg() -> Self {
20 Self("image/jpeg".into())
21 }
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
26pub struct ImageBlock {
27 pub mime_type: ImageMime,
29 pub data: String,
31 #[serde(default, skip_serializing_if = "Option::is_none")]
33 pub media_id: Option<String>,
34 #[serde(default, skip_serializing_if = "Option::is_none")]
36 pub filename: Option<String>,
37 #[serde(default, skip_serializing_if = "Option::is_none")]
39 pub path: Option<String>,
40}
41
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
44#[serde(tag = "type", rename_all = "snake_case")]
45#[non_exhaustive]
46pub enum ContentBlock {
47 Text {
49 text: String,
51 },
52 Image(ImageBlock),
54}
55
56impl ContentBlock {
57 #[must_use]
59 pub fn text(text: impl Into<String>) -> Self {
60 Self::Text { text: text.into() }
61 }
62
63 #[must_use]
65 pub fn as_text(&self) -> Option<&str> {
66 match self {
67 Self::Text { text } => Some(text.as_str()),
68 Self::Image(_) => None,
69 }
70 }
71}
72
73#[must_use]
75pub fn join_text_blocks(blocks: &[ContentBlock]) -> String {
76 blocks
77 .iter()
78 .filter_map(ContentBlock::as_text)
79 .collect::<Vec<_>>()
80 .join("\n")
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn serde_round_trip_text() {
89 let block = ContentBlock::text("hello");
90 let v = serde_json::to_value(&block).expect("ser");
91 let back: ContentBlock = serde_json::from_value(v).expect("de");
92 assert_eq!(back.as_text(), Some("hello"));
93 }
94}