optionstratlib 0.17.1

OptionStratLib is a comprehensive Rust library for options trading and strategy development across multiple asset classes.
Documentation
/******************************************************************************
   Author: Joaquín Béjar García
   Email: jb@taunais.com
   Date: 21/1/25
******************************************************************************/
use crate::error::{CurveError, PositionError, SurfaceError};
use thiserror::Error;

/// Represents errors that can occur during different interpolation operations.
///
/// This enum encapsulates various types of errors that might be encountered when
/// performing numerical interpolation across different methods. Each variant
/// corresponds to a specific interpolation technique and contains a descriptive
/// error message.
///
/// Interpolation errors typically arise from invalid input data, mathematical
/// constraints, or numerical stability issues specific to each interpolation method.
#[derive(Error, Debug)]
pub enum InterpolationError {
    /// Errors that occur during linear interpolation.
    ///
    /// These may include insufficient data points, non-monotonic input values,
    /// or out-of-bounds interpolation attempts.
    #[error("Linear interpolation error: {0}")]
    Linear(String),

    /// Errors that occur during bilinear interpolation.
    ///
    /// These may include insufficient grid points, irregularly spaced grid,
    /// or extrapolation beyond the defined grid boundaries.
    #[error("Bilinear interpolation error: {0}")]
    Bilinear(String),

    /// Errors that occur during cubic interpolation.
    ///
    /// These may include insufficient data points for cubic polynomial fitting,
    /// oscillation issues, or numerical instability.
    #[error("Cubic interpolation error: {0}")]
    Cubic(String),

    /// Errors that occur during spline interpolation.
    ///
    /// These may include insufficient data points for spline construction,
    /// boundary condition issues, or knot placement problems.
    #[error("Spline interpolation error: {0}")]
    Spline(String),

