use std::fmt;
use panproto_inst::{TreeEdit, WInstance};
use crate::Lens;
use crate::edit_error::EditLensError;
use crate::edit_lens::EditLens;
#[derive(Debug)]
#[non_exhaustive]
pub enum EditLawViolation {
Consistency {
detail: String,
},
ComplementCoherence {
detail: String,
},
Error(EditLensError),
}
impl fmt::Display for EditLawViolation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Consistency { detail } => write!(f, "Consistency law violated: {detail}"),
Self::ComplementCoherence { detail } => {
write!(f, "Complement coherence violated: {detail}")
}
Self::Error(e) => write!(f, "error during law check: {e}"),
}
}
}
impl std::error::Error for EditLawViolation {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Error(e) => Some(e),
_ => None,
}
}
}
pub fn check_edit_consistency(
lens: &mut EditLens,
edit: &TreeEdit,
source: &WInstance,
) -> Result<(), EditLawViolation> {
let mut lens_clone = clone_edit_lens(lens);
let view_edit = lens_clone
.get_edit(edit.clone())
.map_err(EditLawViolation::Error)?;
let state_lens = Lens {
compiled: lens.compiled.clone(),
src_schema: lens.src_schema.clone(),
tgt_schema: lens.tgt_schema.clone(),
};
let (mut view, _) = crate::get(&state_lens, source)
.map_err(|e| EditLawViolation::Error(EditLensError::TranslationFailed(e.to_string())))?;
view_edit
.apply(&mut view)
.map_err(|e| EditLawViolation::Error(EditLensError::EditApply(e)))?;
let mut edited_source = source.clone();
edit.apply(&mut edited_source)
.map_err(|e| EditLawViolation::Error(EditLensError::EditApply(e)))?;
let (view2, _) = crate::get(&state_lens, &edited_source)
.map_err(|e| EditLawViolation::Error(EditLensError::TranslationFailed(e.to_string())))?;
if view.node_count() != view2.node_count() {
return Err(EditLawViolation::Consistency {
detail: format!(
"node count mismatch: translate-then-apply={}, apply-then-get={}",
view.node_count(),
view2.node_count()
),
});
}
for (id, node) in &view.nodes {
if let Some(node2) = view2.nodes.get(id) {
if node.anchor != node2.anchor {
return Err(EditLawViolation::Consistency {
detail: format!(
"anchor mismatch at node {id}: {a1} vs {a2}",
a1 = node.anchor,
a2 = node2.anchor,
),
});
}
} else {
return Err(EditLawViolation::Consistency {
detail: format!("node {id} present in path 1 but not path 2"),
});
}
}
Ok(())
}
pub fn check_complement_coherence(
lens: &mut EditLens,
edit: &TreeEdit,
source: &WInstance,
) -> Result<(), EditLawViolation> {
let mut lens_clone = clone_edit_lens(lens);
let _ = lens_clone
.get_edit(edit.clone())
.map_err(EditLawViolation::Error)?;
let mut edited_source = source.clone();
edit.apply(&mut edited_source)
.map_err(|e| EditLawViolation::Error(EditLensError::EditApply(e)))?;
let state_lens = Lens {
compiled: lens.compiled.clone(),
src_schema: lens.src_schema.clone(),
tgt_schema: lens.tgt_schema.clone(),
};
let (_, complement2) = crate::get(&state_lens, &edited_source)
.map_err(|e| EditLawViolation::Error(EditLensError::TranslationFailed(e.to_string())))?;
let c1 = &lens_clone.complement;
if c1.dropped_nodes.len() != complement2.dropped_nodes.len() {
return Err(EditLawViolation::ComplementCoherence {
detail: format!(
"dropped_nodes count mismatch: edit_lens={}, whole_state={}",
c1.dropped_nodes.len(),
complement2.dropped_nodes.len()
),
});
}
Ok(())
}
fn clone_edit_lens(lens: &EditLens) -> EditLens {
EditLens {
compiled: lens.compiled.clone(),
src_schema: lens.src_schema.clone(),
tgt_schema: lens.tgt_schema.clone(),
complement: lens.complement.clone(),
protocol: lens.protocol.clone(),
reverse_vertex_remap: lens.reverse_vertex_remap.clone(),
reverse_edge_remap: lens.reverse_edge_remap.clone(),
pipeline: lens.pipeline.clone(),
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use panproto_gat::Name;
use panproto_inst::{TreeEdit, Value};
use panproto_schema::Protocol;
use crate::edit_lens::EditLens;
use crate::tests::{identity_lens, three_node_instance, three_node_schema};
use super::*;
fn test_protocol() -> Protocol {
Protocol {
name: "test".into(),
schema_theory: "ThTest".into(),
instance_theory: "ThWType".into(),
schema_composition: None,
instance_composition: None,
edge_rules: vec![],
obj_kinds: vec![],
constraint_sorts: vec![],
has_order: false,
has_coproducts: false,
has_recursion: false,
has_causal: false,
nominal_identity: false,
has_defaults: false,
has_coercions: false,
has_mergers: false,
has_policies: false,
}
}
#[test]
fn consistency_identity_lens() {
let schema = three_node_schema();
let lens = identity_lens(&schema);
let instance = three_node_instance();
let mut edit_lens = EditLens::from_lens(lens, test_protocol());
edit_lens.initialize(&instance).unwrap();
let edit = TreeEdit::SetField {
node_id: 1,
field: Name::from("text"),
value: Value::Str("changed".into()),
};
let result = check_edit_consistency(&mut edit_lens, &edit, &instance);
assert!(result.is_ok(), "consistency should hold: {result:?}");
}
#[test]
fn complement_coherence_identity_lens() {
let schema = three_node_schema();
let lens = identity_lens(&schema);
let instance = three_node_instance();
let mut edit_lens = EditLens::from_lens(lens, test_protocol());
edit_lens.initialize(&instance).unwrap();
let edit = TreeEdit::SetField {
node_id: 1,
field: Name::from("text"),
value: Value::Str("changed".into()),
};
let result = check_complement_coherence(&mut edit_lens, &edit, &instance);
assert!(result.is_ok(), "coherence should hold: {result:?}");
}
}