use crate::error::{Error, ErrorCode};
use crate::shared;
use bytes::Bytes;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use crate::types::helpers::{deserialize_base64_as_bytes, serialize_bytes_as_base64};
use crate::types::{
Annotations, CallToolRequestParams, CallToolResponse, Icon, Resource, ResourceContents, Uri,
};
const CHUNK_SIZE: usize = 8192;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum Content {
#[serde(rename = "audio")]
Audio(AudioContent),
#[serde(rename = "image")]
Image(ImageContent),
#[serde(rename = "text")]
Text(TextContent),
#[serde(rename = "resource_link")]
ResourceLink(ResourceLink),
#[serde(rename = "resource")]
Resource(EmbeddedResource),
#[serde(rename = "tool_use")]
ToolUse(ToolUse),
#[serde(rename = "tool_result")]
ToolResult(ToolResult),
#[serde(rename = "empty")]
Empty(EmptyContent),
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct EmptyContent;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TextContent {
pub text: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AudioContent {
#[serde(
serialize_with = "serialize_bytes_as_base64",
deserialize_with = "deserialize_base64_as_bytes"
)]
pub data: Bytes,
#[serde(rename = "mimeType")]
pub mime: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImageContent {
#[serde(
serialize_with = "serialize_bytes_as_base64",
deserialize_with = "deserialize_base64_as_bytes"
)]
pub data: Bytes,
#[serde(rename = "mimeType")]
pub mime: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceLink {
pub uri: Uri,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub size: Option<usize>,
#[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
pub mime: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(rename = "description", skip_serializing_if = "Option::is_none")]
pub descr: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icons: Option<Vec<Icon>>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmbeddedResource {
pub resource: ResourceContents,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolUse {
pub id: String,
pub name: String,
pub input: Option<HashMap<String, Value>>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolResult {
#[serde(rename = "toolUseId")]
pub tool_use_id: String,
pub content: Vec<Content>,
#[serde(rename = "structuredContent", skip_serializing_if = "Option::is_none")]
pub struct_content: Option<Value>,
#[serde(default, rename = "isError")]
pub is_error: bool,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Value>,
}
impl From<&str> for Content {
#[inline]
fn from(value: &str) -> Self {
Self::text(value)
}
}
impl From<String> for Content {
#[inline]
fn from(value: String) -> Self {
Self::text(value)
}
}
impl From<Resource> for ResourceLink {
#[inline]
fn from(res: Resource) -> Self {
Self {
name: res.name,
uri: res.uri,
size: res.size,
mime: res.mime,
title: res.title,
descr: res.descr,
annotations: res.annotations,
meta: res.meta,
icons: res.icons,
}
}
}
impl From<Resource> for Content {
#[inline]
fn from(res: Resource) -> Self {
Self::ResourceLink(res.into())
}
}
impl From<Value> for Content {
#[inline]
fn from(value: Value) -> Self {
Self::Text(TextContent::new(value.to_string()))
}
}
impl From<TextContent> for Content {
#[inline]
fn from(value: TextContent) -> Self {
Self::Text(value)
}
}
impl TryFrom<Content> for TextContent {
type Error = Error;
#[inline]
fn try_from(value: Content) -> Result<Self, Self::Error> {
match value {
Content::Text(text) => Ok(text),
_ => Err(Error::new(ErrorCode::InternalError, "Invalid content type")),
}
}
}
impl<'a> TryFrom<&'a Content> for &'a TextContent {
type Error = Error;
#[inline]
fn try_from(value: &'a Content) -> Result<Self, Self::Error> {
match value {
Content::Text(text) => Ok(text),
_ => Err(Error::new(ErrorCode::InternalError, "Invalid content type")),
}
}
}
impl From<AudioContent> for Content {
#[inline]
fn from(value: AudioContent) -> Self {
Self::Audio(value)
}
}
impl TryFrom<Content> for AudioContent {
type Error = Error;
#[inline]
fn try_from(value: Content) -> Result<Self, Self::Error> {
match value {
Content::Audio(audio) => Ok(audio),
_ => Err(Error::new(ErrorCode::InternalError, "Invalid content type")),
}
}
}
impl From<ImageContent> for Content {
#[inline]
fn from(value: ImageContent) -> Self {
Self::Image(value)
}
}
impl TryFrom<Content> for ImageContent {
type Error = Error;
#[inline]
fn try_from(value: Content) -> Result<Self, Self::Error> {
match value {
Content::Image(img) => Ok(img),
_ => Err(Error::new(ErrorCode::InternalError, "Invalid content type")),
}
}
}
impl From<ResourceLink> for Content {
#[inline]
fn from(value: ResourceLink) -> Self {
Self::ResourceLink(value)
}
}
impl TryFrom<Content> for ResourceLink {
type Error = Error;
#[inline]
fn try_from(value: Content) -> Result<Self, Self::Error> {
match value {
Content::ResourceLink(res) => Ok(res),
_ => Err(Error::new(ErrorCode::InternalError, "Invalid content type")),
}
}
}
impl From<EmbeddedResource> for Content {
#[inline]
fn from(value: EmbeddedResource) -> Self {
Self::Resource(value)
}
}
impl TryFrom<Content> for EmbeddedResource {
type Error = Error;
#[inline]
fn try_from(value: Content) -> Result<Self, Self::Error> {
match value {
Content::Resource(res) => Ok(res),
_ => Err(Error::new(ErrorCode::InternalError, "Invalid content type")),
}
}
}
impl From<ToolUse> for Content {
#[inline]
fn from(value: ToolUse) -> Self {
Self::ToolUse(value)
}
}
impl From<ToolResult> for Content {
#[inline]
fn from(value: ToolResult) -> Self {
Self::ToolResult(value)
}
}
impl From<ToolUse> for CallToolRequestParams {
#[inline]
fn from(value: ToolUse) -> Self {
Self {
name: value.name,
args: value.input,
meta: None,
#[cfg(feature = "tasks")]
task: None,
}
}
}
impl TryFrom<Content> for ToolUse {
type Error = Error;
#[inline]
fn try_from(value: Content) -> Result<Self, Self::Error> {
match value {
Content::ToolUse(tool_use) => Ok(tool_use),
_ => Err(Error::new(ErrorCode::InternalError, "Invalid content type")),
}
}
}
impl TryFrom<Content> for ToolResult {
type Error = Error;
#[inline]
fn try_from(value: Content) -> Result<Self, Self::Error> {
match value {
Content::ToolResult(tool_result) => Ok(tool_result),
_ => Err(Error::new(ErrorCode::InternalError, "Invalid content type")),
}
}
}
impl Content {
#[inline]
pub fn text(text: impl Into<String>) -> Self {
Self::Text(TextContent::new(text))
}
#[inline]
pub fn json<T: Serialize>(json: T) -> Self {
let json = serde_json::to_value(json).unwrap();
Self::from(json)
}
#[inline]
pub fn image(data: impl Into<Bytes>) -> Self {
Self::Image(ImageContent::new(data))
}
#[inline]
pub fn audio(data: impl Into<Bytes>) -> Self {
Self::Audio(AudioContent::new(data))
}
#[inline]
pub fn resource(resource: impl Into<ResourceContents>) -> Self {
Self::Resource(EmbeddedResource::new(resource))
}
#[inline]
pub fn link(resource: impl Into<Resource>) -> Self {
resource.into().into()
}
#[inline]
pub fn tool_result(id: String, resp: CallToolResponse) -> Self {
Self::ToolResult(ToolResult::new(id, resp))
}
#[inline]
pub fn tool_use<N, Args>(name: N, args: Args) -> Self
where
N: Into<String>,
Args: shared::IntoArgs,
{
Self::ToolUse(ToolUse::new(name, args))
}
#[inline]
pub fn empty() -> Self {
Self::Empty(EmptyContent)
}
#[inline]
pub fn get_type(&self) -> &str {
match self {
Self::Empty(_) => "empty",
Self::Audio(_) => "audio",
Self::Image(_) => "image",
Self::Text(_) => "text",
Self::ResourceLink(_) => "resource_link",
Self::Resource(_) => "resource",
Self::ToolUse(_) => "tool_use",
Self::ToolResult(_) => "tool_result",
}
}
#[inline]
pub fn as_text(&self) -> Option<&TextContent> {
match self {
Self::Text(c) => Some(c),
_ => None,
}
}
#[inline]
pub fn as_json<T: DeserializeOwned>(&self) -> Option<T> {
match self {
Self::Text(c) => serde_json::from_str(&c.text).ok(),
_ => None,
}
}
#[inline]
pub fn as_audio(&self) -> Option<&AudioContent> {
match self {
Self::Audio(c) => Some(c),
_ => None,
}
}
#[inline]
pub fn as_image(&self) -> Option<&ImageContent> {
match self {
Self::Image(c) => Some(c),
_ => None,
}
}
#[inline]
pub fn as_link(&self) -> Option<&ResourceLink> {
match self {
Self::ResourceLink(c) => Some(c),
_ => None,
}
}
#[inline]
pub fn as_resource(&self) -> Option<&EmbeddedResource> {
match self {
Self::Resource(c) => Some(c),
_ => None,
}
}
#[inline]
pub fn as_tool(&self) -> Option<&ToolUse> {
match self {
Self::ToolUse(c) => Some(c),
_ => None,
}
}
#[inline]
pub fn as_result(&self) -> Option<&ToolResult> {
match self {
Self::ToolResult(c) => Some(c),
_ => None,
}
}
}
impl TextContent {
#[inline]
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
annotations: None,
meta: None,
}
}
pub fn with_annotations<F>(mut self, config: F) -> Self
where
F: FnOnce(Annotations) -> Annotations,
{
self.annotations = Some(config(Default::default()));
self
}
}
impl AudioContent {
#[inline]
pub fn new(data: impl Into<Bytes>) -> Self {
Self {
data: data.into(),
mime: "audio/wav".into(),
annotations: None,
meta: None,
}
}
pub fn with_mime(mut self, mime: impl Into<String>) -> Self {
self.mime = mime.into();
self
}
pub fn with_annotations<F>(mut self, config: F) -> Self
where
F: FnOnce(Annotations) -> Annotations,
{
self.annotations = Some(config(Default::default()));
self
}
pub fn as_slice(&self) -> &[u8] {
&self.data
}
pub fn into_stream(self) -> impl futures_util::Stream<Item = Bytes> {
futures_util::stream::unfold(self.data, |mut remaining_data| async move {
if remaining_data.is_empty() {
return None;
}
let chunk_size = remaining_data.len().min(CHUNK_SIZE);
let chunk = remaining_data.split_to(chunk_size);
Some((chunk, remaining_data))
})
}
}
impl ImageContent {
#[inline]
pub fn new(data: impl Into<Bytes>) -> Self {
Self {
data: data.into(),
mime: "image/jpg".into(),
annotations: None,
meta: None,
}
}
pub fn with_mime(mut self, mime: impl Into<String>) -> Self {
self.mime = mime.into();
self
}
pub fn with_annotations<F>(mut self, config: F) -> Self
where
F: FnOnce(Annotations) -> Annotations,
{
self.annotations = Some(config(Default::default()));
self
}
pub fn as_slice(&self) -> &[u8] {
&self.data
}
pub fn into_stream(self) -> impl futures_util::Stream<Item = Bytes> {
futures_util::stream::unfold(self.data, |mut remaining_data| async move {
if remaining_data.is_empty() {
return None;
}
let chunk_size = remaining_data.len().min(CHUNK_SIZE);
let chunk = remaining_data.split_to(chunk_size);
Some((chunk, remaining_data))
})
}
}
impl ResourceLink {
pub fn new(resource: impl Into<Resource>) -> Self {
Self::from(resource.into())
}
pub fn with_icons(mut self, icons: impl IntoIterator<Item = Icon>) -> Self {
self.icons = Some(icons.into_iter().collect());
self
}
}
impl EmbeddedResource {
#[inline]
pub fn new(resource: impl Into<ResourceContents>) -> Self {
Self {
resource: resource.into(),
annotations: None,
meta: None,
}
}
}
impl ToolUse {
#[inline]
pub fn new<N, Args>(name: N, args: Args) -> Self
where
N: Into<String>,
Args: shared::IntoArgs,
{
Self {
id: uuid::Uuid::new_v4().to_string(),
name: name.into(),
input: args.into_args(),
meta: None,
}
}
}
impl ToolResult {
#[inline]
pub fn new(id: String, resp: CallToolResponse) -> Self {
Self {
tool_use_id: id,
content: resp.content,
struct_content: resp.struct_content,
is_error: resp.is_error,
meta: None,
}
}
#[inline]
pub fn error(id: String, error: Error) -> Self {
Self {
tool_use_id: id,
content: vec![Content::text(error.to_string())],
struct_content: None,
is_error: true,
meta: None,
}
}
}
#[cfg(test)]
mod test {
use super::*;
use futures_util::StreamExt;
#[derive(Deserialize)]
struct Test {
name: String,
age: u32,
}
#[test]
fn it_serializes_text_content_to_json() {
let content = Content::text("hello world");
let json = serde_json::to_string(&content).unwrap();
assert_eq!(json, r#"{"type":"text","text":"hello world"}"#);
}
#[test]
fn it_deserializes_text_content_to_json() {
let json = r#"{"type":"text","text":"hello world"}"#;
let content = serde_json::from_str::<Content>(json).unwrap();
assert_eq!(content.as_text().unwrap().text, "hello world");
}
#[test]
fn it_deserializes_structures_text_content_to_json() {
let json = r#"{"type":"text","text":"{\"name\":\"John\",\"age\":30}"}"#;
let content = serde_json::from_str::<Content>(json).unwrap();
let user: Test = content.as_json().unwrap();
assert_eq!(user.name, "John");
assert_eq!(user.age, 30);
}
#[test]
fn it_serializes_audio_content_to_json() {
let content = Content::audio("hello world");
let json = serde_json::to_string(&content).unwrap();
assert_eq!(
json,
r#"{"type":"audio","data":"aGVsbG8gd29ybGQ=","mimeType":"audio/wav"}"#
);
}
#[test]
fn it_deserializes_audio_content_to_json() {
let json = r#"{"type":"audio","data":"aGVsbG8gd29ybGQ=","mimeType":"audio/wav"}"#;
let content = serde_json::from_str::<Content>(json).unwrap();
assert_eq!(
String::from_utf8_lossy(content.as_audio().unwrap().as_slice()),
"hello world"
);
assert_eq!(content.as_audio().unwrap().mime, "audio/wav");
}
#[test]
fn it_serializes_image_content_to_json() {
let content = Content::image("hello world");
let json = serde_json::to_string(&content).unwrap();
assert_eq!(
json,
r#"{"type":"image","data":"aGVsbG8gd29ybGQ=","mimeType":"image/jpg"}"#
);
}
#[test]
fn it_deserializes_image_content_to_json() {
let json = r#"{"type":"image","data":"aGVsbG8gd29ybGQ=","mimeType":"image/jpg"}"#;
let content = serde_json::from_str::<Content>(json).unwrap();
assert_eq!(
String::from_utf8_lossy(content.as_image().unwrap().as_slice()),
"hello world"
);
assert_eq!(content.as_image().unwrap().mime, "image/jpg");
}
#[test]
#[cfg(feature = "server")]
fn it_serializes_resource_content_to_json() {
let content = Content::resource(
ResourceContents::new("res://resource")
.with_text("hello world")
.with_title("some resource")
.with_annotations(|a| a.with_audience("user").with_priority(1.0)),
);
let json = serde_json::to_string(&content).unwrap();
assert_eq!(
json,
r#"{"type":"resource","resource":{"uri":"res://resource","text":"hello world","title":"some resource","mimeType":"text/plain","annotations":{"audience":["user"],"priority":1.0}}}"#
);
}
#[test]
#[cfg(feature = "server")]
fn it_deserializes_resource_content_to_json() {
use crate::types::Role;
let json = r#"{"type":"resource","resource":{"uri":"res://resource","text":"hello world","title":"some resource","mimeType":"text/plain","annotations":{"audience":["user"],"priority":1.0}}}"#;
let content = serde_json::from_str::<Content>(json).unwrap();
let res = &content.as_resource().unwrap().resource;
assert_eq!(res.uri().to_string(), "res://resource");
assert_eq!(res.mime().unwrap(), "text/plain");
assert_eq!(res.text().unwrap(), "hello world");
assert_eq!(res.title().unwrap(), "some resource");
assert_eq!(res.annotations().unwrap().audience, [Role::User]);
assert_eq!(res.annotations().unwrap().priority, 1.0);
}
#[test]
#[cfg(feature = "server")]
fn it_serializes_resource_link_content_to_json() {
let content = Content::link(
Resource::new("res://resource", "some resource")
.with_title("some resource")
.with_descr("some resource")
.with_size(2)
.with_annotations(|a| a.with_audience("user").with_priority(1.0)),
);
let json = serde_json::to_string(&content).unwrap();
assert_eq!(
json,
r#"{"type":"resource_link","uri":"res://resource","name":"some resource","size":2,"title":"some resource","description":"some resource","annotations":{"audience":["user"],"priority":1.0}}"#
);
}
#[test]
#[cfg(feature = "server")]
fn it_deserializes_resource_link_content_to_json() {
use crate::types::Role;
let json = r#"{"type":"resource_link","uri":"res://resource","name":"some resource","size":2,"title":"some resource","description":"some resource","annotations":{"audience":["user"],"priority":1.0}}"#;
let content = serde_json::from_str::<Content>(json).unwrap();
let res = content.as_link().unwrap();
assert_eq!(res.uri.to_string(), "res://resource");
assert_eq!(res.name, "some resource");
assert_eq!(res.mime.as_deref(), None);
assert_eq!(res.title.as_deref(), Some("some resource"));
assert_eq!(res.annotations.as_ref().unwrap().audience, [Role::User]);
assert_eq!(res.annotations.as_ref().unwrap().priority, 1.0);
}
#[tokio::test]
async fn it_tests_audio_content_into_stream_single_chunk() {
let test_data = "hello world";
let audio = AudioContent::new(test_data.as_bytes());
let stream = audio.into_stream();
let mut stream = Box::pin(stream);
let mut collected_data = Vec::new();
while let Some(chunk) = stream.next().await {
collected_data.extend_from_slice(&chunk);
}
let result_string = String::from_utf8(collected_data).expect("Should be valid UTF-8");
assert_eq!(result_string, test_data);
}
#[tokio::test]
async fn it_tests_audio_content_into_stream_multiple_chunks() {
let test_data = "hello world".repeat(1000); let audio = AudioContent::new(test_data.clone());
let stream = audio.into_stream();
let mut stream = Box::pin(stream);
let mut collected_data = Vec::new();
let mut chunk_count = 0;
while let Some(chunk) = stream.next().await {
collected_data.extend_from_slice(&chunk);
chunk_count += 1;
}
let result_string = String::from_utf8(collected_data).expect("Should be valid UTF-8");
assert_eq!(result_string, test_data);
assert!(
chunk_count > 1,
"Should have multiple chunks for large data"
);
}
#[tokio::test]
async fn it_tests_audio_content_into_stream_empty() {
let audio = AudioContent::new(Bytes::new());
let stream = audio.into_stream();
let mut stream = Box::pin(stream);
let result = stream.next().await;
assert!(result.is_none(), "Empty data should produce no chunks");
}
#[tokio::test]
async fn it_tests_audio_content_into_stream_exact_chunk_size() {
let test_data = "a".repeat(CHUNK_SIZE);
let audio = AudioContent::new(test_data.clone());
let stream = audio.into_stream();
let mut stream = Box::pin(stream);
let mut collected_data = Vec::new();
let mut chunk_count = 0;
while let Some(chunk) = stream.next().await {
collected_data.extend_from_slice(&chunk);
chunk_count += 1;
}
let result_string = String::from_utf8(collected_data).expect("Should be valid UTF-8");
assert_eq!(result_string, test_data);
assert_eq!(
chunk_count, 1,
"Exactly CHUNK_SIZE data should produce one chunk"
);
}
#[tokio::test]
async fn it_tests_image_content_into_stream_single_chunk() {
let test_data = "hello world";
let image = ImageContent::new(test_data.as_bytes());
let stream = image.into_stream();
let mut stream = Box::pin(stream);
let mut collected_data = Vec::new();
while let Some(chunk) = stream.next().await {
collected_data.extend_from_slice(&chunk);
}
let result_string = String::from_utf8(collected_data).expect("Should be valid UTF-8");
assert_eq!(result_string, test_data);
}
#[tokio::test]
async fn it_tests_image_content_into_stream_multiple_chunks() {
let test_data = "hello world".repeat(1000);
let image = ImageContent::new(test_data.clone());
let stream = image.into_stream();
let mut stream = Box::pin(stream);
let mut collected_data = Vec::new();
let mut chunk_count = 0;
while let Some(chunk) = stream.next().await {
collected_data.extend_from_slice(&chunk);
chunk_count += 1;
}
let result_string = String::from_utf8(collected_data).expect("Should be valid UTF-8");
assert_eq!(result_string, test_data);
assert!(
chunk_count > 1,
"Should have multiple chunks for large data"
);
}
#[tokio::test]
async fn it_tests_stream_chunk_sizes() {
let test_data = "hello world".repeat(500); let audio = AudioContent::new(test_data.clone());
let stream = audio.into_stream();
let mut stream = Box::pin(stream);
let mut total_size = 0;
let mut max_chunk_size = 0;
while let Some(chunk) = stream.next().await {
let chunk_size = chunk.len();
total_size += chunk_size;
max_chunk_size = max_chunk_size.max(chunk_size);
assert!(
chunk_size <= CHUNK_SIZE,
"Chunk size should not exceed CHUNK_SIZE"
);
}
assert_eq!(total_size, test_data.len());
assert!(max_chunk_size <= CHUNK_SIZE);
}
#[tokio::test]
async fn it_tests_stream_preserves_binary_data() {
let test_data: Vec<u8> = (0..=255u8).cycle().take(1000).collect();
let audio = AudioContent::new(test_data.clone());
let stream = audio.into_stream();
let mut stream = Box::pin(stream);
let mut collected_data = Vec::new();
while let Some(chunk) = stream.next().await {
collected_data.extend_from_slice(&chunk);
}
assert_eq!(collected_data, test_data);
}
#[tokio::test]
async fn it_tests_audio_content_collect_alternative() {
let test_data = "hello world";
let audio = AudioContent::new(test_data.as_bytes());
let stream = audio.into_stream();
let chunks: Vec<_> = tokio_stream::StreamExt::collect::<Vec<_>>(stream).await;
let collected_data: Vec<u8> = chunks.into_iter().flatten().collect();
let result_string = String::from_utf8(collected_data).expect("Should be valid UTF-8");
assert_eq!(result_string, test_data);
}
#[test]
fn it_tests_audio_content_serialization() {
let audio = AudioContent::new("hello world");
let json = serde_json::to_string(&audio).expect("Should serialize");
let deserialized: AudioContent = serde_json::from_str(&json).expect("Should deserialize");
assert_eq!(audio.data, deserialized.data);
assert_eq!(audio.mime, deserialized.mime);
}
#[test]
fn it_tests_image_content_serialization() {
let audio = ImageContent::new("hello world");
let json = serde_json::to_string(&audio).expect("Should serialize");
let deserialized: ImageContent = serde_json::from_str(&json).expect("Should deserialize");
assert_eq!(audio.data, deserialized.data);
assert_eq!(audio.mime, deserialized.mime);
}
}