#[allow(unused_imports)]
use super::*;
use backbone_core::FieldRule;
struct EventUrlIsAbsolute;
impl FieldRule<Event> for EventUrlIsAbsolute {
fn validate(&self, entity: &Event) -> Vec<ValidationError> {
match entity.event_url.as_deref() {
Some(url) if !is_absolute_url(url) => vec![ValidationError::new(
"event_url",
"must be an absolute address carrying a scheme and a host, \
for example https://example.org/summit",
)
.with_code("pattern")],
_ => vec![],
}
}
}
fn is_absolute_url(url: &str) -> bool {
let Some((scheme, rest)) = url.split_once("://") else {
return false;
};
let mut scheme_chars = scheme.chars();
let scheme_ok = scheme_chars.next().is_some_and(|c| c.is_ascii_alphabetic())
&& scheme_chars.all(|c| c.is_ascii_alphanumeric() || matches!(c, '+' | '.' | '-'));
let host = rest
.split(['/', '?', '#'])
.next()
.unwrap_or_default();
scheme_ok && !host.is_empty() && !host.chars().any(char::is_whitespace)
}
#[cfg(test)]
mod event_url_tests {
use super::is_absolute_url;
#[test]
fn absolute_addresses_pass_and_bare_hosts_do_not() {
for ok in [
"https://example.org/summit",
"http://example.org",
"webcal://calendar.example.org/feed.ics",
] {
assert!(is_absolute_url(ok), "{ok} should be accepted");
}
for bad in [
"example.org/summit",
"://example.org",
"https://",
"https:// example.org",
"9https://example.org",
] {
assert!(!is_absolute_url(bad), "{bad} should be refused");
}
}
}
pub(crate) fn extend(validator: EventValidator) -> EventValidator {
validator
.rule(EventUrlIsAbsolute)
}