1use serde::{Deserialize, Serialize};
2use std::num::NonZeroU32;
3
4#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
16#[serde(deny_unknown_fields)]
17pub struct SchemaMetadata {
18 #[serde(deserialize_with = "crate::cbor::deserialize_present_option")]
20 schema_version: Option<NonZeroU32>,
21}
22
23impl SchemaMetadata {
24 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 #[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#[non_exhaustive]
53#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
54pub enum SchemaMetadataError {
55 #[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}