avocado_schema/core/field/
string.rs1use 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}