eunoia 0.11.0

A library for creating area-proportional Euler and Venn diagrams
Documentation
//! Error types for diagram operations.

use std::fmt::{self, Display};

/// Errors that can occur when building or working with diagrams.
#[derive(Debug, Clone, PartialEq)]
pub enum DiagramError {
    /// A combination references a set that was never defined.
    UndefinedSet(String),

    /// A value provided is negative or invalid.
    InvalidValue { combination: String, value: f64 },

    /// No sets were defined.
    EmptySets,

    /// Duplicate combination definition.
    DuplicateCombination(String),

    /// Invalid combination format.
    InvalidCombination(String),

    /// Number of sets is not supported for the requested operation.
    ///
    /// For example, canonical Venn arrangements with ellipses only exist for
    /// `n ∈ 1..=5`; higher `n` requires non-ellipse curves (Edwards's
    /// cogwheels) which eunoia does not support.
    UnsupportedSetCount(usize),

    /// Diagram specification exceeds the supported number of sets. The
    /// `max` field reports the limit that was in effect when the spec was
    /// built — by default [`crate::constants::MAX_SETS`], or whatever
    /// override was set via [`crate::DiagramSpecBuilder::max_sets`] (capped
    /// at [`crate::constants::MAX_SETS_HARD_CAP`]).
    TooManySets { requested: usize, max: usize },

    /// A geometric shape was constructed with an out-of-range parameter
    /// (e.g. a non-positive radius). Returned by the `try_new` constructors
    /// on shape types so callers can recover from invalid user input
    /// without catching a panic.
    InvalidShapeParameter {
        /// Shape type name (e.g. `"Circle"`, `"Ellipse"`, `"Square"`).
        shape: &'static str,
        /// Parameter name that failed validation (e.g. `"radius"`,
        /// `"semi_major"`, `"side"`).
        param: &'static str,
        /// The offending value.
        value: f64,
    },
}

impl Display for DiagramError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            DiagramError::UndefinedSet(set) => {
                write!(f, "Set '{}' is referenced but never defined", set)
            }
            DiagramError::InvalidValue { combination, value } => {
                write!(
                    f,
                    "Invalid value {} for combination '{}'",
                    value, combination
                )
            }
            DiagramError::EmptySets => {
                write!(f, "No sets defined in diagram")
            }
            DiagramError::DuplicateCombination(combo) => {
                write!(f, "Combination '{}' defined multiple times", combo)
            }
            DiagramError::InvalidCombination(combo) => {
                write!(f, "Invalid combination format: '{}'", combo)
            }
            DiagramError::UnsupportedSetCount(n) => {
                write!(f, "Unsupported set count: {}", n)
            }
            DiagramError::TooManySets { requested, max } => {
                write!(
                    f,
                    "Too many sets: {} requested, but maximum supported is {}",
                    requested, max
                )
            }
            DiagramError::InvalidShapeParameter {
                shape,
                param,
                value,
            } => {
                write!(f, "Invalid {} for {}: {} must be > 0", param, shape, value)
            }
        }
    }
}

impl std::error::Error for DiagramError {}

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

    #[test]
    fn test_undefined_set_error() {
        let error = DiagramError::UndefinedSet("X".to_string());
        assert_eq!(error, DiagramError::UndefinedSet("X".to_string()));
        assert_eq!(
            format!("{}", error),
            "Set 'X' is referenced but never defined"
        );
    }

    #[test]
    fn test_invalid_value_error() {
        let error = DiagramError::InvalidValue {
            combination: "A&B".to_string(),
            value: -5.0,
        };
        assert_eq!(
            error,
            DiagramError::InvalidValue {
                combination: "A&B".to_string(),
                value: -5.0,
            }
        );
        assert_eq!(
            format!("{}", error),
            "Invalid value -5 for combination 'A&B'"
        );
    }

    #[test]
    fn test_empty_sets_error() {
        let error = DiagramError::EmptySets;
        assert_eq!(error, DiagramError::EmptySets);
        assert_eq!(format!("{}", error), "No sets defined in diagram");
    }

    #[test]
    fn test_duplicate_combination_error() {
        let error = DiagramError::DuplicateCombination("A&B".to_string());
        assert_eq!(error, DiagramError::DuplicateCombination("A&B".to_string()));
        assert_eq!(
            format!("{}", error),
            "Combination 'A&B' defined multiple times"
        );
    }

    #[test]
    fn test_invalid_combination_error() {
        let error = DiagramError::InvalidCombination("A&".to_string());
        assert_eq!(error, DiagramError::InvalidCombination("A&".to_string()));
        assert_eq!(format!("{}", error), "Invalid combination format: 'A&'");
    }

    #[test]
    fn test_unsupported_set_count_error() {
        let error = DiagramError::UnsupportedSetCount(7);
        assert_eq!(error, DiagramError::UnsupportedSetCount(7));
        assert_eq!(format!("{}", error), "Unsupported set count: 7");
    }

    #[test]
    fn test_error_trait_implementation() {
        let error: Box<dyn std::error::Error> = Box::new(DiagramError::EmptySets);
        assert_eq!(format!("{}", error), "No sets defined in diagram");
    }

    #[test]
    fn test_debug_implementation() {
        let error = DiagramError::UndefinedSet("A".to_string());
        let debug_str = format!("{:?}", error);
        assert!(debug_str.contains("UndefinedSet"));
        assert!(debug_str.contains("A"));
    }

    #[test]
    fn test_invalid_shape_parameter_error() {
        let error = DiagramError::InvalidShapeParameter {
            shape: "Circle",
            param: "radius",
            value: -1.5,
        };
        assert_eq!(
            format!("{}", error),
            "Invalid radius for Circle: -1.5 must be > 0"
        );
    }

    #[test]
    fn test_clone_implementation() {
        let error = DiagramError::InvalidValue {
            combination: "test".to_string(),
            value: 1.5,
        };
        let cloned = error.clone();
        assert_eq!(error, cloned);
    }
}