use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use crate::document::DocumentTree;
use crate::index::parse::DocumentFormat;
use crate::metrics::IndexMetrics;
#[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 tree: Option<DocumentTree>,
pub pages: Vec<PageContent>,
pub metrics: Option<IndexMetrics>,
pub reasoning_index: Option<crate::document::ReasoningIndex>,
}
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,
tree: None,
pages: Vec::new(),
metrics: None,
reasoning_index: None,
}
}
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_tree(mut self, tree: DocumentTree) -> Self {
self.tree = Some(tree);
self
}
pub fn with_metrics(mut self, metrics: IndexMetrics) -> Self {
self.metrics = Some(metrics);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PageContent {
pub page: usize,
pub content: String,
}
#[derive(Debug, Clone)]
pub struct FailedItem {
pub source: String,
pub error: String,
}
impl FailedItem {
pub fn new(source: impl Into<String>, error: impl Into<String>) -> Self {
Self {
source: source.into(),
error: error.into(),
}
}
}
#[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,
pub enable_synonym_expansion: bool,
}
impl Default for IndexOptions {
fn default() -> Self {
Self {
mode: IndexMode::Default,
generate_summaries: true,
include_text: true,
generate_ids: true,
generate_description: false,
enable_synonym_expansion: true,
}
}
}
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>,
pub failed: Vec<FailedItem>,
}
impl IndexResult {
pub fn new(items: Vec<IndexItem>) -> Self {
Self {
items,
failed: Vec::new(),
}
}
pub fn with_partial(items: Vec<IndexItem>, failed: Vec<FailedItem>) -> Self {
Self { items, failed }
}
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()
}
pub fn has_failures(&self) -> bool {
!self.failed.is_empty()
}
pub fn total(&self) -> usize {
self.items.len() + self.failed.len()
}
}
#[derive(Debug, Clone)]
pub struct IndexItem {
pub doc_id: String,
pub name: String,
pub format: DocumentFormat,
pub description: Option<String>,
pub source_path: Option<String>,
pub page_count: Option<usize>,
pub metrics: Option<IndexMetrics>,
}
impl IndexItem {
pub fn new(
doc_id: impl Into<String>,
name: impl Into<String>,
format: DocumentFormat,
description: Option<String>,
page_count: Option<usize>,
) -> Self {
Self {
doc_id: doc_id.into(),
name: name.into(),
format,
description,
source_path: None,
page_count,
metrics: None,
}
}
pub fn with_source_path(mut self, path: impl Into<String>) -> Self {
self.source_path = Some(path.into());
self
}
pub fn with_metrics(mut self, metrics: IndexMetrics) -> Self {
self.metrics = Some(metrics);
self
}
pub fn with_metrics_opt(mut self, metrics: Option<IndexMetrics>) -> Self {
self.metrics = metrics;
self
}
}
#[derive(Debug, Clone)]
pub struct QueryResultItem {
pub doc_id: String,
pub node_ids: Vec<String>,
pub content: String,
pub score: f32,
}
#[derive(Debug, Clone)]
pub struct QueryResult {
pub items: Vec<QueryResultItem>,
pub failed: Vec<FailedItem>,
}
impl QueryResult {
pub fn new() -> Self {
Self {
items: Vec::new(),
failed: Vec::new(),
}
}
pub fn from_single(item: QueryResultItem) -> Self {
Self {
items: vec![item],
failed: Vec::new(),
}
}
pub fn with_partial(items: Vec<QueryResultItem>, failed: Vec<FailedItem>) -> Self {
Self { items, failed }
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn len(&self) -> usize {
self.items.len()
}
pub fn single(&self) -> Option<&QueryResultItem> {
self.items.first()
}
pub fn has_failures(&self) -> bool {
!self.failed.is_empty()
}
}
impl Default for QueryResult {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DocumentInfo {
pub id: String,
pub name: String,
pub format: String,
pub description: Option<String>,
pub source_path: 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,
source_path: 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();
assert!(result.is_empty());
assert_eq!(result.len(), 0);
}
#[test]
fn test_query_result_single() {
let item = QueryResultItem {
doc_id: "doc-1".into(),
node_ids: vec!["n1".into()],
content: "content".into(),
score: 0.9,
};
let result = QueryResult::from_single(item);
assert!(!result.is_empty());
assert_eq!(result.len(), 1);
assert!(result.single().is_some());
assert_eq!(result.single().unwrap().doc_id, "doc-1");
}
#[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, None, None);
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, None, None),
IndexItem::new("doc-2", "B", DocumentFormat::Pdf, None, None),
];
let result = IndexResult::new(items);
assert_eq!(result.len(), 2);
assert_eq!(result.doc_id(), None);
}
#[test]
fn test_partial_success() {
let items = vec![IndexItem::new(
"doc-1",
"A",
DocumentFormat::Markdown,
None,
None,
)];
let failed = vec![FailedItem::new("missing.pdf", "File not found")];
let result = IndexResult::with_partial(items, failed);
assert_eq!(result.len(), 1);
assert!(result.has_failures());
assert_eq!(result.total(), 2);
assert_eq!(result.failed[0].source, "missing.pdf");
}
}