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