Rust插件库revision的使用:高效版本控制与代码变更管理工具

use revision::Error;
use revision::revisioned;

// 测试结构体当前版本为3
#[derive(Debug, PartialEq)]
#[revisioned(revision = 3)]
pub struct TestStruct {
    a: u32,
    #[revision(start = 2, end = 3, convert_fn = "convert_b")]
    b: u8,
    #[revision(start = 3)]
    c: u64,
    #[revision(start = 3, default_fn = "default_c")]
    d: String,
}

impl TestStruct {
    // 用于为新添加的字段设置默认值
    fn default_c(_revision: u16) -> String {
        "test_string".to_owned()
    }
    // 用于将字段从旧版本转换到最新版本
    fn convert_b(&mut self, _revision: u16, value: u8) -> Result<(), Error> {
        self.c = value as u64;
        Ok(())
    }
}

// 测试枚举当前版本为3
#[derive(Debug, PartialEq)]
#[revisioned(revision = 3)]
pub enum TestEnum {
    #[revision(end = 2, convert_fn = "upgrade_zero")]
    Zero,
    #[revision(end = 2, convert_fn = "upgrade_one")]
    One(u32),
    #[revision(start = 2)]
    Two(u64),
    #[revision(start = 2)]
    Three {
        a: i64,
        #[revision(end = 2, convert_fn = "upgrade_three_b")]
        b: f32,
        #[revision(start = 2)]
        c: rust_decimal::Decimal,
        #[revision(start = 3)]
        d: String
    },
}

impl TestEnum {
    // 用于将旧的枚举变体转换为新的变体
    fn upgrade_zero((): ()) -> Result<TestEnum, Error> {
        Ok(Self::Two(0))
    }
    // 用于将旧的枚举变体转换为新的变体
    fn upgrade_one((v0,): (u32,)) -> Result<TestEnum, Error> {
        Ok(Self::Two(v0 as u64))
    }
    // 用于将字段从旧版本转换到最新版本
    fn upgrade_three_b(&mut self, _revision: u16, value: f32) -> Result<(), Error> {
        match self {
            TestEnum::Three {
                ref mut c,
                ..
            } => {
                *c = value.into();
            }
            _ => unreachable!(),
        }
        Ok(())
    }
}

// 完整示例代码
use revision::{Revisioned, Error};
use std::io::{Cursor, Read, Write};

fn main() -> Result<(), Error> {
    // 创建一个测试结构体实例
    let mut test_struct = TestStruct {
        a: 42,
        b: 10,
        c: 100,
        d: "example".to_string(),
    };

    // 序列化到字节缓冲区
    let mut buffer = Vec::new();
    test_struct.serialize_revisioned(&mut buffer)?;

    // 从字节缓冲区反序列化
    let mut cursor = Cursor::new(buffer);
    let deserialized: TestStruct = Revisioned::deserialize_revisioned(&mut cursor)?;

    println!("Original: {:?}", test_struct);
    println!("Deserialized: {:?}", deserialized);

    // 测试枚举
    let test_enum = TestEnum::Two(123);
    let mut enum_buffer = Vec::new();
    test_enum.serialize_revisioned(&mut enum_buffer)?;

    let mut enum_cursor = Cursor::new(enum_buffer);
    let deserialized_enum: TestEnum = Revisioned::deserialize_revisioned(&mut enum_cursor)?;

    println!("Enum original: {:?}", test_enum);
    println!("Enum deserialized: {:?}", deserialized_enum);

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_struct_serialization() -> Result<(), Error> {
        let original = TestStruct {
            a: 100,
            b: 5,
            c: 50,
            d: "test".to_string(),
        };

        let mut buffer = Vec::new();
        original.serialize_revisioned(&mut buffer)?;

        let mut cursor = Cursor::new(buffer);
        let deserialized = TestStruct::deserialize_revisioned(&mut cursor)?;

        assert_eq!(original, deserialized);
        Ok(())
    }

    #[test]
    fn test_enum_serialization() -> Result<(), Error> {
        let original = TestEnum::Three {
            a: -42,
            b: 3.14,
            c: rust_decimal::Decimal::new(12345, 2),
            d: "enum_test".to_string(),
        };

        let mut buffer = Vec::new();
        original.serialize_revisioned(&mut buffer)?;

        let mut cursor = Cursor::new(buffer);
        let deserialized = TestEnum::deserialize_revisioned(&mut cursor)?;

        if let (TestEnum::Three { a: a1, c: c1, d: d1, .. }, TestEnum::Three { a: a2, c: c2, d: d2, .. }) = (&original, &deserialized) {
            assert_eq!(a1, a2);
            assert_eq!(c1, c2);
            assert_eq!(d1, d2);
        } else {
            panic!("Enum variants don't match");
        }

        Ok(())
    }
}

