Skip to main content

sim_lib_doc_core/
model.rs

1//! Plain document records shared by office codecs, stores, views, and sites.
2
3use sim_kernel::{Cx, Object, Result, Value};
4
5/// Prose article document kind reserved by the office core vocabulary.
6pub const DOC_KIND_ARTICLE: &str = "article";
7/// Prose report document kind reserved by the office core vocabulary.
8pub const DOC_KIND_REPORT: &str = "report";
9/// Readme document kind reserved by the office core vocabulary.
10pub const DOC_KIND_README: &str = "readme";
11
12/// An office-family document carried as runtime data.
13#[derive(Clone, Debug, PartialEq)]
14pub struct Doc {
15    /// Open document kind string.
16    pub kind: DocKind,
17    /// Stable document id.
18    pub id: DocId,
19    /// Opaque runtime body owned by the domain layer.
20    pub body: Value,
21    /// External records this document came from or syncs with.
22    pub origin: Vec<ExternalRef>,
23}
24
25impl Doc {
26    /// Build a document record.
27    #[must_use]
28    pub fn new(kind: DocKind, id: DocId, body: Value, origin: Vec<ExternalRef>) -> Self {
29        Self {
30            kind,
31            id,
32            body,
33            origin,
34        }
35    }
36}
37
38impl Object for Doc {
39    fn display(&self, _cx: &mut Cx) -> Result<String> {
40        Ok(format!("#<doc {} {}>", self.kind.0, self.id.0))
41    }
42
43    fn as_any(&self) -> &dyn std::any::Any {
44        self
45    }
46}
47
48impl sim_kernel::ObjectCompat for Doc {}
49
50/// Open document kind name.
51#[derive(
52    Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
53)]
54pub struct DocKind(pub String);
55
56impl DocKind {
57    /// Build a document kind from an open string.
58    #[must_use]
59    pub fn new(kind: impl Into<String>) -> Self {
60        Self(kind.into())
61    }
62
63    /// Borrow the kind string.
64    #[must_use]
65    pub fn as_str(&self) -> &str {
66        &self.0
67    }
68}
69
70/// Stable document identifier.
71#[derive(
72    Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
73)]
74pub struct DocId(pub String);
75
76impl DocId {
77    /// Build a document id.
78    #[must_use]
79    pub fn new(id: impl Into<String>) -> Self {
80        Self(id.into())
81    }
82
83    /// Borrow the id string.
84    #[must_use]
85    pub fn as_str(&self) -> &str {
86        &self.0
87    }
88}
89
90/// Reference to an external file, service object, row, task, or source record.
91#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
92pub struct ExternalRef {
93    /// Backend namespace such as a codec or site.
94    pub backend: String,
95    /// Backend-local id.
96    pub external_id: String,
97    /// Optional backend version, revision, row version, ETag, or content hash.
98    pub version: Option<String>,
99    /// Optional browser-facing URL.
100    pub web_url: Option<String>,
101}
102
103impl ExternalRef {
104    /// Build an external reference.
105    #[must_use]
106    pub fn new(
107        backend: impl Into<String>,
108        external_id: impl Into<String>,
109        version: Option<String>,
110        web_url: Option<String>,
111    ) -> Self {
112        Self {
113            backend: backend.into(),
114            external_id: external_id.into(),
115            version,
116            web_url,
117        }
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn doc_json_round_trip() {
127        let refs = vec![ExternalRef::new(
128            "codec/plain",
129            "doc-1",
130            Some("rev-2".to_owned()),
131            Some("https://example.com/doc-1".to_owned()),
132        )];
133        let encoded = serde_json::to_string(&refs).unwrap();
134        let decoded: Vec<ExternalRef> = serde_json::from_str(&encoded).unwrap();
135        assert_eq!(decoded, refs);
136    }
137
138    #[test]
139    fn prose_kind_constants_are_reserved() {
140        assert_eq!(DocKind::new(DOC_KIND_ARTICLE).as_str(), "article");
141        assert_eq!(DocKind::new(DOC_KIND_REPORT).as_str(), "report");
142        assert_eq!(DocKind::new(DOC_KIND_README).as_str(), "readme");
143    }
144}