Skip to main content

ic_memory/
schema.rs

1use serde::{Deserialize, Serialize};
2use std::num::NonZeroU32;
3
4///
5/// SchemaMetadata
6///
7/// Optional diagnostic metadata for an in-place store schema.
8///
9/// This metadata helps humans and frameworks diagnose which schema version was
10/// most recently declared. Construction and decoding require a nonzero
11/// version when present. It does not perform application schema migrations or
12/// validate stable data semantics.
13///
14
15#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
16#[serde(deny_unknown_fields)]
17pub struct SchemaMetadata {
18    /// Optional in-place schema version.
19    #[serde(deserialize_with = "crate::cbor::deserialize_present_option")]
20    schema_version: Option<NonZeroU32>,
21}
22
23impl SchemaMetadata {
24    /// Construct schema metadata after validating the persisted encoding bounds.
25    pub const fn new(schema_version: Option<u32>) -> Result<Self, SchemaMetadataError> {
26        let schema_version = match schema_version {
27            Some(version) => match NonZeroU32::new(version) {
28                Some(version) => Some(version),
29                None => return Err(SchemaMetadataError::InvalidVersion),
30            },
31            None => None,
32        };
33        Ok(Self { schema_version })
34    }
35
36    /// Return the optional in-place schema version.
37    #[must_use]
38    pub const fn schema_version(&self) -> Option<u32> {
39        match self.schema_version {
40            Some(version) => Some(version.get()),
41            None => None,
42        }
43    }
44}
45
46///
47/// SchemaMetadataError
48///
49/// Schema metadata validation failure.
50///
51
52#[non_exhaustive]
53#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
54pub enum SchemaMetadataError {
55    /// Schema version zero is reserved for absence.
56    #[error("schema_version must be greater than zero when present")]
57    InvalidVersion,
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn schema_versions_are_checked_and_preserve_current_encoding() {
66        assert_eq!(
67            SchemaMetadata::new(Some(0)),
68            Err(SchemaMetadataError::InvalidVersion)
69        );
70        for (version, suffix) in [
71            (None, vec![0xf6]),
72            (Some(1), vec![1]),
73            (Some(24), vec![0x18, 24]),
74            (Some(u32::MAX), vec![0x1a, 0xff, 0xff, 0xff, 0xff]),
75        ] {
76            let metadata = SchemaMetadata::new(version).unwrap();
77            assert_eq!(metadata.schema_version(), version);
78            let json = serde_json::json!({ "schema_version": version });
79            assert_eq!(serde_json::to_value(&metadata).unwrap(), json);
80            assert_eq!(
81                serde_json::from_value::<SchemaMetadata>(json).unwrap(),
82                metadata
83            );
84            let mut expected = b"\xa1\x6eschema_version".to_vec();
85            expected.extend(suffix);
86            let bytes = crate::test_cbor::to_vec(&metadata).unwrap();
87            assert_eq!(bytes, expected);
88            assert_eq!(
89                crate::test_cbor::from_slice::<SchemaMetadata>(&bytes).unwrap(),
90                metadata
91            );
92        }
93        assert_eq!(
94            SchemaMetadata::default(),
95            SchemaMetadata::new(None).unwrap()
96        );
97    }
98
99    #[test]
100    fn schema_decode_rejects_invalid_versions_and_shapes() {
101        for value in [
102            serde_json::json!({ "schema_version": 0 }),
103            serde_json::json!({ "schema_version": -1 }),
104            serde_json::json!({ "schema_version": u64::from(u32::MAX) + 1 }),
105            serde_json::json!({ "schema_version": "1" }),
106            serde_json::json!({}),
107            serde_json::json!({ "schema_version": null, "extra": 1 }),
108        ] {
109            assert!(serde_json::from_value::<SchemaMetadata>(value.clone()).is_err());
110            let bytes = crate::test_cbor::to_vec(&value).unwrap();
111            assert!(crate::test_cbor::from_slice::<SchemaMetadata>(&bytes).is_err());
112        }
113
114        let mut value = serde_json::to_value(
115            crate::AllocationDeclaration::memory_manager("app.rows.v1", 100, "rows").unwrap(),
116        )
117        .unwrap();
118        value["schema"]["schema_version"] = 0.into();
119        assert!(serde_json::from_value::<crate::AllocationDeclaration>(value.clone()).is_err());
120        let bytes = crate::test_cbor::to_vec(&value).unwrap();
121        assert!(crate::test_cbor::from_slice::<crate::AllocationDeclaration>(&bytes).is_err());
122    }
123}