1 回复

Rust插件库revision的使用:高效版本控制与代码变更管理工具

简介

revision是一个专为Rust项目设计的轻量级版本控制和代码变更管理工具。它通过简单的宏和配置,帮助开发者跟踪代码变更、管理版本迁移,并支持多版本代码共存。

核心特性

  • 轻量级版本标记系统
  • 自动版本迁移管理
  • 支持并行版本共存
  • 零运行时开销
  • 与Cargo工作流无缝集成

安装方法

在Cargo.toml中添加依赖:

[dependencies]
revision = "0.3"

基本使用方法

1. 版本标记

use revision::revisioned;

#[revisioned]
struct User {
    #[rev(1)]
    id: u64,
    #[rev(1)]
    name: String,
    #[rev(2)]  // 新增字段
    email: Option<String>,
    #[rev(3)]  // 后续新增
    age: Option<u32>,
}

2. 版本迁移实现

use revision::Revisioned;

impl Revisioned for User {
    fn revision() -> u16 {
        3  // 当前最新版本
    }

    fn migrate_to(version: u16) -> Option<Box<dyn Fn(&mut Self)>> {
        match version {
            1 => Some(Box::new(|user| {
                // 从版本1迁移到最新
                user.email = None;
                user.age = None;
            })),
            2 => Some(Box::new(|user| {
                // 从版本2迁移到最新
                user.age = None;
            })),
            _ => None,
        }
    }
}

3. 数据迁移示例

let mut old_user = User {
    id: 1,
    name: "Alice".to_string(),
    email: None,
    age: None,
};

// 将旧版本数据迁移到最新版本
User::migrate(&mut old_user, 1);  // 从版本1迁移

4. 版本检查与验证

// 检查数据版本
let current_version = User::revision();

// 验证数据是否需要迁移
if old_user.version() < current_version {
    User::migrate(&mut old_user, old_user.version());
}

高级用法

多版本序列化

use serde::{Serialize, Deserialize};
use revision::revisioned;

#[revisioned]
#[derive(Serialize, Deserialize)]
struct Config {
    #[rev(1)]
    timeout: u32,
    #[rev(2)]
    retries: u8,
    #[rev(3)]
    enabled: bool,
}

// 根据不同版本进行序列化/反序列化

版本条件编译

#[revisioned]
enum Feature {
    #[rev(1)]
    Basic,
    #[rev(2)]
    Advanced,
    #[rev(3)]
    #[cfg(feature = "premium")]
    Premium,
}

最佳实践

  1. 版本规划:在添加新字段时递增版本号
  2. 向后兼容:确保旧版本数据能够正确迁移
  3. 测试验证:为每个版本迁移编写测试用例
  4. 文档记录:记录每个版本的变更内容

示例:完整使用场景

use revision::{revisioned, Revisioned};

#[revisioned]
struct DatabaseConfig {
    #[rev(1)]
    host: String,
    #[rev(1)]
    port: u16,
    #[rev(2)]
    username: String,
    #[rev(3)]
    password: String,
    #[rev(4)]
    ssl: bool,
}

fn main() {
    // 模拟旧版本配置
    let mut old_config = DatabaseConfig {
        host: "localhost".to_string(),
        port: 5432,
        username: "user".to_string(),
        password: "".to_string(),
        ssl: false,
    };
    
    // 设置版本标记(假设是版本2的数据)
    old_config.set_version(2);
    
    // 迁移到最新版本
    DatabaseConfig::migrate(&mut old_config, old_config.version());
    
    println!("迁移后的配置: {:?}", old_config);
}

完整示例demo

// 完整示例:用户数据版本迁移系统
use revision::{revisioned, Revisioned};
use serde::{Serialize, Deserialize};

