serde_frontmatter/
lib.rs

1//! This crate is a Rust library for using the [Serde](https://github.com/serde-rs/serde) serialization framework
2//! with Jekyll-style front matter.
3//!
4//! ## Examples
5//!
6//! ```
7//! use serde::{Deserialize, Serialize};
8//!
9//! #[derive(Deserialize, Serialize, PartialEq, Debug)]
10//! pub struct MyData {
11//!     pub title: String
12//! }
13//!
14//! fn main() {
15//!    // Serialize
16//!    let front_matter = MyData { title: "Hello, World!".to_string() };
17//!    let content = "This is some content";
18//!    let output = serde_frontmatter::serialize(front_matter, content).unwrap();
19//!    assert_eq!("---\ntitle: \"Hello, World!\"\n\n---\nThis is some content", output);
20//!
21//!    // Deserialize
22//!    let input = "---\ntitle: Hello, World!\n---\nThis is some content";
23//!    let (front_matter, content) = serde_frontmatter::deserialize::<MyData>(input).unwrap();
24//!    assert_eq!(front_matter, MyData { title: "Hello, World!".to_string() });
25//!    assert_eq!(content, "\nThis is some content");
26//! }
27//! ```
28
29use serde::de::DeserializeOwned;
30use serde::Serialize;
31
32/// Errors generated by this crate
33#[derive(Debug)]
34pub enum SerdeFMError {
35    /// Errors originating from serde-yaml
36    YamlParseError(serde_yaml::Error),
37
38    /// Front matter could not be found
39    MissingFrontMatter,
40}
41
42impl From<serde_yaml::Error> for SerdeFMError {
43    fn from(e: serde_yaml::Error) -> SerdeFMError {
44        Self::YamlParseError(e)
45    }
46}
47
48/// Deserialize a string containing front matter into a struct and the content of the string
49pub fn deserialize<T: DeserializeOwned>(data: &str) -> Result<(T, String), SerdeFMError> {
50    // We need frontmatter to be the first thing in the file
51    if !data.starts_with("---") {
52        return Err(SerdeFMError::MissingFrontMatter);
53    }
54
55    // Use split to find the frontmatter content
56    let split_data = data.split("---").map(Into::into).collect::<Vec<String>>();
57
58    // Use the second item in the vector as the frontmatter
59    let frontmatter = match split_data.get(1) {
60        Some(fm) => Ok(fm),
61        None => Err(SerdeFMError::MissingFrontMatter),
62    }?;
63    let content = match split_data.get(2) {
64        Some(content) => content.clone(),
65        None => String::new(),
66    };
67
68    // Parse the frontmatter
69    Ok((serde_yaml::from_str(frontmatter.as_ref())?, content))
70}
71
72/// Serialize a struct and data into a string containing front matter
73pub fn serialize<T: Serialize>(front_matter: T, content: &str) -> Result<String, SerdeFMError> {
74    // Serialize the frontmatter
75    let frontmatter = serde_yaml::to_string(&front_matter)?;
76
77    // Return the result
78    Ok(format!("{}\n---\n{}", frontmatter, content))
79}
80
81#[cfg(test)]
82mod tests {
83    //! These test cases are stolen from: https://github.com/azdle/rust-frontmatter/blob/master/src/lib.rs
84
85    use super::*;
86    use serde::{Deserialize, Serialize};
87
88    #[derive(Serialize, Deserialize)]
89    pub struct FM {
90        pub title: String,
91    }
92
93    #[test]
94    fn test_valid() {
95        let test_string = "---\ntitle: Valid Yaml Test\n---\nsomething that's not yaml";
96
97        let (matter, content) = deserialize::<FM>(&test_string).unwrap();
98        assert_eq!(matter.title, "Valid Yaml Test");
99        assert_eq!(content, "\nsomething that's not yaml");
100    }
101
102    #[test]
103    fn test_invalid() {
104        let test_string = "something that's not yaml even if it has\n---\nsome: yaml\n--";
105
106        let result = deserialize::<FM>(&test_string);
107        assert!(result.is_err());
108    }
109}