use std::path::Path;
use image::GenericImageView;
use super::{ValidationError, ValidationResult};
pub struct ImageDimensionValidator {
min_width: Option<u32>,
max_width: Option<u32>,
min_height: Option<u32>,
max_height: Option<u32>,
aspect_ratio: Option<(u32, u32)>,
aspect_ratio_tolerance: f32,
}
impl ImageDimensionValidator {
pub fn new() -> Self {
Self {
min_width: None,
max_width: None,
min_height: None,
max_height: None,
aspect_ratio: None,
aspect_ratio_tolerance: 0.0,
}
}
pub fn with_min_width(mut self, width: u32) -> Self {
self.min_width = Some(width);
self
}
pub fn with_max_width(mut self, width: u32) -> Self {
self.max_width = Some(width);
self
}
pub fn with_width_range(mut self, min: u32, max: u32) -> Self {
self.min_width = Some(min);
self.max_width = Some(max);
self
}
pub fn with_min_height(mut self, height: u32) -> Self {
self.min_height = Some(height);
self
}
pub fn with_max_height(mut self, height: u32) -> Self {
self.max_height = Some(height);
self
}
pub fn with_height_range(mut self, min: u32, max: u32) -> Self {
self.min_height = Some(min);
self.max_height = Some(max);
self
}
pub fn with_aspect_ratio(mut self, width: u32, height: u32) -> Self {
self.aspect_ratio = Some((width, height));
self
}
pub fn with_aspect_ratio_tolerance(mut self, tolerance: f32) -> Self {
self.aspect_ratio_tolerance = tolerance;
self
}
pub fn validate_file(&self, path: impl AsRef<Path>) -> ValidationResult<()> {
let img = image::open(path).map_err(|e| ValidationError::ImageReadError(e.to_string()))?;
let (width, height) = img.dimensions();
self.validate_dimensions(width, height)
}
pub fn validate_bytes(&self, data: &[u8]) -> ValidationResult<()> {
let img = image::load_from_memory(data)
.map_err(|e| ValidationError::ImageReadError(e.to_string()))?;
let (width, height) = img.dimensions();
self.validate_dimensions(width, height)
}
fn validate_dimensions(&self, width: u32, height: u32) -> ValidationResult<()> {
if let Some(min_width) = self.min_width
&& width < min_width
{
return Err(ValidationError::ImageWidthTooSmall { width, min_width });
}
if let Some(max_width) = self.max_width
&& width > max_width
{
return Err(ValidationError::ImageWidthTooLarge { width, max_width });
}
if let Some(min_height) = self.min_height
&& height < min_height
{
return Err(ValidationError::ImageHeightTooSmall { height, min_height });
}
if let Some(max_height) = self.max_height
&& height > max_height
{
return Err(ValidationError::ImageHeightTooLarge { height, max_height });
}
if let Some((expected_width, expected_height)) = self.aspect_ratio {
let actual_ratio = width as f32 / height as f32;
let expected_ratio = expected_width as f32 / expected_height as f32;
let diff = (actual_ratio - expected_ratio).abs();
let tolerance = expected_ratio * self.aspect_ratio_tolerance;
if diff > tolerance {
return Err(ValidationError::InvalidAspectRatio {
actual_width: width,
actual_height: height,
expected_width,
expected_height,
});
}
}
Ok(())
}
}
impl Default for ImageDimensionValidator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_image(width: u32, height: u32) -> Vec<u8> {
use image::{ImageBuffer, Rgb};
let img: ImageBuffer<Rgb<u8>, Vec<u8>> = ImageBuffer::new(width, height);
let mut bytes = Vec::new();
img.write_to(
&mut std::io::Cursor::new(&mut bytes),
image::ImageFormat::Png,
)
.unwrap();
bytes
}
#[test]
fn test_min_width_pass() {
let validator = ImageDimensionValidator::new().with_min_width(800);
let image_data = create_test_image(1024, 768);
assert!(validator.validate_bytes(&image_data).is_ok());
}
#[test]
fn test_min_width_fail() {
let validator = ImageDimensionValidator::new().with_min_width(1920);
let image_data = create_test_image(1024, 768);
match validator.validate_bytes(&image_data) {
Err(ValidationError::ImageWidthTooSmall { width, min_width }) => {
assert_eq!(width, 1024);
assert_eq!(min_width, 1920);
}
_ => panic!("Expected ImageWidthTooSmall error"),
}
}
#[test]
fn test_max_width_pass() {
let validator = ImageDimensionValidator::new().with_max_width(2048);
let image_data = create_test_image(1024, 768);
assert!(validator.validate_bytes(&image_data).is_ok());
}
#[test]
fn test_max_width_fail() {
let validator = ImageDimensionValidator::new().with_max_width(800);
let image_data = create_test_image(1024, 768);
match validator.validate_bytes(&image_data) {
Err(ValidationError::ImageWidthTooLarge { width, max_width }) => {
assert_eq!(width, 1024);
assert_eq!(max_width, 800);
}
_ => panic!("Expected ImageWidthTooLarge error"),
}
}
#[test]
fn test_width_range_pass() {
let validator = ImageDimensionValidator::new().with_width_range(800, 2048);
let image_data = create_test_image(1024, 768);
assert!(validator.validate_bytes(&image_data).is_ok());
}
#[test]
fn test_min_height_pass() {
let validator = ImageDimensionValidator::new().with_min_height(600);
let image_data = create_test_image(1024, 768);
assert!(validator.validate_bytes(&image_data).is_ok());
}
#[test]
fn test_min_height_fail() {
let validator = ImageDimensionValidator::new().with_min_height(1080);
let image_data = create_test_image(1024, 768);
match validator.validate_bytes(&image_data) {
Err(ValidationError::ImageHeightTooSmall { height, min_height }) => {
assert_eq!(height, 768);
assert_eq!(min_height, 1080);
}
_ => panic!("Expected ImageHeightTooSmall error"),
}
}
#[test]
fn test_max_height_pass() {
let validator = ImageDimensionValidator::new().with_max_height(1080);
let image_data = create_test_image(1024, 768);
assert!(validator.validate_bytes(&image_data).is_ok());
}
#[test]
fn test_max_height_fail() {
let validator = ImageDimensionValidator::new().with_max_height(600);
let image_data = create_test_image(1024, 768);
match validator.validate_bytes(&image_data) {
Err(ValidationError::ImageHeightTooLarge { height, max_height }) => {
assert_eq!(height, 768);
assert_eq!(max_height, 600);
}
_ => panic!("Expected ImageHeightTooLarge error"),
}
}
#[test]
fn test_height_range_pass() {
let validator = ImageDimensionValidator::new().with_height_range(600, 1080);
let image_data = create_test_image(1024, 768);
assert!(validator.validate_bytes(&image_data).is_ok());
}
#[test]
fn test_aspect_ratio_exact_match() {
let validator = ImageDimensionValidator::new().with_aspect_ratio(4, 3);
let image_data = create_test_image(1024, 768); assert!(validator.validate_bytes(&image_data).is_ok());
}
#[test]
fn test_aspect_ratio_mismatch() {
let validator = ImageDimensionValidator::new().with_aspect_ratio(16, 9);
let image_data = create_test_image(1024, 768); match validator.validate_bytes(&image_data) {
Err(ValidationError::InvalidAspectRatio {
actual_width,
actual_height,
expected_width,
expected_height,
}) => {
assert_eq!(actual_width, 1024);
assert_eq!(actual_height, 768);
assert_eq!(expected_width, 16);
assert_eq!(expected_height, 9);
}
_ => panic!("Expected InvalidAspectRatio error"),
}
}
#[test]
fn test_aspect_ratio_with_tolerance() {
let validator = ImageDimensionValidator::new()
.with_aspect_ratio(16, 9)
.with_aspect_ratio_tolerance(0.01);
let image_data = create_test_image(1920, 1081);
assert!(validator.validate_bytes(&image_data).is_ok());
}
#[test]
fn test_combined_constraints_pass() {
let validator = ImageDimensionValidator::new()
.with_width_range(800, 2048)
.with_height_range(600, 1536)
.with_aspect_ratio(4, 3);
let image_data = create_test_image(1024, 768);
assert!(validator.validate_bytes(&image_data).is_ok());
}
#[test]
fn test_combined_constraints_fail_width() {
let validator = ImageDimensionValidator::new()
.with_width_range(1200, 2048)
.with_height_range(600, 1536);
let image_data = create_test_image(1024, 768);
assert!(validator.validate_bytes(&image_data).is_err());
}
#[test]
fn test_invalid_image_data() {
let validator = ImageDimensionValidator::new();
let invalid_data = vec![0u8; 100]; match validator.validate_bytes(&invalid_data) {
Err(ValidationError::ImageReadError(_)) => {}
_ => panic!("Expected ImageReadError"),
}
}
#[test]
fn test_no_constraints() {
let validator = ImageDimensionValidator::new();
let image_data = create_test_image(100, 100);
assert!(validator.validate_bytes(&image_data).is_ok());
}
#[test]
fn test_exact_boundary() {
let validator = ImageDimensionValidator::new()
.with_min_width(1024)
.with_max_width(1024);
let image_data = create_test_image(1024, 768);
assert!(validator.validate_bytes(&image_data).is_ok());
}
}