ic_dbms_api/validate/
web.rs

1use ic_dbms_api::prelude::{IcDbmsError, Value};
2
3use crate::prelude::Validate;
4
5/// A validator that checks if a string is a valid MIME type.
6///
7/// # Example
8///
9/// ```rust
10/// use ic_dbms_api::prelude::{MimeTypeValidator, Validate, Value};
11///
12/// let validator = MimeTypeValidator;
13/// let valid_mime = Value::Text(ic_dbms_api::prelude::Text("text/plain".into()));
14/// assert!(validator.validate(&valid_mime).is_ok());
15/// let invalid_mime = Value::Text(ic_dbms_api::prelude::Text("invalid-mime".into()));
16/// assert!(validator.validate(&invalid_mime).is_err());
17/// ```
18pub struct MimeTypeValidator;
19
20impl Validate for MimeTypeValidator {
21    fn validate(&self, value: &crate::prelude::Value) -> ic_dbms_api::prelude::IcDbmsResult<()> {
22        let Value::Text(text) = value else {
23            return Err(IcDbmsError::Validation("Value is not a Text".to_string()));
24        };
25
26        let s = &text.0;
27
28        // must have exactly '/' character
29        let parts: Vec<&str> = s.split('/').collect();
30        if parts.len() != 2 {
31            return Err(IcDbmsError::Validation(format!(
32                "MIME type '{s}' must contain exactly one '/'"
33            )));
34        }
35
36        let is_valid_part = |part: &str| {
37            !part.is_empty()
38                && part
39                    .chars()
40                    .all(|c| c.is_ascii_alphanumeric() || "+.-".contains(c))
41        };
42
43        if !is_valid_part(parts[0]) {
44            return Err(IcDbmsError::Validation(format!(
45                "MIME type '{s}' has invalid type part"
46            )));
47        }
48        if !is_valid_part(parts[1]) {
49            return Err(IcDbmsError::Validation(format!(
50                "MIME type '{s}' has invalid subtype part"
51            )));
52        }
53
54        Ok(())
55    }
56}
57
58/// A validator that checks if a string is a valid URL.
59///
60/// # Example
61///
62/// ```rust
63/// use ic_dbms_api::prelude::{UrlValidator, Validate, Value};
64/// let validator = UrlValidator;
65/// let valid_url = Value::Text(ic_dbms_api::prelude::Text("http://example.com".into()));
66/// assert!(validator.validate(&valid_url).is_ok());
67/// ```
68pub struct UrlValidator;
69
70impl Validate for UrlValidator {
71    fn validate(&self, value: &crate::prelude::Value) -> ic_dbms_api::prelude::IcDbmsResult<()> {
72        let Value::Text(text) = value else {
73            return Err(IcDbmsError::Validation("Value is not a Text".to_string()));
74        };
75
76        let s = &text.0;
77
78        if url::Url::parse(s).is_err() {
79            return Err(IcDbmsError::Validation(format!(
80                "Value '{s}' is not a valid URL"
81            )));
82        }
83
84        Ok(())
85    }
86}
87
88#[cfg(test)]
89mod tests {
90
91    use super::*;
92
93    #[test]
94    fn test_should_not_validate_mime_if_not_text() {
95        let value = Value::Uint32(crate::prelude::Uint32(42));
96        let result = MimeTypeValidator.validate(&value);
97        assert!(result.is_err());
98    }
99
100    #[test]
101    fn test_mime_type_validator() {
102        let valid_mime_types = vec![
103            "text/plain",
104            "image/jpeg",
105            "application/json",
106            "application/vnd.api+json",
107            "audio/mpeg",
108        ];
109        for mime in valid_mime_types {
110            let value = Value::Text(crate::prelude::Text(mime.to_string()));
111            assert!(
112                MimeTypeValidator.validate(&value).is_ok(),
113                "MIME type '{mime}' should be valid"
114            );
115        }
116    }
117
118    #[test]
119    fn test_invalid_mime_type_validator() {
120        let invalid_mime_types = vec![
121            "textplain",
122            "image//jpeg",
123            "/json",
124            "application/vnd.api+json/extra",
125            "audio/mpeg/",
126            "audio/mpe g",
127        ];
128        for mime in invalid_mime_types {
129            let value = Value::Text(crate::prelude::Text(mime.to_string()));
130            assert!(
131                MimeTypeValidator.validate(&value).is_err(),
132                "MIME type '{mime}' should be invalid"
133            );
134        }
135    }
136
137    #[test]
138    fn test_url_validator() {
139        let valid_urls = vec![
140            "http://example.com",
141            "https://example.com/path?query=param#fragment",
142            "ftp://ftp.example.com/resource",
143            "mailto:christian@example.com",
144        ];
145        for url in valid_urls {
146            let value = Value::Text(crate::prelude::Text(url.to_string()));
147            assert!(
148                UrlValidator.validate(&value).is_ok(),
149                "URL '{url}' should be valid"
150            );
151        }
152    }
153
154    #[test]
155    fn test_invalid_url_validator() {
156        let invalid_urls = vec![
157            //"htp:/example.com",
158            "://missing.scheme.com",
159            "http//missing.colon.com",
160            "justastring",
161            "http://in valid.com",
162        ];
163        for url in invalid_urls {
164            let value = Value::Text(crate::prelude::Text(url.to_string()));
165            assert!(
166                UrlValidator.validate(&value).is_err(),
167                "URL '{url}' should be invalid"
168            );
169        }
170    }
171
172    #[test]
173    fn test_should_not_validate_url_if_not_text() {
174        let value = Value::Uint32(crate::prelude::Uint32(42));
175        let result = UrlValidator.validate(&value);
176        assert!(result.is_err());
177    }
178}