rjango 0.1.1

A full-stack Rust backend framework inspired by Django
Documentation
use crate::core::validators::{
    EmailValidator, MaxLengthValidator, ProhibitNullCharactersValidator, URLValidator,
    ValidationError, Validator, validate_ipv4_address, validate_ipv46_address, validate_slug,
};

use super::FieldType;

pub fn validate_char_field(value: &str, max_length: usize) -> Result<(), ValidationError> {
    ProhibitNullCharactersValidator::default().validate(&value)?;
    MaxLengthValidator::new(max_length).validate(&value)
}

pub fn validate_email_field(value: &str) -> Result<(), ValidationError> {
    EmailValidator::default().validate(&value)
}

pub fn validate_url_field(value: &str) -> Result<(), ValidationError> {
    URLValidator::default().validate(&value)
}

pub fn validate_slug_field(value: &str, max_length: usize) -> Result<(), ValidationError> {
    validate_char_field(value, max_length)?;
    validate_slug(value)
}

pub fn validate_ip_field(value: &str) -> Result<(), ValidationError> {
    validate_ipv4_address(value)
}

pub fn validate_generic_ip_field(value: &str) -> Result<(), ValidationError> {
    validate_ipv46_address(value)
}

pub fn validate_comma_separated_integer_field(
    value: &str,
    max_length: usize,
) -> Result<(), ValidationError> {
    validate_char_field(value, max_length)?;

    if value
        .split(',')
        .filter(|part| !part.is_empty())
        .any(|part| part.trim().parse::<i64>().is_err())
    {
        return Err(
            ValidationError::new("Enter only comma separated integers.", "invalid")
                .with_param("value", value),
        );
    }

    Ok(())
}

#[must_use]
pub fn db_type(field: &FieldType, vendor: &str) -> Option<String> {
    let _ = vendor;

    match field {
        FieldType::Char { max_length }
        | FieldType::Slug { max_length }
        | FieldType::CommaSeparatedInteger { max_length } => Some(format!("varchar({max_length})")),
        FieldType::Email => Some("varchar(254)".to_string()),
        FieldType::Url => Some("varchar(200)".to_string()),
        FieldType::Ip => Some("char(15)".to_string()),
        FieldType::GenericIp => Some("char(39)".to_string()),
        _ => None,
    }
}

pub fn get_prep_value(field: &FieldType, value: &str) -> Result<String, ValidationError> {
    let prepared = if let Some(max_length) = field.max_length() {
        value.chars().take(max_length).collect::<String>()
    } else {
        value.to_string()
    };

    match field {
        FieldType::Char { max_length } => validate_char_field(&prepared, *max_length)?,
        FieldType::Email => validate_email_field(&prepared)?,
        FieldType::Url => validate_url_field(&prepared)?,
        FieldType::Slug { max_length } => validate_slug_field(&prepared, *max_length)?,
        FieldType::Ip => validate_ip_field(&prepared)?,
        FieldType::GenericIp => validate_generic_ip_field(&prepared)?,
        FieldType::CommaSeparatedInteger { max_length } => {
            validate_comma_separated_integer_field(&prepared, *max_length)?;
        }
        _ => {
            return Err(ValidationError::new(
                "Field type is not handled by char preparation.",
                "invalid",
            ));
        }
    }

    Ok(prepared)
}

pub fn from_db_value(value: &str) -> Result<String, ValidationError> {
    ProhibitNullCharactersValidator::default().validate(&value)?;
    Ok(value.to_string())
}

#[must_use]
pub fn formfield(field: &FieldType) -> Option<&'static str> {
    match field {
        FieldType::Char { .. }
        | FieldType::CommaSeparatedInteger { .. }
        | FieldType::Email
        | FieldType::Url
        | FieldType::Slug { .. }
        | FieldType::Ip
        | FieldType::GenericIp => Some("CharField"),
        _ => None,
    }
}

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

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

    #[test]
    fn get_prep_value_truncates_to_max_length() {
        let field = FieldType::Char { max_length: 5 };
        let prepared = get_prep_value(&field, "abcdef").expect("char preparation should succeed");
        assert_eq!(prepared, "abcde");
    }

    #[test]
    fn from_db_value_returns_identity() {
        let value = from_db_value("stored value").expect("db value should round-trip");
        assert_eq!(value, "stored value");
    }

    #[test]
    fn formfield_returns_correct_type() {
        let field = FieldType::Char { max_length: 32 };
        assert_eq!(formfield(&field), Some("CharField"));
    }
}