rjango 0.1.1

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

use super::{
    FieldDescriptor, FieldType, binary, boolean, char, datetime, file, float, integer, json,
    related, text, uuid,
};

#[must_use]
pub fn build_field_descriptor(name: impl Into<String>, field_type: FieldType) -> FieldDescriptor {
    FieldDescriptor::new(name, field_type)
}

pub fn validate_composite_field(field_type: &FieldType) -> Result<(), ValidationError> {
    match field_type {
        FieldType::ForeignKey { to, .. }
        | FieldType::OneToOne { to, .. }
        | FieldType::ManyToMany { to, .. } => related::validate_related_target(to),
        _ => Ok(()),
    }
}

#[must_use]
pub fn db_type(field: &FieldType, vendor: &str) -> Option<String> {
    match field {
        FieldType::Char { .. }
        | FieldType::Email
        | FieldType::Url
        | FieldType::Slug { .. }
        | FieldType::Ip
        | FieldType::GenericIp
        | FieldType::CommaSeparatedInteger { .. } => char::db_type(field, vendor),
        FieldType::Text => text::db_type(field, vendor),
        FieldType::Auto
        | FieldType::BigAuto
        | FieldType::SmallAuto
        | FieldType::Integer
        | FieldType::BigInteger
        | FieldType::SmallInteger
        | FieldType::PositiveInteger
        | FieldType::PositiveBigInteger
        | FieldType::PositiveSmallInteger => integer::db_type(field, vendor),
        FieldType::Float | FieldType::Decimal { .. } => float::db_type(field, vendor),
        FieldType::Boolean | FieldType::NullBoolean => boolean::db_type(field, vendor),
        FieldType::DateTime { .. }
        | FieldType::Date { .. }
        | FieldType::Time
        | FieldType::Duration => datetime::db_type(field, vendor),
        FieldType::Uuid => uuid::db_type(field, vendor),
        FieldType::Binary => binary::db_type(field, vendor),
        FieldType::Json => json::db_type(field, vendor),
        FieldType::File { .. } | FieldType::Image { .. } | FieldType::FilePath { .. } => {
            file::db_type(field, vendor)
        }
        FieldType::ForeignKey { .. }
        | FieldType::OneToOne { .. }
        | FieldType::ManyToMany { .. } => related::db_type(field, vendor),
    }
}

pub fn get_prep_value(field: &FieldType, value: &str) -> Result<String, ValidationError> {
    match field {
        FieldType::Char { .. }
        | FieldType::Email
        | FieldType::Url
        | FieldType::Slug { .. }
        | FieldType::Ip
        | FieldType::GenericIp
        | FieldType::CommaSeparatedInteger { .. } => char::get_prep_value(field, value),
        FieldType::Text => text::get_prep_value(value),
        FieldType::Auto
        | FieldType::BigAuto
        | FieldType::SmallAuto
        | FieldType::Integer
        | FieldType::BigInteger
        | FieldType::SmallInteger
        | FieldType::PositiveInteger
        | FieldType::PositiveBigInteger
        | FieldType::PositiveSmallInteger => integer::get_prep_value(field, value),
        FieldType::Float | FieldType::Decimal { .. } => float::get_prep_value(field, value),
        FieldType::Boolean | FieldType::NullBoolean => boolean::get_prep_value(value),
        FieldType::DateTime { .. }
        | FieldType::Date { .. }
        | FieldType::Time
        | FieldType::Duration => datetime::get_prep_value(field, value),
        FieldType::Uuid => uuid::get_prep_value(value, "postgres"),
        FieldType::Binary => binary::get_prep_value(value.as_bytes()),
        FieldType::Json => json::get_prep_value(value),
        FieldType::File { .. } | FieldType::Image { .. } | FieldType::FilePath { .. } => {
            file::get_prep_value(value)
        }
        FieldType::ForeignKey { .. }
        | FieldType::OneToOne { .. }
        | FieldType::ManyToMany { .. } => related::get_prep_value(value),
    }
}

