use crate::core::validators::{MaxValueValidator, MinValueValidator, ValidationError, Validator};
use super::FieldType;
const SMALL_INTEGER_MIN: i64 = -32_768;
const SMALL_INTEGER_MAX: i64 = 32_767;
const INTEGER_MIN: i64 = -2_147_483_648;
const INTEGER_MAX: i64 = 2_147_483_647;
pub fn validate_integer_field(
value: i64,
min: Option<i64>,
max: Option<i64>,
) -> Result<(), ValidationError> {
if let Some(min) = min {
MinValueValidator::new(min).validate(&value)?;
}
if let Some(max) = max {
MaxValueValidator::new(max).validate(&value)?;
}
Ok(())
}
pub fn validate_positive_integer_field(
value: i64,
max: Option<i64>,
) -> Result<(), ValidationError> {
validate_integer_field(value, Some(0), max)
}
pub fn to_i32(value: i64) -> Result<i32, ValidationError> {
i32::try_from(value).map_err(|_| {
ValidationError::new("Value exceeds the range of a 32-bit integer.", "invalid")
.with_param("value", value.to_string())
})
}
pub fn to_i16(value: i64) -> Result<i16, ValidationError> {
i16::try_from(value).map_err(|_| {
ValidationError::new("Value exceeds the range of a 16-bit integer.", "invalid")
.with_param("value", value.to_string())
})
}
fn range_for(field_type: &FieldType) -> Option<(i64, i64)> {
match field_type {
FieldType::Auto | FieldType::Integer => Some((INTEGER_MIN, INTEGER_MAX)),
FieldType::BigAuto | FieldType::BigInteger => Some((i64::MIN, i64::MAX)),
FieldType::SmallAuto | FieldType::SmallInteger => {
Some((SMALL_INTEGER_MIN, SMALL_INTEGER_MAX))
}
FieldType::PositiveInteger => Some((0, INTEGER_MAX)),
FieldType::PositiveBigInteger => Some((0, i64::MAX)),
FieldType::PositiveSmallInteger => Some((0, SMALL_INTEGER_MAX)),
_ => None,
}
}
pub fn validate_integer_range(value: i64, field_type: &FieldType) -> Result<(), ValidationError> {
let Some((min, max)) = range_for(field_type) else {
return Err(ValidationError::new(
"Field type is not handled by integer range validation.",
"invalid",
));
};
validate_integer_field(value, Some(min), Some(max))
}
#[must_use]
pub fn db_type(field: &FieldType, vendor: &str) -> Option<String> {
let _ = vendor;
match field {
FieldType::Auto | FieldType::Integer | FieldType::PositiveInteger => {
Some(if matches!(field, FieldType::PositiveInteger) {
"integer CHECK (value >= 0)".to_string()
} else {
"integer".to_string()
})
}
FieldType::BigAuto | FieldType::BigInteger | FieldType::PositiveBigInteger => {
Some(if matches!(field, FieldType::PositiveBigInteger) {
"bigint CHECK (value >= 0)".to_string()
} else {
"bigint".to_string()
})
}
FieldType::SmallAuto | FieldType::SmallInteger | FieldType::PositiveSmallInteger => {
Some(if matches!(field, FieldType::PositiveSmallInteger) {
"smallint CHECK (value >= 0)".to_string()
} else {
"smallint".to_string()
})
}
_ => None,
}
}
pub fn get_prep_value(field_type: &FieldType, value: &str) -> Result<String, ValidationError> {
let parsed = value.parse::<i64>().map_err(|_| {
ValidationError::new("Enter a valid integer.", "invalid").with_param("value", value)
})?;
validate_integer_range(parsed, field_type)?;
Ok(parsed.to_string())
}
pub fn from_db_value(value: &str) -> Result<i64, ValidationError> {
value.parse::<i64>().map_err(|_| {
ValidationError::new("Enter a valid integer.", "invalid").with_param("value", value)
})
}
#[must_use]
pub fn formfield(field: &FieldType) -> Option<&'static str> {
match field {
FieldType::Auto
| FieldType::BigAuto
| FieldType::SmallAuto
| FieldType::Integer
| FieldType::BigInteger
| FieldType::SmallInteger
| FieldType::PositiveInteger
| FieldType::PositiveBigInteger
| FieldType::PositiveSmallInteger => Some("IntegerField"),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::{
FieldType, db_type, formfield, from_db_value, get_prep_value, validate_integer_range,
};
#[test]
fn db_type_for_postgres_uses_bigint_for_big_integer() {
assert_eq!(
db_type(&FieldType::BigInteger, "postgres").as_deref(),
Some("bigint")
);
}
#[test]
fn get_prep_value_normalizes_integer_strings() {
let prepared = get_prep_value(&FieldType::Integer, "42")
.expect("integer preparation should parse valid numbers");
assert_eq!(prepared, "42");
}
#[test]
fn from_db_value_parses_integer() {
let parsed = from_db_value("-17").expect("database integer should parse");
assert_eq!(parsed, -17);
}
#[test]
fn formfield_returns_correct_type() {
assert_eq!(formfield(&FieldType::PositiveInteger), Some("IntegerField"));
}
#[test]
fn validate_integer_range_rejects_small_integer_overflow() {
let error = validate_integer_range(40_000, &FieldType::SmallInteger)
.expect_err("small integers should enforce i16 bounds");
assert_eq!(error.code, "max_value");
}
}