From bfcc8240a92b982178f96a2ac936b99d7bc9ff42 Mon Sep 17 00:00:00 2001 From: Eric Liu Date: Wed, 14 Oct 2026 20:48:56 -0700 Subject: [PATCH] Forward serdevalid collection bounds through Nullable Signed-off-by: Eric Liu --- src/nullable_format.rs | 20 ++++++++++ tests/nullable_collection_validation.rs | 52 +++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 tests/nullable_collection_validation.rs diff --git a/src/nullable_format.rs b/src/nullable_format.rs index 232957675..c7ecf1710 100644 --- a/src/nullable_format.rs +++ b/src/nullable_format.rs @@ -703,6 +703,26 @@ impl_generic_composited_validation_nullable!(MinProperties, usize); #[cfg(feature = "serdevalid")] impl_generic_composited_validation_nullable!(Enum); +#[cfg(feature = "serdevalid")] +impl serde_valid::ValidateMinItems for Nullable { + fn validate_min_items(&self, min_items: usize) -> Result<(), serde_valid::MinItemsError> { + match self { + Self::Null => Ok(()), + Self::Present(value) => value.validate_min_items(min_items), + } + } +} + +#[cfg(feature = "serdevalid")] +impl serde_valid::ValidateMaxItems for Nullable { + fn validate_max_items(&self, max_items: usize) -> Result<(), serde_valid::MaxItemsError> { + match self { + Self::Null => Ok(()), + Self::Present(value) => value.validate_max_items(max_items), + } + } +} + /// Serde helper function to create a default `Option>` while /// deserializing pub fn default_optional_nullable() -> Option> { diff --git a/tests/nullable_collection_validation.rs b/tests/nullable_collection_validation.rs new file mode 100644 index 000000000..bc17a3905 --- /dev/null +++ b/tests/nullable_collection_validation.rs @@ -0,0 +1,52 @@ +#![cfg(feature = "serdevalid")] + +use serde_valid::Validate; +use swagger::Nullable; + +#[derive(Validate)] +struct Collection { + #[validate(min_items = 1)] + #[validate(max_items = 2)] + values: Nullable>, +} + +#[derive(Validate)] +struct OptionalCollection { + #[validate(min_items = 1)] + #[validate(max_items = 2)] + values: Option>>, +} + +#[test] +fn nullable_collection_bounds_apply_to_present_values() { + assert!(Collection { + values: Nullable::Null + } + .validate() + .is_ok()); + assert!(OptionalCollection { values: None }.validate().is_ok()); + assert!(OptionalCollection { + values: Some(Nullable::Null) + } + .validate() + .is_ok()); + for size in 0..=3 { + let values = vec![String::new(); size]; + assert_eq!( + Collection { + values: Nullable::Present(values.clone()) + } + .validate() + .is_ok(), + (1..=2).contains(&size) + ); + assert_eq!( + OptionalCollection { + values: Some(Nullable::Present(values)) + } + .validate() + .is_ok(), + (1..=2).contains(&size) + ); + } +}