1use ents::{
2 DatabaseError, DraftError, Edge, EdgeDraft, EdgeValue, Ent, EntExt, Id,
3 IncomingEdgeProvider, Transactional,
4};
5
6#[derive(Debug, thiserror::Error)]
8pub enum AuditError {
9 #[error("Entity not found: {0}")]
10 EntityNotFound(Id),
11
12 #[error("Unexpected entity type: {0} is not {1} type")]
13 UnexpectedEntityType(Id, String),
14
15 #[error("Edge mismatch: existing edges differ from drafted edges")]
16 EdgeMismatch {
17 existing: Vec<EdgeValue>,
18 drafted: Vec<EdgeValue>,
19 },
20
21 #[error("Draft error: {0}")]
22 Draft(#[from] DraftError),
23
24 #[error("Database error: {0}")]
25 Database(#[from] DatabaseError),
26}
27
28pub trait AdminEdgeByDest: Transactional {
31 fn find_edges_by_dest(&self, dest: Id) -> Result<Vec<Edge>, DatabaseError>;
32
33 fn remove_edges_by_dest(&self, dest: Id) -> Result<(), DatabaseError>;
34
35 fn audit_ent_edges<E: Ent>(&self, id: Id) -> Result<(), AuditError>
36 where
37 Self: Sized,
38 {
39 let ent_box = self.get(id)?.ok_or(AuditError::EntityNotFound(id))?;
41
42 let ent = ent_box.as_ent::<E>().ok_or_else(|| {
43 AuditError::UnexpectedEntityType(
44 id,
45 std::any::type_name::<E>().to_string(),
46 )
47 })?;
48
49 let mut existing_edges: Vec<EdgeValue> = self
51 .find_edges_by_dest(id)?
52 .into_iter()
53 .map(|e| EdgeValue::new(e.source, e.sort_key, e.dest))
54 .collect();
55 existing_edges.sort_by(|a, b| {
56 (&a.source, &a.sort_key).cmp(&(&b.source, &b.sort_key))
57 });
58
59 self.remove_edges_by_dest(id)?;
61
62 let draft = E::EdgeProvider::draft(ent);
64 let mut drafted_edges = draft.check(self)?;
65 drafted_edges.sort_by(|a, b| {
66 (&a.source, &a.sort_key).cmp(&(&b.source, &b.sort_key))
67 });
68
69 if existing_edges != drafted_edges {
71 return Err(AuditError::EdgeMismatch {
72 existing: existing_edges,
73 drafted: drafted_edges,
74 });
75 }
76
77 Ok(())
80 }
81
82 fn fix_ent_edges<E: Ent>(self, id: Id) -> Result<(), AuditError>
83 where
84 Self: Sized,
85 {
86 let ent_box = self.get(id)?.ok_or(AuditError::EntityNotFound(id))?;
88
89 let ent = ent_box.as_ent::<E>().ok_or_else(|| {
90 AuditError::UnexpectedEntityType(
91 id,
92 std::any::type_name::<E>().to_string(),
93 )
94 })?;
95
96 self.remove_edges_by_dest(id)?;
98
99 let draft = E::EdgeProvider::draft(ent);
101 let edges = draft.check(&self)?;
102
103 for edge in edges {
104 self.create_edge(edge)?;
105 }
106
107 self.commit()?;
108
109 Ok(())
110 }
111}