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
13use std::path::Path;
14
15#[doc(inline)]
16pub use frontmatter::{has_frontmatter, update_frontmatter};
17#[doc(inline)]
18pub use markdown::extract_name_from_content;
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 std::path::PathBuf;
51
52    use super::*;
53    use crate::model::{ItemType, RelationshipType};
54
55    const SOLUTION_MD: &str = r#"---
56id: "SOL-001"
57type: solution
58name: "Test Solution"
59description: "A test solution"
60is_refined_by:
61  - "UC-001"
62---
63# Test Solution
64
65Body content.
66"#;
67
68    #[test]
69    fn test_parse_metadata_markdown() {
70        let item = parse_metadata(
71            SOLUTION_MD,
72            &PathBuf::from("SOL-001.md"),
73            &PathBuf::from("/repo"),
74            InputFormat::Markdown,
75        )
76        .unwrap();
77
78        assert_eq!(item.id.as_str(), "SOL-001");
79        assert_eq!(item.item_type, ItemType::Solution);
80        assert_eq!(item.name, "Test Solution");
81        let is_refined_by: Vec<_> = item
82            .relationship_ids(RelationshipType::IsRefinedBy)
83            .collect();
84        assert_eq!(is_refined_by.len(), 1);
85        assert_eq!(is_refined_by[0].as_str(), "UC-001");
86    }
87
88    #[test]
89    fn test_input_format_debug() {
90        assert_eq!(format!("{:?}", InputFormat::Markdown), "Markdown");
91    }
92}