Skip to main content

sim_lib_doc_core/
shape.rs

1//! Shape values for open document kinds.
2
3use std::sync::Arc;
4
5use sim_kernel::{
6    Cx, DefaultFactory, Expr, Factory, ShapeRef, Symbol, Value,
7    shape::{MatchScore, Shape, ShapeDoc, ShapeMatch},
8};
9
10use crate::OfficeError;
11use crate::model::{Doc, DocKind};
12
13/// A shape that accepts [`Doc`] values with one open [`DocKind`].
14#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct DocKindShape {
16    kind: DocKind,
17}
18
19impl DocKindShape {
20    /// Build a shape for one document kind.
21    #[must_use]
22    pub fn new(kind: DocKind) -> Self {
23        Self { kind }
24    }
25
26    /// Borrow the document kind matched by this shape.
27    #[must_use]
28    pub fn kind(&self) -> &DocKind {
29        &self.kind
30    }
31}
32
33/// Build a kernel shape value for one open document kind.
34pub fn doc_shape(kind: &DocKind) -> Result<ShapeRef, OfficeError> {
35    DefaultFactory
36        .opaque(Arc::new(DocKindShape::new(kind.clone())))
37        .map_err(|err| OfficeError::ShapeBuild(err.to_string()))
38}
39
40impl Shape for DocKindShape {
41    fn symbol(&self) -> Option<Symbol> {
42        Some(Symbol::qualified("office/doc", self.kind.0.clone()))
43    }
44
45    fn check_value(&self, _cx: &mut Cx, value: Value) -> sim_kernel::Result<ShapeMatch> {
46        let Some(doc) = value.object().downcast_ref::<Doc>() else {
47            return Ok(ShapeMatch::reject(format!(
48                "expected {} document value",
49                self.kind.0
50            )));
51        };
52        if doc.kind == self.kind {
53            Ok(ShapeMatch::accept(MatchScore::exact(20)))
54        } else {
55            Ok(ShapeMatch::reject(format!(
56                "expected {} document value, found {}",
57                self.kind.0, doc.kind.0
58            )))
59        }
60    }
61
62    fn check_expr(&self, _cx: &mut Cx, _expr: &Expr) -> sim_kernel::Result<ShapeMatch> {
63        Ok(ShapeMatch::reject(format!(
64            "expected {} document value",
65            self.kind.0
66        )))
67    }
68
69    fn describe(&self, _cx: &mut Cx) -> sim_kernel::Result<ShapeDoc> {
70        Ok(ShapeDoc::new(format!("office document {}", self.kind.0))
71            .with_detail("matches Doc values by open DocKind string"))
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use std::sync::Arc;
78
79    use sim_kernel::{DefaultFactory, NoopEvalPolicy};
80
81    use super::*;
82
83    #[test]
84    fn doc_shape_is_object_accessible() {
85        let value = doc_shape(&DocKind::new("sheet")).unwrap();
86        assert!(value.object().as_shape().is_some());
87    }
88
89    #[test]
90    fn doc_shape_matches_document_kind() {
91        let mut cx = Cx::new(
92            Arc::new(NoopEvalPolicy),
93            Arc::new(DefaultFactory),
94            sim_kernel::HandleSeed::new(0xfae8_8f06_e204_cabf),
95        );
96        let body = cx.factory().nil().unwrap();
97        let doc = Doc::new(
98            DocKind::new("report"),
99            crate::DocId::new("r1"),
100            body,
101            vec![],
102        );
103        let value = cx.factory().opaque(Arc::new(doc)).unwrap();
104        let shape_value = doc_shape(&DocKind::new("report")).unwrap();
105        let shape = shape_value.object().as_shape().unwrap();
106
107        assert!(shape.check_value(&mut cx, value).unwrap().accepted);
108        assert_eq!(
109            shape.describe(&mut cx).unwrap().name,
110            "office document report"
111        );
112    }
113}