data_modelling_core/export/
knowledge.rs

1//! Knowledge Base exporter
2//!
3//! Exports KnowledgeArticle models to YAML format.
4
5use crate::export::ExportError;
6use crate::models::knowledge::{KnowledgeArticle, KnowledgeIndex};
7
8/// Knowledge exporter for generating YAML from KnowledgeArticle models
9pub struct KnowledgeExporter;
10
11impl KnowledgeExporter {
12    /// Create a new Knowledge exporter instance
13    pub fn new() -> Self {
14        Self
15    }
16
17    /// Export a knowledge article to YAML format
18    ///
19    /// # Arguments
20    ///
21    /// * `article` - The KnowledgeArticle to export
22    ///
23    /// # Returns
24    ///
25    /// A Result containing the YAML string, or an ExportError
26    pub fn export(&self, article: &KnowledgeArticle) -> Result<String, ExportError> {
27        let yaml = article.to_yaml().map_err(|e| {
28            ExportError::SerializationError(format!("Failed to serialize knowledge article: {}", e))
29        })?;
30
31        // Validate exported YAML against knowledge schema (if feature enabled)
32        #[cfg(feature = "schema-validation")]
33        {
34            use crate::validation::schema::validate_knowledge_internal;
35            validate_knowledge_internal(&yaml).map_err(ExportError::ValidationError)?;
36        }
37
38        Ok(yaml)
39    }
40
41    /// Export a knowledge article without validation
42    ///
43    /// Use this when you want to skip schema validation for performance
44    /// or when exporting to a trusted destination.
45    pub fn export_without_validation(
46        &self,
47        article: &KnowledgeArticle,
48    ) -> Result<String, ExportError> {
49        article.to_yaml().map_err(|e| {
50            ExportError::SerializationError(format!("Failed to serialize knowledge article: {}", e))
51        })
52    }
53
54    /// Export a knowledge index to YAML format
55    ///
56    /// # Arguments
57    ///
58    /// * `index` - The KnowledgeIndex to export
59    ///
60    /// # Returns
61    ///
62    /// A Result containing the YAML string, or an ExportError
63    pub fn export_index(&self, index: &KnowledgeIndex) -> Result<String, ExportError> {
64        index.to_yaml().map_err(|e| {
65            ExportError::SerializationError(format!("Failed to serialize knowledge index: {}", e))
66        })
67    }
68
69    /// Export multiple articles to a directory
70    ///
71    /// # Arguments
72    ///
73    /// * `articles` - The articles to export
74    /// * `dir_path` - Directory to export to
75    /// * `workspace_name` - Workspace name for filename generation
76    ///
77    /// # Returns
78    ///
79    /// A Result with the number of files exported, or an ExportError
80    pub fn export_to_directory(
81        &self,
82        articles: &[KnowledgeArticle],
83        dir_path: &std::path::Path,
84        workspace_name: &str,
85    ) -> Result<usize, ExportError> {
86        // Create directory if it doesn't exist
87        if !dir_path.exists() {
88            std::fs::create_dir_all(dir_path)
89                .map_err(|e| ExportError::IoError(format!("Failed to create directory: {}", e)))?;
90        }
91
92        let mut count = 0;
93        for article in articles {
94            let filename = article.filename(workspace_name);
95            let path = dir_path.join(&filename);
96            let yaml = self.export(article)?;
97            std::fs::write(&path, yaml).map_err(|e| {
98                ExportError::IoError(format!("Failed to write {}: {}", filename, e))
99            })?;
100            count += 1;
101        }
102
103        Ok(count)
104    }
105
106    /// Export articles filtered by domain to a directory
107    ///
108    /// # Arguments
109    ///
110    /// * `articles` - The articles to export
111    /// * `dir_path` - Directory to export to
112    /// * `workspace_name` - Workspace name for filename generation
113    /// * `domain` - Domain to filter by
114    ///
115    /// # Returns
116    ///
117    /// A Result with the number of files exported, or an ExportError
118    pub fn export_domain_to_directory(
119        &self,
120        articles: &[KnowledgeArticle],
121        dir_path: &std::path::Path,
122        workspace_name: &str,
123        domain: &str,
124    ) -> Result<usize, ExportError> {
125        let filtered: Vec<&KnowledgeArticle> = articles
126            .iter()
127            .filter(|a| a.domain.as_deref() == Some(domain))
128            .collect();
129
130        // Create domain subdirectory
131        let domain_dir = dir_path.join(domain);
132        if !domain_dir.exists() {
133            std::fs::create_dir_all(&domain_dir)
134                .map_err(|e| ExportError::IoError(format!("Failed to create directory: {}", e)))?;
135        }
136
137        let mut count = 0;
138        for article in filtered {
139            let filename = article.filename(workspace_name);
140            let path = domain_dir.join(&filename);
141            let yaml = self.export(article)?;
142            std::fs::write(&path, yaml).map_err(|e| {
143                ExportError::IoError(format!("Failed to write {}: {}", filename, e))
144            })?;
145            count += 1;
146        }
147
148        Ok(count)
149    }
150}
151
152impl Default for KnowledgeExporter {
153    fn default() -> Self {
154        Self::new()
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161    use crate::models::knowledge::KnowledgeStatus;
162
163    #[test]
164    fn test_export_knowledge_article() {
165        let article = KnowledgeArticle::new(
166            1,
167            "Data Classification Guide",
168            "This guide explains data classification.",
169            "Data classification is essential for governance.",
170            "data-governance@example.com",
171        )
172        .with_status(KnowledgeStatus::Published);
173
174        let exporter = KnowledgeExporter::new();
175        let result = exporter.export_without_validation(&article);
176        assert!(result.is_ok());
177        let yaml = result.unwrap();
178        assert!(yaml.contains("title: Data Classification Guide"));
179        assert!(yaml.contains("status: published"));
180    }
181
182    #[test]
183    fn test_export_knowledge_index() {
184        let index = KnowledgeIndex::new();
185        let exporter = KnowledgeExporter::new();
186        let result = exporter.export_index(&index);
187        assert!(result.is_ok());
188        let yaml = result.unwrap();
189        assert!(yaml.contains("schemaVersion"));
190        assert!(yaml.contains("nextNumber: 1"));
191    }
192}