mermaid-builder 0.1.2

A Rust library for generating Mermaid diagrams using the builder pattern.
Documentation
//! Submodule providing a builder struct for style classes in Mermaid diagrams.

use alloc::{string::String, vec::Vec};

use crate::shared::{
    StyleClass,
    style_class::{StyleClassError, StyleProperty},
};

#[derive(Default, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// Builder struct for creating style classes in Mermaid diagrams.
///
/// # Example
///
/// ```
/// use mermaid_builder::prelude::*;
///
/// fn main() -> Result<(), Box<dyn core::error::Error>> {
///     let style_class = StyleClassBuilder::default()
///         .name("myClass")?
///         .property(StyleProperty::Fill(Color::from((255, 0, 0))))?
///         .build()?;
///     Ok(())
/// }
/// ```
pub struct StyleClassBuilder {
    /// The name of the style class.
    name: Option<String>,
    /// The properties associated with the style class.
    properties: Vec<StyleProperty>,
}

impl TryFrom<StyleClassBuilder> for StyleClass {
    type Error = StyleClassError;

    fn try_from(builder: StyleClassBuilder) -> Result<Self, Self::Error> {
        if builder.properties.is_empty() {
            return Err(StyleClassError::MissingProperties);
        }

        Ok(StyleClass {
            name: builder.name.ok_or(StyleClassError::MissingName)?,
            properties: builder.properties,
        })
    }
}

impl StyleClassBuilder {
    /// Sets the name of the style class.
    ///
    /// # Arguments
    ///
    /// * `name` - A string slice that holds the name of the style class.
    ///
    /// # Errors
    ///
    /// * Returns `StyleClassError::EmptyName` if the provided name is empty.
    pub fn name(mut self, name: impl Into<String>) -> Result<Self, StyleClassError> {
        let name = name.into();

        if name.is_empty() {
            return Err(StyleClassError::EmptyName);
        }

        self.name = Some(name);

        Ok(self)
    }

    /// Adds a property to the style class.
    ///
    /// # Arguments
    ///
    /// * `property` - A `StyleProperty` that will be added to the style class.
    ///
    /// # Errors
    ///
    /// * Returns `StyleClassError::DuplicateProperty` if the property is
    ///   already present.
    pub fn property(mut self, property: StyleProperty) -> Result<Self, StyleClassError> {
        if self.properties.contains(&property) {
            return Err(StyleClassError::DuplicateProperty(property));
        }

        self.properties.push(property);
        Ok(self)
    }

    /// Builds the style class.
    ///
    /// # Errors
    ///
    /// Returns an error if the style class cannot be built.
    pub fn build(self) -> Result<StyleClass, StyleClassError> {
        self.try_into()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::shared::style_class::color::Color;

    #[test]
    fn test_style_class_builder() -> Result<(), StyleClassError> {
        let style_class = StyleClassBuilder::default()
            .name("myClass")?
            .property(StyleProperty::Fill(Color::from((255, 0, 0))))?
            .property(StyleProperty::Stroke(Color::from((0, 0, 255))))?
            .build()?;

        assert_eq!(style_class.name, "myClass");
        assert_eq!(style_class.properties.len(), 2);
        assert!(style_class.properties.contains(&StyleProperty::Fill(Color::from((255, 0, 0)))));
        assert!(style_class.properties.contains(&StyleProperty::Stroke(Color::from((0, 0, 255)))));
        Ok(())
    }

    #[test]
    fn test_style_class_builder_errors() -> Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        let builder = StyleClassBuilder::default();
        assert!(matches!(builder.name(""), Err(StyleClassError::EmptyName)));

        let builder = StyleClassBuilder::default().name("myClass")?;
        assert!(matches!(builder.build(), Err(StyleClassError::MissingProperties)));

        let builder = StyleClassBuilder::default()
            .name("myClass")?
            .property(StyleProperty::Fill(Color::from((255, 0, 0))))?;
        assert!(matches!(
            builder.property(StyleProperty::Fill(Color::from((255, 0, 0)))),
            Err(StyleClassError::DuplicateProperty(_))
        ));
        Ok(())
    }
}