Skip to main content

unistructgen_openapi_parser/
validation.rs

1//! Validation constraint extraction from OpenAPI schemas
2
3use openapiv3::{Schema, SchemaKind, Type};
4use unistructgen_core::FieldConstraints;
5
6/// Extract validation constraints from an OpenAPI schema
7pub fn extract_validation_constraints(schema: &Schema) -> FieldConstraints {
8    let mut constraints = FieldConstraints::default();
9
10    match &schema.schema_kind {
11        SchemaKind::Type(Type::String(string_type)) => {
12            // String length constraints
13            if let Some(min_len) = string_type.min_length {
14                constraints.min_length = Some(min_len);
15            }
16            if let Some(max_len) = string_type.max_length {
17                constraints.max_length = Some(max_len);
18            }
19
20            // Pattern constraint
21            if let Some(pattern) = &string_type.pattern {
22                constraints.pattern = Some(pattern.clone());
23            }
24
25            // Format constraint - now not optional
26            constraints.format = Some(format!("{:?}", string_type.format));
27        }
28
29        SchemaKind::Type(Type::Number(number_type)) => {
30            // Numeric range constraints
31            if let Some(min) = number_type.minimum {
32                constraints.min_value = Some(min);
33            }
34            if let Some(max) = number_type.maximum {
35                constraints.max_value = Some(max);
36            }
37        }
38
39        SchemaKind::Type(Type::Integer(int_type)) => {
40            // Integer range constraints
41            if let Some(min) = int_type.minimum {
42                constraints.min_value = Some(min as f64);
43            }
44            if let Some(max) = int_type.maximum {
45                constraints.max_value = Some(max as f64);
46            }
47        }
48
49        SchemaKind::Type(Type::Array(array_type)) => {
50            // Array length constraints
51            if let Some(min_items) = array_type.min_items {
52                constraints.min_length = Some(min_items);
53            }
54            if let Some(max_items) = array_type.max_items {
55                constraints.max_length = Some(max_items);
56            }
57        }
58
59        _ => {}
60    }
61
62    constraints
63}
64
65/// Generate validator attribute string from constraints
66pub fn generate_validator_attribute(constraints: &FieldConstraints) -> Option<String> {
67    let mut attrs = Vec::new();
68
69    // Length validation
70    if constraints.min_length.is_some() || constraints.max_length.is_some() {
71        let mut length_parts = Vec::new();
72        if let Some(min) = constraints.min_length {
73            length_parts.push(format!("min = {}", min));
74        }
75        if let Some(max) = constraints.max_length {
76            length_parts.push(format!("max = {}", max));
77        }
78        attrs.push(format!("length({})", length_parts.join(", ")));
79    }
80
81    // Range validation
82    if constraints.min_value.is_some() || constraints.max_value.is_some() {
83        let mut range_parts = Vec::new();
84        if let Some(min) = constraints.min_value {
85            range_parts.push(format!("min = {}", min));
86        }
87        if let Some(max) = constraints.max_value {
88            range_parts.push(format!("max = {}", max));
89        }
90        attrs.push(format!("range({})", range_parts.join(", ")));
91    }
92
93    // Format validation
94    if let Some(format) = &constraints.format {
95        match format.as_str() {
96            "email" => attrs.push("email".to_string()),
97            "url" | "uri" => attrs.push("url".to_string()),
98            _ => {}
99        }
100    }
101
102    // Pattern validation
103    if let Some(pattern) = &constraints.pattern {
104        attrs.push(format!("regex = \"{}\"", pattern));
105    }
106
107    if attrs.is_empty() {
108        None
109    } else {
110        Some(format!("#[validate({})]", attrs.join(", ")))
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn test_generate_validator_attribute() {
120        let mut constraints = FieldConstraints::default();
121        constraints.min_length = Some(5);
122        constraints.max_length = Some(50);
123
124        let attr = generate_validator_attribute(&constraints);
125        assert_eq!(attr, Some("#[validate(length(min = 5, max = 50))]".to_string()));
126    }
127
128    #[test]
129    fn test_generate_validator_attribute_email() {
130        let mut constraints = FieldConstraints::default();
131        constraints.format = Some("email".to_string());
132
133        let attr = generate_validator_attribute(&constraints);
134        assert_eq!(attr, Some("#[validate(email)]".to_string()));
135    }
136
137    #[test]
138    fn test_generate_validator_attribute_range() {
139        let mut constraints = FieldConstraints::default();
140        constraints.min_value = Some(0.0);
141        constraints.max_value = Some(150.0);
142
143        let attr = generate_validator_attribute(&constraints);
144        assert_eq!(attr, Some("#[validate(range(min = 0, max = 150))]".to_string()));
145    }
146}