use serde::{Deserialize, Serialize};
use version_migrate::{migrate_path, Forwardable, IntoDomain, MigratesTo, Migrator, Versioned};
#[derive(Debug, Clone, Serialize, Deserialize, Versioned)]
#[versioned(version = "1.0.0")]
struct TaskV1 {
id: String,
title: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct TaskEntity {
id: String,
title: String,
}
impl IntoDomain<TaskEntity> for TaskV1 {
fn into_domain(self) -> TaskEntity {
TaskEntity {
id: self.id,
title: self.title,
}
}
}
fn create_migrator() -> Migrator {
let mut migrator = Migrator::new();
let path = migrate_path!("task", [TaskV1, TaskEntity]);
migrator.register(path).unwrap();
migrator
}
#[test]
fn test_load_forward_known_version() {
let migrator = create_migrator();
let json = r#"{"version":"1.0.0","data":{"id":"1","title":"Task 1"}}"#;
let task: Forwardable<TaskEntity> = migrator.load_forward("task", json).unwrap();
assert_eq!(task.id, "1");
assert_eq!(task.title, "Task 1");
assert_eq!(task.original_version(), "1.0.0");
assert!(!task.was_lossy());
assert!(task.unknown_fields().is_empty());
}
#[test]
fn test_load_forward_unknown_version() {
let migrator = create_migrator();
let json =
r#"{"version":"2.0.0","data":{"id":"1","title":"Task 1","description":"New field"}}"#;
let task: Forwardable<TaskEntity> = migrator.load_forward("task", json).unwrap();
assert_eq!(task.id, "1");
assert_eq!(task.title, "Task 1");
assert_eq!(task.original_version(), "2.0.0");
assert!(task.was_lossy());
assert_eq!(task.unknown_fields().len(), 1);
assert!(task.unknown_fields().contains_key("description"));
}
#[test]
fn test_save_forward_preserves_unknown_fields() {
let migrator = create_migrator();
let json =
r#"{"version":"2.0.0","data":{"id":"1","title":"Original","description":"Preserved"}}"#;
let mut task: Forwardable<TaskEntity> = migrator.load_forward("task", json).unwrap();
task.title = "Updated".to_string();
let saved = migrator.save_forward(&task).unwrap();
let saved_value: serde_json::Value = serde_json::from_str(&saved).unwrap();
assert_eq!(saved_value["version"], "2.0.0");
assert_eq!(saved_value["data"]["title"], "Updated");
assert_eq!(saved_value["data"]["description"], "Preserved");
}
#[test]
fn test_load_forward_flat_format() {
let migrator = create_migrator();
let json = r#"{"version":"2.0.0","id":"1","title":"Task","extra_field":"value"}"#;
let task: Forwardable<TaskEntity> = migrator.load_forward_flat("task", json).unwrap();
assert_eq!(task.id, "1");
assert_eq!(task.original_version(), "2.0.0");
assert!(task.was_lossy());
assert!(task.unknown_fields().contains_key("extra_field"));
}
#[test]
fn test_save_forward_flat_format() {
let migrator = create_migrator();
let json = r#"{"version":"2.0.0","id":"1","title":"Original","extra":"preserved"}"#;
let mut task: Forwardable<TaskEntity> = migrator.load_forward_flat("task", json).unwrap();
task.title = "Updated".to_string();
let saved = migrator.save_forward(&task).unwrap();
let saved_value: serde_json::Value = serde_json::from_str(&saved).unwrap();
assert_eq!(saved_value["version"], "2.0.0");
assert_eq!(saved_value["title"], "Updated");
assert_eq!(saved_value["extra"], "preserved");
assert!(saved_value.get("data").is_none());
}
#[test]
fn test_forwardable_deref() {
let migrator = create_migrator();
let json = r#"{"version":"1.0.0","data":{"id":"1","title":"Task"}}"#;
let task: Forwardable<TaskEntity> = migrator.load_forward("task", json).unwrap();
assert_eq!(task.id, "1");
assert_eq!(task.title, "Task");
}
#[test]
fn test_forwardable_deref_mut() {
let migrator = create_migrator();
let json = r#"{"version":"1.0.0","data":{"id":"1","title":"Original"}}"#;
let mut task: Forwardable<TaskEntity> = migrator.load_forward("task", json).unwrap();
task.title = "Modified".to_string();
assert_eq!(task.title, "Modified");
}
#[test]
fn test_forwardable_into_inner() {
let migrator = create_migrator();
let json = r#"{"version":"1.0.0","data":{"id":"1","title":"Task"}}"#;
let task: Forwardable<TaskEntity> = migrator.load_forward("task", json).unwrap();
let inner: TaskEntity = task.into_inner();
assert_eq!(inner.id, "1");
assert_eq!(inner.title, "Task");
}
#[test]
fn test_roundtrip_known_version() {
let migrator = create_migrator();
let original_json = r#"{"version":"1.0.0","data":{"id":"1","title":"Task"}}"#;
let task: Forwardable<TaskEntity> = migrator.load_forward("task", original_json).unwrap();
let saved = migrator.save_forward(&task).unwrap();
let reloaded: Forwardable<TaskEntity> = migrator.load_forward("task", &saved).unwrap();
assert_eq!(reloaded.id, "1");
assert_eq!(reloaded.title, "Task");
assert_eq!(reloaded.original_version(), "1.0.0");
}
#[test]
fn test_roundtrip_unknown_version_with_modifications() {
let migrator = create_migrator();
let json = r#"{"version":"3.0.0","data":{"id":"1","title":"Original","new_field":"keep_me"}}"#;
let mut task: Forwardable<TaskEntity> = migrator.load_forward("task", json).unwrap();
task.title = "Modified".to_string();
let saved = migrator.save_forward(&task).unwrap();
let reloaded: Forwardable<TaskEntity> = migrator.load_forward("task", &saved).unwrap();
assert_eq!(reloaded.title, "Modified");
assert_eq!(reloaded.original_version(), "3.0.0");
assert!(reloaded.unknown_fields().contains_key("new_field"));
assert_eq!(reloaded.unknown_fields()["new_field"], "keep_me");
}
mod with_migration {
use super::*;
#[derive(Debug, Clone, Serialize, Deserialize, Versioned)]
#[versioned(version = "1.0.0")]
struct ConfigV1 {
name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Versioned)]
#[versioned(version = "2.0.0")]
struct ConfigV2 {
name: String,
enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct ConfigEntity {
name: String,
enabled: bool,
}
impl MigratesTo<ConfigV2> for ConfigV1 {
fn migrate(self) -> ConfigV2 {
ConfigV2 {
name: self.name,
enabled: true, }
}
}
impl IntoDomain<ConfigEntity> for ConfigV2 {
fn into_domain(self) -> ConfigEntity {
ConfigEntity {
name: self.name,
enabled: self.enabled,
}
}
}
fn create_config_migrator() -> Migrator {
let mut migrator = Migrator::new();
let path = migrate_path!("config", [ConfigV1, ConfigV2, ConfigEntity]);
migrator.register(path).unwrap();
migrator
}
#[test]
fn test_load_forward_with_migration() {
let migrator = create_config_migrator();
let json = r#"{"version":"1.0.0","data":{"name":"Test"}}"#;
let config: Forwardable<ConfigEntity> = migrator.load_forward("config", json).unwrap();
assert_eq!(config.name, "Test");
assert!(config.enabled); assert_eq!(config.original_version(), "1.0.0");
assert!(!config.was_lossy());
}
#[test]
fn test_load_forward_unknown_future_version() {
let migrator = create_config_migrator();
let json =
r#"{"version":"3.0.0","data":{"name":"Future","enabled":false,"new_setting":123}}"#;
let config: Forwardable<ConfigEntity> = migrator.load_forward("config", json).unwrap();
assert_eq!(config.name, "Future");
assert!(!config.enabled);
assert_eq!(config.original_version(), "3.0.0");
assert!(config.was_lossy());
assert!(config.unknown_fields().contains_key("new_setting"));
}
}
mod error_cases {
use super::*;
use version_migrate::MigrationError;
#[test]
fn test_load_forward_entity_not_found() {
let migrator = create_migrator();
let json = r#"{"version":"1.0.0","data":{"id":"1","title":"Task"}}"#;
let result: Result<Forwardable<TaskEntity>, MigrationError> =
migrator.load_forward("nonexistent_entity", json);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, MigrationError::EntityNotFound(_)),
"Expected EntityNotFound, got: {:?}",
err
);
}
#[test]
fn test_load_forward_invalid_json() {
let migrator = create_migrator();
let invalid_json = "{ invalid json }";
let result: Result<Forwardable<TaskEntity>, MigrationError> =
migrator.load_forward("task", invalid_json);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, MigrationError::DeserializationError(_)),
"Expected DeserializationError, got: {:?}",
err
);
}
#[test]
fn test_load_forward_missing_version_field() {
let migrator = create_migrator();
let json = r#"{"data":{"id":"1","title":"Task"}}"#;
let result: Result<Forwardable<TaskEntity>, MigrationError> =
migrator.load_forward("task", json);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, MigrationError::DeserializationError(_)),
"Expected DeserializationError, got: {:?}",
err
);
}
#[test]
fn test_load_forward_missing_data_field() {
let migrator = create_migrator();
let json = r#"{"version":"1.0.0"}"#;
let result: Result<Forwardable<TaskEntity>, MigrationError> =
migrator.load_forward("task", json);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, MigrationError::DeserializationError(_)),
"Expected DeserializationError, got: {:?}",
err
);
}
#[test]
fn test_load_forward_domain_deserialization_failure() {
let migrator = create_migrator();
let json = r#"{"version":"1.0.0","data":{"id":"1"}}"#;
let result: Result<Forwardable<TaskEntity>, MigrationError> =
migrator.load_forward("task", json);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, MigrationError::DeserializationError(_)),
"Expected DeserializationError, got: {:?}",
err
);
}
#[test]
fn test_load_forward_flat_missing_version() {
let migrator = create_migrator();
let json = r#"{"id":"1","title":"Task"}"#;
let result: Result<Forwardable<TaskEntity>, MigrationError> =
migrator.load_forward_flat("task", json);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, MigrationError::DeserializationError(_)),
"Expected DeserializationError, got: {:?}",
err
);
}
#[test]
fn test_load_forward_non_object_json() {
let migrator = create_migrator();
let json = r#"[1, 2, 3]"#;
let result: Result<Forwardable<TaskEntity>, MigrationError> =
migrator.load_forward("task", json);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, MigrationError::DeserializationError(_)),
"Expected DeserializationError, got: {:?}",
err
);
}
#[test]
fn test_load_forward_version_not_string() {
let migrator = create_migrator();
let json = r#"{"version":100,"data":{"id":"1","title":"Task"}}"#;
let result: Result<Forwardable<TaskEntity>, MigrationError> =
migrator.load_forward("task", json);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, MigrationError::DeserializationError(_)),
"Expected DeserializationError, got: {:?}",
err
);
}
}
mod edge_cases {
use super::*;
#[test]
fn test_load_forward_empty_unknown_fields() {
let migrator = create_migrator();
let json = r#"{"version":"1.0.0","data":{"id":"1","title":"Task"}}"#;
let task: Forwardable<TaskEntity> = migrator.load_forward("task", json).unwrap();
assert!(task.unknown_fields().is_empty());
assert!(!task.was_lossy());
}
#[test]
fn test_load_forward_nested_unknown_fields() {
let migrator = create_migrator();
let json = r#"{"version":"2.0.0","data":{"id":"1","title":"Task","metadata":{"key":"value","nested":{"deep":true}}}}"#;
let task: Forwardable<TaskEntity> = migrator.load_forward("task", json).unwrap();
assert!(task.was_lossy());
assert!(task.unknown_fields().contains_key("metadata"));
let metadata = &task.unknown_fields()["metadata"];
assert!(metadata.is_object());
assert_eq!(metadata["key"], "value");
assert_eq!(metadata["nested"]["deep"], true);
}
#[test]
fn test_load_forward_array_unknown_field() {
let migrator = create_migrator();
let json = r#"{"version":"2.0.0","data":{"id":"1","title":"Task","tags":["rust","test","array"]}}"#;
let task: Forwardable<TaskEntity> = migrator.load_forward("task", json).unwrap();
assert!(task.was_lossy());
assert!(task.unknown_fields().contains_key("tags"));
let tags = &task.unknown_fields()["tags"];
assert!(tags.is_array());
assert_eq!(tags.as_array().unwrap().len(), 3);
}
#[test]
fn test_save_forward_preserves_nested_unknown_fields() {
let migrator = create_migrator();
let json = r#"{"version":"2.0.0","data":{"id":"1","title":"Original","complex":{"a":1,"b":[1,2,3]}}}"#;
let mut task: Forwardable<TaskEntity> = migrator.load_forward("task", json).unwrap();
task.title = "Updated".to_string();
let saved = migrator.save_forward(&task).unwrap();
let saved_value: serde_json::Value = serde_json::from_str(&saved).unwrap();
assert_eq!(saved_value["data"]["complex"]["a"], 1);
assert_eq!(saved_value["data"]["complex"]["b"][0], 1);
assert_eq!(saved_value["data"]["complex"]["b"][2], 3);
}
#[test]
fn test_load_forward_multiple_unknown_fields() {
let migrator = create_migrator();
let json = r#"{"version":"2.0.0","data":{"id":"1","title":"Task","field1":"a","field2":123,"field3":true,"field4":null}}"#;
let task: Forwardable<TaskEntity> = migrator.load_forward("task", json).unwrap();
assert_eq!(task.unknown_fields().len(), 4);
assert_eq!(task.unknown_fields()["field1"], "a");
assert_eq!(task.unknown_fields()["field2"], 123);
assert_eq!(task.unknown_fields()["field3"], true);
assert!(task.unknown_fields()["field4"].is_null());
}
#[test]
fn test_context_accessors() {
let migrator = create_migrator();
let json = r#"{"version":"2.0.0","data":{"id":"1","title":"Task","extra":"field"}}"#;
let task: Forwardable<TaskEntity> = migrator.load_forward("task", json).unwrap();
let ctx = task.context();
assert_eq!(ctx.original_version(), "2.0.0");
assert!(ctx.was_lossy());
assert_eq!(ctx.unknown_fields().len(), 1);
}
}
mod custom_keys {
use super::*;
#[derive(Debug, Clone, Serialize, Deserialize, Versioned)]
#[versioned(version = "1.0.0")]
struct ItemV1 {
id: String,
name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct ItemEntity {
id: String,
name: String,
}
impl IntoDomain<ItemEntity> for ItemV1 {
fn into_domain(self) -> ItemEntity {
ItemEntity {
id: self.id,
name: self.name,
}
}
}
fn create_custom_keys_migrator() -> Migrator {
let mut migrator = Migrator::new();
let path = migrate_path!(
"item",
[ItemV1, ItemEntity],
version_key = "v",
data_key = "d"
);
migrator.register(path).unwrap();
migrator
}
#[test]
fn test_load_forward_with_custom_keys() {
let migrator = create_custom_keys_migrator();
let json = r#"{"v":"1.0.0","d":{"id":"1","name":"Item"}}"#;
let item: Forwardable<ItemEntity> = migrator.load_forward("item", json).unwrap();
assert_eq!(item.id, "1");
assert_eq!(item.name, "Item");
assert_eq!(item.original_version(), "1.0.0");
assert!(!item.was_lossy());
}
#[test]
fn test_load_forward_unknown_version_with_custom_keys() {
let migrator = create_custom_keys_migrator();
let json = r#"{"v":"2.0.0","d":{"id":"1","name":"Item","extra":"field"}}"#;
let item: Forwardable<ItemEntity> = migrator.load_forward("item", json).unwrap();
assert_eq!(item.id, "1");
assert_eq!(item.original_version(), "2.0.0");
assert!(item.was_lossy());
assert!(item.unknown_fields().contains_key("extra"));
}
#[test]
fn test_save_forward_preserves_custom_keys() {
let migrator = create_custom_keys_migrator();
let json = r#"{"v":"2.0.0","d":{"id":"1","name":"Original","extra":"preserved"}}"#;
let mut item: Forwardable<ItemEntity> = migrator.load_forward("item", json).unwrap();
item.name = "Updated".to_string();
let saved = migrator.save_forward(&item).unwrap();
let saved_value: serde_json::Value = serde_json::from_str(&saved).unwrap();
assert_eq!(saved_value["v"], "2.0.0");
assert_eq!(saved_value["d"]["name"], "Updated");
assert_eq!(saved_value["d"]["extra"], "preserved");
assert!(saved_value.get("version").is_none());
assert!(saved_value.get("data").is_none());
}
#[test]
fn test_roundtrip_with_custom_keys() {
let migrator = create_custom_keys_migrator();
let original = r#"{"v":"2.0.0","d":{"id":"1","name":"Test","future_field":"value"}}"#;
let mut item: Forwardable<ItemEntity> = migrator.load_forward("item", original).unwrap();
item.name = "Modified".to_string();
let saved = migrator.save_forward(&item).unwrap();
let reloaded: Forwardable<ItemEntity> = migrator.load_forward("item", &saved).unwrap();
assert_eq!(reloaded.name, "Modified");
assert_eq!(reloaded.original_version(), "2.0.0");
assert!(reloaded.unknown_fields().contains_key("future_field"));
}
#[test]
fn test_load_forward_flat_with_custom_version_key() {
let migrator = create_custom_keys_migrator();
let json = r#"{"v":"2.0.0","id":"1","name":"Item","extra":"field"}"#;
let item: Forwardable<ItemEntity> = migrator.load_forward_flat("item", json).unwrap();
assert_eq!(item.id, "1");
assert_eq!(item.original_version(), "2.0.0");
assert!(item.was_lossy());
assert!(item.unknown_fields().contains_key("extra"));
}
#[test]
fn test_save_forward_flat_with_custom_version_key() {
let migrator = create_custom_keys_migrator();
let json = r#"{"v":"2.0.0","id":"1","name":"Original","extra":"keep"}"#;
let mut item: Forwardable<ItemEntity> = migrator.load_forward_flat("item", json).unwrap();
item.name = "Updated".to_string();
let saved = migrator.save_forward(&item).unwrap();
let saved_value: serde_json::Value = serde_json::from_str(&saved).unwrap();
assert_eq!(saved_value["v"], "2.0.0");
assert_eq!(saved_value["name"], "Updated");
assert_eq!(saved_value["extra"], "keep");
assert!(saved_value.get("version").is_none());
assert!(saved_value.get("d").is_none());
}
#[test]
fn test_custom_keys_error_with_wrong_keys() {
let migrator = create_custom_keys_migrator();
let json = r#"{"version":"1.0.0","data":{"id":"1","name":"Item"}}"#;
let result: Result<Forwardable<ItemEntity>, _> = migrator.load_forward("item", json);
assert!(result.is_err());
}
}