catcolab_document_types/v1/
notebook.rs

1use crate::v0;
2
3use super::cell::NotebookCell;
4
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use tsify::Tsify;
8use uuid::Uuid;
9
10#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Tsify)]
11#[tsify(into_wasm_abi, from_wasm_abi, hashmap_as_object)]
12pub struct Notebook<T> {
13    #[serde(rename = "cellContents")]
14    pub cell_contents: HashMap<Uuid, NotebookCell<T>>,
15    #[serde(rename = "cellOrder")]
16    pub cell_order: Vec<Uuid>,
17}
18
19#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Tsify)]
20#[tsify(into_wasm_abi, from_wasm_abi)]
21pub struct ModelNotebook(pub Notebook<super::model_judgment::ModelJudgment>);
22
23#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Tsify)]
24#[tsify(into_wasm_abi, from_wasm_abi)]
25pub struct DiagramNotebook(pub Notebook<super::diagram_judgment::DiagramJudgment>);
26
27/// Arbitrary instances for property-based testing.
28#[cfg(feature = "property-tests")]
29pub(crate) mod arbitrary {
30    use super::*;
31    use crate::v0::cell::arbitrary::arb_notebook_cell;
32    use proptest::prelude::*;
33
34    fn arb_uuid() -> BoxedStrategy<Uuid> {
35        any::<u128>().prop_map(Uuid::from_u128).boxed()
36    }
37
38    /// Strategy for a `Notebook<T>` given a strategy for `T`.
39    ///
40    /// Generates a consistent notebook where `cell_order` contains exactly
41    /// the keys in `cell_contents`.
42    pub fn arb_notebook<T: std::fmt::Debug + 'static>(
43        arb_t: impl Strategy<Value = T> + Clone + 'static,
44    ) -> BoxedStrategy<Notebook<T>> {
45        prop::collection::vec((arb_uuid(), arb_notebook_cell(arb_t)), 0..6)
46            .prop_map(|entries| {
47                let mut cell_contents = HashMap::new();
48                let mut cell_order = Vec::new();
49                for (id, cell) in entries {
50                    // Replace the cell's internal id with the map key for
51                    // consistency, matching how real notebooks work.
52                    let cell = match cell {
53                        NotebookCell::RichText { content, .. } => {
54                            NotebookCell::RichText { id, content }
55                        }
56                        NotebookCell::Formal { content, .. } => {
57                            NotebookCell::Formal { id, content }
58                        }
59                        NotebookCell::Stem { .. } => NotebookCell::Stem { id },
60                    };
61                    cell_contents.insert(id, cell);
62                    cell_order.push(id);
63                }
64                Notebook { cell_contents, cell_order }
65            })
66            .boxed()
67    }
68
69    impl Arbitrary for ModelNotebook {
70        type Parameters = ();
71        type Strategy = BoxedStrategy<Self>;
72
73        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
74            arb_notebook(any::<super::super::model_judgment::ModelJudgment>())
75                .prop_map(ModelNotebook)
76                .boxed()
77        }
78    }
79}
80
81impl<T> Notebook<T> {
82    pub fn cells(&self) -> impl Iterator<Item = &NotebookCell<T>> {
83        self.cell_order.iter().filter_map(|id| self.cell_contents.get(id))
84    }
85
86    pub fn formal_content(&self) -> impl Iterator<Item = &T> {
87        self.cells().filter_map(|cell| match cell {
88            NotebookCell::Formal { content, .. } => Some(content),
89            _ => None,
90        })
91    }
92
93    pub fn migrate_from_v0(old: v0::Notebook<T>) -> Self {
94        let mut cell_contents = HashMap::new();
95        let mut cell_order = Vec::new();
96
97        for old_cell in old.cells {
98            let id = match old_cell {
99                v0::NotebookCell::RichText { id, .. }
100                | v0::NotebookCell::Formal { id, .. }
101                | v0::NotebookCell::Stem { id } => id,
102            };
103
104            cell_order.push(id);
105            cell_contents.insert(id, old_cell);
106        }
107
108        Notebook { cell_contents, cell_order }
109    }
110}