1#[cfg(feature = "validation")]
103use reinhardt_core::validators::{ValidationResult, Validator};
104#[cfg(feature = "validation")]
105use std::fmt::{self, Debug};
106use std::ops::Deref;
107
108#[cfg(feature = "validation")]
127pub struct Validated<T, V> {
128 inner: T,
129 _validator: std::marker::PhantomData<V>,
130}
131
132#[cfg(feature = "validation")]
133impl<T, V> Validated<T, V> {
134 pub fn new<U>(inner: T, validator: &V) -> Result<Self, super::ParamError>
140 where
141 V: Validator<U>,
142 T: AsRef<U>,
143 U: ?Sized,
144 {
145 validator.validate(inner.as_ref()).map_err(|e| {
146 super::ParamError::ValidationError(Box::new(
147 super::ParamErrorContext::new(super::ParamType::Form, e.to_string())
148 .with_field("parameter"),
149 ))
150 })?;
151
152 Ok(Self {
153 inner,
154 _validator: std::marker::PhantomData,
155 })
156 }
157
158 pub fn into_inner(self) -> T {
160 self.inner
161 }
162}
163
164#[cfg(feature = "validation")]
165impl<T, V> Deref for Validated<T, V> {
166 type Target = T;
167
168 fn deref(&self) -> &Self::Target {
169 &self.inner
170 }
171}
172
173#[cfg(feature = "validation")]
174impl<T: Debug, V> Debug for Validated<T, V> {
175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176 self.inner.fmt(f)
177 }
178}
179
180#[cfg(feature = "validation")]
184pub struct ValidationConstraints<T> {
185 inner: T,
186 min_length: Option<usize>,
187 max_length: Option<usize>,
188 min_value: Option<String>,
189 max_value: Option<String>,
190 regex: Option<String>,
191 email: bool,
192 url: bool,
193}
194
195#[cfg(feature = "validation")]
196impl<T> ValidationConstraints<T> {
197 pub fn min_length(mut self, min: usize) -> Self {
199 self.min_length = Some(min);
200 self
201 }
202
203 pub fn max_length(mut self, max: usize) -> Self {
205 self.max_length = Some(max);
206 self
207 }
208
209 pub fn min_value<V: ToString>(mut self, min: V) -> Self {
211 self.min_value = Some(min.to_string());
212 self
213 }
214
215 pub fn max_value<V: ToString>(mut self, max: V) -> Self {
217 self.max_value = Some(max.to_string());
218 self
219 }
220
221 pub fn regex(mut self, pattern: impl Into<String>) -> Self {
223 self.regex = Some(pattern.into());
224 self
225 }
226
227 pub fn email(mut self) -> Self {
229 self.email = true;
230 self
231 }
232
233 pub fn url(mut self) -> Self {
235 self.url = true;
236 self
237 }
238
239 const MAX_REGEX_PATTERN_LENGTH: usize = 1024;
242
243 pub fn validate_string(&self, value: &str) -> ValidationResult<()> {
245 if let Some(min) = self.min_length {
247 reinhardt_core::validators::MinLengthValidator::new(min).validate(value)?;
248 }
249 if let Some(max) = self.max_length {
250 reinhardt_core::validators::MaxLengthValidator::new(max).validate(value)?;
251 }
252
253 if let Some(ref pattern) = self.regex {
255 if pattern.len() > Self::MAX_REGEX_PATTERN_LENGTH {
256 return Err(reinhardt_core::validators::ValidationError::Custom(
257 format!(
258 "Regex pattern length {} exceeds maximum allowed length {}",
259 pattern.len(),
260 Self::MAX_REGEX_PATTERN_LENGTH
261 ),
262 ));
263 }
264 reinhardt_core::validators::RegexValidator::new(pattern)
265 .map_err(|e| {
266 reinhardt_core::validators::ValidationError::Custom(format!(
267 "Invalid regex pattern: {}",
268 e
269 ))
270 })?
271 .validate(value)?;
272 }
273
274 if self.email {
276 reinhardt_core::validators::EmailValidator::new().validate(value)?;
277 }
278
279 if self.url {
281 reinhardt_core::validators::UrlValidator::new().validate(value)?;
282 }
283
284 Ok(())
285 }
286
287 pub fn validate_number<N>(&self, value: &N) -> ValidationResult<()>
289 where
290 N: PartialOrd + std::fmt::Display + Clone + std::str::FromStr,
291 <N as std::str::FromStr>::Err: std::fmt::Display,
292 {
293 if let Some(ref min_str) = self.min_value
294 && let Ok(min) = min_str.parse::<N>()
295 {
296 reinhardt_core::validators::MinValueValidator::new(min).validate(value)?;
297 }
298 if let Some(ref max_str) = self.max_value
299 && let Ok(max) = max_str.parse::<N>()
300 {
301 reinhardt_core::validators::MaxValueValidator::new(max).validate(value)?;
302 }
303 Ok(())
304 }
305
306 pub fn into_inner(self) -> T {
308 self.inner
309 }
310}
311
312#[cfg(feature = "validation")]
313impl<T> Deref for ValidationConstraints<T> {
314 type Target = T;
315
316 fn deref(&self) -> &Self::Target {
317 &self.inner
318 }
319}
320
321#[cfg(feature = "validation")]
329pub trait WithValidation: Sized {
330 fn min_length(self, min: usize) -> ValidationConstraints<Self> {
332 ValidationConstraints {
333 inner: self,
334 min_length: Some(min),
335 max_length: None,
336 min_value: None,
337 max_value: None,
338 regex: None,
339 email: false,
340 url: false,
341 }
342 }
343
344 fn max_length(self, max: usize) -> ValidationConstraints<Self> {
346 ValidationConstraints {
347 inner: self,
348 min_length: None,
349 max_length: Some(max),
350 min_value: None,
351 max_value: None,
352 regex: None,
353 email: false,
354 url: false,
355 }
356 }
357
358 fn min_value<V: ToString>(self, min: V) -> ValidationConstraints<Self> {
360 ValidationConstraints {
361 inner: self,
362 min_length: None,
363 max_length: None,
364 min_value: Some(min.to_string()),
365 max_value: None,
366 regex: None,
367 email: false,
368 url: false,
369 }
370 }
371
372 fn max_value<V: ToString>(self, max: V) -> ValidationConstraints<Self> {
374 ValidationConstraints {
375 inner: self,
376 min_length: None,
377 max_length: None,
378 min_value: None,
379 max_value: Some(max.to_string()),
380 regex: None,
381 email: false,
382 url: false,
383 }
384 }
385
386 fn regex(self, pattern: impl Into<String>) -> ValidationConstraints<Self> {
388 ValidationConstraints {
389 inner: self,
390 min_length: None,
391 max_length: None,
392 min_value: None,
393 max_value: None,
394 regex: Some(pattern.into()),
395 email: false,
396 url: false,
397 }
398 }
399
400 fn email(self) -> ValidationConstraints<Self> {
402 ValidationConstraints {
403 inner: self,
404 min_length: None,
405 max_length: None,
406 min_value: None,
407 max_value: None,
408 regex: None,
409 email: true,
410 url: false,
411 }
412 }
413
414 fn url(self) -> ValidationConstraints<Self> {
416 ValidationConstraints {
417 inner: self,
418 min_length: None,
419 max_length: None,
420 min_value: None,
421 max_value: None,
422 regex: None,
423 email: false,
424 url: true,
425 }
426 }
427}
428
429#[cfg(feature = "validation")]
470pub type ValidatedPath<T> = ValidationConstraints<super::Path<T>>;
471
472#[cfg(feature = "validation")]
476pub type ValidatedQuery<T> = ValidationConstraints<super::Query<T>>;
477
478#[cfg(feature = "validation")]
482pub type ValidatedForm<T> = ValidationConstraints<super::Form<T>>;
483
484#[cfg(not(feature = "validation"))]
493pub struct ValidationConstraints<T> {
494 pub inner: T,
496 pub min_length: Option<usize>,
498 pub max_length: Option<usize>,
500 pub min_value: Option<String>,
502 pub max_value: Option<String>,
504 pub regex: Option<String>,
506 pub email: bool,
508 pub url: bool,
510}
511
512#[cfg(not(feature = "validation"))]
513impl<T> ValidationConstraints<T> {
514 pub fn min_length(mut self, min: usize) -> Self {
516 self.min_length = Some(min);
517 self
518 }
519
520 pub fn max_length(mut self, max: usize) -> Self {
522 self.max_length = Some(max);
523 self
524 }
525
526 pub fn min_value<V: ToString>(mut self, min: V) -> Self {
528 self.min_value = Some(min.to_string());
529 self
530 }
531
532 pub fn max_value<V: ToString>(mut self, max: V) -> Self {
534 self.max_value = Some(max.to_string());
535 self
536 }
537
538 pub fn regex(mut self, pattern: impl Into<String>) -> Self {
540 self.regex = Some(pattern.into());
541 self
542 }
543
544 pub fn email(mut self) -> Self {
546 self.email = true;
547 self
548 }
549
550 pub fn url(mut self) -> Self {
552 self.url = true;
553 self
554 }
555
556 const MAX_REGEX_PATTERN_LENGTH: usize = 1024;
559
560 pub fn validate_string(&self, value: &str) -> Result<(), String> {
562 if let Some(min) = self.min_length
563 && value.len() < min
564 {
565 return Err(format!(
566 "String length {} is less than minimum {}",
567 value.len(),
568 min
569 ));
570 }
571 if let Some(max) = self.max_length
572 && value.len() > max
573 {
574 return Err(format!(
575 "String length {} exceeds maximum {}",
576 value.len(),
577 max
578 ));
579 }
580 if let Some(ref pattern) = self.regex {
581 if pattern.len() > Self::MAX_REGEX_PATTERN_LENGTH {
582 return Err(format!(
583 "Regex pattern length {} exceeds maximum allowed length {}",
584 pattern.len(),
585 Self::MAX_REGEX_PATTERN_LENGTH
586 ));
587 }
588 use regex::Regex;
589 let regex = Regex::new(pattern).map_err(|e| format!("Invalid regex: {}", e))?;
590 if !regex.is_match(value) {
591 return Err(format!("String does not match pattern: {}", pattern));
592 }
593 }
594 if self.email {
595 if !value.contains('@') || !value.contains('.') {
596 return Err("Invalid email format".to_string());
597 }
598 let parts: Vec<&str> = value.split('@').collect();
599 if parts.len() != 2 || parts[0].is_empty() || parts[1].is_empty() {
600 return Err("Invalid email format".to_string());
601 }
602 }
603 if self.url && !value.starts_with("http://") && !value.starts_with("https://") {
604 return Err("URL must start with http:// or https://".to_string());
605 }
606 Ok(())
607 }
608
609 pub fn validate_number<N>(&self, value: &N) -> Result<(), String>
611 where
612 N: PartialOrd + std::fmt::Display + Clone + std::str::FromStr,
613 <N as std::str::FromStr>::Err: std::fmt::Display,
614 {
615 if let Some(ref min_str) = self.min_value
616 && let Ok(min) = min_str.parse::<N>()
617 && value < &min
618 {
619 return Err(format!("Value {} is less than minimum {}", value, min));
620 }
621 if let Some(ref max_str) = self.max_value
622 && let Ok(max) = max_str.parse::<N>()
623 && value > &max
624 {
625 return Err(format!("Value {} exceeds maximum {}", value, max));
626 }
627 Ok(())
628 }
629
630 pub fn into_inner(self) -> T {
632 self.inner
633 }
634}
635
636#[cfg(not(feature = "validation"))]
637impl<T> Deref for ValidationConstraints<T> {
638 type Target = T;
639
640 fn deref(&self) -> &Self::Target {
641 &self.inner
642 }
643}
644
645#[cfg(not(feature = "validation"))]
647pub type ValidatedPath<T> = ValidationConstraints<super::Path<T>>;
648
649#[cfg(not(feature = "validation"))]
651pub type ValidatedQuery<T> = ValidationConstraints<super::Query<T>>;
652
653#[cfg(not(feature = "validation"))]
655pub type ValidatedForm<T> = ValidationConstraints<super::Form<T>>;
656
657#[cfg(not(feature = "validation"))]
659impl<T> WithValidation for super::Path<T> {}
660
661#[cfg(not(feature = "validation"))]
662impl<T> WithValidation for super::Query<T> {}
663
664#[cfg(not(feature = "validation"))]
666pub trait WithValidation: Sized {
667 fn min_length(self, min: usize) -> ValidationConstraints<Self> {
669 ValidationConstraints {
670 inner: self,
671 min_length: Some(min),
672 max_length: None,
673 min_value: None,
674 max_value: None,
675 regex: None,
676 email: false,
677 url: false,
678 }
679 }
680
681 fn max_length(self, max: usize) -> ValidationConstraints<Self> {
683 ValidationConstraints {
684 inner: self,
685 min_length: None,
686 max_length: Some(max),
687 min_value: None,
688 max_value: None,
689 regex: None,
690 email: false,
691 url: false,
692 }
693 }
694
695 fn min_value<V: ToString>(self, min: V) -> ValidationConstraints<Self> {
697 ValidationConstraints {
698 inner: self,
699 min_length: None,
700 max_length: None,
701 min_value: Some(min.to_string()),
702 max_value: None,
703 regex: None,
704 email: false,
705 url: false,
706 }
707 }
708
709 fn max_value<V: ToString>(self, max: V) -> ValidationConstraints<Self> {
711 ValidationConstraints {
712 inner: self,
713 min_length: None,
714 max_length: None,
715 min_value: None,
716 max_value: Some(max.to_string()),
717 regex: None,
718 email: false,
719 url: false,
720 }
721 }
722
723 fn regex(self, pattern: impl Into<String>) -> ValidationConstraints<Self> {
725 ValidationConstraints {
726 inner: self,
727 min_length: None,
728 max_length: None,
729 min_value: None,
730 max_value: None,
731 regex: Some(pattern.into()),
732 email: false,
733 url: false,
734 }
735 }
736
737 fn email(self) -> ValidationConstraints<Self> {
739 ValidationConstraints {
740 inner: self,
741 min_length: None,
742 max_length: None,
743 min_value: None,
744 max_value: None,
745 regex: None,
746 email: true,
747 url: false,
748 }
749 }
750
751 fn url(self) -> ValidationConstraints<Self> {
753 ValidationConstraints {
754 inner: self,
755 min_length: None,
756 max_length: None,
757 min_value: None,
758 max_value: None,
759 regex: None,
760 email: false,
761 url: true,
762 }
763 }
764}
765
766#[cfg(test)]
767#[cfg(feature = "validation")]
768mod tests {
769 use super::*;
770 use crate::params::Path;
771 use rstest::rstest;
772
773 #[rstest]
774 fn test_validation_constraints_builder() {
775 let path = Path(42i32);
777 let constrained = path.min_value(0).max_value(100);
778
779 assert!(constrained.validate_number(&42).is_ok());
781 assert!(constrained.validate_number(&-1).is_err());
782 assert!(constrained.validate_number(&101).is_err());
783 }
784
785 #[rstest]
786 fn test_string_validation_constraints() {
787 let path = Path("test".to_string());
789 let constrained = path.min_length(2).max_length(10);
790
791 assert!(constrained.validate_string("test").is_ok());
793 assert!(constrained.validate_string("a").is_err());
794 assert!(constrained.validate_string("this is too long").is_err());
795 }
796
797 #[rstest]
798 fn test_regex_pattern_length_limit_rejects_oversized_patterns() {
799 let path = Path("test".to_string());
801 let oversized_pattern = "a".repeat(2048);
802 let constrained = path.regex(oversized_pattern);
803
804 let result = constrained.validate_string("test");
806
807 assert!(result.is_err());
809 let err_msg = format!("{}", result.unwrap_err());
810 assert!(
811 err_msg.contains("exceeds maximum allowed length"),
812 "Expected pattern length error, got: {}",
813 err_msg
814 );
815 }
816
817 #[rstest]
818 fn test_regex_pattern_within_limit_succeeds() {
819 let path = Path("hello123".to_string());
821 let valid_pattern = r"^[a-zA-Z0-9]+$";
822 let constrained = path.regex(valid_pattern);
823
824 let result = constrained.validate_string("hello123");
826
827 assert!(result.is_ok());
829 }
830
831 #[rstest]
832 fn test_regex_pattern_just_over_limit_is_rejected() {
833 let path = Path("a".to_string());
835 let pattern_over_limit =
836 "a".repeat(ValidationConstraints::<Path<String>>::MAX_REGEX_PATTERN_LENGTH + 1);
837 let constrained = path.regex(pattern_over_limit);
838
839 let result = constrained.validate_string("a");
841
842 assert!(result.is_err());
844 let err_msg = format!("{}", result.unwrap_err());
845 assert!(
846 err_msg.contains("exceeds maximum allowed length"),
847 "Expected pattern length error, got: {}",
848 err_msg
849 );
850 }
851}
852
853#[cfg(test)]
854#[cfg(not(feature = "validation"))]
855mod tests_non_validation {
856 use super::*;
857 use crate::params::Path;
858 use rstest::rstest;
859
860 #[rstest]
861 fn test_regex_pattern_length_limit_rejects_oversized_patterns() {
862 let path = Path("test".to_string());
864 let oversized_pattern = "a".repeat(2048);
865 let constrained = path.regex(oversized_pattern);
866
867 let result = constrained.validate_string("test");
869
870 assert!(result.is_err());
872 let err_msg = result.unwrap_err();
873 assert!(
874 err_msg.contains("exceeds maximum allowed length"),
875 "Expected pattern length error, got: {}",
876 err_msg
877 );
878 }
879
880 #[rstest]
881 fn test_regex_pattern_within_limit_succeeds() {
882 let path = Path("hello123".to_string());
884 let valid_pattern = r"^[a-zA-Z0-9]+$";
885 let constrained = path.regex(valid_pattern);
886
887 let result = constrained.validate_string("hello123");
889
890 assert!(result.is_ok());
892 }
893
894 #[rstest]
895 fn test_regex_pattern_just_over_limit_is_rejected() {
896 let path = Path("a".to_string());
898 let pattern_over_limit =
899 "a".repeat(ValidationConstraints::<Path<String>>::MAX_REGEX_PATTERN_LENGTH + 1);
900 let constrained = path.regex(pattern_over_limit);
901
902 let result = constrained.validate_string("a");
904
905 assert!(result.is_err());
907 let err_msg = result.unwrap_err();
908 assert!(
909 err_msg.contains("exceeds maximum allowed length"),
910 "Expected pattern length error, got: {}",
911 err_msg
912 );
913 }
914}