1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
//! This crate is a Rust library for using the [Serde](https://github.com/serde-rs/serde) serialization framework
//! with Jekyll-style front matter.
//!
//! ## Examples
//!
//! ```
//! use serde::{Deserialize, Serialize};
//!
//! #[derive(Deserialize, Serialize, PartialEq, Debug)]
//! pub struct MyData {
//!     pub title: String
//! }
//!
//! fn main() {
//!    // Serialize
//!    let front_matter = MyData { title: "Hello, World!".to_string() };
//!    let content = "This is some content";
//!    let output = serde_frontmatter::serialize(front_matter, content).unwrap();
//!    assert_eq!("---\ntitle: \"Hello, World!\"\n\n---\nThis is some content", output);
//!
//!    // Deserialize
//!    let input = "---\ntitle: Hello, World!\n---\nThis is some content";
//!    let (front_matter, content) = serde_frontmatter::deserialize::<MyData>(input).unwrap();
//!    assert_eq!(front_matter, MyData { title: "Hello, World!".to_string() });
//!    assert_eq!(content, "\nThis is some content");
//! }
//! ```

use serde::de::DeserializeOwned;
use serde::Serialize;

/// Errors generated by this crate
#[derive(Debug)]
pub enum SerdeFMError {
    /// Errors originating from serde-yaml
    YamlParseError(serde_yaml::Error),

    /// Front matter could not be found
    MissingFrontMatter,
}

impl From<serde_yaml::Error> for SerdeFMError {
    fn from(e: serde_yaml::Error) -> SerdeFMError {
        Self::YamlParseError(e)
    }
}

/// Deserialize a string containing front matter into a struct and the content of the string
pub fn deserialize<T: DeserializeOwned>(data: &str) -> Result<(T, String), SerdeFMError> {
    // We need frontmatter to be the first thing in the file
    if !data.starts_with("---") {
        return Err(SerdeFMError::MissingFrontMatter);
    }

    // Use split to find the frontmatter content
    let split_data = data.split("---").map(Into::into).collect::<Vec<String>>();

    // Use the second item in the vector as the frontmatter
    let frontmatter = match split_data.get(1) {
        Some(fm) => Ok(fm),
        None => Err(SerdeFMError::MissingFrontMatter),
    }?;
    let content = match split_data.get(2) {
        Some(content) => content.clone(),
        None => String::new(),
    };

    // Parse the frontmatter
    Ok((serde_yaml::from_str(frontmatter.as_ref())?, content))
}

/// Serialize a struct and data into a string containing front matter
pub fn serialize<T: Serialize>(front_matter: T, content: &str) -> Result<String, SerdeFMError> {
    // Serialize the frontmatter
    let frontmatter = serde_yaml::to_string(&front_matter)?;

    // Return the result
    Ok(format!("{}\n---\n{}", frontmatter, content))
}

#[cfg(test)]
mod tests {
    //! These test cases are stolen from: https://github.com/azdle/rust-frontmatter/blob/master/src/lib.rs

    use super::*;
    use serde::{Deserialize, Serialize};

    #[derive(Serialize, Deserialize)]
    pub struct FM {
        pub title: String,
    }

    #[test]
    fn test_valid() {
        let test_string = "---\ntitle: Valid Yaml Test\n---\nsomething that's not yaml";

        let (matter, content) = deserialize::<FM>(&test_string).unwrap();
        assert_eq!(matter.title, "Valid Yaml Test");
        assert_eq!(content, "\nsomething that's not yaml");
    }

    #[test]
    fn test_invalid() {
        let test_string = "something that's not yaml even if it has\n---\nsome: yaml\n--";

        let result = deserialize::<FM>(&test_string);
        assert!(result.is_err());
    }
}