pub(crate) mod decisions;
mod nodes;
use crate::bindings::BindingError;
use crate::conflict::{ConflictOwner, FieldPath};
use crate::document::Document;
use crate::identity::{Key, Label};
use crate::ids::{ComponentId, DecisionId, DriverId, ElementId, NodeId, OptionId};
use crate::index::{Index, IndexErrors, IntegrityError, SupersessionCycle};
use crate::names::TagName;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ValidationError {
#[error(transparent)]
Integrity(#[from] IntegrityError),
#[error(transparent)]
Cycle(#[from] SupersessionCycle),
#[error(transparent)]
Binding(#[from] BindingError),
#[error("{node} lists the tag `{tag}` twice")]
RepeatedTag { node: NodeId, tag: TagName },
#[error("decision {decision} is {state} and names the former label {label}")]
FormerLabelInLiveDecision {
decision: DecisionId,
state: &'static str,
label: Label,
},
#[error("the former label {label} is a label of {element}")]
FormerLabelInUse { label: Label, element: ElementId },
#[error("{component} is `private` beneath the external Component {ancestor}")]
PrivateUnderExternal {
component: ComponentId,
ancestor: ComponentId,
},
#[error("decision {0} is resolved with fewer than two options")]
TooFewOptions(DecisionId),
#[error("decision {0} is resolved without a driver")]
NoDriver(DecisionId),
#[error("decision {decision} has no assessment of option {option} against driver {driver}")]
MissingAssessment {
decision: DecisionId,
option: OptionId,
driver: DriverId,
},
#[error("decision {0} has no consequence")]
NoConsequence(DecisionId),
#[error("decision {0} has no `subject` edge of role `outcome`")]
NoOutcomeSubject(DecisionId),
#[error("{owner} holds an unresolved merge conflict over `{field}`")]
Conflict {
owner: ConflictOwner,
field: FieldPath,
},
}
impl ValidationError {
#[must_use]
pub fn keys(&self) -> Vec<Key> {
match self {
Self::Integrity(error) => integrity_keys(*error),
Self::Cycle(cycle) => cycle.0.iter().map(|decision| decision.key()).collect(),
Self::Binding(error) => binding_keys(error),
Self::RepeatedTag { node, .. } => vec![node.key()],
Self::FormerLabelInUse { element, .. } => vec![element.key()],
Self::PrivateUnderExternal {
component,
ancestor,
} => vec![component.key(), ancestor.key()],
Self::FormerLabelInLiveDecision { decision, .. }
| Self::TooFewOptions(decision)
| Self::NoDriver(decision)
| Self::MissingAssessment { decision, .. }
| Self::NoConsequence(decision)
| Self::NoOutcomeSubject(decision) => vec![decision.key()],
Self::Conflict { owner, .. } => owner.key().into_iter().collect(),
}
}
}
fn integrity_keys(error: IntegrityError) -> Vec<Key> {
match error {
IntegrityError::DuplicateKey(key) => vec![key],
IntegrityError::UnknownEdgeEndpoint(element)
| IntegrityError::UnknownBindingElement(element) => vec![element.key()],
IntegrityError::UnknownDecisionReference { decision, .. }
| IntegrityError::UnknownSuccessor { decision, .. }
| IntegrityError::SupersedesItself(decision) => vec![decision.key()],
}
}
fn binding_keys(error: &BindingError) -> Vec<Key> {
match *error {
BindingError::Unbound(element)
| BindingError::AlreadyBound(element)
| BindingError::PrefixMismatch { element, .. }
| BindingError::WithdrawnIsOwnLabel { element, .. } => vec![element.key()],
BindingError::LabelUnderTwoKeys { first, second, .. }
| BindingError::AliasUnderTwoKeys { first, second, .. } => {
vec![first.key(), second.key()]
}
BindingError::AliasIsLiveLabel {
alias_element,
label_element,
..
} => vec![alias_element.key(), label_element.key()],
}
}
#[must_use]
pub fn index_errors(errors: IndexErrors) -> Vec<ValidationError> {
let integrity = errors.integrity.into_iter().map(ValidationError::from);
let cycles = errors.cycles.into_iter().map(ValidationError::from);
integrity.chain(cycles).collect()
}
#[must_use]
pub fn validate(document: &Document) -> Vec<ValidationError> {
let (_, integrity, cycles) = Index::build(document);
let mut errors = index_errors(IndexErrors { integrity, cycles });
errors.extend(
document
.bindings
.validate()
.into_iter()
.map(ValidationError::from),
);
nodes::check(document, &mut errors);
decisions::check(document, &mut errors);
errors.extend(conflict_errors(document));
errors
}
#[must_use]
pub fn conflict_errors(document: &Document) -> Vec<ValidationError> {
document
.conflicts
.iter()
.map(|conflict| ValidationError::Conflict {
owner: conflict.owner,
field: conflict.field.clone(),
})
.collect()
}