rjango 0.1.1

A full-stack Rust backend framework inspired by Django
Documentation
use thiserror::Error;

#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum CoreException {
    #[error("object does not exist")]
    ObjectDoesNotExist,
    #[error("get() returned multiple objects")]
    MultipleObjectsReturned,
    #[error("suspicious operation: {0}")]
    SuspiciousOperation(String),
    #[error("permission denied: {0}")]
    PermissionDenied(String),
    #[error("view does not exist: {0}")]
    ViewDoesNotExist(String),
    #[error("middleware not used: {0}")]
    MiddlewareNotUsed(String),
    #[error("improperly configured: {0}")]
    ImproperlyConfigured(String),
    #[error("field error: {0}")]
    FieldError(String),
    #[error("{0}")]
    ValidationError(String),
    #[error("suspicious file operation: {0}")]
    SuspiciousFileOperation(String),
    #[error("request data too big")]
    RequestDataTooBig,
    #[error("too many fields sent")]
    TooManyFieldsSent,
    #[error("request aborted")]
    RequestAborted,
    #[error("bad request: {0}")]
    BadRequest(String),
    #[error("disallowed host: {0}")]
    DisallowedHost(String),
    #[error("disallowed redirect: {0}")]
    DisallowedRedirect(String),
}

impl CoreException {
    #[must_use]
    pub fn is_suspicious(&self) -> bool {
        matches!(
            self,
            Self::SuspiciousOperation(_)
                | Self::SuspiciousFileOperation(_)
                | Self::DisallowedHost(_)
                | Self::DisallowedRedirect(_)
        )
    }
}

impl From<crate::core::validators::ValidationError> for CoreException {
    fn from(error: crate::core::validators::ValidationError) -> Self {
        Self::ValidationError(error.message)
    }
}

#[cfg(test)]
mod tests {
    use super::CoreException;
    use crate::core::validators::ValidationError as ValidatorValidationError;
    use std::error::Error as StdError;

    #[test]
    fn object_does_not_exist_display_matches_django_text() {
        assert_eq!(
            CoreException::ObjectDoesNotExist.to_string(),
            "object does not exist"
        );
    }

    #[test]
    fn multiple_objects_returned_display_matches_django_text() {
        assert_eq!(
            CoreException::MultipleObjectsReturned.to_string(),
            "get() returned multiple objects"
        );
    }

    #[test]
    fn suspicious_operation_display_includes_message() {
        assert_eq!(
            CoreException::SuspiciousOperation("tampered payload".into()).to_string(),
            "suspicious operation: tampered payload"
        );
    }

    #[test]
    fn permission_denied_display_includes_message() {
        assert_eq!(
            CoreException::PermissionDenied("missing scope".into()).to_string(),
            "permission denied: missing scope"
        );
    }

    #[test]
    fn view_does_not_exist_display_includes_message() {
        assert_eq!(
            CoreException::ViewDoesNotExist("admin.index".into()).to_string(),
            "view does not exist: admin.index"
        );
    }

    #[test]
    fn middleware_not_used_display_includes_message() {
        assert_eq!(
            CoreException::MiddlewareNotUsed("csrf".into()).to_string(),
            "middleware not used: csrf"
        );
    }

    #[test]
    fn improperly_configured_display_includes_message() {
        assert_eq!(
            CoreException::ImproperlyConfigured("missing database url".into()).to_string(),
            "improperly configured: missing database url"
        );
    }

    #[test]
    fn field_error_display_includes_message() {
        assert_eq!(
            CoreException::FieldError("unknown field".into()).to_string(),
            "field error: unknown field"
        );
    }

    #[test]
    fn validation_error_display_is_raw_message() {
        assert_eq!(
            CoreException::ValidationError("Enter a valid value.".into()).to_string(),
            "Enter a valid value."
        );
    }

    #[test]
    fn suspicious_file_operation_display_includes_message() {
        assert_eq!(
            CoreException::SuspiciousFileOperation("path traversal".into()).to_string(),
            "suspicious file operation: path traversal"
        );
    }

    #[test]
    fn request_data_too_big_display_matches_django_text() {
        assert_eq!(
            CoreException::RequestDataTooBig.to_string(),
            "request data too big"
        );
    }

    #[test]
    fn too_many_fields_sent_display_matches_django_text() {
        assert_eq!(
            CoreException::TooManyFieldsSent.to_string(),
            "too many fields sent"
        );
    }

    #[test]
    fn request_aborted_display_matches_django_text() {
        assert_eq!(CoreException::RequestAborted.to_string(), "request aborted");
    }

    #[test]
    fn bad_request_display_includes_message() {
        assert_eq!(
            CoreException::BadRequest("malformed query".into()).to_string(),
            "bad request: malformed query"
        );
    }

    #[test]
    fn disallowed_host_display_includes_message() {
        assert_eq!(
            CoreException::DisallowedHost("evil.example".into()).to_string(),
            "disallowed host: evil.example"
        );
    }

    #[test]
    fn disallowed_redirect_display_includes_message() {
        assert_eq!(
            CoreException::DisallowedRedirect("https://evil.example".into()).to_string(),
            "disallowed redirect: https://evil.example"
        );
    }

    #[test]
    fn suspicious_variants_are_marked_suspicious() {
        assert!(CoreException::SuspiciousOperation("tampered".into()).is_suspicious());
        assert!(CoreException::SuspiciousFileOperation("path traversal".into()).is_suspicious());
        assert!(CoreException::DisallowedHost("evil.example".into()).is_suspicious());
        assert!(CoreException::DisallowedRedirect("https://evil.example".into()).is_suspicious());
    }

    #[test]
    fn non_suspicious_variants_are_not_marked_suspicious() {
        assert!(!CoreException::ObjectDoesNotExist.is_suspicious());
        assert!(!CoreException::PermissionDenied("missing scope".into()).is_suspicious());
        assert!(!CoreException::BadRequest("bad body".into()).is_suspicious());
    }

    #[test]
    fn core_exception_implements_std_error() {
        fn assert_error<E: StdError>() {}

        assert_error::<CoreException>();
    }

    #[test]
    fn core_exception_has_no_error_source() {
        let error = CoreException::FieldError("unknown field".into());
        assert!(StdError::source(&error).is_none());
    }

    #[test]
    fn validator_validation_error_converts_into_core_exception() {
        let validation_error = ValidatorValidationError::new("Enter a valid value.", "invalid");
        let exception = CoreException::from(validation_error);

        assert_eq!(
            exception,
            CoreException::ValidationError("Enter a valid value.".into())
        );
    }

    #[test]
    fn core_exception_is_cloneable_and_equatable() {
        let original = CoreException::MiddlewareNotUsed("csrf".into());
        let cloned = original.clone();

        assert_eq!(cloned, original);
        assert_eq!(cloned, CoreException::MiddlewareNotUsed("csrf".into()));
    }
}