pub fn from_db_value(field: &FieldType, value: &str) -> Result<String, ValidationError> {
    match field {
        FieldType::Char { .. }
        | FieldType::Email
        | FieldType::Url
        | FieldType::Slug { .. }
        | FieldType::Ip
        | FieldType::GenericIp
        | FieldType::CommaSeparatedInteger { .. } => char::from_db_value(value),
        FieldType::Text => text::from_db_value(value),
        FieldType::Auto
        | FieldType::BigAuto
        | FieldType::SmallAuto
        | FieldType::Integer
        | FieldType::BigInteger
        | FieldType::SmallInteger
        | FieldType::PositiveInteger
        | FieldType::PositiveBigInteger
        | FieldType::PositiveSmallInteger => {
            integer::from_db_value(value).map(|parsed| parsed.to_string())
        }
        FieldType::Float | FieldType::Decimal { .. } => float::from_db_value(field, value),
        FieldType::Boolean | FieldType::NullBoolean => {
            boolean::from_db_value(value).map(|parsed| parsed.to_string())
        }
        FieldType::DateTime { .. }
        | FieldType::Date { .. }
        | FieldType::Time
        | FieldType::Duration => datetime::from_db_value(field, value),
        FieldType::Uuid => uuid::from_db_value(value),
        FieldType::Binary => {
            binary::from_db_value(value)?;
            Ok(value.to_string())
        }
        FieldType::Json => json::from_db_value(value).map(|parsed| parsed.to_string()),
        FieldType::File { .. } | FieldType::Image { .. } | FieldType::FilePath { .. } => {
            file::from_db_value(value)
        }
        FieldType::ForeignKey { .. }
        | FieldType::OneToOne { .. }
        | FieldType::ManyToMany { .. } => related::from_db_value(value),
    }
}

#[must_use]
pub fn formfield(field: &FieldType) -> Option<&'static str> {
    match field {
        FieldType::Char { .. }
        | FieldType::Email
        | FieldType::Url
        | FieldType::Slug { .. }
        | FieldType::Ip
        | FieldType::GenericIp
        | FieldType::CommaSeparatedInteger { .. } => char::formfield(field),
        FieldType::Text => text::formfield(field),
        FieldType::Auto
        | FieldType::BigAuto
        | FieldType::SmallAuto
        | FieldType::Integer
        | FieldType::BigInteger
        | FieldType::SmallInteger
        | FieldType::PositiveInteger
        | FieldType::PositiveBigInteger
        | FieldType::PositiveSmallInteger => integer::formfield(field),
        FieldType::Float | FieldType::Decimal { .. } => float::formfield(field),
        FieldType::Boolean | FieldType::NullBoolean => boolean::formfield(field),
        FieldType::DateTime { .. }
        | FieldType::Date { .. }
        | FieldType::Time
        | FieldType::Duration => datetime::formfield(field),
        FieldType::Uuid => uuid::formfield(field),
        FieldType::Binary => binary::formfield(field),
        FieldType::Json => json::formfield(field),
        FieldType::File { .. } | FieldType::Image { .. } | FieldType::FilePath { .. } => {
            file::formfield(field)
        }
        FieldType::ForeignKey { .. }
        | FieldType::OneToOne { .. }
        | FieldType::ManyToMany { .. } => related::formfield(field),
    }
}

#[cfg(test)]
mod tests {
    use super::{FieldType, db_type, formfield, from_db_value, get_prep_value};
    use crate::db::models::fields::OnDelete;

    #[test]
    fn db_type_delegates_to_char_fields() {
        let field = FieldType::Char { max_length: 8 };
        assert_eq!(db_type(&field, "sqlite").as_deref(), Some("varchar(8)"));
    }

    #[test]
    fn get_prep_value_dispatches_integer_processing() {
        let prepared = get_prep_value(&FieldType::PositiveInteger, "9")
            .expect("composite preparation should delegate integer parsing");
        assert_eq!(prepared, "9");
    }

    #[test]
    fn from_db_value_dispatches_boolean_processing() {
        let parsed = from_db_value(&FieldType::Boolean, "1")
            .expect("composite conversion should delegate boolean parsing");
        assert_eq!(parsed, "true");
    }

    #[test]
    fn formfield_dispatches_related_fields() {
        let field = FieldType::ForeignKey {
            to: "Author".to_string(),
            on_delete: OnDelete::Cascade,
        };
        assert_eq!(formfield(&field), Some("ModelChoiceField"));
    }
}