1use sim_kernel::{Cx, Value};
4
5use crate::{Doc, DocId, OfficeError};
6
7#[derive(Clone, Debug, PartialEq)]
9pub struct Edit {
10 pub doc: DocId,
12 pub domain: String,
14 pub op: Value,
16 pub inverse: Value,
18}
19
20impl Edit {
21 #[must_use]
23 pub fn new(doc: DocId, domain: impl Into<String>, op: Value, inverse: Value) -> Self {
24 Self {
25 doc,
26 domain: domain.into(),
27 op,
28 inverse,
29 }
30 }
31
32 #[must_use]
34 pub fn inverted(&self) -> Self {
35 Self {
36 doc: self.doc.clone(),
37 domain: self.domain.clone(),
38 op: self.inverse.clone(),
39 inverse: self.op.clone(),
40 }
41 }
42}
43
44pub trait DomainEdit {
46 fn domain(&self) -> &'static str;
48 fn apply(&self, cx: &mut Cx, doc: &mut Doc, op: &Value) -> Result<(), OfficeError>;
50 fn invert(&self, op: &Value) -> Value;
52}
53
54#[must_use]
56pub fn invert(edit: &Edit) -> Edit {
57 edit.inverted()
58}
59
60#[cfg(test)]
61mod tests {
62 use std::sync::Arc;
63
64 use sim_kernel::{DefaultFactory, NoopEvalPolicy};
65
66 use crate::DocKind;
67
68 use super::*;
69
70 struct BodySwapEdit {
71 inverse: Value,
72 }
73
74 impl DomainEdit for BodySwapEdit {
75 fn domain(&self) -> &'static str {
76 "office/body-swap"
77 }
78
79 fn apply(&self, _cx: &mut Cx, doc: &mut Doc, op: &Value) -> Result<(), OfficeError> {
80 doc.body = op.clone();
81 Ok(())
82 }
83
84 fn invert(&self, _op: &Value) -> Value {
85 self.inverse.clone()
86 }
87 }
88
89 #[test]
90 fn edit_round_trips_through_double_invert() {
91 let cx = Cx::new(
92 Arc::new(NoopEvalPolicy),
93 Arc::new(DefaultFactory),
94 sim_kernel::HandleSeed::new(0xc37b_b833_0649_c0de),
95 );
96 let op = cx.factory().string("set title".to_owned()).unwrap();
97 let inverse = cx.factory().string("restore title".to_owned()).unwrap();
98 let edit = Edit::new(DocId::new("doc-1"), "office/body", op, inverse);
99
100 assert_eq!(invert(&invert(&edit)), edit);
101 }
102
103 #[test]
104 fn domain_edit_round_trips_without_core_enum_variant() {
105 let mut cx = Cx::new(
106 Arc::new(NoopEvalPolicy),
107 Arc::new(DefaultFactory),
108 sim_kernel::HandleSeed::new(0x251d_1f52_8144_2a42),
109 );
110 let old_body = cx.factory().string("old".to_owned()).unwrap();
111 let new_body = cx.factory().string("new".to_owned()).unwrap();
112 let domain = BodySwapEdit {
113 inverse: old_body.clone(),
114 };
115 let mut doc = Doc::new(
116 DocKind::new("report"),
117 DocId::new("doc-1"),
118 old_body.clone(),
119 vec![],
120 );
121 let edit = Edit::new(
122 doc.id.clone(),
123 domain.domain(),
124 new_body.clone(),
125 domain.invert(&new_body),
126 );
127
128 domain.apply(&mut cx, &mut doc, &edit.op).unwrap();
129 assert_eq!(doc.body, new_body);
130 domain.apply(&mut cx, &mut doc, &edit.inverse).unwrap();
131 assert_eq!(doc.body, old_body);
132 assert_eq!(invert(&invert(&edit)), edit);
133 }
134}