Skip to main content

daimon_core/
document.rs

1//! Document types shared across the Daimon framework.
2
3use std::collections::HashMap;
4
5/// A retrieved document fragment with optional metadata and relevance score.
6#[derive(Debug, Clone)]
7pub struct Document {
8    /// The text content of the document.
9    pub content: String,
10    /// Arbitrary key-value metadata (e.g., source URL, page number, title).
11    pub metadata: HashMap<String, serde_json::Value>,
12    /// Relevance score assigned by the retrieval backend (higher = more relevant).
13    /// `None` if the backend does not provide scores.
14    pub score: Option<f64>,
15}
16
17impl Document {
18    /// Creates a document with only text content.
19    pub fn new(content: impl Into<String>) -> Self {
20        Self {
21            content: content.into(),
22            metadata: HashMap::new(),
23            score: None,
24        }
25    }
26
27    /// Adds a metadata entry.
28    pub fn with_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
29        self.metadata.insert(key.into(), value);
30        self
31    }
32
33    /// Sets the relevance score.
34    pub fn with_score(mut self, score: f64) -> Self {
35        self.score = Some(score);
36        self
37    }
38}
39
40/// A document paired with a similarity score from a vector query.
41///
42/// `id` is the backend's stable identifier for this document — the same id
43/// passed to [`VectorStore::upsert`](crate::vector_store::VectorStore::upsert)
44/// when the document was stored. Callers that need to act on a search
45/// result (e.g. delete it) must be able to round-trip through this `id`;
46/// implementations of `VectorStore::query` must therefore always populate it
47/// with the real, stable id rather than a synthetic/rank-derived value.
48#[derive(Debug, Clone)]
49pub struct ScoredDocument {
50    pub id: String,
51    pub document: Document,
52    pub score: f64,
53}
54
55impl ScoredDocument {
56    pub fn new(id: impl Into<String>, document: Document, score: f64) -> Self {
57        Self {
58            id: id.into(),
59            document,
60            score,
61        }
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn test_document_new() {
71        let doc = Document::new("hello world");
72        assert_eq!(doc.content, "hello world");
73        assert!(doc.metadata.is_empty());
74        assert!(doc.score.is_none());
75    }
76
77    #[test]
78    fn test_document_with_metadata_and_score() {
79        let doc = Document::new("text")
80            .with_metadata("source", serde_json::json!("wiki"))
81            .with_score(0.95);
82        assert_eq!(doc.metadata["source"], "wiki");
83        assert_eq!(doc.score, Some(0.95));
84    }
85
86    #[test]
87    fn test_scored_document() {
88        let doc = Document::new("content");
89        let scored = ScoredDocument::new("doc-1", doc, 0.87);
90        assert_eq!(scored.id, "doc-1");
91        assert_eq!(scored.document.content, "content");
92        assert_eq!(scored.score, 0.87);
93    }
94}