catlog_wasm/
model.rs

1//! Wasm bindings for models of double theories.
2
3use std::hash::BuildHasherDefault;
4
5use all_the_same::all_the_same;
6use derive_more::{From, TryInto};
7use ustr::{IdentityHasher, Ustr};
8use uuid::Uuid;
9
10use serde::{Deserialize, Serialize};
11use tsify_next::Tsify;
12use wasm_bindgen::prelude::*;
13
14use catlog::dbl::model::{self as dbl_model, FgDblModel, InvalidDblModel, MutDblModel};
15use catlog::one::fin_category::UstrFinCategory;
16use catlog::one::{Category as _, FgCategory, Path};
17use catlog::validate::Validate;
18
19use super::result::JsResult;
20use super::theory::{DblTheory, DblTheoryBox, MorType, ObType};
21
22/// An object in a model of a double theory.
23#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Tsify)]
24#[serde(tag = "tag", content = "content")]
25#[tsify(into_wasm_abi, from_wasm_abi)]
26pub enum Ob {
27    /// Basic or generating object.
28    Basic(Uuid),
29
30    /// Morphism viewed as an object of a tabulator.
31    Tabulated(Mor),
32}
33
34/// A morphism in a model of a double theory.
35#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Tsify)]
36#[serde(tag = "tag", content = "content")]
37#[tsify(into_wasm_abi, from_wasm_abi)]
38pub enum Mor {
39    /// Basic or generating morphism.
40    Basic(Uuid),
41
42    /// Composite of morphisms.
43    Composite(Box<Path<Ob, Mor>>),
44
45    /// Morphism between tabulated morphisms, a commutative square.
46    TabulatorSquare {
47        dom: Box<Mor>,
48        cod: Box<Mor>,
49        pre: Box<Mor>,
50        post: Box<Mor>,
51    },
52}
53
54/// Convert from an object in a model of discrete double theory.
55impl From<Uuid> for Ob {
56    fn from(value: Uuid) -> Self {
57        Ob::Basic(value)
58    }
59}
60
61/// Convert from a morphism in a model of a discrete double theory.
62impl From<Path<Uuid, Uuid>> for Mor {
63    fn from(path: Path<Uuid, Uuid>) -> Self {
64        if path.len() == 1 {
65            Mor::Basic(path.only().unwrap())
66        } else {
67            Mor::Composite(Box::new(path.map(Ob::Basic, Mor::Basic)))
68        }
69    }
70}
71
72/// Convert into an object in a model of a discrete double theory.
73impl TryFrom<Ob> for Uuid {
74    type Error = String;
75
76    fn try_from(ob: Ob) -> Result<Self, Self::Error> {
77        match ob {
78            Ob::Basic(id) => Ok(id),
79            _ => Err(format!("Cannot cast object for discrete double theory: {:#?}", ob)),
80        }
81    }
82}
83
84/// Convert into a morphism in a model of a discrete double theory.
85impl TryFrom<Mor> for Path<Uuid, Uuid> {
86    type Error = String;
87
88    fn try_from(mor: Mor) -> Result<Self, Self::Error> {
89        match mor {
90            Mor::Basic(id) => Ok(Path::single(id)),
91            Mor::Composite(path) => {
92                let result_path = (*path).try_map(|ob| ob.try_into(), |mor| mor.try_into());
93                result_path.map(|path| path.flatten())
94            }
95            _ => Err(format!("Cannot cast morphism for discrete double theory: {:#?}", mor)),
96        }
97    }
98}
99
100/// Convert into an object in a model of a discrete tabulator theory.
101impl TryFrom<Ob> for dbl_model::TabOb<Uuid, Uuid> {
102    type Error = String;
103
104    fn try_from(ob: Ob) -> Result<Self, Self::Error> {
105        match ob {
106            Ob::Basic(id) => Ok(dbl_model::TabOb::Basic(id)),
107            Ob::Tabulated(f) => Ok(dbl_model::TabOb::Tabulated(Box::new(f.try_into()?))),
108        }
109    }
110}
111
112/// Convert into a morphism in a model of a discrete tabulator theory.
113impl TryFrom<Mor> for dbl_model::TabMor<Uuid, Uuid> {
114    type Error = String;
115
116    fn try_from(mor: Mor) -> Result<Self, Self::Error> {
117        match mor {
118            Mor::Basic(id) => Ok(Path::single(dbl_model::TabEdge::Basic(id))),
119            Mor::Composite(path) => {
120                let result_path = (*path).try_map(|ob| ob.try_into(), |mor| mor.try_into());
121                result_path.map(|path| path.flatten())
122            }
123            Mor::TabulatorSquare {
124                dom,
125                cod,
126                pre,
127                post,
128            } => Ok(Path::single(dbl_model::TabEdge::Square {
129                dom: Box::new((*dom).try_into()?),
130                cod: Box::new((*cod).try_into()?),
131                pre: Box::new((*pre).try_into()?),
132                post: Box::new((*post).try_into()?),
133            })),
134        }
135    }
136}
137
138impl TryFrom<Mor> for dbl_model::TabEdge<Uuid, Uuid> {
139    type Error = String;
140
141    fn try_from(mor: Mor) -> Result<Self, Self::Error> {
142        match mor {
143            Mor::Basic(id) => Ok(dbl_model::TabEdge::Basic(id)),
144            Mor::TabulatorSquare {
145                dom,
146                cod,
147                pre,
148                post,
149            } => Ok(dbl_model::TabEdge::Square {
150                dom: Box::new((*dom).try_into()?),
151                cod: Box::new((*cod).try_into()?),
152                pre: Box::new((*pre).try_into()?),
153                post: Box::new((*post).try_into()?),
154            }),
155            _ => Err(format!("Cannot cast morphism for discrete tabulator theory: {:#?}", mor)),
156        }
157    }
158}
159
160/// Convert from an object in a model of a discrete tabulator theory.
161impl From<dbl_model::TabOb<Uuid, Uuid>> for Ob {
162    fn from(value: dbl_model::TabOb<Uuid, Uuid>) -> Self {
163        match value {
164            dbl_model::TabOb::Basic(x) => Ob::Basic(x),
165            dbl_model::TabOb::Tabulated(f) => Ob::Tabulated((*f).into()),
166        }
167    }
168}
169
170/// Convert from a morphism in a model of a discrete tabulator theory.
171impl From<dbl_model::TabMor<Uuid, Uuid>> for Mor {
172    fn from(path: dbl_model::TabMor<Uuid, Uuid>) -> Self {
173        if path.len() == 1 {
174            path.only().unwrap().into()
175        } else {
176            Mor::Composite(Box::new(path.map(|ob| ob.into(), |mor| mor.into())))
177        }
178    }
179}
180
181impl From<dbl_model::TabEdge<Uuid, Uuid>> for Mor {
182    fn from(value: dbl_model::TabEdge<Uuid, Uuid>) -> Self {
183        match value {
184            dbl_model::TabEdge::Basic(f) => Mor::Basic(f),
185            dbl_model::TabEdge::Square {
186                dom,
187                cod,
188                pre,
189                post,
190            } => Mor::TabulatorSquare {
191                dom: Box::new((*dom).into()),
192                cod: Box::new((*cod).into()),
193                pre: Box::new((*pre).into()),
194                post: Box::new((*post).into()),
195            },
196        }
197    }
198}
199
200/// Declares an object in a model of a double theory.
201#[derive(Serialize, Deserialize, Tsify)]
202#[tsify(into_wasm_abi, from_wasm_abi, missing_as_null)]
203pub struct ObDecl {
204    /// Globally unique identifier of object.
205    pub id: Uuid,
206
207    /// The object's type in the double theory.
208    #[serde(rename = "obType")]
209    pub ob_type: ObType,
210}
211
212/// Declares a morphism in a model of a double theory.
213#[derive(Serialize, Deserialize, Tsify)]
214#[tsify(into_wasm_abi, from_wasm_abi, missing_as_null)]
215pub struct MorDecl {
216    /// Globally unique identifier of morphism.
217    pub id: Uuid,
218
219    /// The morphism's type in the double theory.
220    #[serde(rename = "morType")]
221    pub mor_type: MorType,
222
223    /// Domain of morphism, if defined.
224    pub dom: Option<Ob>,
225
226    /// Codomain of morphism, if defined.
227    pub cod: Option<Ob>,
228}
229
230pub(crate) type DiscreteDblModel = dbl_model::DiscreteDblModel<Uuid, UstrFinCategory>;
231pub(crate) type DiscreteTabModel =
232    dbl_model::DiscreteTabModel<Uuid, Ustr, BuildHasherDefault<IdentityHasher>>;
233
234/** A box containing a model of a double theory of any kind.
235
236See [`DblTheoryBox`] for motivation.
237 */
238#[derive(From, TryInto)]
239#[try_into(ref)]
240pub enum DblModelBox {
241    Discrete(DiscreteDblModel),
242    DiscreteTab(DiscreteTabModel),
243}
244
245/// Wasm bindings for a model of a double theory.
246#[wasm_bindgen]
247pub struct DblModel(#[wasm_bindgen(skip)] pub DblModelBox);
248
249#[wasm_bindgen]
250impl DblModel {
251    /// Creates an empty model of the given theory.
252    #[wasm_bindgen(constructor)]
253    pub fn new(theory: &DblTheory) -> Self {
254        Self(match &theory.0 {
255            DblTheoryBox::Discrete(th) => DiscreteDblModel::new(th.clone()).into(),
256            DblTheoryBox::DiscreteTab(th) => DiscreteTabModel::new(th.clone()).into(),
257        })
258    }
259
260    /// Adds an object to the model.
261    #[wasm_bindgen(js_name = "addOb")]
262    pub fn add_ob(&mut self, decl: ObDecl) -> Result<bool, String> {
263        all_the_same!(match &mut self.0 {
264            DblModelBox::[Discrete, DiscreteTab](model) => {
265                let ob_type = decl.ob_type.try_into()?;
266                Ok(model.add_ob(decl.id, ob_type))
267            }
268        })
269    }
270
271    /// Adds a morphism to the model.
272    #[wasm_bindgen(js_name = "addMor")]
273    pub fn add_mor(&mut self, decl: MorDecl) -> Result<bool, String> {
274        all_the_same!(match &mut self.0 {
275            DblModelBox::[Discrete, DiscreteTab](model) => {
276                let mor_type = decl.mor_type.try_into()?;
277                let res = model.make_mor(decl.id, mor_type);
278                if let Some(dom) = decl.dom.map(|ob| ob.try_into()).transpose()? {
279                    model.set_dom(decl.id, dom);
280                }
281                if let Some(cod) = decl.cod.map(|ob| ob.try_into()).transpose()? {
282                    model.set_cod(decl.id, cod);
283                }
284                Ok(res)
285            }
286        })
287    }
288
289    /// Is the object contained in the model?
290    #[wasm_bindgen(js_name = "hasOb")]
291    pub fn has_ob(&self, ob: Ob) -> Result<bool, String> {
292        all_the_same!(match &self.0 {
293            DblModelBox::[Discrete, DiscreteTab](model) => {
294                let ob = ob.try_into()?;
295                Ok(model.has_ob(&ob))
296            }
297        })
298    }
299
300    /// Is the morphism contained in the model?
301    #[wasm_bindgen(js_name = "hasMor")]
302    pub fn has_mor(&self, mor: Mor) -> Result<bool, String> {
303        all_the_same!(match &self.0 {
304            DblModelBox::[Discrete, DiscreteTab](model) => {
305                let mor = mor.try_into()?;
306                Ok(model.has_mor(&mor))
307            }
308        })
309    }
310
311    /// Returns array of all basic objects in the model.
312    #[wasm_bindgen]
313    pub fn objects(&self) -> Vec<Ob> {
314        all_the_same!(match &self.0 {
315            DblModelBox::[Discrete, DiscreteTab](model) => model.objects().map(|x| x.into()).collect()
316        })
317    }
318
319    /// Returns array of all basic morphisms in the model.
320    #[wasm_bindgen]
321    pub fn morphisms(&self) -> Vec<Mor> {
322        all_the_same!(match &self.0 {
323            DblModelBox::[Discrete, DiscreteTab](model) => model.morphisms().map(|f| f.into()).collect()
324        })
325    }
326
327    /// Returns array of basic objects with the given type.
328    #[wasm_bindgen(js_name = "objectsWithType")]
329    pub fn objects_with_type(&self, ob_type: ObType) -> Result<Vec<Ob>, String> {
330        all_the_same!(match &self.0 {
331            DblModelBox::[Discrete, DiscreteTab](model) => {
332                let ob_type = ob_type.try_into()?;
333                Ok(model.objects_with_type(&ob_type).map(|ob| ob.into()).collect())
334            }
335        })
336    }
337
338    /// Returns array of basic morphisms with the given type.
339    #[wasm_bindgen(js_name = "morphismsWithType")]
340    pub fn morphisms_with_type(&self, mor_type: MorType) -> Result<Vec<Mor>, String> {
341        all_the_same!(match &self.0 {
342            DblModelBox::[Discrete, DiscreteTab](model) => {
343                let mor_type = mor_type.try_into()?;
344                Ok(model.morphisms_with_type(&mor_type).map(|mor| mor.into()).collect())
345            }
346        })
347    }
348
349    /// Validates that the model is well defined.
350    #[wasm_bindgen]
351    pub fn validate(&self) -> ModelValidationResult {
352        all_the_same!(match &self.0 {
353            DblModelBox::[Discrete, DiscreteTab](model) => {
354                let res = model.validate();
355                ModelValidationResult(res.map_err(|errs| errs.into()).into())
356            }
357        })
358    }
359}
360
361/// Result of validating a model of a double theory.
362#[derive(Serialize, Deserialize, Tsify)]
363#[tsify(into_wasm_abi, from_wasm_abi)]
364pub struct ModelValidationResult(pub JsResult<(), Vec<InvalidDblModel<Uuid>>>);
365
366#[cfg(test)]
367pub(crate) mod tests {
368    use super::*;
369    use crate::theories::*;
370
371    pub(crate) fn sch_walking_attr(th: &DblTheory, ids: [Uuid; 3]) -> DblModel {
372        let mut model = DblModel::new(th);
373        let [attr, entity, attr_type] = ids;
374        assert!(
375            model
376                .add_ob(ObDecl {
377                    id: entity,
378                    ob_type: ObType::Basic("Entity".into()),
379                })
380                .is_ok()
381        );
382        assert!(
383            model
384                .add_ob(ObDecl {
385                    id: attr_type,
386                    ob_type: ObType::Basic("AttrType".into()),
387                })
388                .is_ok()
389        );
390        assert!(
391            model
392                .add_mor(MorDecl {
393                    id: attr,
394                    mor_type: MorType::Basic("Attr".into()),
395                    dom: Some(Ob::Basic(entity)),
396                    cod: Some(Ob::Basic(attr_type)),
397                })
398                .is_ok()
399        );
400        model
401    }
402
403    #[test]
404    fn model_schema() {
405        let th = ThSchema::new().theory();
406        let [a, x, y] = [Uuid::now_v7(), Uuid::now_v7(), Uuid::now_v7()];
407        let model = sch_walking_attr(&th, [a, x, y]);
408
409        assert_eq!(model.has_ob(Ob::Basic(x)), Ok(true));
410        assert_eq!(model.has_mor(Mor::Basic(a)), Ok(true));
411        assert_eq!(model.objects().len(), 2);
412        assert_eq!(model.morphisms().len(), 1);
413        assert_eq!(model.objects_with_type(ObType::Basic("Entity".into())), Ok(vec![Ob::Basic(x)]));
414        assert_eq!(
415            model.morphisms_with_type(MorType::Basic("Attr".into())),
416            Ok(vec![Mor::Basic(a)])
417        );
418        assert_eq!(model.validate().0, JsResult::Ok(()));
419
420        let mut model = DblModel::new(&th);
421        assert!(
422            model
423                .add_mor(MorDecl {
424                    id: a,
425                    mor_type: MorType::Basic("Attr".into()),
426                    dom: None,
427                    cod: Some(Ob::Basic(y)),
428                })
429                .is_ok()
430        );
431        let JsResult::Err(errs) = model.validate().0 else {
432            panic!("Model should not validate")
433        };
434        assert_eq!(errs.len(), 2);
435    }
436
437    #[test]
438    fn model_category_links() {
439        let th = ThCategoryLinks::new().theory();
440        let mut model = DblModel::new(&th);
441        let [f, x, y, link] = [Uuid::now_v7(), Uuid::now_v7(), Uuid::now_v7(), Uuid::now_v7()];
442        assert!(
443            model
444                .add_ob(ObDecl {
445                    id: x,
446                    ob_type: ObType::Basic("Object".into()),
447                })
448                .is_ok()
449        );
450        assert!(
451            model
452                .add_ob(ObDecl {
453                    id: y,
454                    ob_type: ObType::Basic("Object".into()),
455                })
456                .is_ok()
457        );
458        assert!(
459            model
460                .add_mor(MorDecl {
461                    id: f,
462                    mor_type: MorType::Hom(Box::new(ObType::Basic("Object".into()))),
463                    dom: Some(Ob::Basic(x)),
464                    cod: Some(Ob::Basic(y)),
465                })
466                .is_ok()
467        );
468        assert!(
469            model
470                .add_mor(MorDecl {
471                    id: link,
472                    mor_type: MorType::Basic("Link".into()),
473                    dom: Some(Ob::Basic(x)),
474                    cod: Some(Ob::Tabulated(Mor::Basic(f))),
475                })
476                .is_ok()
477        );
478        assert_eq!(model.objects().len(), 2);
479        assert_eq!(model.morphisms().len(), 2);
480        assert_eq!(model.validate().0, JsResult::Ok(()));
481    }
482}