Skip to main content

ferrox_types/
lib.rs

1use serde::{Deserialize, Serialize};
2use ferrox_errors::AppError;
3
4/// A strongly typed Pagination primitive.
5/// It guarantees that limit and offset are always valid (e.g. limit > 0).
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
7pub struct Pagination {
8    limit: u32,
9    offset: u32,
10}
11
12impl Pagination {
13    /// Attempts to construct a Pagination object. Returns a ValidationError if invalid.
14    pub fn new(limit: u32, offset: u32) -> Result<Self, AppError> {
15        if limit == 0 {
16            return Err(AppError::ValidationError("Limit must be strictly greater than 0".into()));
17        }
18        if limit > 100 {
19            return Err(AppError::ValidationError("Limit cannot exceed 100".into()));
20        }
21
22        Ok(Self { limit, offset })
23    }
24
25    pub fn limit(&self) -> u32 {
26        self.limit
27    }
28
29    pub fn offset(&self) -> u32 {
30        self.offset
31    }
32}
33
34use validator::Validate;
35use ts_rs::TS;
36
37/// Example of a Validatable DTO using the `validator` crate (like class-validator in TS)
38/// It is also exported to TypeScript automatically!
39#[derive(Debug, Clone, Serialize, Deserialize, Validate, TS)]
40#[ts(export)]
41pub struct CreateUserDto {
42    #[validate(email)]
43    pub email: String,
44    
45    #[validate(length(min = 8, message = "Password must be at least 8 characters"))]
46    pub password: String,
47    
48    #[validate(range(min = 18, max = 130))]
49    pub age: u8,
50}
51
52pub fn setup() {
53    println!("ferrox-types initialized: Provides domain primitives and marker traits.");
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    // TDD: Verify that we cannot construct an invalid Pagination object.
61    #[test]
62    fn test_pagination_validation_fails_on_zero_limit() {
63        let result = Pagination::new(0, 10);
64        assert!(result.is_err());
65        if let Err(AppError::ValidationError(msg)) = result {
66            assert_eq!(msg, "Limit must be strictly greater than 0");
67        } else {
68            panic!("Expected ValidationError");
69        }
70    }
71
72    #[test]
73    fn test_pagination_validation_fails_on_large_limit() {
74        let result = Pagination::new(101, 0);
75        assert!(result.is_err());
76    }
77
78    #[test]
79    fn test_pagination_creation_success() {
80        let pagination = Pagination::new(50, 20).unwrap();
81        assert_eq!(pagination.limit(), 50);
82        assert_eq!(pagination.offset(), 20);
83    }
84
85    #[test]
86    fn test_dto_validation() {
87        let bad_dto = CreateUserDto {
88            email: "invalid_email".into(),
89            password: "short".into(),
90            age: 15, // too young
91        };
92
93        let result = bad_dto.validate();
94        assert!(result.is_err());
95        let errs = result.unwrap_err();
96        
97        // Assert all 3 validations failed
98        let err_map = errs.field_errors();
99        assert!(err_map.contains_key("email"));
100        assert!(err_map.contains_key("password"));
101        assert!(err_map.contains_key("age"));
102    }
103}