use super::SchemaValidator;
use crate::error::ValidationError;
use crate::validator::{
MaxIntValidator, MinIntValidator, RequiredValidator, StrictTypeValidator, ValidatorChain,
};
use crate::value::Value;
#[derive(Default)]
pub struct IntegerSchema {
min: Option<i64>,
max: Option<i64>,
default: Option<i64>,
required: bool,
optional: bool,
strict: bool,
positive: bool,
negative: bool,
non_zero: bool,
}
impl IntegerSchema {
pub fn new() -> Self {
Default::default()
}
pub fn min(mut self, n: i64) -> Self {
self.min = Some(n);
self
}
pub fn max(mut self, n: i64) -> Self {
self.max = Some(n);
self
}
pub fn positive(mut self) -> Self {
self.positive = true;
self
}
pub fn negative(mut self) -> Self {
self.negative = true;
self
}
pub fn non_zero(mut self) -> Self {
self.non_zero = true;
self
}
pub fn default(mut self, val: i64) -> Self {
self.default = Some(val);
self
}
pub fn required(mut self) -> Self {
self.required = true;
self
}
pub fn optional(mut self) -> Self {
self.optional = true;
self
}
pub fn strict(mut self) -> Self {
self.strict = true;
self
}
fn build_chain(&self) -> ValidatorChain {
let mut chain = ValidatorChain::new();
if self.required {
chain = chain.push_validator(RequiredValidator);
}
if self.strict {
chain = chain.push_validator(StrictTypeValidator { expected: "int" });
}
if self.positive {
chain = chain.push_validator(crate::validator::PositiveValidator);
}
if self.negative {
chain = chain.push_validator(crate::validator::NegativeValidator);
}
if self.non_zero {
chain = chain.push_validator(crate::validator::NonZeroValidator);
}
if let Some(n) = self.min {
chain = chain.push_validator(MinIntValidator(n));
}
if let Some(n) = self.max {
chain = chain.push_validator(MaxIntValidator(n));
}
chain
}
}
impl SchemaValidator for IntegerSchema {
fn validate(&self, value: &Value, field: &str) -> Result<(), ValidationError> {
if self.optional && value.is_null() {
return Ok(());
}
let effective = if !self.strict && !value.is_int() && !value.is_null() {
if let Some(n) = value.coerce_to_int() {
Value::Int(n)
} else {
value.clone()
}
} else {
value.clone()
};
self.build_chain()
.validate(&effective, field)
.map_err(|e| e.errors[0].clone())
}
fn is_required(&self) -> bool {
self.required && self.default.is_none()
}
fn default_value(&self) -> Option<Value> {
self.default.map(Value::Int)
}
fn schema_type(&self) -> &'static str {
"integer"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_int_valid() {
let s = IntegerSchema::new().min(0).max(100);
assert!(s.validate(&Value::Int(50), "n").is_ok());
}
#[test]
fn test_int_too_small() {
let s = IntegerSchema::new().min(18);
assert!(s.validate(&Value::Int(10), "age").is_err());
}
#[test]
fn test_int_too_large() {
let s = IntegerSchema::new().max(100);
assert!(s.validate(&Value::Int(200), "n").is_err());
}
#[test]
fn test_int_required_null() {
let s = IntegerSchema::new().required();
assert!(s.validate(&Value::Null, "n").is_err());
}
#[test]
fn test_int_optional_null() {
let s = IntegerSchema::new().optional();
assert!(s.validate(&Value::Null, "n").is_ok());
}
#[test]
fn test_int_strict_rejects_string() {
let s = IntegerSchema::new().strict();
assert!(s.validate(&Value::String("18".into()), "age").is_err());
}
#[test]
fn test_int_nonstrict_coerces_string() {
let s = IntegerSchema::new().min(18);
assert!(s.validate(&Value::String("25".into()), "age").is_ok());
}
#[test]
fn test_int_default() {
let s = IntegerSchema::new().default(42);
assert_eq!(s.default_value(), Some(Value::Int(42)));
}
#[test]
fn test_int_positive() {
let s = IntegerSchema::new().positive();
assert!(s.validate(&Value::Int(10), "val").is_ok());
assert!(s.validate(&Value::Int(0), "val").is_err());
assert!(s.validate(&Value::Int(-1), "val").is_err());
}
#[test]
fn test_int_negative() {
let s = IntegerSchema::new().negative();
assert!(s.validate(&Value::Int(-10), "val").is_ok());
assert!(s.validate(&Value::Int(0), "val").is_err());
assert!(s.validate(&Value::Int(5), "val").is_err());
}
#[test]
fn test_int_non_zero() {
let s = IntegerSchema::new().non_zero();
assert!(s.validate(&Value::Int(10), "val").is_ok());
assert!(s.validate(&Value::Int(-10), "val").is_ok());
assert!(s.validate(&Value::Int(0), "val").is_err());
}
}