rjango 0.1.1

A full-stack Rust backend framework inspired by Django
Documentation
use crate::forms::ValidationError;

/// Django-style metadata options exposed on model types.
pub trait ModelMeta {
    fn table_name() -> &'static str;

    fn verbose_name() -> &'static str {
        Self::table_name()
    }

    fn verbose_name_plural() -> &'static str {
        ""
    }

    fn ordering() -> &'static [&'static str] {
        &[]
    }

    fn get_latest_by() -> Option<&'static str> {
        None
    }

    fn unique_together() -> &'static [&'static [&'static str]] {
        &[]
    }
}

/// Field-level and model-level validation hooks.
pub trait ModelValidation {
    fn validate(&self) -> Result<(), ValidationError> {
        Ok(())
    }

    fn full_clean(&self) -> Result<(), ValidationError> {
        self.validate()
    }
}

#[cfg(test)]
mod tests {
    use super::{ModelMeta, ModelValidation};
    use crate::forms::ValidationError;

    struct DefaultMetaModel;

    impl ModelMeta for DefaultMetaModel {
        fn table_name() -> &'static str {
            "widgets"
        }
    }

    struct CustomMetaModel;

    impl ModelMeta for CustomMetaModel {
        fn table_name() -> &'static str {
            "inventory_item"
        }

        fn verbose_name() -> &'static str {
            "inventory item"
        }

        fn verbose_name_plural() -> &'static str {
            "inventory items"
        }

        fn ordering() -> &'static [&'static str] {
            &["name", "-created_at"]
        }

        fn get_latest_by() -> Option<&'static str> {
            Some("created_at")
        }

        fn unique_together() -> &'static [&'static [&'static str]] {
            &[&["sku", "warehouse_id"]]
        }
    }

    #[derive(Default)]
    struct ValidModel;

    impl ModelValidation for ValidModel {}

    #[derive(Default)]
    struct InvalidModel;

    impl ModelValidation for InvalidModel {
        fn validate(&self) -> Result<(), ValidationError> {
            Err(ValidationError {
                field: "name".to_string(),
                message: "This field is required.".to_string(),
                code: "required".to_string(),
            })
        }
    }

    #[test]
    fn model_meta_defaults_use_table_name_and_empty_options() {
        assert_eq!(DefaultMetaModel::table_name(), "widgets");
        assert_eq!(DefaultMetaModel::verbose_name(), "widgets");
        assert_eq!(DefaultMetaModel::verbose_name_plural(), "");
        assert!(DefaultMetaModel::ordering().is_empty());
        assert_eq!(DefaultMetaModel::get_latest_by(), None);
        assert!(DefaultMetaModel::unique_together().is_empty());
    }

    #[test]
    fn model_meta_supports_custom_options() {
        assert_eq!(CustomMetaModel::table_name(), "inventory_item");
        assert_eq!(CustomMetaModel::verbose_name(), "inventory item");
        assert_eq!(CustomMetaModel::verbose_name_plural(), "inventory items");
        assert_eq!(CustomMetaModel::ordering(), &["name", "-created_at"]);
        assert_eq!(CustomMetaModel::get_latest_by(), Some("created_at"));
        assert_eq!(
            CustomMetaModel::unique_together(),
            &[&["sku", "warehouse_id"]][..]
        );
    }

    #[test]
    fn model_validation_defaults_to_noop() {
        ValidModel
            .full_clean()
            .expect("default validation should pass");
    }

    #[test]
    fn model_validation_full_clean_delegates_to_validate() {
        let error = InvalidModel
            .full_clean()
            .expect_err("full_clean should return validate errors");

        assert_eq!(
            error,
            ValidationError {
                field: "name".to_string(),
                message: "This field is required.".to_string(),
                code: "required".to_string(),
            }
        );
    }
}