Skip to main content

mecha10_core/schema/
versioning.rs

1//! Versioned schema operations built on top of [`super::SchemaRegistryExt`].
2
3use super::{CompatibilityCheck, CompatibilityMode, MessageSchema, SchemaRegistryExt};
4use crate::context::Context;
5use crate::error::{Mecha10Error, Result};
6use serde::{Deserialize, Serialize};
7
8/// Message version metadata
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct MessageVersionInfo {
11    /// Schema name
12    pub name: String,
13
14    /// Current version being used
15    pub current_version: u32,
16
17    /// Latest available version
18    pub latest_version: u32,
19
20    /// Whether current version is deprecated
21    pub deprecated: bool,
22
23    /// Deprecation message if applicable
24    pub deprecation_message: Option<String>,
25
26    /// Minimum supported version
27    pub min_supported_version: u32,
28
29    /// Compatibility mode for this message type
30    pub compatibility_mode: CompatibilityMode,
31}
32
33/// Extension trait for versioned schema operations
34pub trait SchemaVersioningExt: SchemaRegistryExt {
35    /// Check compatibility between two schema versions
36    fn check_compatibility(
37        &self,
38        name: &str,
39        old_version: u32,
40        new_version: u32,
41        mode: CompatibilityMode,
42    ) -> impl std::future::Future<Output = Result<CompatibilityCheck>> + Send;
43
44    /// Register a new schema version with compatibility check
45    fn register_versioned_schema(
46        &self,
47        schema: &MessageSchema,
48        mode: CompatibilityMode,
49    ) -> impl std::future::Future<Output = Result<CompatibilityCheck>> + Send;
50
51    /// Get version info for a message type
52    fn get_version_info(
53        &self,
54        name: &str,
55    ) -> impl std::future::Future<Output = Result<Option<MessageVersionInfo>>> + Send;
56
57    /// Mark a schema version as deprecated
58    fn deprecate_version(
59        &self,
60        name: &str,
61        version: u32,
62        message: String,
63    ) -> impl std::future::Future<Output = Result<()>> + Send;
64
65    /// Check if using a deprecated version
66    fn is_version_deprecated(&self, name: &str, version: u32)
67        -> impl std::future::Future<Output = Result<bool>> + Send;
68}
69
70impl SchemaVersioningExt for Context {
71    async fn check_compatibility(
72        &self,
73        name: &str,
74        old_version: u32,
75        new_version: u32,
76        mode: CompatibilityMode,
77    ) -> Result<CompatibilityCheck> {
78        let old_schema = self
79            .get_schema(name, old_version)
80            .await?
81            .ok_or_else(|| Mecha10Error::Other(format!("Schema {} v{} not found", name, old_version)))?;
82
83        let new_schema = self
84            .get_schema(name, new_version)
85            .await?
86            .ok_or_else(|| Mecha10Error::Other(format!("Schema {} v{} not found", name, new_version)))?;
87
88        Ok(CompatibilityCheck::check(&old_schema, &new_schema, mode))
89    }
90
91    async fn register_versioned_schema(
92        &self,
93        schema: &MessageSchema,
94        mode: CompatibilityMode,
95    ) -> Result<CompatibilityCheck> {
96        // Get previous version if exists
97        if schema.version > 1 {
98            let prev_version = schema.version - 1;
99            let check = self
100                .check_compatibility(&schema.name, prev_version, schema.version, mode)
101                .await?;
102
103            if !check.compatible {
104                return Err(Mecha10Error::Other(format!(
105                    "Schema compatibility check failed: {:?}",
106                    check.incompatibilities
107                )));
108            }
109
110            // Register the schema
111            self.register_schema(schema).await?;
112
113            Ok(check)
114        } else {
115            // First version - no compatibility check needed
116            self.register_schema(schema).await?;
117
118            Ok(CompatibilityCheck {
119                compatible: true,
120                incompatibilities: Vec::new(),
121                evolutions: Vec::new(),
122                warnings: Vec::new(),
123            })
124        }
125    }
126
127    async fn get_version_info(&self, name: &str) -> Result<Option<MessageVersionInfo>> {
128        let latest = self.get_latest_schema(name).await?;
129
130        if let Some(latest_schema) = latest {
131            // Check for deprecation metadata
132            let deprecated = latest_schema.metadata.contains_key("deprecated");
133            let deprecation_message = latest_schema.metadata.get("deprecation_message").cloned();
134            let min_supported = latest_schema
135                .metadata
136                .get("min_supported_version")
137                .and_then(|v| v.parse().ok())
138                .unwrap_or(1);
139
140            let compatibility_mode = latest_schema
141                .metadata
142                .get("compatibility_mode")
143                .and_then(|m| serde_json::from_str(m).ok())
144                .unwrap_or(CompatibilityMode::Backward);
145
146            Ok(Some(MessageVersionInfo {
147                name: name.to_string(),
148                current_version: latest_schema.version,
149                latest_version: latest_schema.version,
150                deprecated,
151                deprecation_message,
152                min_supported_version: min_supported,
153                compatibility_mode,
154            }))
155        } else {
156            Ok(None)
157        }
158    }
159
160    async fn deprecate_version(&self, name: &str, version: u32, message: String) -> Result<()> {
161        let mut schema = self
162            .get_schema(name, version)
163            .await?
164            .ok_or_else(|| Mecha10Error::Other(format!("Schema {} v{} not found", name, version)))?;
165
166        schema.metadata.insert("deprecated".to_string(), "true".to_string());
167        schema.metadata.insert("deprecation_message".to_string(), message);
168
169        self.register_schema(&schema).await
170    }
171
172    async fn is_version_deprecated(&self, name: &str, version: u32) -> Result<bool> {
173        let schema = self
174            .get_schema(name, version)
175            .await?
176            .ok_or_else(|| Mecha10Error::Other(format!("Schema {} v{} not found", name, version)))?;
177
178        Ok(schema.metadata.contains_key("deprecated"))
179    }
180}