mecha10-core 0.6.2

Mecha10 core runtime and foundational types
Documentation
//! Versioned schema operations built on top of [`super::SchemaRegistryExt`].

use super::{CompatibilityCheck, CompatibilityMode, MessageSchema, SchemaRegistryExt};
use crate::context::Context;
use crate::error::{Mecha10Error, Result};
use serde::{Deserialize, Serialize};

/// Message version metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MessageVersionInfo {
    /// Schema name
    pub name: String,

    /// Current version being used
    pub current_version: u32,

    /// Latest available version
    pub latest_version: u32,

    /// Whether current version is deprecated
    pub deprecated: bool,

    /// Deprecation message if applicable
    pub deprecation_message: Option<String>,

    /// Minimum supported version
    pub min_supported_version: u32,

    /// Compatibility mode for this message type
    pub compatibility_mode: CompatibilityMode,
}

/// Extension trait for versioned schema operations
pub trait SchemaVersioningExt: SchemaRegistryExt {
    /// Check compatibility between two schema versions
    fn check_compatibility(
        &self,
        name: &str,
        old_version: u32,
        new_version: u32,
        mode: CompatibilityMode,
    ) -> impl std::future::Future<Output = Result<CompatibilityCheck>> + Send;

    /// Register a new schema version with compatibility check
    fn register_versioned_schema(
        &self,
        schema: &MessageSchema,
        mode: CompatibilityMode,
    ) -> impl std::future::Future<Output = Result<CompatibilityCheck>> + Send;

    /// Get version info for a message type
    fn get_version_info(
        &self,
        name: &str,
    ) -> impl std::future::Future<Output = Result<Option<MessageVersionInfo>>> + Send;

    /// Mark a schema version as deprecated
    fn deprecate_version(
        &self,
        name: &str,
        version: u32,
        message: String,
    ) -> impl std::future::Future<Output = Result<()>> + Send;

    /// Check if using a deprecated version
    fn is_version_deprecated(&self, name: &str, version: u32)
        -> impl std::future::Future<Output = Result<bool>> + Send;
}

impl SchemaVersioningExt for Context {
    async fn check_compatibility(
        &self,
        name: &str,
        old_version: u32,
        new_version: u32,
        mode: CompatibilityMode,
    ) -> Result<CompatibilityCheck> {
        let old_schema = self
            .get_schema(name, old_version)
            .await?
            .ok_or_else(|| Mecha10Error::Other(format!("Schema {} v{} not found", name, old_version)))?;

        let new_schema = self
            .get_schema(name, new_version)
            .await?
            .ok_or_else(|| Mecha10Error::Other(format!("Schema {} v{} not found", name, new_version)))?;

        Ok(CompatibilityCheck::check(&old_schema, &new_schema, mode))
    }

    async fn register_versioned_schema(
        &self,
        schema: &MessageSchema,
        mode: CompatibilityMode,
    ) -> Result<CompatibilityCheck> {
        // Get previous version if exists
        if schema.version > 1 {
            let prev_version = schema.version - 1;
            let check = self
                .check_compatibility(&schema.name, prev_version, schema.version, mode)
                .await?;

            if !check.compatible {
                return Err(Mecha10Error::Other(format!(
                    "Schema compatibility check failed: {:?}",
                    check.incompatibilities
                )));
            }

            // Register the schema
            self.register_schema(schema).await?;

            Ok(check)
        } else {
            // First version - no compatibility check needed
            self.register_schema(schema).await?;

            Ok(CompatibilityCheck {
                compatible: true,
                incompatibilities: Vec::new(),
                evolutions: Vec::new(),
                warnings: Vec::new(),
            })
        }
    }

    async fn get_version_info(&self, name: &str) -> Result<Option<MessageVersionInfo>> {
        let latest = self.get_latest_schema(name).await?;

        if let Some(latest_schema) = latest {
            // Check for deprecation metadata
            let deprecated = latest_schema.metadata.contains_key("deprecated");
            let deprecation_message = latest_schema.metadata.get("deprecation_message").cloned();
            let min_supported = latest_schema
                .metadata
                .get("min_supported_version")
                .and_then(|v| v.parse().ok())
                .unwrap_or(1);

            let compatibility_mode = latest_schema
                .metadata
                .get("compatibility_mode")
                .and_then(|m| serde_json::from_str(m).ok())
                .unwrap_or(CompatibilityMode::Backward);

            Ok(Some(MessageVersionInfo {
                name: name.to_string(),
                current_version: latest_schema.version,
                latest_version: latest_schema.version,
                deprecated,
                deprecation_message,
                min_supported_version: min_supported,
                compatibility_mode,
            }))
        } else {
            Ok(None)
        }
    }

    async fn deprecate_version(&self, name: &str, version: u32, message: String) -> Result<()> {
        let mut schema = self
            .get_schema(name, version)
            .await?
            .ok_or_else(|| Mecha10Error::Other(format!("Schema {} v{} not found", name, version)))?;

        schema.metadata.insert("deprecated".to_string(), "true".to_string());
        schema.metadata.insert("deprecation_message".to_string(), message);

        self.register_schema(&schema).await
    }

    async fn is_version_deprecated(&self, name: &str, version: u32) -> Result<bool> {
        let schema = self
            .get_schema(name, version)
            .await?
            .ok_or_else(|| Mecha10Error::Other(format!("Schema {} v{} not found", name, version)))?;

        Ok(schema.metadata.contains_key("deprecated"))
    }
}