use crate::error::Error;
use crate::types::Format;
pub fn extract_raw_frontmatter(
content: &str,
) -> Result<(&str, &str), Error> {
if let Some(yaml) =
extract_delimited_frontmatter(content, "---\n", "\n---")
.or_else(|| {
extract_delimited_frontmatter(
content, "---\r\n", "\r\n---",
)
})
{
let remaining = &content[content
.find("\n---\n")
.or_else(|| content.find("\r\n---\r\n"))
.map_or(content.len(), |i| i + 5)..];
return Ok((yaml, remaining.trim_start()));
}
if let Some(toml) =
extract_delimited_frontmatter(content, "+++\n", "\n+++")
.or_else(|| {
extract_delimited_frontmatter(
content, "+++\r\n", "\r\n+++",
)
})
{
let remaining = &content[content
.find("\n+++\n")
.or_else(|| content.find("\r\n+++\r\n"))
.map_or(content.len(), |i| i + 5)..];
return Ok((toml, remaining.trim_start()));
}
if let Ok(json) = extract_json_frontmatter(content) {
let remaining = &content[json.len()..];
return Ok((json, remaining.trim_start()));
}
if content.starts_with("---\n---")
|| content.starts_with("+++\n+++")
{
return Err(Error::InvalidFormat);
}
Err(Error::InvalidFormat)
}
pub fn extract_json_frontmatter(content: &str) -> Result<&str, Error> {
const MAX_DEPTH: usize = 100; let trimmed = content.trim_start();
if !trimmed.starts_with('{') {
return Err(Error::InvalidJson);
}
let mut brace_count = 0;
let mut depth = 0;
let mut in_string = false;
let mut escape_next = false;
for (idx, ch) in trimmed.char_indices() {
if escape_next {
escape_next = false;
continue;
}
match ch {
'"' if !in_string => in_string = true,
'"' if in_string => in_string = false,
'\\' if in_string => escape_next = true,
'{' if !in_string => {
brace_count += 1;
depth += 1;
if depth > MAX_DEPTH {
return Err(Error::JsonDepthLimitExceeded);
}
}
'}' if !in_string => {
brace_count -= 1;
if depth > 0 {
depth = depth.saturating_sub(1);
}
if brace_count == 0 {
return Ok(&trimmed[..=idx]);
}
}
_ => {}
}
}
Err(Error::InvalidJson)
}
pub fn detect_format(raw_frontmatter: &str) -> Result<Format, Error> {
let trimmed = raw_frontmatter.trim_start();
if trimmed.starts_with("---") {
return Ok(Format::Yaml);
}
if trimmed.starts_with('{') {
return Ok(Format::Json);
}
if trimmed.contains(':') && !trimmed.contains('{') {
return Ok(Format::Yaml);
}
if trimmed.contains('=') {
return Ok(Format::Toml);
}
Err(Error::InvalidFormat)
}
#[must_use]
pub fn extract_delimited_frontmatter<'a>(
content: &'a str,
start_delim: &str,
end_delim: &str,
) -> Option<&'a str> {
let start_index = content.find(start_delim)? + start_delim.len();
let end_index = content.find(end_delim)?;
if start_index <= end_index {
Some(content[start_index..end_index].trim())
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
mod extract_raw_frontmatter {
use super::*;
#[test]
fn test_extract_yaml() {
let content = r"---
title: Example
---
Content here";
let result = extract_raw_frontmatter(content).unwrap();
assert_eq!(result.0, "title: Example");
assert_eq!(result.1, "Content here");
}
#[test]
fn test_extract_toml() {
let content = r#"+++
title = "Example"
+++
Content here"#;
let result = extract_raw_frontmatter(content).unwrap();
assert_eq!(result.0, r#"title = "Example""#);
assert_eq!(result.1, "Content here");
}
#[test]
fn test_extract_json() {
let content = r#"{ "title": "Example" }
Content here"#;
let result = extract_raw_frontmatter(content).unwrap();
assert_eq!(result.0, r#"{ "title": "Example" }"#);
assert_eq!(result.1, "Content here");
}
#[test]
fn test_invalid_format() {
let content = "Invalid frontmatter";
let result = detect_format(content);
if let Err(Error::InvalidFormat) = result {
} else {
panic!("Expected Err(InvalidFormat), got {:?}", result);
}
}
}
mod extract_json_frontmatter {
use super::*;
#[test]
fn test_valid_json() {
let content = r#"{ "title": "Example" }
Content here"#;
let result = extract_json_frontmatter(content).unwrap();
assert_eq!(result, r#"{ "title": "Example" }"#);
}
#[test]
fn test_nested_json() {
let content = r#"{ "a": { "b": { "c": { "d": { "e": {} }}}}}
Content here"#;
let result = extract_json_frontmatter(content);
assert!(result.is_ok());
assert_eq!(
result.unwrap(),
r#"{ "a": { "b": { "c": { "d": { "e": {} }}}}}"#
);
}
#[test]
fn test_too_deep_json() {
let mut content = String::from("{ ");
for _ in 0..101 {
content.push_str(r#""a": { "#);
}
content.push_str(&"}".repeat(101));
content.push_str("\nContent here");
let result = extract_json_frontmatter(&content);
assert!(matches!(
result,
Err(Error::JsonDepthLimitExceeded)
));
}
#[test]
fn test_escaped_characters() {
let content = r#"{ "title": "Example with \"quotes\" and {braces}", "content": "Some text with \\ backslash" }
Actual content starts here"#;
let result = extract_json_frontmatter(content).unwrap();
assert_eq!(
result,
r#"{ "title": "Example with \"quotes\" and {braces}", "content": "Some text with \\ backslash" }"#
);
}
#[test]
fn test_invalid_json() {
let content = "Not a JSON frontmatter";
let result = extract_json_frontmatter(content);
assert!(matches!(result, Err(Error::InvalidJson)));
}
}
mod detect_format {
use super::*;
#[test]
fn test_yaml_format() {
let content = "title: Example";
let result = detect_format(content).unwrap();
assert_eq!(result, Format::Yaml);
}
#[test]
fn test_toml_format() {
let content = "title = \"Example\"";
let result = detect_format(content).unwrap();
assert_eq!(result, Format::Toml);
}
#[test]
fn test_json_format() {
let content = r#"{ "title": "Example" }"#;
let result = detect_format(content).unwrap();
assert_eq!(result, Format::Json);
}
#[test]
fn test_invalid_format() {
let content = "Invalid content";
let result = detect_format(content);
assert!(matches!(result, Err(Error::InvalidFormat)));
}
}
mod extract_delimited_frontmatter {
use super::*;
#[test]
fn test_valid_yaml() {
let content = "---\ntitle: Example\n---\nContent here";
let result = extract_delimited_frontmatter(
content, "---\n", "\n---\n",
)
.unwrap();
assert_eq!(result, "title: Example");
}
#[test]
fn test_valid_toml() {
let content = "+++\ntitle = \"Example\"\n+++\nContent here";
let result = extract_delimited_frontmatter(
content, "+++\n", "\n+++\n",
)
.unwrap();
assert_eq!(result, r#"title = "Example""#);
}
#[test]
fn test_valid_windows_format() {
let content =
"---\r\ntitle: Example\r\n---\r\nContent here";
let result = extract_delimited_frontmatter(
content,
"---\r\n",
"\r\n---\r\n",
)
.unwrap();
assert_eq!(result, "title: Example");
}
#[test]
fn test_missing_start_delimiter() {
let content = "title: Example\n---\nContent here";
let result = extract_delimited_frontmatter(
content, "---\n", "\n---\n",
);
assert!(result.is_none());
}
#[test]
fn test_missing_end_delimiter() {
let content = "---\ntitle: Example\nContent here";
let result = extract_delimited_frontmatter(
content, "---\n", "\n---\n",
);
assert!(result.is_none());
}
}
}