errcraft 0.1.0

Beautiful, structured, and colorful error handling for Rust.
Documentation
//! Serialization support for error frames.

use crate::ErrFrame;
use serde::{Serialize, Serializer};
use serde_json::Value;
use std::collections::HashMap;

impl Serialize for ErrFrame {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        use serde::ser::SerializeMap;

        let mut map = serializer.serialize_map(None)?;

        map.serialize_entry("message", self.message())?;

        if !self.context.is_empty() {
            let context: HashMap<String, String> = self
                .context
                .iter()
                .filter_map(|layer| {
                    layer
                        .key()
                        .map(|k| (k.to_string(), layer.value().as_str().to_string()))
                })
                .collect();

            if !context.is_empty() {
                map.serialize_entry("context", &context)?;
            }

            let text_context: Vec<String> = self
                .context
                .iter()
                .filter(|layer| layer.key().is_none())
                .map(|layer| layer.value().as_str().to_string())
                .collect();

            if !text_context.is_empty() {
                map.serialize_entry("notes", &text_context)?;
            }
        }

        #[cfg(feature = "std")]
        if let Some(source) = self.source.as_ref() {
            map.serialize_entry("source", &source.to_string())?;
        }

        map.end()
    }
}

impl ErrFrame {
    /// Converts the error frame to a JSON value.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use errcraft::ErrFrame;
    ///
    /// let err = ErrFrame::new("Test error")
    ///     .context("key", "value");
    ///
    /// let json = err.to_json();
    /// ```
    pub fn to_json(&self) -> Value {
        serde_json::to_value(self).unwrap_or_else(|_| {
            serde_json::json!({
                "message": self.message(),
                "error": "Failed to serialize error"
            })
        })
    }

    /// Converts the error frame to a JSON string.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use errcraft::ErrFrame;
    ///
    /// let err = ErrFrame::new("Test error")
    ///     .context("key", "value");
    ///
    /// let json_str = err.to_json_string();
    /// ```
    pub fn to_json_string(&self) -> String {
        serde_json::to_string_pretty(&self.to_json())
            .unwrap_or_else(|_| format!("{{\"message\": \"{}\"}}", self.message()))
    }

    /// Converts the error frame to Markdown format.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use errcraft::ErrFrame;
    ///
    /// let err = ErrFrame::new("Test error")
    ///     .context("key", "value");
    ///
    /// let markdown = err.to_markdown();
    /// ```
    #[cfg(feature = "markdown")]
    pub fn to_markdown(&self) -> String {
        let mut output = String::new();

        output.push_str("# Error\n\n");
        output.push_str(&format!("**{}**\n\n", self.message()));

        if !self.context.is_empty() {
            output.push_str("## Context\n\n");

            for layer in self.context.iter() {
                if let Some(key) = layer.key() {
                    output.push_str(&format!("- **{}**: {}\n", key, layer.value().as_str()));
                } else {
                    output.push_str(&format!("- {}\n", layer.value().as_str()));
                }
            }

            output.push('\n');
        }

        #[cfg(feature = "std")]
        if let Some(source) = self.source.as_ref() {
            output.push_str("## Cause\n\n");
            output.push_str(&format!("```\n{}\n```\n", source));
        }

        output
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_to_json() {
        let err = ErrFrame::new("test error").context("key", "value");

        let json = err.to_json();
        assert!(json["message"].as_str().unwrap().contains("test"));
    }

    #[test]
    fn test_to_json_string() {
        let err = ErrFrame::new("test error");
        let json_str = err.to_json_string();
        assert!(json_str.contains("test error"));
    }

    #[cfg(feature = "markdown")]
    #[test]
    fn test_to_markdown() {
        let err = ErrFrame::new("test error").context("key", "value");

        let md = err.to_markdown();
        assert!(md.contains("# Error"));
        assert!(md.contains("test error"));
    }
}