1use 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#[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(Uuid),
29
30 Tabulated(Mor),
32}
33
34#[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(Uuid),
41
42 Composite(Box<Path<Ob, Mor>>),
44
45 TabulatorSquare {
47 dom: Box<Mor>,
48 cod: Box<Mor>,
49 pre: Box<Mor>,
50 post: Box<Mor>,
51 },
52}
53
54impl From<Uuid> for Ob {
56 fn from(value: Uuid) -> Self {
57 Ob::Basic(value)
58 }
59}
60
61impl 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
72impl 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
84impl 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
100impl 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
112impl 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
160impl 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
170impl 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#[derive(Serialize, Deserialize, Tsify)]
202#[tsify(into_wasm_abi, from_wasm_abi, missing_as_null)]
203pub struct ObDecl {
204 pub id: Uuid,
206
207 #[serde(rename = "obType")]
209 pub ob_type: ObType,
210}
211
212#[derive(Serialize, Deserialize, Tsify)]
214#[tsify(into_wasm_abi, from_wasm_abi, missing_as_null)]
215pub struct MorDecl {
216 pub id: Uuid,
218
219 #[serde(rename = "morType")]
221 pub mor_type: MorType,
222
223 pub dom: Option<Ob>,
225
226 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#[derive(From, TryInto)]
239#[try_into(ref)]
240pub enum DblModelBox {
241 Discrete(DiscreteDblModel),
242 DiscreteTab(DiscreteTabModel),
243}
244
245#[wasm_bindgen]
247pub struct DblModel(#[wasm_bindgen(skip)] pub DblModelBox);
248
249#[wasm_bindgen]
250impl DblModel {
251 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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#[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}