Skip to main content

sara_core/parser/
mod.rs

1//! Input parsers for reading documents into core structures.
2//!
3//! This module is the input adapter in the hexagonal architecture. It reads
4//! various formats and fills core structures (`Item`, `Relationship`, etc.).
5//!
6//! Use [`InputFormat`] with [`parse_metadata`] or [`parse_document`] to parse
7//! content without depending on format-specific functions.
8
9mod frontmatter;
10mod markdown;
11mod yaml;
12
13#[doc(inline)]
14pub use frontmatter::{has_frontmatter, update_frontmatter};
15#[doc(inline)]
16pub use markdown::extract_name_from_content;
17
18use std::path::Path;
19
20use crate::error::SaraError;
21use crate::model::Item;
22
23/// Supported input formats for document parsing.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum InputFormat {
26    /// Markdown with YAML frontmatter.
27    Markdown,
28}
29
30/// Parses content and extracts an [`Item`].
31///
32/// Dispatches to the appropriate format-specific parser based on `format`.
33///
34/// # Errors
35///
36/// Returns `SaraError` if the content cannot be parsed in the given format.
37pub fn parse_metadata(
38    content: &str,
39    file_path: &Path,
40    repository: &Path,
41    format: InputFormat,
42) -> Result<Item, SaraError> {
43    match format {
44        InputFormat::Markdown => markdown::parse_markdown_file(content, file_path, repository),
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use crate::model::{ItemType, RelationshipType};
52    use std::path::PathBuf;
53
54    const SOLUTION_MD: &str = r#"---
55id: "SOL-001"
56type: solution
57name: "Test Solution"
58description: "A test solution"
59is_refined_by:
60  - "UC-001"
61---
62# Test Solution
63
64Body content.
65"#;
66
67    #[test]
68    fn test_parse_metadata_markdown() {
69        let item = parse_metadata(
70            SOLUTION_MD,
71            &PathBuf::from("SOL-001.md"),
72            &PathBuf::from("/repo"),
73            InputFormat::Markdown,
74        )
75        .unwrap();
76
77        assert_eq!(item.id.as_str(), "SOL-001");
78        assert_eq!(item.item_type, ItemType::Solution);
79        assert_eq!(item.name, "Test Solution");
80        let is_refined_by: Vec<_> = item
81            .relationship_ids(RelationshipType::IsRefinedBy)
82            .collect();
83        assert_eq!(is_refined_by.len(), 1);
84        assert_eq!(is_refined_by[0].as_str(), "UC-001");
85    }
86
87    #[test]
88    fn test_input_format_debug() {
89        assert_eq!(format!("{:?}", InputFormat::Markdown), "Markdown");
90    }
91}