Skip to main content

ic_memory/
schema.rs

1use serde::{Deserialize, Serialize};
2
3///
4/// SchemaMetadata
5///
6/// Optional diagnostic metadata for an in-place store schema.
7///
8/// This metadata helps humans and frameworks diagnose which schema version or
9/// fingerprint was declared in each generation. It is bounded and validated for
10/// durable ledger encoding, but it does not perform application schema
11/// migrations or validate stable data semantics.
12#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
13#[serde(deny_unknown_fields)]
14pub struct SchemaMetadata {
15    /// Optional in-place schema version.
16    pub schema_version: Option<u32>,
17    /// Optional opaque schema fingerprint.
18    pub schema_fingerprint: Option<String>,
19}
20
21impl SchemaMetadata {
22    /// Construct schema metadata after validating the persisted encoding bounds.
23    pub fn new(
24        schema_version: Option<u32>,
25        schema_fingerprint: Option<String>,
26    ) -> Result<Self, SchemaMetadataError> {
27        let metadata = Self {
28            schema_version,
29            schema_fingerprint,
30        };
31        metadata.validate()?;
32        Ok(metadata)
33    }
34
35    /// Validate schema metadata encoding rules.
36    pub fn validate(&self) -> Result<(), SchemaMetadataError> {
37        if self.schema_version == Some(0) {
38            return Err(SchemaMetadataError::InvalidVersion);
39        }
40
41        let Some(fingerprint) = &self.schema_fingerprint else {
42            return Ok(());
43        };
44
45        if fingerprint.is_empty() {
46            return Err(SchemaMetadataError::EmptyFingerprint);
47        }
48        if fingerprint.len() > 256 {
49            return Err(SchemaMetadataError::FingerprintTooLong);
50        }
51        if !fingerprint.is_ascii() {
52            return Err(SchemaMetadataError::NonAsciiFingerprint);
53        }
54        if fingerprint.bytes().any(|byte| byte.is_ascii_control()) {
55            return Err(SchemaMetadataError::ControlCharacterFingerprint);
56        }
57
58        Ok(())
59    }
60}
61
62///
63/// SchemaMetadataError
64///
65/// Schema metadata validation failure.
66#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
67pub enum SchemaMetadataError {
68    /// Schema version zero is reserved for absence.
69    #[error("schema_version must be greater than zero when present")]
70    InvalidVersion,
71    /// Present fingerprints must be non-empty.
72    #[error("schema_fingerprint must not be empty when present")]
73    EmptyFingerprint,
74    /// Fingerprints must stay bounded for durable ledger storage.
75    #[error("schema_fingerprint must be at most 256 bytes")]
76    FingerprintTooLong,
77    /// Fingerprints must not require Unicode normalization.
78    #[error("schema_fingerprint must be ASCII")]
79    NonAsciiFingerprint,
80    /// Fingerprints must be printable metadata.
81    #[error("schema_fingerprint must not contain ASCII control characters")]
82    ControlCharacterFingerprint,
83}