use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use crate::document::DocumentTree;
use crate::parser::DocumentFormat;
#[derive(Debug, Clone)]
pub struct IndexedDocument {
pub id: String,
pub format: DocumentFormat,
pub name: String,
pub description: Option<String>,
pub source_path: Option<PathBuf>,
pub page_count: Option<usize>,
pub line_count: Option<usize>,
pub tree: Option<DocumentTree>,
pub pages: Vec<PageContent>,
}
impl IndexedDocument {
pub fn new(id: impl Into<String>, format: DocumentFormat) -> Self {
Self {
id: id.into(),
format,
name: String::new(),
description: None,
source_path: None,
page_count: None,
line_count: None,
tree: None,
pages: Vec::new(),
}
}
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = name.into();
self
}
pub fn with_description(mut self, desc: impl Into<String>) -> Self {
self.description = Some(desc.into());
self
}
pub fn with_source_path(mut self, path: impl Into<PathBuf>) -> Self {
self.source_path = Some(path.into());
self
}
pub fn with_page_count(mut self, count: usize) -> Self {
self.page_count = Some(count);
self
}
pub fn with_line_count(mut self, count: usize) -> Self {
self.line_count = Some(count);
self
}
pub fn with_tree(mut self, tree: DocumentTree) -> Self {
self.tree = Some(tree);
self
}
pub fn add_page(&mut self, page: usize, content: impl Into<String>) {
self.pages.push(PageContent {
page,
content: content.into(),
});
}
pub fn is_loaded(&self) -> bool {
self.tree.is_some()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PageContent {
pub page: usize,
pub content: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum IndexMode {
#[default]
Default,
Force,
Incremental,
}
#[derive(Debug, Clone)]
pub struct IndexOptions {
pub mode: IndexMode,
pub generate_summaries: bool,
pub include_text: bool,
pub generate_ids: bool,
pub generate_description: bool,
}
impl Default for IndexOptions {
fn default() -> Self {
Self {
mode: IndexMode::Default,
generate_summaries: true,
include_text: true,
generate_ids: true,
generate_description: false,
}
}
}
impl IndexOptions {
pub fn new() -> Self {
Self::default()
}
pub fn with_summaries(mut self) -> Self {
self.generate_summaries = true;
self
}
pub fn with_description(mut self) -> Self {
self.generate_description = true;
self
}
pub fn with_mode(mut self, mode: IndexMode) -> Self {
self.mode = mode;
self
}
}
#[derive(Debug, Clone)]
pub struct IndexResult {
pub items: Vec<IndexItem>,
}
impl IndexResult {
pub fn new(items: Vec<IndexItem>) -> Self {
Self { items }
}
pub fn doc_id(&self) -> Option<&str> {
if self.items.len() == 1 {
Some(&self.items[0].doc_id)
} else {
None
}
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn len(&self) -> usize {
self.items.len()
}
}
#[derive(Debug, Clone)]
pub struct IndexItem {
pub doc_id: String,
pub name: String,
pub format: DocumentFormat,
}
impl IndexItem {
pub fn new(
doc_id: impl Into<String>,
name: impl Into<String>,
format: DocumentFormat,
) -> Self {
Self {
doc_id: doc_id.into(),
name: name.into(),
format,
}
}
}
#[derive(Debug, Clone)]
pub struct QueryResult {
pub doc_id: String,
pub node_ids: Vec<String>,
pub content: String,
pub score: f32,
}
impl QueryResult {
pub fn new(doc_id: impl Into<String>) -> Self {
Self {
doc_id: doc_id.into(),
node_ids: Vec::new(),
content: String::new(),
score: 0.0,
}
}
pub fn is_empty(&self) -> bool {
self.node_ids.is_empty()
}
pub fn len(&self) -> usize {
self.node_ids.len()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DocumentInfo {
pub id: String,
pub name: String,
pub format: String,
pub description: Option<String>,
pub page_count: Option<usize>,
pub line_count: Option<usize>,
}
impl DocumentInfo {
pub fn new(id: impl Into<String>, name: impl Into<String>) -> Self {
Self {
id: id.into(),
name: name.into(),
format: String::new(),
description: None,
page_count: None,
line_count: None,
}
}
pub fn with_format(mut self, format: impl Into<String>) -> Self {
self.format = format.into();
self
}
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum ClientError {
#[error("Document not found: {0}")]
NotFound(String),
#[error("Invalid operation: {0}")]
InvalidOperation(String),
#[error("Configuration error: {0}")]
Config(String),
#[error("Operation timed out")]
Timeout,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_indexed_document() {
let doc = IndexedDocument::new("doc-1", DocumentFormat::Markdown)
.with_name("Test Document")
.with_description("A test document");
assert_eq!(doc.id, "doc-1");
assert_eq!(doc.name, "Test Document");
assert!(doc.tree.is_none());
}
#[test]
fn test_index_options() {
let options = IndexOptions::new()
.with_summaries()
.with_mode(IndexMode::Force);
assert!(options.generate_summaries);
assert_eq!(options.mode, IndexMode::Force);
}
#[test]
fn test_query_result() {
let result = QueryResult::new("doc-1");
assert!(result.is_empty());
assert_eq!(result.len(), 0);
}
#[test]
fn test_document_info() {
let info = DocumentInfo::new("doc-1", "Test").with_format("markdown");
assert_eq!(info.id, "doc-1");
assert_eq!(info.format, "markdown");
}
#[test]
fn test_index_result() {
let item = IndexItem::new("doc-1", "Test", DocumentFormat::Markdown);
let result = IndexResult::new(vec![item]);
assert_eq!(result.doc_id(), Some("doc-1"));
assert_eq!(result.len(), 1);
assert!(!result.is_empty());
}
#[test]
fn test_index_result_empty() {
let result = IndexResult::new(vec![]);
assert!(result.is_empty());
assert_eq!(result.doc_id(), None);
}
#[test]
fn test_index_result_multiple() {
let items = vec![
IndexItem::new("doc-1", "A", DocumentFormat::Markdown),
IndexItem::new("doc-2", "B", DocumentFormat::Pdf),
];
let result = IndexResult::new(items);
assert_eq!(result.len(), 2);
assert_eq!(result.doc_id(), None);
}
}