catlog_wasm/
model_diagram.rs

1//! Wasm bindings for diagrams in models of a double theory.
2
3use all_the_same::all_the_same;
4use derive_more::From;
5use uuid::Uuid;
6
7use serde::{Deserialize, Serialize};
8use tsify_next::Tsify;
9use wasm_bindgen::prelude::*;
10
11use catlog::dbl::model::{FgDblModel, MutDblModel};
12use catlog::dbl::model_diagram as diagram;
13use catlog::dbl::model_morphism::DblModelMapping;
14use catlog::one::FgCategory;
15
16use super::model::{DblModel, DblModelBox, DiscreteDblModel, Mor, Ob};
17use super::model_morphism::DiscreteDblModelMapping;
18use super::result::JsResult;
19use super::theory::{DblTheory, MorType, ObType};
20
21/// Declares an object of a diagram in a model.
22#[derive(Serialize, Deserialize, Tsify)]
23#[tsify(into_wasm_abi, from_wasm_abi, missing_as_null)]
24pub struct DiagramObDecl {
25    /// Globally unique identifier of object.
26    pub id: Uuid,
27
28    /// The object's type in the double theory.
29    #[serde(rename = "obType")]
30    pub ob_type: ObType,
31
32    /// Object in the model that this object is over, if defined.
33    pub over: Option<Ob>,
34}
35
36/// Declares a morphism of a diagram in a model.
37#[derive(Serialize, Deserialize, Tsify)]
38#[tsify(into_wasm_abi, from_wasm_abi, missing_as_null)]
39pub struct DiagramMorDecl {
40    /// Globally unique identifier of morphism.
41    pub id: Uuid,
42
43    /// The morphism's type in the double theory.
44    #[serde(rename = "morType")]
45    pub mor_type: MorType,
46
47    /// Morphism in the model that this morphism is over, if defined.
48    pub over: Option<Mor>,
49
50    /// Domain of this morphism, if defined.
51    pub dom: Option<Ob>,
52
53    /// Codomain of this morphism, if defined.
54    pub cod: Option<Ob>,
55}
56
57/// A box containing a diagram in a model of a double theory.
58#[derive(From)]
59pub enum DblModelDiagramBox {
60    Discrete(diagram::DblModelDiagram<DiscreteDblModelMapping, DiscreteDblModel>),
61    // DiscreteTab(), # TODO: Not implemented.
62}
63
64/// Wasm bindings for a diagram in a model of a double theory.
65#[wasm_bindgen]
66pub struct DblModelDiagram(#[wasm_bindgen(skip)] pub DblModelDiagramBox);
67
68#[wasm_bindgen]
69impl DblModelDiagram {
70    /// Creates an empty diagram for the given theory.
71    #[wasm_bindgen(constructor)]
72    pub fn new(theory: &DblTheory) -> Self {
73        let model = DblModel::new(theory);
74        Self(match model.0 {
75            DblModelBox::Discrete(model) => {
76                let mapping = Default::default();
77                diagram::DblModelDiagram(mapping, model).into()
78            }
79            DblModelBox::DiscreteTab(_) => {
80                panic!("Diagrams not implemented for tabulator theories")
81            }
82        })
83    }
84
85    /// Adds an object to the diagram.
86    #[wasm_bindgen(js_name = "addOb")]
87    pub fn add_ob(&mut self, decl: DiagramObDecl) -> Result<bool, String> {
88        all_the_same!(match &mut self.0 {
89            DblModelDiagramBox::[Discrete](diagram) => {
90                let (mapping, model) = diagram.into();
91                let ob_type = decl.ob_type.try_into()?;
92                if let Some(over) = decl.over.map(|ob| ob.try_into()).transpose()? {
93                    mapping.assign_ob(decl.id, over);
94                }
95                Ok(model.add_ob(decl.id, ob_type))
96            }
97        })
98    }
99
100    /// Adds a morphism to the diagram.
101    #[wasm_bindgen(js_name = "addMor")]
102    pub fn add_mor(&mut self, decl: DiagramMorDecl) -> Result<bool, String> {
103        all_the_same!(match &mut self.0 {
104            DblModelDiagramBox::[Discrete](diagram) => {
105                let (mapping, model) = diagram.into();
106                let mor_type = decl.mor_type.try_into()?;
107                let res = model.make_mor(decl.id, mor_type);
108                if let Some(dom) = decl.dom.map(|ob| ob.try_into()).transpose()? {
109                    model.set_dom(decl.id, dom);
110                }
111                if let Some(cod) = decl.cod.map(|ob| ob.try_into()).transpose()? {
112                    model.set_cod(decl.id, cod);
113                }
114                if let Some(over) = decl.over.map(|mor| mor.try_into()).transpose()? {
115                    mapping.assign_basic_mor(decl.id, over);
116                }
117                Ok(res)
118            }
119        })
120    }
121
122    /// Returns array of all basic objects in the diagram.
123    #[wasm_bindgen]
124    pub fn objects(&self) -> Vec<Ob> {
125        all_the_same!(match &self.0 {
126            DblModelDiagramBox::[Discrete](diagram) => {
127                let (_, model) = diagram.into();
128                model.objects().map(|x| x.into()).collect()
129            }
130        })
131    }
132
133    /// Returns array of all basic morphisms in the diagram.
134    #[wasm_bindgen]
135    pub fn morphisms(&self) -> Vec<Mor> {
136        all_the_same!(match &self.0 {
137            DblModelDiagramBox::[Discrete](diagram) => {
138                let (_, model) = diagram.into();
139                model.morphisms().map(|f| f.into()).collect()
140            }
141        })
142    }
143
144    /// Returns array of basic objects with the given type.
145    #[wasm_bindgen(js_name = "objectsWithType")]
146    pub fn objects_with_type(&self, ob_type: ObType) -> Result<Vec<Ob>, String> {
147        all_the_same!(match &self.0 {
148            DblModelDiagramBox::[Discrete](diagram) => {
149                let (_, model) = diagram.into();
150                let ob_type = ob_type.try_into()?;
151                Ok(model.objects_with_type(&ob_type).map(|x| x.into()).collect())
152            }
153        })
154    }
155
156    /// Returns array of basic morphisms with the given type.
157    #[wasm_bindgen(js_name = "morphismsWithType")]
158    pub fn morphisms_with_type(&self, mor_type: MorType) -> Result<Vec<Mor>, String> {
159        all_the_same!(match &self.0 {
160            DblModelDiagramBox::[Discrete](diagram) => {
161                let (_, model) = diagram.into();
162                let mor_type = mor_type.try_into()?;
163                Ok(model.morphisms_with_type(&mor_type).map(|f| f.into()).collect())
164            }
165        })
166    }
167
168    /// Returns array of declarations of basic objects.
169    #[wasm_bindgen(js_name = "objectDeclarations")]
170    pub fn object_declarations(&self) -> Vec<DiagramObDecl> {
171        all_the_same!(match &self.0 {
172            DblModelDiagramBox::[Discrete](diagram) => {
173                let (mapping, model) = diagram.into();
174                let decls = model.ob_generators().map(|x| {
175                    DiagramObDecl {
176                        id: x,
177                        ob_type: model.ob_generator_type(&x).into(),
178                        over: mapping.apply_ob(&x).map(|ob| ob.into())
179                    }
180                });
181                decls.collect()
182            }
183        })
184    }
185
186    /// Returns array of declarations of basic morphisms.
187    #[wasm_bindgen(js_name = "morphismDeclarations")]
188    pub fn morphism_declarations(&self) -> Vec<DiagramMorDecl> {
189        all_the_same!(match &self.0 {
190            DblModelDiagramBox::[Discrete](diagram) => {
191                let (mapping, model) = diagram.into();
192                let decls = model.mor_generators().map(|f| {
193                    DiagramMorDecl {
194                        id: f,
195                        mor_type: model.mor_generator_type(&f).into(),
196                        over: mapping.apply_basic_mor(&f).map(|mor| mor.into()),
197                        dom: model.get_dom(&f).cloned().map(|ob| ob.into()),
198                        cod: model.get_cod(&f).cloned().map(|ob| ob.into()),
199                    }
200                });
201                decls.collect()
202            }
203        })
204    }
205
206    /// Infers missing data in the diagram from the model, where possible.
207    #[wasm_bindgen(js_name = "inferMissingFrom")]
208    pub fn infer_missing_from(&mut self, model: &DblModel) -> Result<(), String> {
209        all_the_same!(match &mut self.0 {
210            DblModelDiagramBox::[Discrete](diagram) => {
211                let model = (&model.0).try_into().map_err(
212                    |_| "Type of model should match type of diagram")?;
213                diagram.infer_missing_from(model);
214                Ok(())
215            }
216        })
217    }
218
219    /// Validates that the diagram is well defined in a model.
220    #[wasm_bindgen(js_name = "validateIn")]
221    pub fn validate_in(&self, model: &DblModel) -> Result<ModelDiagramValidationResult, String> {
222        all_the_same!(match &self.0 {
223            DblModelDiagramBox::[Discrete](diagram) => {
224                let model = (&model.0).try_into().map_err(
225                    |_| "Type of model should match type of diagram")?;
226                let res = diagram.validate_in(model);
227                Ok(ModelDiagramValidationResult(res.map_err(|errs| errs.into()).into()))
228            }
229        })
230    }
231}
232
233/// Result of validating a diagram in a model.
234#[derive(Serialize, Deserialize, Tsify)]
235#[tsify(into_wasm_abi, from_wasm_abi)]
236pub struct ModelDiagramValidationResult(
237    pub JsResult<(), Vec<diagram::InvalidDiscreteDblModelDiagram<Uuid>>>,
238);
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243    use crate::model::tests::sch_walking_attr;
244    use crate::theories::*;
245
246    #[test]
247    fn diagram_schema() {
248        let th = ThSchema::new().theory();
249        let [attr, entity, attr_type] = [Uuid::now_v7(), Uuid::now_v7(), Uuid::now_v7()];
250        let model = sch_walking_attr(&th, [attr, entity, attr_type]);
251
252        let mut diagram = DblModelDiagram::new(&th);
253        let [x, y, var] = [Uuid::now_v7(), Uuid::now_v7(), Uuid::now_v7()];
254        assert!(
255            diagram
256                .add_ob(DiagramObDecl {
257                    id: var,
258                    ob_type: ObType::Basic("AttrType".into()),
259                    over: Some(Ob::Basic(attr_type))
260                })
261                .is_ok()
262        );
263        for indiv in [x, y] {
264            assert!(
265                diagram
266                    .add_ob(DiagramObDecl {
267                        id: indiv,
268                        ob_type: ObType::Basic("Entity".into()),
269                        over: Some(Ob::Basic(entity))
270                    })
271                    .is_ok()
272            );
273            assert!(
274                diagram
275                    .add_mor(DiagramMorDecl {
276                        id: Uuid::now_v7(),
277                        mor_type: MorType::Basic("Attr".into()),
278                        dom: Some(Ob::Basic(indiv)),
279                        cod: Some(Ob::Basic(var)),
280                        over: Some(Mor::Basic(attr)),
281                    })
282                    .is_ok()
283            );
284        }
285        assert_eq!(diagram.objects().len(), 3);
286        assert_eq!(diagram.object_declarations().len(), 3);
287        assert_eq!(diagram.morphisms().len(), 2);
288        assert_eq!(diagram.morphism_declarations().len(), 2);
289        assert_eq!(diagram.validate_in(&model).unwrap().0, JsResult::Ok(()));
290    }
291}