wasm_dbms_api/dbms/validate/
web.rs1use crate::prelude::{DbmsError, Validate, Value};
2
3pub struct MimeTypeValidator;
21
22impl Validate for MimeTypeValidator {
23 fn validate(&self, value: &crate::prelude::Value) -> crate::prelude::DbmsResult<()> {
24 let Value::Text(text) = value else {
25 return Err(DbmsError::Validation("Value is not a Text".to_string()));
26 };
27
28 let s = &text.0;
29
30 let parts: Vec<&str> = s.split('/').collect();
32 if parts.len() != 2 {
33 return Err(DbmsError::Validation(format!(
34 "MIME type '{s}' must contain exactly one '/'"
35 )));
36 }
37
38 if !is_restricted_name(parts[0]) {
39 return Err(DbmsError::Validation(format!(
40 "MIME type '{s}' has invalid type part"
41 )));
42 }
43 if !is_restricted_name(parts[1]) {
44 return Err(DbmsError::Validation(format!(
45 "MIME type '{s}' has invalid subtype part"
46 )));
47 }
48
49 Ok(())
50 }
51}
52
53const MIME_RESTRICTED_NAME_MAX_LEN: usize = 127;
55
56fn is_restricted_name(name: &str) -> bool {
61 let mut chars = name.chars();
62 name.len() <= MIME_RESTRICTED_NAME_MAX_LEN
63 && chars.next().is_some_and(|c| c.is_ascii_alphanumeric())
64 && chars.all(|c| c.is_ascii_alphanumeric() || "!#$&-^_.+".contains(c))
65}
66
67pub struct UrlValidator;
78
79impl Validate for UrlValidator {
80 fn validate(&self, value: &crate::prelude::Value) -> crate::prelude::DbmsResult<()> {
81 let Value::Text(text) = value else {
82 return Err(DbmsError::Validation("Value is not a Text".to_string()));
83 };
84
85 let s = &text.0;
86
87 if url::Url::parse(s).is_err() {
88 return Err(DbmsError::Validation(format!(
89 "Value '{s}' is not a valid URL"
90 )));
91 }
92
93 Ok(())
94 }
95}
96
97#[cfg(test)]
98mod tests {
99
100 use super::*;
101 use crate::prelude::Text;
102
103 #[test]
104 fn test_should_not_validate_mime_if_not_text() {
105 let value = Value::Uint32(crate::prelude::Uint32(42));
106 let result = MimeTypeValidator.validate(&value);
107 assert!(result.is_err());
108 }
109
110 #[test]
111 fn test_mime_type_validator() {
112 let valid_mime_types = vec![
113 "text/plain",
114 "image/jpeg",
115 "application/json",
116 "application/vnd.api+json",
117 "audio/mpeg",
118 ];
119 for mime in valid_mime_types {
120 let value = Value::Text(Text(mime.to_string()));
121 assert!(
122 MimeTypeValidator.validate(&value).is_ok(),
123 "MIME type '{mime}' should be valid"
124 );
125 }
126 }
127
128 #[test]
129 fn test_invalid_mime_type_validator() {
130 let invalid_mime_types = vec![
131 "textplain",
132 "image//jpeg",
133 "/json",
134 "application/vnd.api+json/extra",
135 "audio/mpeg/",
136 "audio/mpe g",
137 ];
138 for mime in invalid_mime_types {
139 let value = Value::Text(Text(mime.to_string()));
140 assert!(
141 MimeTypeValidator.validate(&value).is_err(),
142 "MIME type '{mime}' should be invalid"
143 );
144 }
145 }
146
147 #[test]
148 fn test_mime_type_validator_accepts_rfc6838_restricted_name_chars() {
149 let max_name = "a".repeat(127);
150 let max_len_mime = format!("{max_name}/{max_name}");
151 let valid_mime_types = [
152 "application/vnd.example_test",
153 "application/e!example",
154 "application/x#y$z&w^v",
155 "1type/2sub",
156 "application/vnd.example+json",
157 "Text/Plain",
158 max_len_mime.as_str(),
159 ];
160 for mime in valid_mime_types {
161 let value = Value::Text(Text(mime.to_string()));
162 assert!(
163 MimeTypeValidator.validate(&value).is_ok(),
164 "MIME type '{mime}' should be valid"
165 );
166 }
167 }
168
169 #[test]
170 fn test_mime_type_validator_rejects_invalid_restricted_names() {
171 let long_name = "a".repeat(128);
172 let long_type = format!("{long_name}/plain");
173 let long_subtype = format!("text/{long_name}");
174 let invalid_mime_types = [
175 "-/plain",
176 "+/plain",
177 "./plain",
178 "_/plain",
179 "text/-plain",
180 "text/.plain",
181 "text/+json",
182 "text/!plain",
183 "text/",
184 "text /plain",
185 "text/pl ain",
186 "text/plain; charset=utf-8",
187 "text/pl=ain",
188 "text/pl*ain",
189 "text/plĂ in",
190 long_type.as_str(),
191 long_subtype.as_str(),
192 ];
193 for mime in invalid_mime_types {
194 let value = Value::Text(Text(mime.to_string()));
195 assert!(
196 MimeTypeValidator.validate(&value).is_err(),
197 "MIME type '{mime}' should be invalid"
198 );
199 }
200 }
201
202 #[test]
203 fn test_url_validator() {
204 let valid_urls = vec![
205 "http://example.com",
206 "https://example.com/path?query=param#fragment",
207 "ftp://ftp.example.com/resource",
208 "mailto:christian@example.com",
209 ];
210 for url in valid_urls {
211 let value = Value::Text(Text(url.to_string()));
212 assert!(
213 UrlValidator.validate(&value).is_ok(),
214 "URL '{url}' should be valid"
215 );
216 }
217 }
218
219 #[test]
220 fn test_invalid_url_validator() {
221 let invalid_urls = vec![
222 "://missing.scheme.com",
224 "http//missing.colon.com",
225 "justastring",
226 "http://in valid.com",
227 ];
228 for url in invalid_urls {
229 let value = Value::Text(Text(url.to_string()));
230 assert!(
231 UrlValidator.validate(&value).is_err(),
232 "URL '{url}' should be invalid"
233 );
234 }
235 }
236
237 #[test]
238 fn test_should_not_validate_url_if_not_text() {
239 let value = Value::Uint32(crate::prelude::Uint32(42));
240 let result = UrlValidator.validate(&value);
241 assert!(result.is_err());
242 }
243}