1use std::collections::BTreeMap;
4
5use khive_types::{Entity, Id128, Link, Note, NoteStatus};
6
7use crate::{
8 ChangeSet, CreateOp, CreateTarget, DeleteOp, DeletePreimage, EdgePatch, EdgePreimage,
9 EntityPatch, EntityPreimage, Envelope, LinkOp, NotePatch, NotePreimage, Op, UpdateOp,
10 UpdatePatch, UpdatePreimage,
11};
12
13#[derive(Clone, Debug, Default)]
22pub struct CurrentState {
23 pub entities: BTreeMap<Id128, Entity>,
25 pub notes: BTreeMap<Id128, Note>,
27 pub edges: BTreeMap<Id128, Link>,
29}
30
31#[derive(Debug, thiserror::Error)]
35pub enum InvertError {
36 #[error("the inverse envelope must differ from the forward envelope")]
38 ReusedEnvelope,
39 #[error("operation {index} ({op}) is not invertible")]
41 NotInvertible {
42 index: usize,
44 op: &'static str,
46 },
47 #[error("operation {index} is missing current record {id}")]
49 MissingCurrent {
50 index: usize,
52 id: Id128,
54 },
55 #[error("operation {index} record {id} differs in field {field}")]
57 Invalidated {
58 index: usize,
60 id: Id128,
62 field: &'static str,
64 },
65 #[error("operation {index} inverse update is invalid: {reason}")]
67 InverseUpdate {
68 index: usize,
70 reason: String,
72 },
73}
74
75impl ChangeSet {
76 pub fn invert(
93 &self,
94 envelope: Envelope,
95 current: &CurrentState,
96 ) -> Result<ChangeSet, InvertError> {
97 if envelope == self.envelope {
98 return Err(InvertError::ReusedEnvelope);
99 }
100 let mut working = current.clone();
101 let mut ops = Vec::with_capacity(self.ops.len());
102 for (index, op) in self.ops.iter().enumerate().rev() {
103 let inverse = match op {
104 Op::Create(create) => invert_create(index, create, &mut working)?,
105 Op::Link(link) => invert_link(index, link, &mut working)?,
106 Op::Update(update) => invert_update(index, update, &mut working)?,
107 Op::Delete(_) => {
108 return Err(InvertError::NotInvertible {
109 index,
110 op: "delete",
111 });
112 }
113 Op::Merge(_) => {
114 return Err(InvertError::NotInvertible { index, op: "merge" });
115 }
116 };
117 ops.push(inverse);
118 }
119 Ok(ChangeSet::new(envelope, ops))
120 }
121}
122
123fn check(index: usize, id: Id128, field: &'static str, matches: bool) -> Result<(), InvertError> {
124 if matches {
125 Ok(())
126 } else {
127 Err(InvertError::Invalidated { index, id, field })
128 }
129}
130
131fn invert_create(
132 index: usize,
133 create: &CreateOp,
134 working: &mut CurrentState,
135) -> Result<Op, InvertError> {
136 let id = create.id;
137 let preimage = match &create.target {
138 CreateTarget::Entity(fields) => {
139 let row = working
140 .entities
141 .get(&id)
142 .ok_or(InvertError::MissingCurrent { index, id })?;
143 check(index, id, "id", row.header.id == id)?;
144 check(
145 index,
146 id,
147 "namespace",
148 row.header.namespace == create.namespace,
149 )?;
150 check(index, id, "deleted_at", row.deleted_at.is_none())?;
151 check(index, id, "kind", row.kind == fields.entity_kind)?;
152 check(
153 index,
154 id,
155 "entity_type",
156 row.entity_type == fields.entity_type,
157 )?;
158 check(index, id, "name", row.name == fields.name)?;
159 check(
160 index,
161 id,
162 "description",
163 row.description == fields.description,
164 )?;
165 check(index, id, "properties", row.properties == fields.properties)?;
166 check(index, id, "tags", row.tags == fields.tags)?;
167 let captured = row.clone();
168 working.entities.remove(&id);
169 DeletePreimage::Entity(Box::new(captured))
170 }
171 CreateTarget::Note(fields) => {
172 let row = working
173 .notes
174 .get(&id)
175 .ok_or(InvertError::MissingCurrent { index, id })?;
176 check(index, id, "id", row.header.id == id)?;
177 check(
178 index,
179 id,
180 "namespace",
181 row.header.namespace == create.namespace,
182 )?;
183 check(index, id, "deleted_at", row.deleted_at.is_none())?;
184 check(index, id, "kind", row.kind == fields.note_kind)?;
185 check(index, id, "status", row.status == NoteStatus::default())?;
186 check(index, id, "expires_at", row.expires_at.is_none())?;
187 check(index, id, "content", row.content == fields.content)?;
188 check(index, id, "properties", row.properties == fields.properties)?;
189 check(index, id, "tags", row.tags == fields.tags)?;
190 check(index, id, "salience", row.salience == fields.salience)?;
191 check(
192 index,
193 id,
194 "decay_factor",
195 row.decay_factor == fields.decay_factor,
196 )?;
197 let captured = row.clone();
198 working.notes.remove(&id);
199 DeletePreimage::Note(Box::new(captured))
200 }
201 };
202 Ok(Op::Delete(DeleteOp {
203 target_id: id,
204 hard: false,
205 preimage,
206 }))
207}
208
209fn invert_link(index: usize, link: &LinkOp, working: &mut CurrentState) -> Result<Op, InvertError> {
210 let id = link.id;
211 let row = working
212 .edges
213 .get(&id)
214 .ok_or(InvertError::MissingCurrent { index, id })?;
215 check(index, id, "id", row.id == id)?;
216 check(
217 index,
218 id,
219 "namespace",
220 row.namespace == link.namespace.as_str(),
221 )?;
222 check(index, id, "deleted_at", row.deleted_at.is_none())?;
223 check(index, id, "source", row.source == link.source)?;
224 check(index, id, "target", row.target == link.target)?;
225 check(index, id, "relation", row.relation == link.relation)?;
226 check(index, id, "weight", row.weight == link.weight)?;
227 check(index, id, "properties", row.properties == link.properties)?;
228 let captured = row.clone();
229 working.edges.remove(&id);
230 Ok(Op::Delete(DeleteOp {
231 target_id: id,
232 hard: false,
233 preimage: DeletePreimage::Edge(Box::new(captured)),
234 }))
235}
236
237fn invert_update(
238 index: usize,
239 update: &UpdateOp,
240 working: &mut CurrentState,
241) -> Result<Op, InvertError> {
242 let id = update.target_id();
243 match (update.patch(), update.preimage()) {
244 (UpdatePatch::Entity(patch), UpdatePreimage::Entity(prior)) => {
245 let row = working
246 .entities
247 .get_mut(&id)
248 .ok_or(InvertError::MissingCurrent { index, id })?;
249 check(index, id, "id", row.header.id == id)?;
250 if let Some(value) = &patch.name {
251 check(index, id, "name", &row.name == value)?;
252 }
253 if let Some(value) = &patch.description {
254 check(
255 index,
256 id,
257 "description",
258 row.description.as_ref() == value.as_ref(),
259 )?;
260 }
261 if let Some(value) = &patch.entity_type {
262 check(
263 index,
264 id,
265 "entity_type",
266 row.entity_type.as_ref() == value.as_ref(),
267 )?;
268 }
269 if let Some(value) = &patch.properties {
270 check(index, id, "properties", &row.properties == value)?;
271 }
272 if let Some(value) = &patch.tags {
273 check(index, id, "tags", &row.tags == value)?;
274 }
275 let inverse = swapped_update(index, update)?;
276 if let Some(value) = &prior.name {
277 row.name = value.clone();
278 }
279 if let Some(value) = &prior.description {
280 row.description = value.clone();
281 }
282 if let Some(value) = &prior.entity_type {
283 row.entity_type = value.clone();
284 }
285 if let Some(value) = &prior.properties {
286 row.properties = value.clone();
287 }
288 if let Some(value) = &prior.tags {
289 row.tags = value.clone();
290 }
291 Ok(Op::Update(inverse))
292 }
293 (UpdatePatch::Note(patch), UpdatePreimage::Note(prior)) => {
294 let row = working
295 .notes
296 .get_mut(&id)
297 .ok_or(InvertError::MissingCurrent { index, id })?;
298 check(index, id, "id", row.header.id == id)?;
299 if let Some(value) = &patch.content {
300 check(index, id, "content", &row.content == value)?;
301 }
302 if let Some(value) = patch.salience {
303 check(index, id, "salience", row.salience == value)?;
304 }
305 if let Some(value) = patch.decay_factor {
306 check(index, id, "decay_factor", row.decay_factor == value)?;
307 }
308 if let Some(value) = &patch.properties {
309 check(index, id, "properties", &row.properties == value)?;
310 }
311 if let Some(value) = &patch.tags {
312 check(index, id, "tags", &row.tags == value)?;
313 }
314 let inverse = swapped_update(index, update)?;
315 if let Some(value) = &prior.content {
316 row.content = value.clone();
317 }
318 if let Some(value) = prior.salience {
319 row.salience = value;
320 }
321 if let Some(value) = prior.decay_factor {
322 row.decay_factor = value;
323 }
324 if let Some(value) = &prior.properties {
325 row.properties = value.clone();
326 }
327 if let Some(value) = &prior.tags {
328 row.tags = value.clone();
329 }
330 Ok(Op::Update(inverse))
331 }
332 (UpdatePatch::Edge(patch), UpdatePreimage::Edge(prior)) => {
333 let row = working
334 .edges
335 .get_mut(&id)
336 .ok_or(InvertError::MissingCurrent { index, id })?;
337 check(index, id, "id", row.id == id)?;
338 if let Some(value) = patch.relation {
339 check(index, id, "relation", row.relation == value)?;
340 }
341 if let Some(value) = patch.weight {
342 check(index, id, "weight", row.weight == value)?;
343 }
344 let inverse = swapped_update(index, update)?;
345 if let Some(value) = prior.relation {
346 row.relation = value;
347 }
348 if let Some(value) = prior.weight {
349 row.weight = value;
350 }
351 Ok(Op::Update(inverse))
352 }
353 _ => swapped_update(index, update).map(Op::Update),
356 }
357}
358
359fn swapped_update(index: usize, update: &UpdateOp) -> Result<UpdateOp, InvertError> {
360 let patch = match update.preimage() {
361 UpdatePreimage::Entity(prior) => UpdatePatch::Entity(EntityPatch {
362 name: prior.name.clone(),
363 description: prior.description.clone(),
364 properties: prior.properties.clone(),
365 tags: prior.tags.clone(),
366 entity_type: prior.entity_type.clone(),
367 }),
368 UpdatePreimage::Note(prior) => UpdatePatch::Note(NotePatch {
369 content: prior.content.clone(),
370 salience: prior.salience,
371 decay_factor: prior.decay_factor,
372 properties: prior.properties.clone(),
373 tags: prior.tags.clone(),
374 }),
375 UpdatePreimage::Edge(prior) => UpdatePatch::Edge(EdgePatch {
376 relation: prior.relation,
377 weight: prior.weight,
378 }),
379 };
380 let preimage = match update.patch() {
381 UpdatePatch::Entity(patch) => UpdatePreimage::Entity(EntityPreimage {
382 name: patch.name.clone(),
383 description: patch.description.clone(),
384 properties: patch.properties.clone(),
385 tags: patch.tags.clone(),
386 entity_type: patch.entity_type.clone(),
387 }),
388 UpdatePatch::Note(patch) => UpdatePreimage::Note(NotePreimage {
389 content: patch.content.clone(),
390 salience: patch.salience,
391 decay_factor: patch.decay_factor,
392 properties: patch.properties.clone(),
393 tags: patch.tags.clone(),
394 }),
395 UpdatePatch::Edge(patch) => UpdatePreimage::Edge(EdgePreimage {
396 relation: patch.relation,
397 weight: patch.weight,
398 }),
399 };
400 UpdateOp::new(update.target_id(), patch, preimage)
401 .map_err(|reason| InvertError::InverseUpdate { index, reason })
402}