error2 0.13.2

A simple error handle library for Rust
Documentation
use std::{fmt, panic};

use crate::StrId;

/// Represents a source code location (file, line, column).
///
/// `Location` is used to track where errors are created and propagated,
/// providing detailed backtraces for debugging.
///
/// # Creation
///
/// Locations are typically created automatically by methods with `#[track_caller]`:
///
/// ```
/// use error2::Location;
///
/// let loc = Location::caller(); // Captures current location
/// println!("At {}:{}:{}", loc.file(), loc.line(), loc.column());
/// ```
///
/// # Usage in Backtraces
///
/// Locations are automatically recorded when using `.context()`, `.attach()`,
/// `.build()`, and other error handling methods.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Location {
    file: StrId,
    line: u32,
    column: u32,
}

impl Location {
    #[doc(hidden)]
    #[inline]
    pub fn new(file: &'static str, line: u32, column: u32) -> Self {
        Self {
            file: file.into(),
            line,
            column,
        }
    }

    /// Captures the caller's location using `#[track_caller]`.
    #[track_caller]
    #[inline]
    pub fn caller() -> Self {
        Self::from_std(panic::Location::caller())
    }

    /// Returns the file path.
    #[inline]
    pub fn file(&self) -> &'static str {
        self.file.into()
    }

    /// Returns the line number.
    #[inline]
    pub const fn line(&self) -> u32 {
        self.line
    }

    /// Returns the column number.
    #[inline]
    pub const fn column(&self) -> u32 {
        self.column
    }

    /// Converts from `std::panic::Location`.
    #[inline]
    pub fn from_std(location: &panic::Location<'static>) -> Self {
        Self {
            file: location.file().into(),
            line: location.line(),
            column: location.column(),
        }
    }

    pub(crate) const fn uninit() -> Self {
        Self {
            file: StrId::uninit(),
            line: 0,
            column: 0,
        }
    }

    pub(crate) const fn is_uninit(&self) -> bool {
        self.file.is_uninit()
    }
}

impl fmt::Display for Location {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}:{}:{}", self.file, self.line, self.column)
    }
}

impl<'a> From<&'a panic::Location<'static>> for Location {
    #[inline]
    fn from(location: &'a panic::Location<'static>) -> Self {
        Self::from_std(location)
    }
}

#[cfg(test)]
mod tests {
    use std::panic;

    use super::*;

    #[test]
    fn test_the_effects_of_tracker_caller() {
        let Tuple {
            from_std: loc_from_std_by_fn,
            from_crate: loc_from_crate_by_fn,
        } = location_by_fn();

        assert_eq!(loc_from_std_by_fn, loc_from_crate_by_fn);

        let Tuple {
            from_std: loc_from_std_by_macro,
            from_crate: loc_from_crate_by_macro,
        } = location_by_macro();

        assert_ne!(loc_from_std_by_macro, loc_from_crate_by_macro);

        assert_ne!(loc_from_std_by_fn, loc_from_std_by_macro);
        assert_ne!(loc_from_std_by_fn, loc_from_crate_by_macro);
    }

    struct Tuple {
        from_std: Location,
        from_crate: Location,
    }

    #[track_caller]
    fn location_by_fn() -> Tuple {
        let from_std = Location::from_std(panic::Location::caller());
        let from_crate = Location::caller();

        Tuple {
            from_std,
            from_crate,
        }
    }

    #[track_caller]
    fn location_by_macro() -> Tuple {
        let from_std = Location {
            file: file!().into(),
            line: line!(),
            column: column!(),
        };

        let from_crate = crate::location!();

        Tuple {
            from_std,
            from_crate,
        }
    }

    #[cfg(feature = "serde")]
    #[test]
    fn test_serialize_deserialize_locations() {
        macro_rules! location {
            ($file:literal) => {
                Location {
                    file: $file.into(),
                    line: line!(),
                    column: column!(),
                }
            };
        }

        let origin = vec![
            location!("你好,世界"),
            location!("Hello World"),
            location!("Bonjour le monde"),
            location!("Hola Mundo"),
            location!("Hallo Welt"),
            location!("Ciao Mondo"),
            location!("Привет мир"),
            location!("こんにちは世界"),
            location!("안녕하세요 세계"),
            location!("مرحبا بالعالم"),
            location!("שלום עולם"),
            location!("Γειά σου Κόσμε"),
        ];

        let serialized = serde_json::to_vec(&origin).unwrap();
        let deserialized = serde_json::from_slice::<Vec<Location>>(&serialized).unwrap();

        assert_eq!(origin, deserialized);
    }
}