rule page in the validation client
This commit is contained in:
@@ -136,15 +136,15 @@ profileName: string
|
||||
set:
|
||||
name: string
|
||||
description: optional string
|
||||
ruleNames: repeated string
|
||||
ruleItems: repeated ValidationSetRuleItem
|
||||
```
|
||||
|
||||
Frontend rules:
|
||||
|
||||
- `set.name` is required and unique inside a profile.
|
||||
- `ruleNames` must contain at least one rule.
|
||||
- `ruleNames` are ordered.
|
||||
- Every rule name must already exist.
|
||||
- `ruleItems` must contain at least one item.
|
||||
- `ruleItems` are ordered.
|
||||
- Every global rule reference must already exist.
|
||||
- Duplicate rule names in the same set are rejected.
|
||||
- Conflicting singleton fragments are rejected.
|
||||
|
||||
@@ -362,7 +362,7 @@ Recommended UI:
|
||||
```text
|
||||
name
|
||||
description
|
||||
ordered rule picker
|
||||
ordered global/inline rule item picker
|
||||
resolved preview
|
||||
```
|
||||
|
||||
@@ -430,11 +430,11 @@ validation:
|
||||
Create set `phone`:
|
||||
|
||||
```text
|
||||
ruleNames:
|
||||
- required
|
||||
- phone-length
|
||||
- digits-only
|
||||
- phone-mask
|
||||
ruleItems:
|
||||
- globalRuleName: required
|
||||
- globalRuleName: phone-length
|
||||
- globalRuleName: digits-only
|
||||
- globalRuleName: phone-mask
|
||||
```
|
||||
|
||||
Apply set:
|
||||
|
||||
@@ -11,4 +11,6 @@ pub use rules::{
|
||||
count_text, CharacterFilter, CharacterLimits, CountMode, DisplayMask, LimitCheckResult,
|
||||
MaskDisplayMode, PatternFilters, PositionFilter, PositionRange,
|
||||
};
|
||||
pub use set::{AppliedValidation, ValidationRule, ValidationSet};
|
||||
pub use set::{
|
||||
AppliedValidation, ValidationRule, ValidationSet, ValidationSetItem, ValidationSetResolveError,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::{ValidationConfig, ValidationMergeError, ValidationSettings};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ValidationRule {
|
||||
@@ -18,19 +19,52 @@ impl ValidationRule {
|
||||
pub struct ValidationSet {
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub rules: Vec<ValidationRule>,
|
||||
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(&self) -> Result<ValidationSettings, ValidationMergeError> {
|
||||
ValidationSettings::merge_rules(self.rules.iter().map(|rule| &rule.settings))
|
||||
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(&self) -> Result<ValidationConfig, ValidationMergeError> {
|
||||
Ok(self.resolve_settings()?.resolve())
|
||||
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>,
|
||||
@@ -56,19 +90,17 @@ mod tests {
|
||||
let set = ValidationSet {
|
||||
name: "phone".to_string(),
|
||||
description: None,
|
||||
rules: vec![
|
||||
ValidationRule {
|
||||
name: "phone-length".to_string(),
|
||||
description: None,
|
||||
settings: ValidationSettings {
|
||||
items: vec![
|
||||
ValidationSetItem::InlineRule {
|
||||
name: Some("phone-length".to_string()),
|
||||
validation: ValidationSettings {
|
||||
character_limits: Some(CharacterLimits::new_range(10, 15)),
|
||||
..ValidationSettings::default()
|
||||
},
|
||||
},
|
||||
ValidationRule {
|
||||
name: "digits-only".to_string(),
|
||||
description: None,
|
||||
settings: ValidationSettings {
|
||||
ValidationSetItem::InlineRule {
|
||||
name: Some("digits-only".to_string()),
|
||||
validation: ValidationSettings {
|
||||
pattern: Some(PatternSettings {
|
||||
filters: vec![PositionFilterSettings {
|
||||
positions: PositionRange::From(0),
|
||||
@@ -82,7 +114,9 @@ mod tests {
|
||||
],
|
||||
};
|
||||
|
||||
let settings = set.resolve_settings().expect("set should resolve");
|
||||
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);
|
||||
@@ -93,19 +127,17 @@ mod tests {
|
||||
let set = ValidationSet {
|
||||
name: "conflict".to_string(),
|
||||
description: None,
|
||||
rules: vec![
|
||||
ValidationRule {
|
||||
name: "short".to_string(),
|
||||
description: None,
|
||||
settings: ValidationSettings {
|
||||
items: vec![
|
||||
ValidationSetItem::InlineRule {
|
||||
name: Some("short".to_string()),
|
||||
validation: ValidationSettings {
|
||||
character_limits: Some(CharacterLimits::new(10)),
|
||||
..ValidationSettings::default()
|
||||
},
|
||||
},
|
||||
ValidationRule {
|
||||
name: "long".to_string(),
|
||||
description: None,
|
||||
settings: ValidationSettings {
|
||||
ValidationSetItem::InlineRule {
|
||||
name: Some("long".to_string()),
|
||||
validation: ValidationSettings {
|
||||
character_limits: Some(CharacterLimits::new(20)),
|
||||
..ValidationSettings::default()
|
||||
},
|
||||
@@ -113,6 +145,6 @@ mod tests {
|
||||
],
|
||||
};
|
||||
|
||||
assert!(set.resolve_settings().is_err());
|
||||
assert!(set.resolve_settings_with_rules(|_| None).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user