use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
use chrono::{DateTime, Utc};
use serde_json::Value;
use crate::types::message::MessageResponse;
pub(crate) struct MessageInner {
workspace_id: String,
id: String,
content: String,
peer_id: String,
session_id: String,
metadata: HashMap<String, Value>,
created_at: DateTime<Utc>,
token_count: u64,
}
#[derive(Clone)]
pub struct Message {
inner: Arc<MessageInner>,
}
impl Message {
#[allow(clippy::needless_pass_by_value)]
pub(crate) fn from_raw(workspace_id: String, resp: MessageResponse) -> Self {
Self {
inner: Arc::new(MessageInner {
workspace_id,
id: resp.id,
content: resp.content,
peer_id: resp.peer_id,
session_id: resp.session_id,
metadata: resp.metadata,
created_at: resp.created_at,
token_count: resp.token_count,
}),
}
}
#[must_use]
pub fn id(&self) -> &str {
&self.inner.id
}
#[must_use]
pub fn content(&self) -> &str {
&self.inner.content
}
#[must_use]
pub fn peer_id(&self) -> &str {
&self.inner.peer_id
}
#[must_use]
pub fn session_id(&self) -> &str {
&self.inner.session_id
}
#[must_use]
pub fn metadata(&self) -> &HashMap<String, Value> {
&self.inner.metadata
}
#[must_use]
pub fn created_at(&self) -> &DateTime<Utc> {
&self.inner.created_at
}
#[must_use]
pub fn token_count(&self) -> u64 {
self.inner.token_count
}
#[must_use]
pub fn workspace_id(&self) -> &str {
&self.inner.workspace_id
}
}
impl fmt::Debug for Message {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let content = &self.inner.content;
let truncated: Cow<'_, str> = match content.char_indices().nth(50) {
Some((idx, _)) => Cow::Owned(format!("{}...", &content[..idx])),
None => Cow::Borrowed(content),
};
f.debug_struct("Message")
.field("id", &self.inner.id)
.field("content", &truncated)
.field("peer_id", &self.inner.peer_id)
.field("session_id", &self.inner.session_id)
.finish_non_exhaustive()
}
}
impl fmt::Display for Message {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.inner.content)
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::unnecessary_wraps,
clippy::needless_pass_by_value,
clippy::unused_async
)]
mod tests {
use static_assertions::assert_impl_all;
use super::*;
assert_impl_all!(Message: Send, Sync, Clone, fmt::Debug, fmt::Display);
fn fake_response() -> MessageResponse {
MessageResponse {
id: "msg_1".to_owned(),
content: "hello world".to_owned(),
peer_id: "peer_a".to_owned(),
session_id: "sess_x".to_owned(),
metadata: HashMap::new(),
created_at: Utc::now(),
workspace_id: "ws_1".to_owned(),
token_count: 3,
}
}
fn make_msg(honcho: &crate::Honcho, resp: MessageResponse) -> Message {
Message::from_raw(honcho.workspace_id().to_owned(), resp)
}
#[test]
fn from_raw_maps_fields() {
let resp = fake_response();
let honcho = crate::Honcho::new("http://localhost:9999", "ws_1").unwrap();
let msg = make_msg(&honcho, resp);
assert_eq!(msg.id(), "msg_1");
assert_eq!(msg.content(), "hello world");
assert_eq!(msg.peer_id(), "peer_a");
assert_eq!(msg.session_id(), "sess_x");
assert_eq!(msg.workspace_id(), "ws_1");
assert_eq!(msg.token_count(), 3);
assert!(msg.metadata().is_empty());
}
#[test]
fn debug_truncates_long_content() {
let mut resp = fake_response();
resp.content = "a".repeat(80);
let honcho = crate::Honcho::new("http://localhost:9999", "ws_1").unwrap();
let msg = make_msg(&honcho, resp);
let dbg = format!("{msg:?}");
assert!(dbg.contains("aaa..."));
assert!(!dbg.contains(&"a".repeat(80)));
}
#[test]
fn debug_short_content_not_truncated() {
let resp = fake_response();
let honcho = crate::Honcho::new("http://localhost:9999", "ws_1").unwrap();
let msg = make_msg(&honcho, resp);
let dbg = format!("{msg:?}");
assert!(dbg.contains("hello world"));
assert!(!dbg.contains("..."));
}
#[test]
fn display_returns_full_content() {
let mut resp = fake_response();
resp.content = "a".repeat(80);
let honcho = crate::Honcho::new("http://localhost:9999", "ws_1").unwrap();
let msg = make_msg(&honcho, resp);
assert_eq!(format!("{msg}"), "a".repeat(80));
}
#[test]
fn debug_truncation_multibyte_utf8() {
let mut resp = fake_response();
resp.content = "\u{4e00}".repeat(60);
let honcho = crate::Honcho::new("http://localhost:9999", "ws_1").unwrap();
let msg = make_msg(&honcho, resp);
let dbg = format!("{msg:?}");
assert!(dbg.contains("..."));
}
}