use crate::base::string_rules::StringMandatoryRules;
use crate::common::locale::{
LocaleData, LocaleMessage, ValidateErrorCollector, ValidateErrorStore,
};
use crate::common::string_validator::{StrValidationExtension, StringValidator};
use crate::common::validation_check::ValidationCheck;
use std::sync::Arc;
use thiserror::Error;
use url::Url as UrlValue;
pub struct UrlRules {
pub is_mandatory: bool,
}
impl Default for UrlRules {
fn default() -> Self {
Self { is_mandatory: true }
}
}
impl Into<StringMandatoryRules> for &UrlRules {
fn into(self) -> StringMandatoryRules {
StringMandatoryRules {
is_mandatory: self.is_mandatory,
}
}
}
impl UrlRules {
fn rule(&self) -> StringMandatoryRules {
self.into()
}
fn check(
&self,
messages: &mut ValidateErrorCollector,
subject: &StringValidator,
is_none: bool,
) {
if !self.is_mandatory && is_none {
return;
}
let rule = self.rule();
rule.check(messages, subject);
}
}
#[derive(Debug, Error, PartialEq, Clone, Default)]
#[error("Url Validation Error")]
pub struct UrlError(pub ValidateErrorStore);
impl ValidationCheck for UrlError {
fn validate_new(messages: ValidateErrorStore) -> Self {
Self(messages)
}
}
impl Into<ValidateErrorStore> for &UrlError {
fn into(self) -> ValidateErrorStore {
self.0.clone()
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct Url(String, Option<UrlValue>, bool);
#[cfg(any(feature = "allow-default-value", test))]
impl Default for Url {
fn default() -> Self {
Self(String::default(), None, true)
}
}
pub struct UrlValueLocale;
impl LocaleMessage for UrlValueLocale {
fn get_locale_data(&self) -> Arc<LocaleData> {
LocaleData::new("validate-invalid-url")
}
}
impl Url {
pub fn parse_custom(s: Option<&str>, rules: UrlRules) -> Result<Self, UrlError> {
let is_none = s.is_none();
let s = s.unwrap_or_default();
let subject = s.as_string_validator();
let mut messages = ValidateErrorCollector::new();
rules.check(&mut messages, &subject, is_none);
UrlError::validate_check(messages)?;
let url = match UrlValue::parse(s) {
Ok(url) => url,
Err(_) => {
let mut messages = ValidateErrorCollector::new();
messages.push(("Invalid URL".to_string(), Box::new(UrlValueLocale)));
return Err(UrlError(messages.into()));
}
};
Ok(Self(s.to_string(), Some(url), is_none))
}
pub fn parse(s: Option<&str>) -> Result<Self, UrlError> {
Self::parse_custom(s, UrlRules::default())
}
pub fn as_url(&self) -> Option<&UrlValue> {
self.1.as_ref()
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn into_option(self) -> Option<Url> {
if self.2 { None } else { Some(self) }
}
}
impl Into<String> for &Url {
fn into(self) -> String {
self.as_str().to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_url() {
let url = Url::parse(Some("https://www.example.com"));
assert!(url.is_ok());
}
#[test]
fn test_invalid_url() {
let url = Url::parse(Some("www.example.com"));
assert!(url.is_err());
}
}