avocado_schema/core/field/
string.rs

1use crate::core::constraint::common::typed::Type;
2use crate::core::constraint::string::enumeration::Enumeration;
3use crate::core::constraint::string::max_length::MaxLength;
4use crate::core::constraint::string::min_length::MinLength;
5use crate::core::constraint::string::pattern::Pattern;
6use crate::core::constraint::Constraint;
7use crate::core::field::{Field, FieldType};
8use regex::Regex;
9use serde::{Deserialize, Serialize};
10
11#[derive(Serialize, Deserialize, Debug)]
12#[serde(tag = "type", rename = "string")]
13pub struct StringField {
14    pub name: String,
15    #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
16    pub enumeration: Option<Vec<String>>,
17    #[serde(rename = "maxLength", skip_serializing_if = "Option::is_none")]
18    pub max_length: Option<usize>,
19    #[serde(rename = "minLength", skip_serializing_if = "Option::is_none")]
20    pub min_length: Option<usize>,
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub pattern: Option<Pattern>,
23}
24
25impl Field for StringField {
26    const FIELD_TYPE: FieldType = FieldType::String;
27
28    fn name(&self) -> String {
29        self.name.clone()
30    }
31
32    fn constrains(&self) -> Vec<Box<dyn Constraint>> {
33        let mut constraints: Vec<Box<dyn Constraint>> = vec![Box::new(Type {
34            typed: Self::FIELD_TYPE,
35        })];
36        if let Some(c) = &self.enumeration {
37            constraints.push(Box::new(Enumeration { values: c.clone() }))
38        }
39        if let Some(c) = &self.max_length {
40            constraints.push(Box::new(MaxLength { max_length: *c }))
41        }
42        if let Some(c) = &self.min_length {
43            constraints.push(Box::new(MinLength { min_length: *c }))
44        }
45        if let Some(c) = &self.pattern {
46            constraints.push(Box::new(c.clone()))
47        }
48        constraints
49    }
50}
51
52#[derive(Default)]
53pub struct StringFieldBuilder {
54    name: String,
55    enumeration: Option<Vec<String>>,
56    max_length: Option<usize>,
57    min_length: Option<usize>,
58    pattern: Option<Regex>,
59}
60
61impl StringFieldBuilder {
62    pub fn new() -> Self {
63        StringFieldBuilder::default()
64    }
65
66    pub fn name(mut self, name: &'static str) -> Self {
67        self.name = name.to_string();
68        self
69    }
70
71    pub fn enumeration(mut self, strings: Vec<String>) -> Self {
72        self.enumeration = Some(strings);
73        self
74    }
75
76    pub fn max_length(mut self, length: usize) -> Self {
77        self.max_length = Some(length);
78        self
79    }
80
81    pub fn min_length(mut self, length: usize) -> Self {
82        self.min_length = Some(length);
83        self
84    }
85
86    pub fn pattern(mut self, pattern: Regex) -> Self {
87        self.pattern = Some(pattern);
88        self
89    }
90
91    pub fn build(self) -> StringField {
92        StringField {
93            name: self.name,
94            enumeration: self.enumeration,
95            max_length: self.max_length,
96            min_length: self.min_length,
97            pattern: self.pattern.map(|pattern| Pattern { pattern }),
98        }
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use crate::core::field::string::{StringField, StringFieldBuilder};
105    use crate::visitor::validator::Validator;
106    use regex::Regex;
107
108    #[test]
109    fn test_serialize() {
110        let field = StringFieldBuilder::new()
111            .name("subtype")
112            .enumeration(vec!["meeting".to_string(), "email".to_string()])
113            .max_length(32)
114            .min_length(8)
115            .pattern(Regex::new(r"[a-z]+").unwrap())
116            .build();
117        let field_json = serde_json::to_string(&field).unwrap();
118        assert_eq!(
119            field_json,
120            r#"{"type":"string","name":"subtype","enum":["meeting","email"],"maxLength":32,"minLength":8,"pattern":"[a-z]+"}"#
121        );
122    }
123
124    #[test]
125    fn test_deserialize() {
126        let field_json = r#"
127        {
128            "type":"string",
129            "name": "subtype",
130            "enum": ["meeting", "email"],
131            "maxLength": 32,
132            "minLength": 8,
133            "pattern": "[a-z]+"
134        }"#;
135        let field: StringField = serde_json::from_str(field_json).unwrap();
136        assert_eq!(field.name, "subtype");
137        assert_eq!(field.enumeration.unwrap(), vec!["meeting", "email"]);
138        assert_eq!(field.max_length.unwrap(), 32);
139        assert_eq!(field.min_length.unwrap(), 8);
140        assert_eq!(field.pattern.unwrap().pattern.to_string(), "[a-z]+");
141    }
142
143    #[test]
144    fn test_type() {
145        let field = StringFieldBuilder::new().build();
146        let validator = Validator::new(field);
147
148        assert!(validator.validate(&"meeting").is_ok());
149        assert!(validator.validate(&10).is_err());
150    }
151
152    #[test]
153    fn test_enumeration() {
154        let field = StringFieldBuilder::new()
155            .enumeration(vec!["meeting".to_string(), "kickoff".to_string()])
156            .build();
157        let validator = Validator::new(field);
158
159        assert!(validator.validate(&"meeting").is_ok());
160        assert!(validator.validate(&"email").is_err());
161    }
162
163    #[test]
164    fn test_max_length() {
165        let field = StringFieldBuilder::new().max_length(6).build();
166        let validator = Validator::new(field);
167
168        assert!(validator.validate(&"email").is_ok());
169        assert!(validator.validate(&"emails").is_ok());
170        assert!(validator.validate(&"meeting").is_err());
171    }
172
173    #[test]
174    fn test_min_length() {
175        let field = StringFieldBuilder::new().min_length(6).build();
176        let validator = Validator::new(field);
177
178        assert!(validator.validate(&"meeting").is_ok());
179        assert!(validator.validate(&"emails").is_ok());
180        assert!(validator.validate(&"email").is_err());
181    }
182
183    #[test]
184    fn test_pattern() {
185        let field = StringFieldBuilder::new()
186            .pattern(Regex::new(r"[a-z]+").unwrap())
187            .build();
188        let validator = Validator::new(field);
189
190        assert!(validator.validate(&"email").is_ok());
191        assert!(validator.validate(&"1234").is_err());
192    }
193}