Skip to main content

context_walkthrough/
context_walkthrough.rs

1//! Why an edit followed by Undo still invalidates an older draft.
2//! An original synchronous teaching model, not an application integration test.
3use edit_session::{EditSession, FinishError};
4
5#[derive(Clone, Copy, PartialEq, Eq)]
6struct Context {
7    document: u64,
8    target: u64,
9    revision: u64,
10}
11
12struct Row {
13    action: &'static str,
14    live: u8,
15    revision: u64,
16    preview: u8,
17    value_matches_original: bool,
18    check: &'static str,
19}
20
21fn observe(
22    action: &'static str,
23    live: u8,
24    context: Context,
25    draft: &EditSession<u8, Context>,
26) -> Row {
27    let check = match draft.check(&live, &context) {
28        Ok(()) => "Ready",
29        Err(FinishError::ContextChanged) => "ContextChanged",
30        Err(FinishError::ValueChanged) => "ValueChanged",
31        Err(FinishError::InvalidProposal) => "InvalidProposal",
32    };
33    Row {
34        action,
35        live,
36        revision: context.revision,
37        preview: *draft.preview(),
38        value_matches_original: live == *draft.original(),
39        check,
40    }
41}
42
43fn main() {
44    let args: Vec<_> = std::env::args().skip(1).collect();
45    let json = match args.as_slice() {
46        [] => false,
47        [flag] if flag == "--json" => true,
48        _ => {
49            eprintln!("Usage: context_walkthrough [--json]");
50            std::process::exit(2);
51        }
52    };
53    let mut live = 100_u8;
54    let mut context = Context {
55        document: 7,
56        target: 2,
57        revision: 42,
58    };
59    let mut draft = EditSession::new(live, context);
60    assert_eq!(draft.propose::<&str>(Ok(75)), Ok(()));
61    let mut rows = vec![observe("Open draft", live, context, &draft)];
62    assert_eq!(draft.check(&live, &context), Ok(()));
63
64    // The host owns both the external edit and its monotonic revision.
65    live = 120;
66    context.revision += 1;
67    rows.push(observe("Host edits", live, context, &draft));
68    assert_eq!(
69        draft.check(&live, &context),
70        Err(FinishError::ContextChanged)
71    );
72
73    // Undo restores the VALUE, but must advance the host's revision again.
74    live = 100;
75    context.revision += 1;
76    rows.push(observe("Host undoes", live, context, &draft));
77    assert_eq!(live, *draft.original());
78    assert_eq!(
79        draft.check(&live, &context),
80        Err(FinishError::ContextChanged)
81    );
82
83    // Reset changes the preview, never the captured document/target/revision.
84    draft.reset();
85    rows.push(observe("Reset draft", live, context, &draft));
86    assert_eq!(*draft.preview(), live);
87    assert_eq!(draft.context().revision, 42);
88    assert_eq!(
89        draft.finish(&live, &context),
90        Err(FinishError::ContextChanged)
91    );
92
93    if json {
94        println!("{{\"rows\":[");
95        for (i, row) in rows.iter().enumerate() {
96            println!(
97                "{{\"action\":\"{}\",\"live\":{},\"revision\":{},\"preview\":{},\"value_matches_original\":{},\"check\":\"{}\"}}{}",
98                row.action,
99                row.live,
100                row.revision,
101                row.preview,
102                row.value_matches_original,
103                row.check,
104                if i + 1 == rows.len() { "" } else { "," }
105            );
106        }
107        println!("],\"finish\":\"ContextChanged\"}}");
108    } else {
109        println!("Action        | Live | Revision | Preview | Value equals original | check()");
110        for row in &rows {
111            println!(
112                "{:<13} | {:>4} | {:>8} | {:>7} | {:<21} | {}",
113                row.action,
114                row.live,
115                row.revision,
116                row.preview,
117                row.value_matches_original,
118                row.check
119            );
120        }
121        println!("finish(): ContextChanged. The host must not apply the old draft.");
122    }
123}