    /// Error from position operations.
    #[error(transparent)]
    Position(#[from] PositionError),

    /// Error from curve operations.
    #[error(transparent)]
    Curve(CurveError),

    /// Error from surface operations.
    #[error(transparent)]
    Surface(#[from] SurfaceError),

    /// Input data set was empty so interpolation could not proceed.
    #[error("interpolation input has no data points")]
    EmptyData,

    /// Requested point falls outside the valid interpolation range.
    #[error("interpolation target {target} is outside the supported range")]
    OutOfRange {
        /// The offending coordinate (stringified for flexibility across axes).
        target: String,
    },

    /// Interval degenerated into a point (two consecutive knots coincide).
    #[error("interpolation interval is degenerate: knots collapse to a single point")]
    DegenerateInterval,
}

impl From<CurveError> for InterpolationError {
    fn from(err: CurveError) -> Self {
        InterpolationError::Curve(err)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::error::position::PositionValidationErrorKind;
    use std::error::Error as StdError;
    use std::fmt;

    // Mock implementations of the error types used in From implementations
    #[derive(Debug)]
    struct MockPositionError;

    impl fmt::Display for MockPositionError {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "Mock position error")
        }
    }

    impl StdError for MockPositionError {}

    #[derive(Debug)]
    struct MockCurveError;

    impl fmt::Display for MockCurveError {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "Mock curve error")
        }
    }

    impl StdError for MockCurveError {}

    #[derive(Debug)]
    struct MockSurfaceError;

    impl fmt::Display for MockSurfaceError {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "Mock surface error")
        }
    }

    impl StdError for MockSurfaceError {}

    impl From<MockSurfaceError> for SurfaceError {
        fn from(err: MockSurfaceError) -> Self {
            SurfaceError::AnalysisError(err.to_string())
        }
    }

    // Mock implementation of the From trait for testing
    impl From<MockCurveError> for CurveError {
        fn from(err: MockCurveError) -> Self {
            // The actual implementation doesn't matter for this test
            CurveError::AnalysisError(err.to_string())
        }
    }

    // Mock implementation of the From trait for testing
    impl From<MockPositionError> for PositionError {
        fn from(err: MockPositionError) -> Self {
            // The actual implementation doesn't matter for this test
            PositionError::ValidationError(PositionValidationErrorKind::InvalidPosition {
                reason: err.to_string(),
            })
        }
    }

    #[test]
    fn test_create_interpolation_errors() {
        // Test creating instances of each variant
        let linear_err = InterpolationError::Linear("insufficient data points".to_string());
        let bilinear_err = InterpolationError::Bilinear("out of grid boundary".to_string());
        let cubic_err = InterpolationError::Cubic("numerical instability".to_string());
        let spline_err = InterpolationError::Spline("invalid knot placement".to_string());
        let empty_err = InterpolationError::EmptyData;
        let out_of_range = InterpolationError::OutOfRange {
            target: "1.23".to_string(),
        };
        let degenerate = InterpolationError::DegenerateInterval;

        // Verify the variants are created correctly
        match linear_err {
            InterpolationError::Linear(msg) => assert_eq!(msg, "insufficient data points"),
            _ => panic!("Expected Linear variant"),
        }

        match bilinear_err {
            InterpolationError::Bilinear(msg) => assert_eq!(msg, "out of grid boundary"),
            _ => panic!("Expected Bilinear variant"),
        }

        match cubic_err {
            InterpolationError::Cubic(msg) => assert_eq!(msg, "numerical instability"),
            _ => panic!("Expected Cubic variant"),
        }

        match spline_err {
            InterpolationError::Spline(msg) => assert_eq!(msg, "invalid knot placement"),
            _ => panic!("Expected Spline variant"),
        }

        assert!(matches!(empty_err, InterpolationError::EmptyData));
        assert!(matches!(
            out_of_range,
            InterpolationError::OutOfRange { .. }
        ));
        assert!(matches!(degenerate, InterpolationError::DegenerateInterval));
    }

    #[test]
    fn test_display_implementation() {
        // Test that the Display implementation formats messages correctly
        let linear_err = InterpolationError::Linear("test error".to_string());
        let bilinear_err = InterpolationError::Bilinear("test error".to_string());
        let cubic_err = InterpolationError::Cubic("test error".to_string());
        let spline_err = InterpolationError::Spline("test error".to_string());
        let empty_err = InterpolationError::EmptyData;

        assert_eq!(
            format!("{linear_err}"),
            "Linear interpolation error: test error"
        );
        assert_eq!(
            format!("{bilinear_err}"),
            "Bilinear interpolation error: test error"
        );
        assert_eq!(
            format!("{cubic_err}"),
            "Cubic interpolation error: test error"
        );
        assert_eq!(
            format!("{spline_err}"),
            "Spline interpolation error: test error"
        );
        assert_eq!(
            format!("{empty_err}"),
            "interpolation input has no data points"
        );
    }

    #[test]
    fn test_conversion_from_position_error() {
        let position_err = MockPositionError;
        let position_err = PositionError::from(position_err);
        let interpolation_err = InterpolationError::from(position_err);

        match interpolation_err {
            InterpolationError::Position(_) => {
                // Conversion successful
            }
            _ => panic!("Expected Position variant"),
        }
    }

    #[test]
    fn test_conversion_from_curve_error() {
        let curve_err = MockCurveError;
        let curve_err = CurveError::from(curve_err);
        let interpolation_err = InterpolationError::from(curve_err);
        match interpolation_err {
            InterpolationError::Curve(_) => {
                // Conversion successful
            }
            _ => panic!("Expected Curve variant"),
        }
    }

    #[test]
    fn test_conversion_from_surface_error() {
        let surface_err = MockSurfaceError;
        let surface_err = SurfaceError::from(surface_err);
        let interpolation_err = InterpolationError::from(surface_err);

        match interpolation_err {
            InterpolationError::Surface(_) => {
                // Conversion successful
            }
            _ => panic!("Expected Surface variant"),
        }
    }

    #[test]
    fn test_debug_implementation() {
        let err = InterpolationError::Linear("test debug".to_string());
        let debug_str = format!("{err:?}");
        assert!(
            debug_str.contains("Linear"),
            "Debug representation should include the variant"
        );
        assert!(
            debug_str.contains("test debug"),
            "Debug representation should include the error message"
        );
    }
}