#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ValidationIssue {
pub path: String,
pub message: String,
}
impl ValidationIssue {
pub(crate) fn new(path: &str, message: impl Into<String>) -> Self {
Self {
path: path.to_string(),
message: message.into(),
}
}
}
pub trait Validate {
fn validate(&self) -> Vec<ValidationIssue>;
fn is_valid(&self) -> bool {
self.validate().is_empty()
}
}
impl<T: Validate> Validate for Option<T> {
fn validate(&self) -> Vec<ValidationIssue> {
self.as_ref().map(Validate::validate).unwrap_or_default()
}
}
impl<T: Validate> Validate for Vec<T> {
fn validate(&self) -> Vec<ValidationIssue> {
self.iter().flat_map(Validate::validate).collect()
}
}
impl<T: Validate> Validate for ::vec1::Vec1<T> {
fn validate(&self) -> Vec<ValidationIssue> {
self.iter().flat_map(Validate::validate).collect()
}
}
impl<T: Validate> Validate for Box<T> {
fn validate(&self) -> Vec<ValidationIssue> {
(**self).validate()
}
}
impl<T: ?Sized> Validate for ::std::marker::PhantomData<T> {
fn validate(&self) -> Vec<ValidationIssue> {
Vec::new()
}
}
impl Validate for ::serde_json::Value {
fn validate(&self) -> Vec<ValidationIssue> {
Vec::new()
}
}
impl Validate for ::std::string::String {
fn validate(&self) -> Vec<ValidationIssue> {
Vec::new()
}
}
pub(crate) fn is_valid_code(s: &str) -> bool {
!s.is_empty()
&& s == s.trim()
&& !s.split(' ').any(str::is_empty)
&& !s.chars().any(|c| c != ' ' && c.is_whitespace())
}
pub(crate) fn is_valid_id(s: &str) -> bool {
(1..=64).contains(&s.len())
&& s.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '.')
}
pub(crate) fn is_valid_uri_like(s: &str) -> bool {
!s.is_empty() && s.trim() == s
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn code_rules() {
assert!(is_valid_code("final"));
assert!(is_valid_code("entered in error"));
assert!(!is_valid_code(" leading"));
assert!(!is_valid_code("double space"));
assert!(!is_valid_code(""));
}
#[test]
fn id_rules() {
assert!(is_valid_id("abc-123.4"));
assert!(!is_valid_id("bad id!"));
assert!(!is_valid_id(&"x".repeat(65)));
}
#[test]
fn uri_rules() {
assert!(is_valid_uri_like("http://example.org"));
assert!(!is_valid_uri_like(" http://example.org "));
assert!(!is_valid_uri_like(""));
}
#[test]
fn containers_delegate() {
let some: Option<String> = Some("x".to_string());
assert!(some.validate().is_empty());
assert!(vec!["a".to_string(), "b".to_string()].validate().is_empty());
assert!(Box::new("a".to_string()).validate().is_empty());
assert!(::serde_json::Value::Null.validate().is_empty());
}
}