// 定义版本化的用户结构体
#[revisioned]
#[derive(Debug, Serialize, Deserialize)]
struct User {
    #[rev(1)]  // 版本1引入
    id: u64,
    #[rev(1)]  // 版本1引入
    name: String,
    #[rev(2)]  // 版本2新增邮箱字段
    email: Option<String>,
    #[rev(3)]  // 版本3新增年龄字段
    age: Option<u32>,
    #[rev(4)]  // 版本4新增电话号码字段
    phone: Option<String>,
}

// 实现Revisioned trait
impl Revisioned for User {
    fn revision() -> u16 {
        4  // 当前最新版本
    }

    fn migrate_to(version: u16) -> Option<Box<dyn Fn(&mut Self)>> {
        match version {
            1 => Some(Box::new(|user| {
                // 从版本1迁移到最新:添加缺失字段的默认值
                user.email = None;
                user.age = None;
                user.phone = None;
            })),
            2 => Some(Box::new(|user| {
                // 从版本2迁移到最新:添加缺失字段的默认值
                user.age = None;
                user.phone = None;
            })),
            3 => Some(Box::new(|user| {
                // 从版本3迁移到最新:添加缺失字段的默认值
                user.phone = None;
            })),
            _ => None,
        }
    }
}

fn main() {
    println!("=== 用户数据版本迁移演示 ===");
    
    // 模拟版本1的用户数据(只有id和name)
    let mut user_v1 = User {
        id: 1,
        name: "张三".to_string(),
        email: None,
        age: None,
        phone: None,
    };
    user_v1.set_version(1);  // 标记为版本1
    
    println!("版本1用户数据: {:?}", user_v1);
    println!("当前数据版本: {}", user_v1.version());
    println!("最新结构体版本: {}", User::revision());
    
    // 检查并执行迁移
    if user_v1.version() < User::revision() {
        println!("检测到需要版本迁移...");
        User::migrate(&mut user_v1, user_v1.version());
        println!("迁移完成后的用户数据: {:?}", user_v1);
        println!("迁移后的数据版本: {}", user_v1.version());
    }
    
    // 模拟版本2的用户数据(有id、name、email)
    let mut user_v2 = User {
        id: 2,
        name: "李四".to_string(),
        email: Some("lisi@example.com".to_string()),
        age: None,
        phone: None,
    };
    user_v2.set_version(2);  // 标记为版本2
    
    println!("\n版本2用户数据迁移演示:");
    println!("迁移前: {:?}", user_v2);
    User::migrate(&mut user_v2, user_v2.version());
    println!("迁移后: {:?}", user_v2);
    
    // 序列化和反序列化演示
    println!("\n=== 序列化演示 ===");
    let serialized = serde_json::to_string(&user_v2).unwrap();
    println!("序列化结果: {}", serialized);
    
    let deserialized: User = serde_json::from_str(&serialized).unwrap();
    println!("反序列化后版本: {}", deserialized.version());
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_version_1_migration() {
        let mut user = User {
            id: 1,
            name: "测试用户".to_string(),
            email: None,
            age: None,
            phone: None,
        };
        user.set_version(1);
        
        // 执行迁移
        User::migrate(&mut user, user.version());
        
        assert_eq!(user.version(), 4);
        assert!(user.email.is_none());
        assert!(user.age.is_none());
        assert!(user.phone.is_none());
    }
    
    #[test]
    fn test_version_2_migration() {
        let mut user = User {
            id: 2,
            name: "测试用户".to_string(),
            email: Some("test@example.com".to_string()),
            age: None,
            phone: None,
        };
        user.set_version(2);
        
        // 执行迁移
        User::migrate(&mut user, user.version());
        
        assert_eq!(user.version(), 4);
        assert!(user.email.is_some());
        assert!(user.age.is_none());
        assert!(user.phone.is_none());
    }
}

注意事项

  • 版本号从1开始递增
  • 确保所有迁移路径都经过测试
  • 在生产环境中建议记录迁移日志
  • 考虑使用特性标志来控制不同版本的代码路径

revision库为Rust项目提供了简单而强大的版本管理能力,特别适合需要长期维护和渐进式升级的项目。

回到顶部