botlib/src/models.rs

296 lines
8.1 KiB
Rust
Raw Normal View History

2025-12-03 19:45:09 -03:00
//! Common models shared across bot ecosystem
//!
//! Contains DTOs, API response types, and common structures.
use crate::message_types::MessageType;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
/// Standard API response wrapper
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiResponse<T> {
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<T>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
impl<T> ApiResponse<T> {
/// Create a success response with data
pub fn success(data: T) -> Self {
Self {
success: true,
data: Some(data),
error: None,
message: None,
}
}
/// Create a success response with message
pub fn success_message(data: T, message: impl Into<String>) -> Self {
Self {
success: true,
data: Some(data),
error: None,
message: Some(message.into()),
}
}
/// Create an error response
pub fn error(message: impl Into<String>) -> Self {
Self {
success: false,
data: None,
error: Some(message.into()),
message: None,
}
}
}
impl<T: Default> Default for ApiResponse<T> {
fn default() -> Self {
Self::success(T::default())
}
}
/// User session information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Session {
pub id: Uuid,
pub user_id: Uuid,
pub bot_id: Uuid,
pub title: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<DateTime<Utc>>,
}
impl Session {
/// Check if session is expired
pub fn is_expired(&self) -> bool {
if let Some(expires) = self.expires_at {
Utc::now() > expires
} else {
false
}
}
}
/// User message sent to bot
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserMessage {
pub bot_id: String,
pub user_id: String,
pub session_id: String,
pub channel: String,
pub content: String,
pub message_type: MessageType,
#[serde(skip_serializing_if = "Option::is_none")]
pub media_url: Option<String>,
pub timestamp: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context_name: Option<String>,
}
impl UserMessage {
/// Create a new text message
pub fn text(
bot_id: impl Into<String>,
user_id: impl Into<String>,
session_id: impl Into<String>,
channel: impl Into<String>,
content: impl Into<String>,
) -> Self {
Self {
bot_id: bot_id.into(),
user_id: user_id.into(),
session_id: session_id.into(),
channel: channel.into(),
content: content.into(),
message_type: MessageType::USER,
media_url: None,
timestamp: Utc::now(),
context_name: None,
}
}
}
/// Suggestion for user
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Suggestion {
pub text: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub context: Option<String>,
}
impl Suggestion {
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
context: None,
}
}
pub fn with_context(text: impl Into<String>, context: impl Into<String>) -> Self {
Self {
text: text.into(),
context: Some(context.into()),
}
}
}
/// Bot response to user
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BotResponse {
pub bot_id: String,
pub user_id: String,
pub session_id: String,
pub channel: String,
pub content: String,
pub message_type: MessageType,
#[serde(skip_serializing_if = "Option::is_none")]
pub stream_token: Option<String>,
pub is_complete: bool,
#[serde(default)]
pub suggestions: Vec<Suggestion>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context_name: Option<String>,
#[serde(default)]
pub context_length: usize,
#[serde(default)]
pub context_max_length: usize,
}
impl BotResponse {
/// Create a new bot response
pub fn new(
bot_id: impl Into<String>,
session_id: impl Into<String>,
user_id: impl Into<String>,
content: impl Into<String>,
channel: impl Into<String>,
) -> Self {
Self {
bot_id: bot_id.into(),
user_id: user_id.into(),
session_id: session_id.into(),
channel: channel.into(),
content: content.into(),
message_type: MessageType::BOT_RESPONSE,
stream_token: None,
is_complete: true,
suggestions: Vec::new(),
context_name: None,
context_length: 0,
context_max_length: 0,
}
}
/// Create a streaming response
pub fn streaming(
bot_id: impl Into<String>,
session_id: impl Into<String>,
user_id: impl Into<String>,
channel: impl Into<String>,
stream_token: impl Into<String>,
) -> Self {
Self {
bot_id: bot_id.into(),
user_id: user_id.into(),
session_id: session_id.into(),
channel: channel.into(),
content: String::new(),
message_type: MessageType::BOT_RESPONSE,
stream_token: Some(stream_token.into()),
is_complete: false,
suggestions: Vec::new(),
context_name: None,
context_length: 0,
context_max_length: 0,
}
}
/// Add suggestions to response
pub fn with_suggestions(mut self, suggestions: Vec<Suggestion>) -> Self {
self.suggestions = suggestions;
self
}
}
impl Default for BotResponse {
fn default() -> Self {
Self {
bot_id: String::new(),
user_id: String::new(),
session_id: String::new(),
channel: String::new(),
content: String::new(),
message_type: MessageType::BOT_RESPONSE,
stream_token: None,
is_complete: true,
suggestions: Vec::new(),
context_name: None,
context_length: 0,
context_max_length: 0,
}
}
}
2025-12-03 19:45:09 -03:00
/// Attachment for media files in messages
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Attachment {
/// Type of attachment (image, audio, video, file, etc.)
pub attachment_type: String,
/// URL or path to the attachment
pub url: String,
/// MIME type of the attachment
#[serde(skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
/// File name if available
#[serde(skip_serializing_if = "Option::is_none")]
pub filename: Option<String>,
/// File size in bytes
#[serde(skip_serializing_if = "Option::is_none")]
pub size: Option<u64>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_api_response_success() {
let response: ApiResponse<String> = ApiResponse::success("test".to_string());
assert!(response.success);
assert_eq!(response.data, Some("test".to_string()));
assert!(response.error.is_none());
}
#[test]
fn test_api_response_error() {
let response: ApiResponse<String> = ApiResponse::error("something went wrong");
assert!(!response.success);
assert!(response.data.is_none());
assert_eq!(response.error, Some("something went wrong".to_string()));
}
#[test]
fn test_user_message_creation() {
let msg = UserMessage::text("bot1", "user1", "sess1", "web", "Hello!");
assert_eq!(msg.content, "Hello!");
assert_eq!(msg.message_type, MessageType::USER);
}
#[test]
fn test_bot_response_creation() {
let response = BotResponse::new("bot1", "sess1", "user1", "Hi there!", "web");
assert!(response.is_complete);
assert_eq!(response.message_type, MessageType::BOT_RESPONSE);
}
}