Skip to main content

cdx_core/document/
builder.rs

1use crate::archive::{CONTENT_PATH, DUBLIN_CORE_PATH};
2use crate::content::{Block, Content, Text};
3use crate::metadata::DublinCore;
4use crate::{DocumentId, DocumentState, HashAlgorithm, Manifest, Result};
5
6use super::Document;
7
8/// Builder for creating Codex documents.
9#[derive(Debug, Clone)]
10pub struct DocumentBuilder {
11    title: String,
12    creator: String,
13    description: Option<String>,
14    language: Option<String>,
15    blocks: Vec<Block>,
16    state: DocumentState,
17    hash_algorithm: HashAlgorithm,
18    content_override: Option<Content>,
19    dublin_core_override: Option<DublinCore>,
20}
21
22impl Default for DocumentBuilder {
23    fn default() -> Self {
24        Self::new()
25    }
26}
27
28impl DocumentBuilder {
29    /// Create a new document builder.
30    #[must_use]
31    pub fn new() -> Self {
32        Self {
33            title: "Untitled".to_string(),
34            creator: "Unknown".to_string(),
35            description: None,
36            language: None,
37            blocks: Vec::new(),
38            state: DocumentState::Draft,
39            hash_algorithm: HashAlgorithm::default(),
40            content_override: None,
41            dublin_core_override: None,
42        }
43    }
44
45    /// Set the document title.
46    #[must_use]
47    pub fn title(mut self, title: impl Into<String>) -> Self {
48        self.title = title.into();
49        self
50    }
51
52    /// Set the document creator.
53    #[must_use]
54    pub fn creator(mut self, creator: impl Into<String>) -> Self {
55        self.creator = creator.into();
56        self
57    }
58
59    /// Set the document description.
60    #[must_use]
61    pub fn description(mut self, description: impl Into<String>) -> Self {
62        self.description = Some(description.into());
63        self
64    }
65
66    /// Set the document language.
67    #[must_use]
68    pub fn language(mut self, language: impl Into<String>) -> Self {
69        self.language = Some(language.into());
70        self
71    }
72
73    /// Set the document state.
74    #[must_use]
75    pub fn state(mut self, state: DocumentState) -> Self {
76        self.state = state;
77        self
78    }
79
80    /// Set the hash algorithm.
81    #[must_use]
82    pub fn hash_algorithm(mut self, algorithm: HashAlgorithm) -> Self {
83        self.hash_algorithm = algorithm;
84        self
85    }
86
87    /// Add a content block.
88    #[must_use]
89    pub fn add_block(mut self, block: Block) -> Self {
90        self.blocks.push(block);
91        self
92    }
93
94    /// Add a heading block.
95    #[must_use]
96    pub fn add_heading(self, level: u8, text: impl Into<String>) -> Self {
97        self.add_block(Block::heading(level, vec![Text::plain(text)]))
98    }
99
100    /// Add a paragraph block.
101    #[must_use]
102    pub fn add_paragraph(self, text: impl Into<String>) -> Self {
103        self.add_block(Block::paragraph(vec![Text::plain(text)]))
104    }
105
106    /// Add a code block.
107    #[must_use]
108    pub fn add_code_block(self, code: impl Into<String>, language: Option<String>) -> Self {
109        self.add_block(Block::code_block(code, language))
110    }
111
112    /// Set pre-built content, overriding any blocks added via `add_block()`.
113    #[must_use]
114    pub fn with_content(mut self, content: Content) -> Self {
115        self.content_override = Some(content);
116        self
117    }
118
119    /// Set pre-built Dublin Core metadata, overriding title/creator/description/language.
120    #[must_use]
121    pub fn with_dublin_core(mut self, dublin_core: DublinCore) -> Self {
122        self.dublin_core_override = Some(dublin_core);
123        self
124    }
125
126    /// Build the document.
127    ///
128    /// # Errors
129    ///
130    /// Returns an error if the document cannot be constructed.
131    pub fn build(self) -> Result<Document> {
132        use crate::manifest::{ContentRef, Metadata};
133
134        let content = self
135            .content_override
136            .unwrap_or_else(|| Content::new(self.blocks));
137        let dublin_core = self.dublin_core_override.unwrap_or_else(|| {
138            let mut dc = DublinCore::new(&self.title, &self.creator);
139            dc.terms.description = self.description;
140            dc.terms.language = self.language;
141            dc
142        });
143
144        let content_ref = ContentRef {
145            path: CONTENT_PATH.to_string(),
146            hash: DocumentId::pending(),
147            compression: Some("deflate".to_string()),
148            merkle_root: None,
149            block_count: None,
150        };
151
152        let metadata = Metadata {
153            dublin_core: DUBLIN_CORE_PATH.to_string(),
154            custom: None,
155        };
156
157        let mut manifest = Manifest::new(content_ref, metadata);
158        manifest.state = self.state;
159        manifest.hash_algorithm = self.hash_algorithm;
160
161        Ok(Document {
162            manifest,
163            content,
164            dublin_core,
165            #[cfg(feature = "signatures")]
166            signature_file: None,
167            #[cfg(feature = "encryption")]
168            encryption_metadata: None,
169            academic_numbering: None,
170            comments: None,
171            phantom_clusters: None,
172            form_data: None,
173            bibliography: None,
174            jsonld_metadata: None,
175        })
176    }
177}