Skip to main content

jeff/
jeff.rs

1//! Current definition of the jeff format.
2//!
3//! This thin wrapper over the Cap'n Proto-generated code provides a safe
4//! interface to load and store jeff files, converting old versions to the
5//! current one as needed.
6
7use capnp::message::TypedReader;
8use capnp::serialize::{BufferSegments, OwnedSegments};
9
10use crate::capnp::jeff_capnp;
11use crate::reader::{Module, ReadJeff};
12use crate::JeffError;
13
14/// Copy-on-write representation of jeff programs.
15///
16/// This thin wrapper over the Cap'n Proto-generated code provides a safe
17/// interface to load and store jeff files, converting old versions to the
18/// current one as needed.
19#[derive(Debug, Clone)]
20pub struct Jeff<'a> {
21    /// Internal representation of the jeff file.
22    module: JeffCow<'a>,
23}
24
25/// A [`Cow`]-like enum for jeff programs that may be borrowed from a slice or
26/// encoded in an owned buffer.
27enum JeffCow<'a> {
28    /// A borrowed jeff program.
29    Borrowed(TypedReader<BufferSegments<&'a [u8]>, jeff_capnp::module::Owned>),
30    /// An owned jeff program.
31    Owned(TypedReader<OwnedSegments, jeff_capnp::module::Owned>),
32}
33
34impl<'a> Jeff<'a> {
35    /// Current version of the jeff format.
36    ///
37    /// Loading a jeff file with a previous version will automatically upgrade it
38    /// to this version.
39    pub const VERSION: semver::Version = crate::SCHEMA_VERSION;
40
41    /// The minimum version of the jeff format supported by this reader.
42    pub const MIN_COMPATIBLE_VERSION: semver::Version = semver::Version::new(0, 3, 0);
43    /// The maximum version of the jeff format supported by this reader.
44    pub const MAX_COMPATIBLE_VERSION: semver::Version = semver::Version::new(0, 3, u64::MAX);
45
46    /// Read a jeff program from a slice without copying the data.
47    ///
48    /// The data is not copied, but the buffer must outlive the jeff object.
49    /// After this call, the slice will be advanced to the end of the jeff data.
50    pub fn read_slice(slice: &mut &'a [u8]) -> Result<Self, JeffError> {
51        let reader = capnp::serialize::read_message_from_flat_slice(
52            slice,
53            capnp::message::ReaderOptions::new(),
54        )?;
55        let module = reader.into_typed::<jeff_capnp::module::Owned>();
56
57        // Ensure the root type is correct.
58        module.get()?;
59
60        let slf = Self {
61            module: JeffCow::Borrowed(module),
62        };
63        slf.check_version()?;
64        Ok(slf)
65    }
66
67    /// Load a jeff program from a reader.
68    ///
69    /// This will consume the reader and copy the data into an internal buffer.
70    /// For a zero-copy version, use [`Jeff::read_slice`].
71    ///
72    /// For optimal performance, `reader` should be a buffered reader type.
73    pub fn read(reader: impl std::io::Read) -> Result<Self, JeffError> {
74        let reader = capnp::serialize::read_message(reader, capnp::message::ReaderOptions::new())?;
75        let module = reader.into_typed::<jeff_capnp::module::Owned>();
76
77        // Ensure the root type is correct.
78        module.get()?;
79
80        let slf = Self {
81            module: JeffCow::Owned(module),
82        };
83        slf.check_version()?;
84        Ok(slf)
85    }
86
87    /// Check if the schema version is compatible with the current version.
88    ///
89    /// The version must be between [`Self::MIN_COMPATIBLE_VERSION`] and [`Self::MAX_COMPATIBLE_VERSION`].
90    //
91    // TODO: Upgrade older versions to the current one.
92    fn check_version(&self) -> Result<(), JeffError> {
93        let version = self.module().version();
94
95        if version < Self::MIN_COMPATIBLE_VERSION {
96            return Err(JeffError::VersionTooOld {
97                v: version,
98                min: Self::MIN_COMPATIBLE_VERSION.to_string(),
99            });
100        }
101        if version > Self::MAX_COMPATIBLE_VERSION {
102            // User-friendly formatting of the maximum compatible version.
103            let x_if_max = |v: u64| match v {
104                u64::MAX => "x".to_string(),
105                _ => v.to_string(),
106            };
107            let max = format!(
108                "{}.{}.{}",
109                x_if_max(Self::MAX_COMPATIBLE_VERSION.major),
110                x_if_max(Self::MAX_COMPATIBLE_VERSION.minor),
111                x_if_max(Self::MAX_COMPATIBLE_VERSION.patch)
112            );
113            return Err(JeffError::VersionTooNew { v: version, max });
114        }
115        Ok(())
116    }
117}
118
119impl ReadJeff for Jeff<'_> {
120    fn module(&self) -> Module<'_> {
121        Module::read_capnp(self.module.module())
122    }
123}
124
125impl JeffCow<'_> {
126    /// Get a reference to the internal jeff module.
127    pub fn module(&self) -> jeff_capnp::module::Reader<'_> {
128        match self {
129            Self::Borrowed(module) => module.get().expect("Root type should be correct"),
130            Self::Owned(module) => module.get().expect("Root type should be correct"),
131        }
132    }
133}
134
135impl Clone for JeffCow<'_> {
136    fn clone(&self) -> Self {
137        todo!()
138    }
139}
140
141impl std::fmt::Debug for JeffCow<'_> {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        match self {
144            Self::Borrowed(_) => f.debug_tuple("JeffCow::Borrowed").finish_non_exhaustive(),
145            Self::Owned(_) => f.debug_tuple("JeffCow::Owned").finish_non_exhaustive(),
146        }
147    }
148}
149
150#[cfg(test)]
151mod test {
152    use super::*;
153    use crate::test::entangled_qs;
154    use rstest::rstest;
155
156    #[rstest]
157    fn simple_jeff(entangled_qs: Jeff<'static>) {
158        entangled_qs.check_version().unwrap();
159    }
160}