// Structural validation for documents assembled by editors or decoders.
///|
pub(all) enum ValidationIssue {
DuplicateNodeId(String)
MissingParent(NodeID)
MissingChild(NodeID, NodeID)
ParentCycle(NodeID)
OrphanNode(NodeID)
InvalidDimension(NodeID)
RootMismatch(NodeID)
} derive(Eq, Debug)
///|
pub(all) struct DocumentValidationReport {
valid : Bool
issues : Array[ValidationIssue]
} derive(Eq, Debug)
///|
fn visit_for_validation(
doc : ArtboardDocument,
id : NodeID,
states : Map[String, Int],
reachable : Map[String, Bool],
issues : Array[ValidationIssue],
) -> Unit {
match states.get(id.value) {
Some(state) => if state == 1 { issues.push(ParentCycle(id)) }
None =>
match doc.nodes.get(id.value) {
None => ()
Some(node) => {
states[id.value] = 1
reachable[id.value] = true
match node.parent_id {
None => ()
Some(parent_id) =>
match doc.nodes.get(parent_id.value) {
None => issues.push(MissingParent(id))
Some(_) => ()
}
}
match node.node_type {
GroupNode(children) =>
for i = 0; i < children.length(); i = i + 1 {
let child_id = children[i]
match doc.nodes.get(child_id.value) {
None => issues.push(MissingChild(id, child_id))
Some(child) => {
match child.parent_id {
None => issues.push(OrphanNode(child_id))
Some(parent_id) =>
if parent_id != id {
issues.push(OrphanNode(child_id))
}
}
visit_for_validation(
doc, child_id, states, reachable, issues,
)
}
}
}
_ => ()
}
states[id.value] = 2
}
}
}
}
///|
pub fn ArtboardDocument::validate(
self : ArtboardDocument,
) -> DocumentValidationReport {
let issues : Array[ValidationIssue] = []
if self.width <= 0.0 {
issues.push(InvalidDimension(NodeID::new("document.width")))
}
if self.height <= 0.0 {
issues.push(InvalidDimension(NodeID::new("document.height")))
}
let states : Map[String, Int] = Map([])
let reachable : Map[String, Bool] = Map([])
for i = 0; i < self.root_ids.length(); i = i + 1 {
let root_id = self.root_ids[i]
match self.nodes.get(root_id.value) {
None => issues.push(RootMismatch(root_id))
Some(root) => {
match root.parent_id {
None => ()
Some(_) => issues.push(RootMismatch(root_id))
}
visit_for_validation(self, root_id, states, reachable, issues)
}
}
}
for key, node in self.nodes {
if node.id.value != key {
issues.push(DuplicateNodeId(key))
}
match reachable.get(key) {
Some(_) => ()
None => issues.push(OrphanNode(node.id))
}
}
{ valid: issues.is_empty(), issues }
}