yaml_schema/schemas/
string.rs1use std::collections::HashMap;
2
3use log::debug;
4use regex::Regex;
5use saphyr::AnnotatedMapping;
6use saphyr::MarkedYaml;
7use saphyr::Scalar;
8use saphyr::YamlData;
9
10use crate::loader;
11use crate::schemas::StringFormat;
12use crate::utils::format_hash_map;
13use crate::utils::format_marker;
14
15#[derive(Default)]
17pub struct StringSchema {
18 pub min_length: Option<usize>,
19 pub max_length: Option<usize>,
20 pub pattern: Option<Regex>,
21 pub format: Option<StringFormat>,
22}
23
24impl std::fmt::Debug for StringSchema {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 let mut h = HashMap::new();
27 if let Some(min_length) = self.min_length {
28 h.insert("minLength".to_string(), min_length.to_string());
29 }
30 if let Some(max_length) = self.max_length {
31 h.insert("maxLength".to_string(), max_length.to_string());
32 }
33 if let Some(pattern) = &self.pattern {
34 h.insert("pattern".to_string(), pattern.as_str().to_string());
35 }
36 if let Some(format) = &self.format {
37 h.insert("format".to_string(), format.to_string());
38 }
39 write!(f, "StringSchema {}", format_hash_map(&h))
40 }
41}
42
43impl StringSchema {
44 pub fn builder() -> StringSchemaBuilder {
45 StringSchemaBuilder::new()
46 }
47}
48
49impl PartialEq for StringSchema {
50 fn eq(&self, other: &Self) -> bool {
51 self.min_length == other.min_length
52 && self.max_length == other.max_length
53 && are_patterns_equivalent(&self.pattern, &other.pattern)
54 && self.format == other.format
55 }
56}
57
58impl TryFrom<&MarkedYaml<'_>> for StringSchema {
59 type Error = crate::Error;
60
61 fn try_from(value: &MarkedYaml) -> Result<StringSchema, Self::Error> {
62 if let YamlData::Mapping(mapping) = &value.data {
63 Ok(StringSchema::try_from(mapping)?)
64 } else {
65 Err(expected_mapping!(value))
66 }
67 }
68}
69
70impl TryFrom<&AnnotatedMapping<'_, MarkedYaml<'_>>> for StringSchema {
71 type Error = crate::Error;
72
73 fn try_from(mapping: &AnnotatedMapping<'_, MarkedYaml<'_>>) -> crate::Result<Self> {
74 let mut string_schema = StringSchema::default();
75 for (key, value) in mapping.iter() {
76 if let YamlData::Value(Scalar::String(key)) = &key.data {
77 match key.as_ref() {
78 "minLength" => {
79 if let Ok(i) = loader::load_integer_marked(value) {
80 string_schema.min_length = Some(i as usize);
81 } else {
82 return Err(unsupported_type!(
83 "minLength expected integer, but got: {:?}",
84 value
85 ));
86 }
87 }
88 "maxLength" => {
89 if let Ok(i) = loader::load_integer_marked(value) {
90 string_schema.max_length = Some(i as usize);
91 } else {
92 return Err(unsupported_type!(
93 "maxLength expected integer, but got: {:?}",
94 value
95 ));
96 }
97 }
98 "pattern" => {
99 if let YamlData::Value(Scalar::String(s)) = &value.data {
100 let regex = regex::Regex::new(s.as_ref())?;
101 string_schema.pattern = Some(regex);
102 } else {
103 return Err(unsupported_type!(
104 "pattern expected string, but got: {:?}",
105 value
106 ));
107 }
108 }
109 "format" => {
110 if let YamlData::Value(Scalar::String(s)) = &value.data {
111 string_schema.format = Some(
112 s.as_ref()
113 .parse::<StringFormat>()
114 .unwrap_or_else(|e| match e {}),
115 );
116 } else {
117 return Err(unsupported_type!(
118 "format expected string, but got: {:?}",
119 value
120 ));
121 }
122 }
123 "type" => {
125 if let YamlData::Value(Scalar::String(s)) = &value.data {
126 if s != "string" {
127 return Err(unsupported_type!(
128 "Expected type: string, but got: {}",
129 s
130 ));
131 }
132 } else if let YamlData::Sequence(values) = &value.data {
133 if !values
134 .iter()
135 .any(|v| v.data == MarkedYaml::value_from_str("string").data)
136 {
137 return Err(unsupported_type!(
138 "Expected type: string, but got: {:?}",
139 value
140 ));
141 }
142 } else {
143 return Err(expected_type_is_string!(value));
144 }
145 }
146 _ => {
147 debug!("[StringSchema] Unsupported key for `type: string`: {key}");
148 }
149 }
150 } else {
151 return Err(expected_scalar!(
152 "{} Expected a scalar key, got: {:?}",
153 format_marker(&key.span.start),
154 key
155 ));
156 }
157 }
158 Ok(string_schema)
159 }
160}
161fn are_patterns_equivalent(a: &Option<Regex>, b: &Option<Regex>) -> bool {
165 match (a, b) {
166 (Some(a), Some(b)) => a.as_str() == b.as_str(),
167 (None, None) => true,
168 _ => false,
169 }
170}
171
172impl std::fmt::Display for StringSchema {
173 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174 write!(
175 f,
176 "StringSchema {{ min_length: {:?}, max_length: {:?}, pattern: {:?}, format: {:?} }}",
177 self.min_length, self.max_length, self.pattern, self.format
178 )
179 }
180}
181
182pub struct StringSchemaBuilder(StringSchema);
183
184impl Default for StringSchemaBuilder {
185 fn default() -> Self {
186 Self::new()
187 }
188}
189
190impl StringSchemaBuilder {
191 pub fn new() -> Self {
192 Self(StringSchema::default())
193 }
194
195 pub fn build(&mut self) -> StringSchema {
196 std::mem::take(&mut self.0)
197 }
198
199 pub fn min_length(&mut self, min_length: usize) -> &mut Self {
200 self.0.min_length = Some(min_length);
201 self
202 }
203
204 pub fn max_length(&mut self, max_length: usize) -> &mut Self {
205 self.0.max_length = Some(max_length);
206 self
207 }
208
209 pub fn pattern(&mut self, pattern: Regex) -> &mut Self {
210 self.0.pattern = Some(pattern);
211 self
212 }
213
214 pub fn format(&mut self, format: StringFormat) -> &mut Self {
215 self.0.format = Some(format);
216 self
217 }
218}