icydb_model/base/normalizer/
web.rs1use crate::{prelude::*, visitor::Normalizer};
8
9#[normalizer]
15pub struct MimeType;
16
17impl Normalizer<String> for MimeType {
18 fn normalize(&self, value: &mut String) -> Result<(), String> {
19 let trimmed = value.trim();
20
21 if trimmed.len() != value.len() {
22 *value = trimmed.to_owned();
23 }
24
25 value.make_ascii_lowercase();
26
27 Ok(())
28 }
29}
30
31#[normalizer]
37pub struct Url;
38
39impl Normalizer<String> for Url {
40 fn normalize(&self, value: &mut String) -> Result<(), String> {
41 let trimmed = value.trim();
42
43 let normalized = if trimmed.starts_with("http://") || trimmed.starts_with("https://") {
44 trimmed.to_owned()
45 } else if trimmed.contains("://") || has_explicit_non_numeric_colon(trimmed) {
46 return Err("URL scheme must be http or https".to_string());
47 } else {
48 format!("https://{trimmed}")
49 };
50
51 *value = normalized;
52
53 Ok(())
54 }
55}
56
57fn has_explicit_non_numeric_colon(value: &str) -> bool {
58 let boundary = value.find(['/', '?', '#']).unwrap_or(value.len());
59 let head = &value[..boundary];
60 let Some((_host, port_or_scheme)) = head.rsplit_once(':') else {
61 return false;
62 };
63
64 port_or_scheme.is_empty() || !port_or_scheme.chars().all(|ch| ch.is_ascii_digit())
65}
66
67#[cfg(test)]
72mod tests;