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