adrscope/infrastructure/parser/
mod.rs1mod frontmatter;
7mod markdown;
8
9use std::path::Path;
10
11use crate::domain::{Adr, AdrId};
12use crate::error::Result;
13
14pub use frontmatter::FrontmatterParser;
15pub use markdown::MarkdownRenderer;
16
17pub trait AdrParser: Send + Sync {
19 fn parse(&self, path: &Path, content: &str) -> Result<Adr>;
21}
22
23#[derive(Debug, Clone, Default)]
25pub struct DefaultAdrParser {
26 frontmatter_parser: FrontmatterParser,
27 markdown_renderer: MarkdownRenderer,
28}
29
30impl DefaultAdrParser {
31 #[must_use]
33 pub fn new() -> Self {
34 Self::default()
35 }
36}
37
38impl AdrParser for DefaultAdrParser {
39 fn parse(&self, path: &Path, content: &str) -> Result<Adr> {
40 let id = AdrId::from_path(path);
42
43 let filename = path
45 .file_name()
46 .and_then(|n| n.to_str())
47 .unwrap_or("unknown.md")
48 .to_string();
49
50 let (frontmatter, body_markdown) = self.frontmatter_parser.parse(path, content)?;
52
53 let body_html = self.markdown_renderer.render(body_markdown);
55
56 let body_text = self.markdown_renderer.render_plain_text(body_markdown);
58
59 Ok(Adr::new(
60 id,
61 filename,
62 path.to_path_buf(),
63 frontmatter,
64 body_markdown.to_string(),
65 body_html,
66 body_text,
67 ))
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use crate::domain::Status;
75 use std::path::PathBuf;
76
77 #[test]
78 fn test_parse_full_adr() {
79 let content = r#"---
80title: Use PostgreSQL for Primary Storage
81description: Decision to adopt PostgreSQL as our primary database
82status: accepted
83category: architecture
84tags:
85 - database
86 - postgresql
87author: Architecture Team
88created: "2025-01-15"
89---
90
91# Context
92
93We need a reliable primary database for our application.
94
95## Decision
96
97We will use PostgreSQL.
98
99## Consequences
100
101PostgreSQL provides the features we need.
102"#;
103
104 let parser = DefaultAdrParser::new();
105 let path = PathBuf::from("adr_0001.md");
106 let adr = parser.parse(&path, content).expect("should parse");
107
108 assert_eq!(adr.id().as_str(), "adr_0001");
109 assert_eq!(adr.title(), "Use PostgreSQL for Primary Storage");
110 assert_eq!(adr.status(), Status::Accepted);
111 assert_eq!(adr.category(), "architecture");
112 assert!(adr.body_html().contains("<h1>"));
113 assert!(adr.body_text().contains("Context"));
114 }
115
116 #[test]
117 fn test_parse_minimal_adr() {
118 let content = r"---
119title: Minimal ADR
120---
121
122Simple content.
123";
124
125 let parser = DefaultAdrParser::new();
126 let path = PathBuf::from("minimal.md");
127 let adr = parser.parse(&path, content).expect("should parse");
128
129 assert_eq!(adr.title(), "Minimal ADR");
130 assert_eq!(adr.status(), Status::Proposed); }
132}