151 lines
4.8 KiB
Rust
151 lines
4.8 KiB
Rust
use crate::{ValidationConfig, ValidationMergeError, ValidationSettings};
|
|
use serde::{Deserialize, Serialize};
|
|
use thiserror::Error;
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ValidationRule {
|
|
pub name: String,
|
|
pub description: Option<String>,
|
|
pub settings: ValidationSettings,
|
|
}
|
|
|
|
impl ValidationRule {
|
|
pub fn resolve(&self) -> ValidationConfig {
|
|
self.settings.resolve()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ValidationSet {
|
|
pub name: String,
|
|
pub description: Option<String>,
|
|
pub items: Vec<ValidationSetItem>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub enum ValidationSetItem {
|
|
GlobalRuleRef(String),
|
|
InlineRule {
|
|
name: Option<String>,
|
|
validation: ValidationSettings,
|
|
},
|
|
}
|
|
|
|
impl ValidationSet {
|
|
pub fn resolve_settings_with_rules<'a>(
|
|
&'a self,
|
|
rules: impl Fn(&str) -> Option<&'a ValidationRule>,
|
|
) -> Result<ValidationSettings, ValidationSetResolveError> {
|
|
let settings = self.items.iter().map(|item| match item {
|
|
ValidationSetItem::GlobalRuleRef(name) => {
|
|
rules(name).map(|rule| &rule.settings).ok_or_else(|| {
|
|
ValidationSetResolveError::MissingGlobalRule { name: name.clone() }
|
|
})
|
|
}
|
|
ValidationSetItem::InlineRule { validation, .. } => Ok(validation),
|
|
});
|
|
|
|
let settings = settings.collect::<Result<Vec<_>, _>>()?;
|
|
Ok(ValidationSettings::merge_rules(settings)?)
|
|
}
|
|
|
|
pub fn resolve_with_rules<'a>(
|
|
&'a self,
|
|
rules: impl Fn(&str) -> Option<&'a ValidationRule>,
|
|
) -> Result<ValidationConfig, ValidationSetResolveError> {
|
|
Ok(self.resolve_settings_with_rules(rules)?.resolve())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Error)]
|
|
pub enum ValidationSetResolveError {
|
|
#[error("validation set references missing global rule '{name}'")]
|
|
MissingGlobalRule { name: String },
|
|
#[error(transparent)]
|
|
Merge(#[from] ValidationMergeError),
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AppliedValidation {
|
|
pub set_name: Option<String>,
|
|
pub settings: ValidationSettings,
|
|
}
|
|
|
|
impl AppliedValidation {
|
|
pub fn resolve(&self) -> ValidationConfig {
|
|
self.settings.resolve()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::{
|
|
CharacterFilterSettings, CharacterLimits, PatternSettings, PositionFilterSettings,
|
|
PositionRange,
|
|
};
|
|
|
|
#[test]
|
|
fn validation_set_merges_rule_fragments() {
|
|
let set = ValidationSet {
|
|
name: "phone".to_string(),
|
|
description: None,
|
|
items: vec![
|
|
ValidationSetItem::InlineRule {
|
|
name: Some("phone-length".to_string()),
|
|
validation: ValidationSettings {
|
|
character_limits: Some(CharacterLimits::new_range(10, 15)),
|
|
..ValidationSettings::default()
|
|
},
|
|
},
|
|
ValidationSetItem::InlineRule {
|
|
name: Some("digits-only".to_string()),
|
|
validation: ValidationSettings {
|
|
pattern: Some(PatternSettings {
|
|
filters: vec![PositionFilterSettings {
|
|
positions: PositionRange::From(0),
|
|
filter: CharacterFilterSettings::Numeric,
|
|
}],
|
|
description: None,
|
|
}),
|
|
..ValidationSettings::default()
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
let settings = set
|
|
.resolve_settings_with_rules(|_| None)
|
|
.expect("set should resolve");
|
|
|
|
assert!(settings.character_limits.is_some());
|
|
assert_eq!(settings.pattern.expect("pattern").filters.len(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn validation_set_rejects_duplicate_singleton_rules() {
|
|
let set = ValidationSet {
|
|
name: "conflict".to_string(),
|
|
description: None,
|
|
items: vec![
|
|
ValidationSetItem::InlineRule {
|
|
name: Some("short".to_string()),
|
|
validation: ValidationSettings {
|
|
character_limits: Some(CharacterLimits::new(10)),
|
|
..ValidationSettings::default()
|
|
},
|
|
},
|
|
ValidationSetItem::InlineRule {
|
|
name: Some("long".to_string()),
|
|
validation: ValidationSettings {
|
|
character_limits: Some(CharacterLimits::new(20)),
|
|
..ValidationSettings::default()
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
assert!(set.resolve_settings_with_rules(|_| None).is_err());
|
|
}
|
|
}
|