Skip to main content

kcode_k1_web_source_object/
lib.rs

1//! Capture canonical K1 Web source packages as Objects transactions.
2
3use std::fmt;
4use std::sync::Mutex;
5
6use kcode_k1_objects::{K1Objects, TxId};
7use kcode_k1_web_package::SourcePackage;
8
9/// The file type used for preserved source-package objects.
10const SOURCE_PACKAGE_FILE_TYPE: &str = "k1-web-source-package-v1";
11
12/// Captures the Objects transaction created while preserving a source package.
13pub struct SourceObjectCapture<'a> {
14    objects: &'a K1Objects,
15    captured: Mutex<Option<TxId>>,
16}
17
18impl<'a> SourceObjectCapture<'a> {
19    /// Creates an empty capture associated with `objects`.
20    pub fn new(objects: &'a K1Objects) -> Self {
21        Self {
22            objects,
23            captured: Mutex::new(None),
24        }
25    }
26
27    /// Encodes and saves `package`, recording the successful transaction ID.
28    pub fn preserve(&self, package: &SourcePackage) -> Result<(), String> {
29        let bytes = kcode_k1_web_transaction::encode(package).map_err(|error| error.to_string())?;
30        let mut captured = self
31            .captured
32            .lock()
33            .map_err(|_| CaptureStateError::Poisoned.to_string())?;
34        let id = self
35            .objects
36            .save("", SOURCE_PACKAGE_FILE_TYPE, "", &bytes)?;
37        *captured = Some(id);
38        Ok(())
39    }
40
41    /// Returns the captured transaction ID, or `None` if absent or poisoned.
42    pub fn captured(&self) -> Option<TxId> {
43        self.captured.lock().ok().and_then(|captured| *captured)
44    }
45
46    /// Consumes the capture and returns its recorded transaction ID.
47    pub fn finish(self) -> Result<TxId, CaptureStateError> {
48        self.captured
49            .into_inner()
50            .map_err(|_| CaptureStateError::Poisoned)?
51            .ok_or(CaptureStateError::Missing)
52    }
53}
54
55/// Failure to recover a captured source-object transaction ID.
56#[derive(Clone, Copy, Debug, Eq, PartialEq)]
57pub enum CaptureStateError {
58    /// The capture mutex was poisoned.
59    Poisoned,
60    /// Publication completed without capturing a source object.
61    Missing,
62}
63
64impl fmt::Display for CaptureStateError {
65    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
66        formatter.write_str(match self {
67            Self::Poisoned => "source object capture mutex poisoned",
68            Self::Missing => "publication completed without a captured source object",
69        })
70    }
71}
72
73impl std::error::Error for CaptureStateError {}
74
75#[cfg(test)]
76mod tests {
77    use super::CaptureStateError;
78
79    #[test]
80    fn capture_state_errors_have_stable_messages() {
81        assert_eq!(
82            CaptureStateError::Poisoned.to_string(),
83            "source object capture mutex poisoned"
84        );
85        assert_eq!(
86            CaptureStateError::Missing.to_string(),
87            "publication completed without a captured source object"
88        );
89    }
90}