rustrails-record 0.1.2

ORM layer (ActiveRecord equivalent)
Documentation
/// Error raised when an association is lazily loaded in strict-loading mode.
#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)]
#[error("`{association}` was lazily loaded on `{model}` which has strict_loading enabled")]
pub struct StrictLoadingViolation {
    /// The model type that rejected the lazy-loaded association.
    pub model: String,
    /// The association name that would have been loaded lazily.
    pub association: String,
}

/// Trait for models that support strict-loading mode.
pub trait StrictLoading {
    /// Returns whether strict loading is enabled for this instance.
    fn strict_loading(&self) -> bool;

    /// Enables strict loading on this instance.
    fn set_strict_loading(&mut self, enabled: bool);

    /// Checks whether lazily loading `association` is allowed.
    fn check_strict_loading(&self, association: &str) -> Result<(), StrictLoadingViolation> {
        if self.strict_loading() {
            let model = std::any::type_name::<Self>()
                .rsplit("::")
                .next()
                .map_or_else(|| "unknown".to_owned(), str::to_owned);

            return Err(StrictLoadingViolation {
                model,
                association: association.to_owned(),
            });
        }

        Ok(())
    }
}

/// Stores per-record strict-loading state.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct StrictLoadingState {
    enabled: bool,
}

impl StrictLoadingState {
    /// Creates strict-loading state with lazy loading allowed.
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Creates strict-loading state with lazy loading rejected.
    #[must_use]
    pub fn enabled() -> Self {
        Self { enabled: true }
    }

    /// Returns whether strict loading is enabled.
    #[must_use]
    pub fn is_enabled(&self) -> bool {
        self.enabled
    }

    /// Updates the strict-loading flag.
    pub fn set(&mut self, enabled: bool) {
        self.enabled = enabled;
    }
}

impl StrictLoading for StrictLoadingState {
    fn strict_loading(&self) -> bool {
        self.is_enabled()
    }

    fn set_strict_loading(&mut self, enabled: bool) {
        self.set(enabled);
    }
}

#[cfg(test)]
mod tests {
    use super::{StrictLoading, StrictLoadingState, StrictLoadingViolation};

    #[test]
    fn strict_loading_state_new_is_disabled_by_default() {
        let state = StrictLoadingState::new();

        assert!(!state.is_enabled());
        assert!(!state.strict_loading());
    }

    #[test]
    fn strict_loading_state_enabled_is_enabled() {
        let state = StrictLoadingState::enabled();

        assert!(state.is_enabled());
        assert!(state.strict_loading());
    }

    #[test]
    fn check_strict_loading_on_enabled_state_returns_violation() {
        let state = StrictLoadingState::enabled();

        let error = state
            .check_strict_loading("comments")
            .expect_err("enabled strict loading should reject lazy loads");

        assert_eq!(
            error,
            StrictLoadingViolation {
                model: "StrictLoadingState".to_owned(),
                association: "comments".to_owned(),
            }
        );
    }

    #[test]
    fn check_strict_loading_on_disabled_state_returns_ok() {
        let state = StrictLoadingState::new();

        assert!(state.check_strict_loading("comments").is_ok());
    }
}