version_migrate/
lib.rs

1//! # version-migrate
2//!
3//! A library for explicit, type-safe schema versioning and migration.
4//!
5//! ## Features
6//!
7//! - **Type-safe migrations**: Define migrations between versions using traits
8//! - **Validation**: Automatic validation of migration paths (circular path detection, version ordering)
9//! - **Multi-format support**: Load from JSON, TOML, YAML, or any serde-compatible format
10//! - **Legacy data support**: Automatic fallback for data without version information
11//! - **Vec support**: Migrate collections of versioned entities
12//! - **Hierarchical structures**: Support for nested versioned entities
13//! - **Async migrations**: Optional async support for I/O-heavy migrations
14//!
15//! ## Basic Example
16//!
17//! ```ignore
18//! use version_migrate::{Versioned, MigratesTo, IntoDomain, Migrator};
19//! use serde::{Serialize, Deserialize};
20//!
21//! // Version 1.0.0
22//! #[derive(Serialize, Deserialize, Versioned)]
23//! #[versioned(version = "1.0.0")]
24//! struct TaskV1_0_0 {
25//!     id: String,
26//!     title: String,
27//! }
28//!
29//! // Version 1.1.0
30//! #[derive(Serialize, Deserialize, Versioned)]
31//! #[versioned(version = "1.1.0")]
32//! struct TaskV1_1_0 {
33//!     id: String,
34//!     title: String,
35//!     description: Option<String>,
36//! }
37//!
38//! // Domain model
39//! struct TaskEntity {
40//!     id: String,
41//!     title: String,
42//!     description: Option<String>,
43//! }
44//!
45//! impl MigratesTo<TaskV1_1_0> for TaskV1_0_0 {
46//!     fn migrate(self) -> TaskV1_1_0 {
47//!         TaskV1_1_0 {
48//!             id: self.id,
49//!             title: self.title,
50//!             description: None,
51//!         }
52//!     }
53//! }
54//!
55//! impl IntoDomain<TaskEntity> for TaskV1_1_0 {
56//!     fn into_domain(self) -> TaskEntity {
57//!         TaskEntity {
58//!             id: self.id,
59//!             title: self.title,
60//!             description: self.description,
61//!         }
62//!     }
63//! }
64//! ```
65//!
66//! ## Working with Collections (Vec)
67//!
68//! ```ignore
69//! // Save multiple versioned entities
70//! let tasks = vec![
71//!     TaskV1_0_0 { id: "1".into(), title: "Task 1".into() },
72//!     TaskV1_0_0 { id: "2".into(), title: "Task 2".into() },
73//! ];
74//! let json = migrator.save_vec(tasks)?;
75//!
76//! // Load and migrate multiple entities
77//! let domains: Vec<TaskEntity> = migrator.load_vec("task", &json)?;
78//! ```
79//!
80//! ## Legacy Data Support
81//!
82//! Handle data that was created before versioning was introduced:
83//!
84//! ```ignore
85//! // Legacy data without version information
86//! let legacy_json = r#"{"id": "task-1", "title": "Legacy Task"}"#;
87//!
88//! // Automatically treats legacy data as the first version and migrates
89//! let domain: TaskEntity = migrator.load_with_fallback("task", legacy_json)?;
90//!
91//! // Also works with properly versioned data
92//! let versioned_json = r#"{"version":"1.0.0","data":{"id":"task-1","title":"My Task"}}"#;
93//! let domain: TaskEntity = migrator.load_with_fallback("task", versioned_json)?;
94//! ```
95//!
96//! ## Hierarchical Structures
97//!
98//! For complex configurations with nested versioned entities:
99//!
100//! ```ignore
101//! #[derive(Serialize, Deserialize, Versioned)]
102//! #[versioned(version = "1.0.0")]
103//! struct ConfigV1 {
104//!     setting: SettingV1,
105//!     items: Vec<ItemV1>,
106//! }
107//!
108//! #[derive(Serialize, Deserialize, Versioned)]
109//! #[versioned(version = "2.0.0")]
110//! struct ConfigV2 {
111//!     setting: SettingV2,
112//!     items: Vec<ItemV2>,
113//! }
114//!
115//! impl MigratesTo<ConfigV2> for ConfigV1 {
116//!     fn migrate(self) -> ConfigV2 {
117//!         ConfigV2 {
118//!             // Migrate nested entities
119//!             setting: self.setting.migrate(),
120//!             items: self.items.into_iter()
121//!                 .map(|item| item.migrate())
122//!                 .collect(),
123//!         }
124//!     }
125//! }
126//! ```
127//!
128//! ## Design Philosophy
129//!
130//! This library follows the **explicit versioning** approach:
131//!
132//! - Each version has its own type (V1, V2, V3, etc.)
133//! - Migration logic is explicit and testable
134//! - Version changes are tracked in code
135//! - Root-level versioning ensures consistency
136//!
137//! This differs from ProtoBuf's "append-only" approach but allows for:
138//! - Schema refactoring and cleanup
139//! - Type-safe migration paths
140//! - Clear version history in code
141
142use serde::{Deserialize, Serialize};
143
144pub mod dir_storage;
145pub mod errors;
146mod migrator;
147pub mod paths;
148pub mod storage;
149
150// Re-export the derive macros
151pub use version_migrate_macro::Versioned;
152
153// Re-export Queryable derive macro (same name as trait is OK in Rust)
154#[doc(inline)]
155pub use version_migrate_macro::Queryable as DeriveQueryable;
156
157// Re-export VersionMigrate derive macro
158#[doc(inline)]
159pub use version_migrate_macro::VersionMigrate;
160/// Creates a migration path with simplified syntax.
161///
162/// This macro provides a concise way to define migration paths between versioned types.
163/// Use this when you need just the path without creating a Migrator instance.
164///
165/// # Syntax
166///
167/// Basic usage:
168/// ```ignore
169/// migrate_path!("entity", [V1, V2, V3])
170/// ```
171///
172/// With custom version/data keys:
173/// ```ignore
174/// migrate_path!("entity", [V1, V2, V3], version_key = "v", data_key = "d")
175/// ```
176///
177/// # Arguments
178///
179/// * `entity` - The entity name as a string literal (e.g., `"user"`, `"task"`)
180/// * `versions` - A list of version types in migration order (e.g., `[V1, V2, V3]`)
181/// * `version_key` - (Optional) Custom key for the version field (default: `"version"`)
182/// * `data_key` - (Optional) Custom key for the data field (default: `"data"`)
183///
184/// # Examples
185///
186/// ```ignore
187/// use version_migrate::{migrate_path, Migrator};
188///
189/// // Simple two-step migration
190/// let path = migrate_path!("task", [TaskV1, TaskV2]);
191///
192/// // Multi-step migration
193/// let path = migrate_path!("task", [TaskV1, TaskV2, TaskV3]);
194///
195/// // Many versions (arbitrary length supported)
196/// let path = migrate_path!("task", [TaskV1, TaskV2, TaskV3, TaskV4, TaskV5, TaskV6]);
197///
198/// // With custom keys
199/// let path = migrate_path!("task", [TaskV1, TaskV2], version_key = "v", data_key = "d");
200///
201/// // Register with migrator
202/// let mut migrator = Migrator::new();
203/// migrator.register(path).unwrap();
204/// ```
205///
206/// # Generated Code
207///
208/// The macro expands to the equivalent builder pattern:
209/// ```ignore
210/// // migrate_path!("entity", [V1, V2])
211/// // expands to:
212/// Migrator::define("entity")
213///     .from::<V1>()
214///     .into::<V2>()
215/// ```
216#[macro_export]
217macro_rules! migrate_path {
218    // Basic: migrate_path!("entity", [V1, V2, V3, ...])
219    ($entity:expr, [$first:ty, $($rest:ty),+ $(,)?]) => {
220        $crate::migrator_vec_helper!($first; $($rest),+; $entity)
221    };
222
223    // With custom keys: migrate_path!("entity", [V1, V2, ...], version_key = "v", data_key = "d")
224    ($entity:expr, [$first:ty, $($rest:ty),+ $(,)?], version_key = $version_key:expr, data_key = $data_key:expr) => {
225        $crate::migrator_vec_helper_with_keys!($first; $($rest),+; $entity; $version_key; $data_key)
226    };
227}
228
229/// Helper macro for Vec notation without custom keys
230#[doc(hidden)]
231#[macro_export]
232macro_rules! migrator_vec_helper {
233    // Base case: two versions left
234    ($first:ty; $last:ty; $entity:expr) => {
235        $crate::Migrator::define($entity)
236            .from::<$first>()
237            .into::<$last>()
238    };
239
240    // Recursive case: more than two versions
241    ($first:ty; $second:ty, $($rest:ty),+; $entity:expr) => {
242        $crate::migrator_vec_build_steps!($first; $($rest),+; $entity; {
243            $crate::Migrator::define($entity).from::<$first>().step::<$second>()
244        })
245    };
246}
247
248/// Helper for building all steps, then applying final .into()
249#[doc(hidden)]
250#[macro_export]
251macro_rules! migrator_vec_build_steps {
252    // Final case: last version, call .into()
253    ($first:ty; $last:ty; $entity:expr; { $builder:expr }) => {
254        $builder.into::<$last>()
255    };
256
257    // Recursive case: add .step() and continue
258    ($first:ty; $current:ty, $($rest:ty),+; $entity:expr; { $builder:expr }) => {
259        $crate::migrator_vec_build_steps!($first; $($rest),+; $entity; {
260            $builder.step::<$current>()
261        })
262    };
263}
264
265/// Helper macro for Vec notation with custom keys
266#[doc(hidden)]
267#[macro_export]
268macro_rules! migrator_vec_helper_with_keys {
269    // Base case: two versions left
270    ($first:ty; $last:ty; $entity:expr; $version_key:expr; $data_key:expr) => {
271        $crate::Migrator::define($entity)
272            .with_keys($version_key, $data_key)
273            .from::<$first>()
274            .into::<$last>()
275    };
276
277    // Recursive case: more than two versions
278    ($first:ty; $second:ty, $($rest:ty),+; $entity:expr; $version_key:expr; $data_key:expr) => {
279        $crate::migrator_vec_build_steps_with_keys!($first; $($rest),+; $entity; $version_key; $data_key; {
280            $crate::Migrator::define($entity).with_keys($version_key, $data_key).from::<$first>().step::<$second>()
281        })
282    };
283}
284
285/// Helper for building all steps with custom keys, then applying final .into()
286#[doc(hidden)]
287#[macro_export]
288macro_rules! migrator_vec_build_steps_with_keys {
289    // Final case: last version, call .into()
290    ($first:ty; $last:ty; $entity:expr; $version_key:expr; $data_key:expr; { $builder:expr }) => {
291        $builder.into::<$last>()
292    };
293
294    // Recursive case: add .step() and continue
295    ($first:ty; $current:ty, $($rest:ty),+; $entity:expr; $version_key:expr; $data_key:expr; { $builder:expr }) => {
296        $crate::migrator_vec_build_steps_with_keys!($first; $($rest),+; $entity; $version_key; $data_key; {
297            $builder.step::<$current>()
298        })
299    };
300}
301
302/// Helper macro for Vec notation with save support (without custom keys)
303#[doc(hidden)]
304#[macro_export]
305macro_rules! migrator_vec_helper_with_save {
306    // Base case: two versions left
307    ($first:ty; $last:ty; $entity:expr) => {
308        $crate::Migrator::define($entity)
309            .from::<$first>()
310            .into_with_save::<$last>()
311    };
312
313    // Recursive case: more than two versions
314    ($first:ty; $second:ty, $($rest:ty),+; $entity:expr) => {
315        $crate::migrator_vec_build_steps_with_save!($first; $($rest),+; $entity; {
316            $crate::Migrator::define($entity).from::<$first>().step::<$second>()
317        })
318    };
319}
320
321/// Helper for building all steps with save support, then applying final .into_with_save()
322#[doc(hidden)]
323#[macro_export]
324macro_rules! migrator_vec_build_steps_with_save {
325    // Final case: last version, call .into_with_save()
326    ($first:ty; $last:ty; $entity:expr; { $builder:expr }) => {
327        $builder.into_with_save::<$last>()
328    };
329
330    // Recursive case: add .step() and continue
331    ($first:ty; $current:ty, $($rest:ty),+; $entity:expr; { $builder:expr }) => {
332        $crate::migrator_vec_build_steps_with_save!($first; $($rest),+; $entity; {
333            $builder.step::<$current>()
334        })
335    };
336}
337
338/// Helper macro for Vec notation with custom keys and save support
339#[doc(hidden)]
340#[macro_export]
341macro_rules! migrator_vec_helper_with_keys_and_save {
342    // Base case: two versions left
343    ($first:ty; $last:ty; $entity:expr; $version_key:expr; $data_key:expr) => {
344        $crate::Migrator::define($entity)
345            .with_keys($version_key, $data_key)
346            .from::<$first>()
347            .into_with_save::<$last>()
348    };
349
350    // Recursive case: more than two versions
351    ($first:ty; $second:ty, $($rest:ty),+; $entity:expr; $version_key:expr; $data_key:expr) => {
352        $crate::migrator_vec_build_steps_with_keys_and_save!($first; $($rest),+; $entity; $version_key; $data_key; {
353            $crate::Migrator::define($entity).with_keys($version_key, $data_key).from::<$first>().step::<$second>()
354        })
355    };
356}
357
358/// Helper for building all steps with custom keys and save support, then applying final .into_with_save()
359#[doc(hidden)]
360#[macro_export]
361macro_rules! migrator_vec_build_steps_with_keys_and_save {
362    // Final case: last version, call .into_with_save()
363    ($first:ty; $last:ty; $entity:expr; $version_key:expr; $data_key:expr; { $builder:expr }) => {
364        $builder.into_with_save::<$last>()
365    };
366
367    // Recursive case: add .step() and continue
368    ($first:ty; $current:ty, $($rest:ty),+; $entity:expr; $version_key:expr; $data_key:expr; { $builder:expr }) => {
369        $crate::migrator_vec_build_steps_with_keys_and_save!($first; $($rest),+; $entity; $version_key; $data_key; {
370            $builder.step::<$current>()
371        })
372    };
373}
374
375/// Creates a fully initialized `Migrator` with registered migration paths.
376///
377/// This macro creates a `Migrator` instance and registers one or more migration paths,
378/// returning a ready-to-use migrator. This is the recommended way to create a migrator
379/// as it's more concise than manually calling `Migrator::new()` and `register()` for each path.
380///
381/// # Syntax
382///
383/// Single path:
384/// ```ignore
385/// migrator!("entity" => [V1, V2, V3])
386/// ```
387///
388/// Multiple paths:
389/// ```ignore
390/// migrator!(
391///     "task" => [TaskV1, TaskV2, TaskV3],
392///     "user" => [UserV1, UserV2]
393/// )
394/// ```
395///
396/// Single path with custom keys:
397/// ```ignore
398/// migrator!(
399///     "task" => [TaskV1, TaskV2], version_key = "v", data_key = "d"
400/// )
401/// ```
402///
403/// Multiple paths with custom keys (requires `@keys` prefix):
404/// ```ignore
405/// migrator!(
406///     @keys version_key = "v", data_key = "d";
407///     "task" => [TaskV1, TaskV2],
408///     "user" => [UserV1, UserV2]
409/// )
410/// ```
411///
412/// # Examples
413///
414/// ```ignore
415/// use version_migrate::migrator;
416///
417/// // Single entity migration
418/// let migrator = migrator!("task" => [TaskV1, TaskV2, TaskV3]).unwrap();
419///
420/// // Multiple entities
421/// let migrator = migrator!(
422///     "task" => [TaskV1, TaskV2],
423///     "user" => [UserV1, UserV2]
424/// ).unwrap();
425///
426/// // Single entity with custom keys
427/// let migrator = migrator!(
428///     "task" => [TaskV1, TaskV2], version_key = "v", data_key = "d"
429/// ).unwrap();
430///
431/// // Multiple entities with custom keys
432/// let migrator = migrator!(
433///     @keys version_key = "v", data_key = "d";
434///     "task" => [TaskV1, TaskV2],
435///     "user" => [UserV1, UserV2]
436/// ).unwrap();
437///
438/// // Now ready to use
439/// let domain: TaskEntity = migrator.load("task", json_str)?;
440/// ```
441///
442/// # Returns
443///
444/// Returns `Result<Migrator, MigrationError>`. The migrator is ready to use if `Ok`.
445#[macro_export]
446macro_rules! migrator {
447    // Single path with custom keys and save support (most specific)
448    ($entity:expr => [$first:ty, $($rest:ty),+ $(,)?], version_key = $version_key:expr, data_key = $data_key:expr, save = true) => {{
449        let mut migrator = $crate::Migrator::new();
450        let path = $crate::migrator_vec_helper_with_keys_and_save!($first; $($rest),+; $entity; $version_key; $data_key);
451        migrator.register(path).map(|_| migrator)
452    }};
453
454    // Single path with custom keys (no save)
455    ($entity:expr => [$first:ty, $($rest:ty),+ $(,)?], version_key = $version_key:expr, data_key = $data_key:expr) => {{
456        let mut migrator = $crate::Migrator::new();
457        let path = $crate::migrate_path!($entity, [$first, $($rest),+], version_key = $version_key, data_key = $data_key);
458        migrator.register(path).map(|_| migrator)
459    }};
460
461    // Multiple paths with custom keys and save support
462    (@keys version_key = $version_key:expr, data_key = $data_key:expr, save = true; $($entity:expr => [$first:ty, $($rest:ty),+ $(,)?]),+ $(,)?) => {{
463        let mut migrator = $crate::Migrator::new();
464        $(
465            let path = $crate::migrator_vec_helper_with_keys_and_save!($first; $($rest),+; $entity; $version_key; $data_key);
466            migrator.register(path)?;
467        )+
468        Ok::<$crate::Migrator, $crate::MigrationError>(migrator)
469    }};
470
471    // Multiple paths with custom keys (no save)
472    (@keys version_key = $version_key:expr, data_key = $data_key:expr; $($entity:expr => [$first:ty, $($rest:ty),+ $(,)?]),+ $(,)?) => {{
473        let mut migrator = $crate::Migrator::new();
474        $(
475            let path = $crate::migrate_path!($entity, [$first, $($rest),+], version_key = $version_key, data_key = $data_key);
476            migrator.register(path)?;
477        )+
478        Ok::<$crate::Migrator, $crate::MigrationError>(migrator)
479    }};
480
481    // Single path with save support (no custom keys)
482    ($entity:expr => [$first:ty, $($rest:ty),+ $(,)?], save = true) => {{
483        let mut migrator = $crate::Migrator::new();
484        let path = $crate::migrator_vec_helper_with_save!($first; $($rest),+; $entity);
485        migrator.register(path).map(|_| migrator)
486    }};
487
488    // Single path without custom keys (no save)
489    ($entity:expr => [$first:ty, $($rest:ty),+ $(,)?]) => {{
490        let mut migrator = $crate::Migrator::new();
491        let path = $crate::migrate_path!($entity, [$first, $($rest),+]);
492        migrator.register(path).map(|_| migrator)
493    }};
494
495    // Multiple paths with save support (no custom keys)
496    (@save; $($entity:expr => [$first:ty, $($rest:ty),+ $(,)?]),+ $(,)?) => {{
497        let mut migrator = $crate::Migrator::new();
498        $(
499            let path = $crate::migrator_vec_helper_with_save!($first; $($rest),+; $entity);
500            migrator.register(path)?;
501        )+
502        Ok::<$crate::Migrator, $crate::MigrationError>(migrator)
503    }};
504
505    // Multiple paths without custom keys (no save, must come last)
506    ($($entity:expr => [$first:ty, $($rest:ty),+ $(,)?]),+ $(,)?) => {{
507        let mut migrator = $crate::Migrator::new();
508        $(
509            let path = $crate::migrate_path!($entity, [$first, $($rest),+]);
510            migrator.register(path)?;
511        )+
512        Ok::<$crate::Migrator, $crate::MigrationError>(migrator)
513    }};
514}
515
516// Re-export error types
517pub use errors::{IoOperationKind, MigrationError};
518
519// Re-export migrator types
520pub use migrator::{ConfigMigrator, MigrationPath, Migrator};
521
522// Re-export storage types
523pub use storage::{
524    AtomicWriteConfig, FileStorage, FileStorageStrategy, FormatStrategy, LoadBehavior,
525};
526
527// Re-export dir_storage types
528pub use dir_storage::{DirStorage, DirStorageStrategy, FilenameEncoding};
529
530#[cfg(feature = "async")]
531pub use dir_storage::AsyncDirStorage;
532
533// Re-export paths types
534pub use paths::{AppPaths, PathStrategy, PrefPath};
535
536// Re-export async-trait for user convenience
537#[cfg(feature = "async")]
538pub use async_trait::async_trait;
539
540/// A trait for versioned data schemas.
541///
542/// This trait marks a type as representing a specific version of a data schema.
543/// It should be derived using `#[derive(Versioned)]` along with the `#[versioned(version = "x.y.z")]` attribute.
544///
545/// # Custom Keys
546///
547/// You can customize the serialization keys:
548///
549/// ```ignore
550/// #[derive(Versioned)]
551/// #[versioned(
552///     version = "1.0.0",
553///     version_key = "schema_version",
554///     data_key = "payload"
555/// )]
556/// struct Task { ... }
557/// // Serializes to: {"schema_version":"1.0.0","payload":{...}}
558/// ```
559pub trait Versioned {
560    /// The semantic version of this schema.
561    const VERSION: &'static str;
562
563    /// The key name for the version field in serialized data.
564    /// Defaults to "version".
565    const VERSION_KEY: &'static str = "version";
566
567    /// The key name for the data field in serialized data.
568    /// Defaults to "data".
569    const DATA_KEY: &'static str = "data";
570}
571
572/// Defines explicit migration logic from one version to another.
573///
574/// Implementing this trait establishes a migration path from `Self` (the source version)
575/// to `T` (the target version).
576pub trait MigratesTo<T: Versioned>: Versioned {
577    /// Migrates from the current version to the target version.
578    fn migrate(self) -> T;
579}
580
581/// Converts a versioned DTO into the application's domain model.
582///
583/// This trait should be implemented on the latest version of a DTO to convert
584/// it into the clean, version-agnostic domain model.
585pub trait IntoDomain<D>: Versioned {
586    /// Converts this versioned data into the domain model.
587    fn into_domain(self) -> D;
588}
589
590/// Converts a domain model back into a versioned DTO.
591///
592/// This trait should be implemented on versioned DTOs to enable conversion
593/// from the domain model back to the versioned format for serialization.
594///
595/// # Example
596///
597/// ```ignore
598/// impl FromDomain<TaskEntity> for TaskV1_1_0 {
599///     fn from_domain(domain: TaskEntity) -> Self {
600///         TaskV1_1_0 {
601///             id: domain.id,
602///             title: domain.title,
603///             description: domain.description,
604///         }
605///     }
606/// }
607/// ```
608pub trait FromDomain<D>: Versioned + Serialize {
609    /// Converts a domain model into this versioned format.
610    fn from_domain(domain: D) -> Self;
611}
612
613/// Associates a domain entity with its latest versioned representation.
614///
615/// This trait enables automatic saving of domain entities using their latest version.
616/// It should typically be derived using the `#[version_migrate]` attribute macro.
617///
618/// # Example
619///
620/// ```ignore
621/// #[derive(Serialize, Deserialize)]
622/// #[version_migrate(entity = "task", latest = TaskV1_1_0)]
623/// struct TaskEntity {
624///     id: String,
625///     title: String,
626///     description: Option<String>,
627/// }
628///
629/// // Now you can save entities directly
630/// let entity = TaskEntity { ... };
631/// let json = migrator.save_entity(entity)?;
632/// ```
633pub trait LatestVersioned: Sized {
634    /// The latest versioned type for this entity.
635    type Latest: Versioned + Serialize + FromDomain<Self>;
636
637    /// The entity name used for migration paths.
638    const ENTITY_NAME: &'static str;
639
640    /// Whether this entity supports saving functionality.
641    /// When `false` (default), uses `into()` for read-only access.
642    /// When `true`, uses `into_with_save()` to enable domain entity saving.
643    const SAVE: bool = false;
644
645    /// Converts this domain entity into its latest versioned format.
646    fn to_latest(self) -> Self::Latest {
647        Self::Latest::from_domain(self)
648    }
649}
650
651/// Marks a domain type as queryable, associating it with an entity name.
652///
653/// This trait enables `ConfigMigrator` to automatically determine which entity
654/// path to use when querying or updating data.
655///
656/// # Example
657///
658/// ```ignore
659/// impl Queryable for TaskEntity {
660///     const ENTITY_NAME: &'static str = "task";
661/// }
662///
663/// let tasks: Vec<TaskEntity> = config.query("tasks")?;
664/// ```
665pub trait Queryable {
666    /// The entity name used to look up migration paths in the `Migrator`.
667    const ENTITY_NAME: &'static str;
668}
669
670/// Async version of `MigratesTo` for migrations requiring I/O operations.
671///
672/// Use this trait when migrations need to perform asynchronous operations
673/// such as database queries or API calls.
674#[cfg(feature = "async")]
675#[async_trait::async_trait]
676pub trait AsyncMigratesTo<T: Versioned>: Versioned + Send {
677    /// Asynchronously migrates from the current version to the target version.
678    ///
679    /// # Errors
680    ///
681    /// Returns `MigrationError` if the migration fails.
682    async fn migrate(self) -> Result<T, MigrationError>;
683}
684
685/// Async version of `IntoDomain` for domain conversions requiring I/O operations.
686///
687/// Use this trait when converting to the domain model requires asynchronous
688/// operations such as fetching additional data from external sources.
689#[cfg(feature = "async")]
690#[async_trait::async_trait]
691pub trait AsyncIntoDomain<D>: Versioned + Send {
692    /// Asynchronously converts this versioned data into the domain model.
693    ///
694    /// # Errors
695    ///
696    /// Returns `MigrationError` if the conversion fails.
697    async fn into_domain(self) -> Result<D, MigrationError>;
698}
699
700/// A wrapper for serialized data that includes explicit version information.
701///
702/// This struct is used for persistence to ensure that the version of the data
703/// is always stored alongside the data itself.
704#[derive(Serialize, Deserialize, Debug, Clone)]
705pub struct VersionedWrapper<T> {
706    /// The semantic version of the data.
707    pub version: String,
708    /// The actual data.
709    pub data: T,
710}
711
712impl<T> VersionedWrapper<T> {
713    /// Creates a new versioned wrapper with the specified version and data.
714    pub fn new(version: String, data: T) -> Self {
715        Self { version, data }
716    }
717}
718
719impl<T: Versioned> VersionedWrapper<T> {
720    /// Creates a wrapper from a versioned value, automatically extracting its version.
721    pub fn from_versioned(data: T) -> Self {
722        Self {
723            version: T::VERSION.to_string(),
724            data,
725        }
726    }
727}
728
729#[cfg(test)]
730mod tests {
731    use super::*;
732
733    #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
734    struct TestData {
735        value: String,
736    }
737
738    impl Versioned for TestData {
739        const VERSION: &'static str = "1.0.0";
740    }
741
742    #[test]
743    fn test_versioned_wrapper_from_versioned() {
744        let data = TestData {
745            value: "test".to_string(),
746        };
747        let wrapper = VersionedWrapper::from_versioned(data);
748
749        assert_eq!(wrapper.version, "1.0.0");
750        assert_eq!(wrapper.data.value, "test");
751    }
752
753    #[test]
754    fn test_versioned_wrapper_new() {
755        let data = TestData {
756            value: "manual".to_string(),
757        };
758        let wrapper = VersionedWrapper::new("2.0.0".to_string(), data);
759
760        assert_eq!(wrapper.version, "2.0.0");
761        assert_eq!(wrapper.data.value, "manual");
762    }
763
764    #[test]
765    fn test_versioned_wrapper_serialization() {
766        let data = TestData {
767            value: "serialize_test".to_string(),
768        };
769        let wrapper = VersionedWrapper::from_versioned(data);
770
771        // Serialize
772        let json = serde_json::to_string(&wrapper).expect("Serialization failed");
773
774        // Deserialize
775        let deserialized: VersionedWrapper<TestData> =
776            serde_json::from_str(&json).expect("Deserialization failed");
777
778        assert_eq!(deserialized.version, "1.0.0");
779        assert_eq!(deserialized.data.value, "serialize_test");
780    }
781
782    #[test]
783    fn test_versioned_wrapper_with_complex_data() {
784        #[derive(Serialize, Deserialize, Debug, PartialEq)]
785        struct ComplexData {
786            id: u64,
787            name: String,
788            tags: Vec<String>,
789            metadata: Option<String>,
790        }
791
792        impl Versioned for ComplexData {
793            const VERSION: &'static str = "3.2.1";
794        }
795
796        let data = ComplexData {
797            id: 42,
798            name: "complex".to_string(),
799            tags: vec!["tag1".to_string(), "tag2".to_string()],
800            metadata: Some("meta".to_string()),
801        };
802
803        let wrapper = VersionedWrapper::from_versioned(data);
804        assert_eq!(wrapper.version, "3.2.1");
805        assert_eq!(wrapper.data.id, 42);
806        assert_eq!(wrapper.data.tags.len(), 2);
807    }
808
809    #[test]
810    fn test_versioned_wrapper_clone() {
811        let data = TestData {
812            value: "clone_test".to_string(),
813        };
814        let wrapper = VersionedWrapper::from_versioned(data);
815        let cloned = wrapper.clone();
816
817        assert_eq!(cloned.version, wrapper.version);
818        assert_eq!(cloned.data.value, wrapper.data.value);
819    }
820
821    #[test]
822    fn test_versioned_wrapper_debug() {
823        let data = TestData {
824            value: "debug".to_string(),
825        };
826        let wrapper = VersionedWrapper::from_versioned(data);
827        let debug_str = format!("{:?}", wrapper);
828
829        assert!(debug_str.contains("1.0.0"));
830        assert!(debug_str.contains("debug"));
831    }
832}