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