use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Document {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
pub content: String,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub metadata: HashMap<String, serde_json::Value>,
}
impl Document {
pub fn new(content: impl Into<String>) -> Self {
Self {
id: None,
content: content.into(),
metadata: HashMap::new(),
}
}
pub fn with_id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
}
pub fn with_metadata(
mut self,
key: impl Into<String>,
value: impl Into<serde_json::Value>,
) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
pub fn with_metadata_map(mut self, m: HashMap<String, serde_json::Value>) -> Self {
self.metadata = m;
self
}
}
impl From<String> for Document {
fn from(s: String) -> Self {
Self::new(s)
}
}
impl From<&str> for Document {
fn from(s: &str) -> Self {
Self::new(s.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builder_chains() {
let d = Document::new("hello")
.with_id("doc-1")
.with_metadata("source", "file.txt");
assert_eq!(d.id.as_deref(), Some("doc-1"));
assert_eq!(d.content, "hello");
assert_eq!(d.metadata["source"], "file.txt");
}
#[test]
fn from_str_works() {
let d: Document = "hello".into();
assert_eq!(d.content, "hello");
assert!(d.metadata.is_empty());
}
#[test]
fn serde_skips_optional_when_empty() {
let d = Document::new("x");
let s = serde_json::to_string(&d).unwrap();
assert!(!s.contains("\"id\""));
assert!(!s.contains("\"metadata\""));
}
}