version_migrate/
migrator.rs

1//! Migration manager and builder pattern for defining type-safe migration paths.
2
3use crate::errors::MigrationError;
4use crate::{IntoDomain, MigratesTo, Versioned};
5use serde::de::DeserializeOwned;
6use serde::Serialize;
7use std::collections::HashMap;
8use std::marker::PhantomData;
9
10type MigrationFn =
11    Box<dyn Fn(serde_json::Value) -> Result<serde_json::Value, MigrationError> + Send + Sync>;
12
13/// Type-erased function for saving domain entities
14type DomainSaveFn =
15    Box<dyn Fn(serde_json::Value, &str, &str) -> Result<String, MigrationError> + Send + Sync>;
16type DomainSaveFlatFn =
17    Box<dyn Fn(serde_json::Value, &str) -> Result<String, MigrationError> + Send + Sync>;
18
19/// A registered migration path for a specific entity type.
20struct EntityMigrationPath {
21    /// Maps version -> migration function to next version
22    steps: HashMap<String, MigrationFn>,
23    /// The final conversion to domain model
24    finalize:
25        Box<dyn Fn(serde_json::Value) -> Result<serde_json::Value, MigrationError> + Send + Sync>,
26    /// Ordered list of versions in the migration path
27    versions: Vec<String>,
28    /// The key name for version field in serialized data
29    version_key: String,
30    /// The key name for data field in serialized data
31    data_key: String,
32}
33
34/// Type-erased functions for saving domain entities by entity name
35struct DomainSavers {
36    save_fn: DomainSaveFn,
37    save_flat_fn: DomainSaveFlatFn,
38}
39
40/// The migration manager that orchestrates all migrations.
41pub struct Migrator {
42    paths: HashMap<String, EntityMigrationPath>,
43    default_version_key: Option<String>,
44    default_data_key: Option<String>,
45    domain_savers: HashMap<String, DomainSavers>,
46}
47
48impl Migrator {
49    /// Creates a new, empty migrator.
50    pub fn new() -> Self {
51        Self {
52            paths: HashMap::new(),
53            default_version_key: None,
54            default_data_key: None,
55            domain_savers: HashMap::new(),
56        }
57    }
58
59    /// Gets the latest version for a given entity.
60    ///
61    /// # Returns
62    ///
63    /// The latest version string if the entity is registered, `None` otherwise.
64    pub fn get_latest_version(&self, entity: &str) -> Option<&str> {
65        self.paths
66            .get(entity)
67            .and_then(|path| path.versions.last())
68            .map(|v| v.as_str())
69    }
70
71    /// Creates a builder for configuring the migrator.
72    ///
73    /// # Example
74    ///
75    /// ```ignore
76    /// let migrator = Migrator::builder()
77    ///     .default_version_key("schema_version")
78    ///     .default_data_key("payload")
79    ///     .build();
80    /// ```
81    pub fn builder() -> MigratorBuilder {
82        MigratorBuilder::new()
83    }
84
85    /// Starts defining a migration path for an entity.
86    pub fn define(entity: &str) -> MigrationPathBuilder<Start> {
87        MigrationPathBuilder::new(entity.to_string())
88    }
89
90    /// Registers a migration path with validation.
91    ///
92    /// This method validates the migration path before registering it:
93    /// - Checks for circular migration paths
94    /// - Validates version ordering follows semver rules
95    ///
96    /// # Errors
97    ///
98    /// Returns an error if validation fails.
99    pub fn register<D>(&mut self, path: MigrationPath<D>) -> Result<(), MigrationError> {
100        Self::validate_migration_path(&path.entity, &path.versions)?;
101
102        // Resolve key priority: Path custom > Migrator default > EntityPath (trait constants)
103        let version_key = path
104            .custom_version_key
105            .or_else(|| self.default_version_key.clone())
106            .unwrap_or_else(|| path.inner.version_key.clone());
107
108        let data_key = path
109            .custom_data_key
110            .or_else(|| self.default_data_key.clone())
111            .unwrap_or_else(|| path.inner.data_key.clone());
112
113        let entity_name = path.entity.clone();
114        let final_path = EntityMigrationPath {
115            steps: path.inner.steps,
116            finalize: path.inner.finalize,
117            versions: path.versions,
118            version_key,
119            data_key,
120        };
121
122        self.paths.insert(path.entity, final_path);
123
124        // Register domain savers if available
125        if let (Some(save_fn), Some(save_flat_fn)) = (path.save_fn, path.save_flat_fn) {
126            self.domain_savers.insert(
127                entity_name,
128                DomainSavers {
129                    save_fn,
130                    save_flat_fn,
131                },
132            );
133        }
134
135        Ok(())
136    }
137
138    /// Validates a migration path for correctness.
139    fn validate_migration_path(entity: &str, versions: &[String]) -> Result<(), MigrationError> {
140        // Check for circular paths
141        Self::check_circular_path(entity, versions)?;
142
143        // Check version ordering
144        Self::check_version_ordering(entity, versions)?;
145
146        Ok(())
147    }
148
149    /// Checks if there are any circular dependencies in the migration path.
150    fn check_circular_path(entity: &str, versions: &[String]) -> Result<(), MigrationError> {
151        let mut seen = std::collections::HashSet::new();
152
153        for version in versions {
154            if !seen.insert(version) {
155                // Found a duplicate - circular path detected
156                let path = versions.join(" -> ");
157                return Err(MigrationError::CircularMigrationPath {
158                    entity: entity.to_string(),
159                    path,
160                });
161            }
162        }
163
164        Ok(())
165    }
166
167    /// Checks if versions are ordered according to semver rules.
168    fn check_version_ordering(entity: &str, versions: &[String]) -> Result<(), MigrationError> {
169        for i in 0..versions.len().saturating_sub(1) {
170            let current = &versions[i];
171            let next = &versions[i + 1];
172
173            // Parse versions
174            let current_ver = semver::Version::parse(current).map_err(|e| {
175                MigrationError::DeserializationError(format!("Invalid semver '{}': {}", current, e))
176            })?;
177
178            let next_ver = semver::Version::parse(next).map_err(|e| {
179                MigrationError::DeserializationError(format!("Invalid semver '{}': {}", next, e))
180            })?;
181
182            // Check that next version is greater than current
183            if next_ver <= current_ver {
184                return Err(MigrationError::InvalidVersionOrder {
185                    entity: entity.to_string(),
186                    from: current.clone(),
187                    to: next.clone(),
188                });
189            }
190        }
191
192        Ok(())
193    }
194
195    /// Loads and migrates data from any serde-compatible format.
196    ///
197    /// This is the generic version that accepts any type implementing `Serialize`.
198    /// For JSON strings, use the convenience method `load` instead.
199    ///
200    /// # Arguments
201    ///
202    /// * `entity` - The entity name used when registering the migration path
203    /// * `data` - Versioned data in any serde-compatible format (e.g., `toml::Value`, `serde_json::Value`)
204    ///
205    /// # Returns
206    ///
207    /// The migrated data as the domain model type
208    ///
209    /// # Errors
210    ///
211    /// Returns an error if:
212    /// - The data cannot be converted to the internal format
213    /// - The entity is not registered
214    /// - A migration step fails
215    ///
216    /// # Example
217    ///
218    /// ```ignore
219    /// // Load from TOML
220    /// let toml_data: toml::Value = toml::from_str(toml_str)?;
221    /// let domain: TaskEntity = migrator.load_from("task", toml_data)?;
222    ///
223    /// // Load from JSON Value
224    /// let json_data: serde_json::Value = serde_json::from_str(json_str)?;
225    /// let domain: TaskEntity = migrator.load_from("task", json_data)?;
226    /// ```
227    pub fn load_from<D, T>(&self, entity: &str, data: T) -> Result<D, MigrationError>
228    where
229        D: DeserializeOwned,
230        T: Serialize,
231    {
232        // Convert the input data to serde_json::Value for internal processing
233        let value = serde_json::to_value(data).map_err(|e| {
234            MigrationError::DeserializationError(format!(
235                "Failed to convert input data to internal format: {}",
236                e
237            ))
238        })?;
239
240        // Get the migration path for this entity
241        let path = self
242            .paths
243            .get(entity)
244            .ok_or_else(|| MigrationError::EntityNotFound(entity.to_string()))?;
245
246        let version_key = &path.version_key;
247        let data_key = &path.data_key;
248
249        // Extract version and data using custom keys
250        let obj = value.as_object().ok_or_else(|| {
251            MigrationError::DeserializationError(
252                "Expected object with version and data fields".to_string(),
253            )
254        })?;
255
256        let current_version = obj
257            .get(version_key)
258            .and_then(|v| v.as_str())
259            .ok_or_else(|| {
260                MigrationError::DeserializationError(format!(
261                    "Missing or invalid '{}' field",
262                    version_key
263                ))
264            })?
265            .to_string();
266
267        let mut current_data = obj
268            .get(data_key)
269            .ok_or_else(|| {
270                MigrationError::DeserializationError(format!("Missing '{}' field", data_key))
271            })?
272            .clone();
273
274        let mut current_version = current_version;
275
276        // Apply migration steps until we reach a version with no further steps
277        while let Some(migrate_fn) = path.steps.get(&current_version) {
278            // Migration function returns raw value, no wrapping
279            current_data = migrate_fn(current_data.clone())?;
280
281            // Update version to the next step
282            // Find the next version in the path
283            match path.versions.iter().position(|v| v == &current_version) {
284                Some(idx) if idx + 1 < path.versions.len() => {
285                    current_version = path.versions[idx + 1].clone();
286                }
287                _ => break,
288            }
289        }
290
291        // Finalize into domain model
292        let domain_value = (path.finalize)(current_data)?;
293
294        serde_json::from_value(domain_value).map_err(|e| {
295            MigrationError::DeserializationError(format!("Failed to convert to domain: {}", e))
296        })
297    }
298
299    /// Loads and migrates data from a JSON string.
300    ///
301    /// This is a convenience method for the common case of loading from JSON.
302    /// For other formats, use `load_from` instead.
303    ///
304    /// # Arguments
305    ///
306    /// * `entity` - The entity name used when registering the migration path
307    /// * `json` - A JSON string containing versioned data
308    ///
309    /// # Returns
310    ///
311    /// The migrated data as the domain model type
312    ///
313    /// # Errors
314    ///
315    /// Returns an error if:
316    /// - The JSON cannot be parsed
317    /// - The entity is not registered
318    /// - A migration step fails
319    ///
320    /// # Example
321    ///
322    /// ```ignore
323    /// let json = r#"{"version":"1.0.0","data":{"id":"task-1","title":"My Task"}}"#;
324    /// let domain: TaskEntity = migrator.load("task", json)?;
325    /// ```
326    pub fn load<D: DeserializeOwned>(&self, entity: &str, json: &str) -> Result<D, MigrationError> {
327        let data: serde_json::Value = serde_json::from_str(json).map_err(|e| {
328            MigrationError::DeserializationError(format!("Failed to parse JSON: {}", e))
329        })?;
330        self.load_from(entity, data)
331    }
332
333    /// Loads and migrates data from any serde-compatible format with fallback for legacy data.
334    ///
335    /// This method attempts to load data as versioned first. If version field is missing,
336    /// it treats the data as version 0 (the first version in the migration chain).
337    ///
338    /// # Arguments
339    ///
340    /// * `entity` - The entity name used when registering the migration path
341    /// * `data` - Data that may or may not have version information
342    ///
343    /// # Returns
344    ///
345    /// The migrated data as the domain model type
346    ///
347    /// # Errors
348    ///
349    /// Returns an error if:
350    /// - The data cannot be converted to the internal format
351    /// - The entity is not registered
352    /// - A migration step fails
353    ///
354    /// # Example
355    ///
356    /// ```ignore
357    /// // Works with versioned data
358    /// let versioned_json = r#"{"version":"1.0.0","data":{"id":"task-1","title":"My Task"}}"#;
359    /// let domain: TaskEntity = migrator.load_with_fallback("task", versioned_json)?;
360    ///
361    /// // Works with legacy data (no version field) - treats as V1
362    /// let legacy_json = r#"{"id":"task-1","title":"My Task"}"#;
363    /// let domain: TaskEntity = migrator.load_with_fallback("task", legacy_json)?;
364    /// ```
365    pub fn load_from_with_fallback<D, T>(&self, entity: &str, data: T) -> Result<D, MigrationError>
366    where
367        D: DeserializeOwned,
368        T: Serialize,
369    {
370        // Convert the input data to serde_json::Value for internal processing
371        let value = serde_json::to_value(data).map_err(|e| {
372            MigrationError::DeserializationError(format!(
373                "Failed to convert input data to internal format: {}",
374                e
375            ))
376        })?;
377
378        // Get the migration path for this entity
379        let path = self
380            .paths
381            .get(entity)
382            .ok_or_else(|| MigrationError::EntityNotFound(entity.to_string()))?;
383
384        let version_key = &path.version_key;
385        let data_key = &path.data_key;
386
387        // Try to extract version and data using custom keys
388        let (current_version, current_data) = if let Some(obj) = value.as_object() {
389            if let Some(version_value) = obj.get(version_key) {
390                if let Some(version_str) = version_value.as_str() {
391                    // Versioned data format
392                    let data = obj
393                        .get(data_key)
394                        .ok_or_else(|| {
395                            MigrationError::DeserializationError(format!(
396                                "Missing '{}' field",
397                                data_key
398                            ))
399                        })?
400                        .clone();
401                    (version_str.to_string(), data)
402                } else {
403                    // Version field exists but is not a string - fallback to first version
404                    if path.versions.is_empty() {
405                        return Err(MigrationError::DeserializationError(
406                            "No migration versions defined for fallback".to_string(),
407                        ));
408                    }
409                    (path.versions[0].clone(), value)
410                }
411            } else {
412                // No version field - fallback to first version
413                if path.versions.is_empty() {
414                    return Err(MigrationError::DeserializationError(
415                        "No migration versions defined for fallback".to_string(),
416                    ));
417                }
418                (path.versions[0].clone(), value)
419            }
420        } else {
421            return Err(MigrationError::DeserializationError(
422                "Expected object format for versioned data".to_string(),
423            ));
424        };
425
426        let mut current_version = current_version;
427        let mut current_data = current_data;
428
429        // Apply migration steps until we reach a version with no further steps
430        while let Some(migrate_fn) = path.steps.get(&current_version) {
431            // Migration function returns raw value, no wrapping
432            current_data = migrate_fn(current_data.clone())?;
433
434            // Update version to the next step
435            // Find the next version in the path
436            match path.versions.iter().position(|v| v == &current_version) {
437                Some(idx) if idx + 1 < path.versions.len() => {
438                    current_version = path.versions[idx + 1].clone();
439                }
440                _ => break,
441            }
442        }
443
444        // Finalize into domain model
445        let domain_value = (path.finalize)(current_data)?;
446
447        serde_json::from_value(domain_value).map_err(|e| {
448            MigrationError::DeserializationError(format!("Failed to convert to domain: {}", e))
449        })
450    }
451
452    /// Loads and migrates data from a JSON string with fallback for legacy data.
453    ///
454    /// This is a convenience method for `load_from_with_fallback` that accepts JSON strings.
455    ///
456    /// # Arguments
457    ///
458    /// * `entity` - The entity name used when registering the migration path
459    /// * `json` - A JSON string that may or may not contain version information
460    ///
461    /// # Returns
462    ///
463    /// The migrated data as the domain model type
464    ///
465    /// # Errors
466    ///
467    /// Returns an error if:
468    /// - The JSON cannot be parsed
469    /// - The entity is not registered
470    /// - A migration step fails
471    ///
472    /// # Example
473    ///
474    /// ```ignore
475    /// // Works with versioned data
476    /// let versioned_json = r#"{"version":"1.0.0","data":{"id":"task-1","title":"My Task"}}"#;
477    /// let domain: TaskEntity = migrator.load_with_fallback("task", versioned_json)?;
478    ///
479    /// // Works with legacy data (no version field) - treats as V1
480    /// let legacy_json = r#"{"id":"task-1","title":"My Task"}"#;
481    /// let domain: TaskEntity = migrator.load_with_fallback("task", legacy_json)?;
482    /// ```
483    pub fn load_with_fallback<D: DeserializeOwned>(
484        &self,
485        entity: &str,
486        json: &str,
487    ) -> Result<D, MigrationError> {
488        let data: serde_json::Value = serde_json::from_str(json).map_err(|e| {
489            MigrationError::DeserializationError(format!("Failed to parse JSON: {}", e))
490        })?;
491        self.load_from_with_fallback(entity, data)
492    }
493
494    /// Loads and migrates data from a flat format JSON string.
495    ///
496    /// This is a convenience method for loading from flat format JSON where the version
497    /// field is at the same level as the data fields.
498    ///
499    /// # Arguments
500    ///
501    /// * `entity` - The entity name used when registering the migration path
502    /// * `json` - A JSON string containing versioned data in flat format
503    ///
504    /// # Returns
505    ///
506    /// The migrated data as the domain model type
507    ///
508    /// # Errors
509    ///
510    /// Returns an error if:
511    /// - The JSON cannot be parsed
512    /// - The entity is not registered
513    /// - A migration step fails
514    ///
515    /// # Example
516    ///
517    /// ```ignore
518    /// let json = r#"{"version":"1.0.0","id":"task-1","title":"My Task"}"#;
519    /// let domain: TaskEntity = migrator.load_flat("task", json)?;
520    /// ```
521    pub fn load_flat<D: DeserializeOwned>(
522        &self,
523        entity: &str,
524        json: &str,
525    ) -> Result<D, MigrationError> {
526        let data: serde_json::Value = serde_json::from_str(json).map_err(|e| {
527            MigrationError::DeserializationError(format!("Failed to parse JSON: {}", e))
528        })?;
529        self.load_flat_from(entity, data)
530    }
531
532    /// Loads and migrates data from any serde-compatible format in flat format.
533    ///
534    /// This method expects the version field to be at the same level as the data fields.
535    /// It uses the registered migration path's runtime-configured keys (respecting the
536    /// Path > Migrator > Trait priority).
537    ///
538    /// # Arguments
539    ///
540    /// * `entity` - The entity name used when registering the migration path
541    /// * `value` - A serde-compatible value containing versioned data in flat format
542    ///
543    /// # Returns
544    ///
545    /// The migrated data as the domain model type
546    ///
547    /// # Errors
548    ///
549    /// Returns an error if:
550    /// - The entity is not registered
551    /// - The data format is invalid
552    /// - A migration step fails
553    ///
554    /// # Example
555    ///
556    /// ```ignore
557    /// let toml_value: toml::Value = toml::from_str(toml_str)?;
558    /// let domain: TaskEntity = migrator.load_flat_from("task", toml_value)?;
559    /// ```
560    pub fn load_flat_from<D, T>(&self, entity: &str, value: T) -> Result<D, MigrationError>
561    where
562        D: DeserializeOwned,
563        T: Serialize,
564    {
565        let path = self
566            .paths
567            .get(entity)
568            .ok_or_else(|| MigrationError::EntityNotFound(entity.to_string()))?;
569
570        let version_key = &path.version_key;
571
572        // Convert to serde_json::Value for manipulation
573        let mut value = serde_json::to_value(value).map_err(|e| {
574            MigrationError::SerializationError(format!("Failed to convert input: {}", e))
575        })?;
576
577        // Extract version from the flat structure
578        let obj = value.as_object_mut().ok_or_else(|| {
579            MigrationError::DeserializationError(
580                "Expected object with version field at top level".to_string(),
581            )
582        })?;
583
584        let current_version = obj
585            .remove(version_key)
586            .ok_or_else(|| {
587                MigrationError::DeserializationError(format!(
588                    "Missing '{}' field in flat format",
589                    version_key
590                ))
591            })?
592            .as_str()
593            .ok_or_else(|| {
594                MigrationError::DeserializationError(format!(
595                    "Invalid '{}' field type",
596                    version_key
597                ))
598            })?
599            .to_string();
600
601        // Now obj contains only data fields (version has been removed)
602        let mut current_data = serde_json::Value::Object(obj.clone());
603        let mut current_version = current_version;
604
605        // Apply migration steps until we reach a version with no further steps
606        while let Some(migrate_fn) = path.steps.get(&current_version) {
607            // Migration function returns raw value, no wrapping
608            current_data = migrate_fn(current_data.clone())?;
609
610            // Update version to the next step
611            match path.versions.iter().position(|v| v == &current_version) {
612                Some(idx) if idx + 1 < path.versions.len() => {
613                    current_version = path.versions[idx + 1].clone();
614                }
615                _ => break,
616            }
617        }
618
619        // Finalize into domain model
620        let domain_value = (path.finalize)(current_data)?;
621
622        serde_json::from_value(domain_value).map_err(|e| {
623            MigrationError::DeserializationError(format!("Failed to convert to domain: {}", e))
624        })
625    }
626
627    /// Saves versioned data to a JSON string.
628    ///
629    /// This method wraps the provided data with its version information and serializes
630    /// it to JSON format. The resulting JSON can later be loaded and migrated using
631    /// the `load` method.
632    ///
633    /// # Arguments
634    ///
635    /// * `data` - The versioned data to save
636    ///
637    /// # Returns
638    ///
639    /// A JSON string with the format: `{"version":"x.y.z","data":{...}}`
640    ///
641    /// # Errors
642    ///
643    /// Returns `SerializationError` if the data cannot be serialized to JSON.
644    ///
645    /// # Example
646    ///
647    /// ```ignore
648    /// let task = TaskV1_0_0 {
649    ///     id: "task-1".to_string(),
650    ///     title: "My Task".to_string(),
651    /// };
652    ///
653    /// let migrator = Migrator::new();
654    /// let json = migrator.save(task)?;
655    /// // json: {"version":"1.0.0","data":{"id":"task-1","title":"My Task"}}
656    /// ```
657    pub fn save<T: Versioned + Serialize>(&self, data: T) -> Result<String, MigrationError> {
658        // Use custom keys from the type's Versioned trait
659        let version_key = T::VERSION_KEY;
660        let data_key = T::DATA_KEY;
661
662        // Serialize the data
663        let data_value = serde_json::to_value(&data).map_err(|e| {
664            MigrationError::SerializationError(format!("Failed to serialize data: {}", e))
665        })?;
666
667        // Build the wrapper with custom keys
668        let mut map = serde_json::Map::new();
669        map.insert(
670            version_key.to_string(),
671            serde_json::Value::String(T::VERSION.to_string()),
672        );
673        map.insert(data_key.to_string(), data_value);
674
675        serde_json::to_string(&map).map_err(|e| {
676            MigrationError::SerializationError(format!("Failed to serialize wrapper: {}", e))
677        })
678    }
679
680    /// Saves versioned data to a JSON string in flat format.
681    ///
682    /// Unlike `save()`, this method produces a flat JSON structure where the version
683    /// field is at the same level as the data fields, not wrapped in a separate object.
684    ///
685    /// # Arguments
686    ///
687    /// * `data` - The versioned data to save
688    ///
689    /// # Returns
690    ///
691    /// A JSON string with the format: `{"version":"x.y.z","field1":"value1",...}`
692    ///
693    /// # Errors
694    ///
695    /// Returns `SerializationError` if the data cannot be serialized to JSON.
696    ///
697    /// # Example
698    ///
699    /// ```ignore
700    /// let task = TaskV1_0_0 {
701    ///     id: "task-1".to_string(),
702    ///     title: "My Task".to_string(),
703    /// };
704    ///
705    /// let migrator = Migrator::new();
706    /// let json = migrator.save_flat(task)?;
707    /// // json: {"version":"1.0.0","id":"task-1","title":"My Task"}
708    /// ```
709    pub fn save_flat<T: Versioned + Serialize>(&self, data: T) -> Result<String, MigrationError> {
710        let version_key = T::VERSION_KEY;
711
712        // Serialize the data to a JSON object
713        let mut data_value = serde_json::to_value(&data).map_err(|e| {
714            MigrationError::SerializationError(format!("Failed to serialize data: {}", e))
715        })?;
716
717        // Ensure it's an object so we can add the version field
718        let obj = data_value.as_object_mut().ok_or_else(|| {
719            MigrationError::SerializationError(
720                "Data must serialize to a JSON object for flat format".to_string(),
721            )
722        })?;
723
724        // Add the version field to the same level as data fields
725        obj.insert(
726            version_key.to_string(),
727            serde_json::Value::String(T::VERSION.to_string()),
728        );
729
730        serde_json::to_string(&obj).map_err(|e| {
731            MigrationError::SerializationError(format!("Failed to serialize flat format: {}", e))
732        })
733    }
734
735    /// Loads and migrates multiple entities from any serde-compatible format.
736    ///
737    /// This is the generic version that accepts any type implementing `Serialize`.
738    /// For JSON arrays, use the convenience method `load_vec` instead.
739    ///
740    /// # Arguments
741    ///
742    /// * `entity` - The entity name used when registering the migration path
743    /// * `data` - Array of versioned data in any serde-compatible format
744    ///
745    /// # Returns
746    ///
747    /// A vector of migrated data as domain model types
748    ///
749    /// # Errors
750    ///
751    /// Returns an error if:
752    /// - The data cannot be converted to the internal format
753    /// - The entity is not registered
754    /// - Any migration step fails
755    ///
756    /// # Example
757    ///
758    /// ```ignore
759    /// // Load from TOML array
760    /// let toml_array: Vec<toml::Value> = /* ... */;
761    /// let domains: Vec<TaskEntity> = migrator.load_vec_from("task", toml_array)?;
762    ///
763    /// // Load from JSON Value array
764    /// let json_array: Vec<serde_json::Value> = /* ... */;
765    /// let domains: Vec<TaskEntity> = migrator.load_vec_from("task", json_array)?;
766    /// ```
767    pub fn load_vec_from<D, T>(&self, entity: &str, data: Vec<T>) -> Result<Vec<D>, MigrationError>
768    where
769        D: DeserializeOwned,
770        T: Serialize,
771    {
772        data.into_iter()
773            .map(|item| self.load_from(entity, item))
774            .collect()
775    }
776
777    /// Loads and migrates multiple entities from a JSON array string.
778    ///
779    /// This is a convenience method for the common case of loading from a JSON array.
780    /// For other formats, use `load_vec_from` instead.
781    ///
782    /// # Arguments
783    ///
784    /// * `entity` - The entity name used when registering the migration path
785    /// * `json` - A JSON array string containing versioned data
786    ///
787    /// # Returns
788    ///
789    /// A vector of migrated data as domain model types
790    ///
791    /// # Errors
792    ///
793    /// Returns an error if:
794    /// - The JSON cannot be parsed
795    /// - The entity is not registered
796    /// - Any migration step fails
797    ///
798    /// # Example
799    ///
800    /// ```ignore
801    /// let json = r#"[
802    ///     {"version":"1.0.0","data":{"id":"task-1","title":"Task 1"}},
803    ///     {"version":"1.0.0","data":{"id":"task-2","title":"Task 2"}}
804    /// ]"#;
805    /// let domains: Vec<TaskEntity> = migrator.load_vec("task", json)?;
806    /// ```
807    pub fn load_vec<D: DeserializeOwned>(
808        &self,
809        entity: &str,
810        json: &str,
811    ) -> Result<Vec<D>, MigrationError> {
812        let data: Vec<serde_json::Value> = serde_json::from_str(json).map_err(|e| {
813            MigrationError::DeserializationError(format!("Failed to parse JSON array: {}", e))
814        })?;
815        self.load_vec_from(entity, data)
816    }
817
818    /// Loads and migrates multiple entities from a flat format JSON array string.
819    ///
820    /// This is a convenience method for loading from a JSON array where each element
821    /// has the version field at the same level as the data fields.
822    ///
823    /// # Arguments
824    ///
825    /// * `entity` - The entity name used when registering the migration path
826    /// * `json` - A JSON array string containing versioned data in flat format
827    ///
828    /// # Returns
829    ///
830    /// A vector of migrated data as domain model types
831    ///
832    /// # Errors
833    ///
834    /// Returns an error if:
835    /// - The JSON cannot be parsed
836    /// - The entity is not registered
837    /// - Any migration step fails
838    ///
839    /// # Example
840    ///
841    /// ```ignore
842    /// let json = r#"[
843    ///     {"version":"1.0.0","id":"task-1","title":"Task 1"},
844    ///     {"version":"1.0.0","id":"task-2","title":"Task 2"}
845    /// ]"#;
846    /// let domains: Vec<TaskEntity> = migrator.load_vec_flat("task", json)?;
847    /// ```
848    pub fn load_vec_flat<D: DeserializeOwned>(
849        &self,
850        entity: &str,
851        json: &str,
852    ) -> Result<Vec<D>, MigrationError> {
853        let data: Vec<serde_json::Value> = serde_json::from_str(json).map_err(|e| {
854            MigrationError::DeserializationError(format!("Failed to parse JSON array: {}", e))
855        })?;
856        self.load_vec_flat_from(entity, data)
857    }
858
859    /// Loads and migrates multiple entities from any serde-compatible format in flat format.
860    ///
861    /// This method expects each element to have the version field at the same level
862    /// as the data fields. It uses the registered migration path's runtime-configured
863    /// keys (respecting the Path > Migrator > Trait priority).
864    ///
865    /// # Arguments
866    ///
867    /// * `entity` - The entity name used when registering the migration path
868    /// * `data` - Vector of serde-compatible values in flat format
869    ///
870    /// # Returns
871    ///
872    /// A vector of migrated data as domain model types
873    ///
874    /// # Errors
875    ///
876    /// Returns an error if:
877    /// - The entity is not registered
878    /// - The data format is invalid
879    /// - Any migration step fails
880    ///
881    /// # Example
882    ///
883    /// ```ignore
884    /// let toml_array: Vec<toml::Value> = /* ... */;
885    /// let domains: Vec<TaskEntity> = migrator.load_vec_flat_from("task", toml_array)?;
886    /// ```
887    pub fn load_vec_flat_from<D, T>(
888        &self,
889        entity: &str,
890        data: Vec<T>,
891    ) -> Result<Vec<D>, MigrationError>
892    where
893        D: DeserializeOwned,
894        T: Serialize,
895    {
896        data.into_iter()
897            .map(|item| self.load_flat_from(entity, item))
898            .collect()
899    }
900
901    /// Saves multiple versioned entities to a JSON array string.
902    ///
903    /// This method wraps each item with its version information and serializes
904    /// them as a JSON array. The resulting JSON can later be loaded and migrated
905    /// using the `load_vec` method.
906    ///
907    /// # Arguments
908    ///
909    /// * `data` - Vector of versioned data to save
910    ///
911    /// # Returns
912    ///
913    /// A JSON array string where each element has the format: `{"version":"x.y.z","data":{...}}`
914    ///
915    /// # Errors
916    ///
917    /// Returns `SerializationError` if the data cannot be serialized to JSON.
918    ///
919    /// # Example
920    ///
921    /// ```ignore
922    /// let tasks = vec![
923    ///     TaskV1_0_0 {
924    ///         id: "task-1".to_string(),
925    ///         title: "Task 1".to_string(),
926    ///     },
927    ///     TaskV1_0_0 {
928    ///         id: "task-2".to_string(),
929    ///         title: "Task 2".to_string(),
930    ///     },
931    /// ];
932    ///
933    /// let migrator = Migrator::new();
934    /// let json = migrator.save_vec(tasks)?;
935    /// // json: [{"version":"1.0.0","data":{"id":"task-1",...}}, ...]
936    /// ```
937    pub fn save_vec<T: Versioned + Serialize>(
938        &self,
939        data: Vec<T>,
940    ) -> Result<String, MigrationError> {
941        let version_key = T::VERSION_KEY;
942        let data_key = T::DATA_KEY;
943
944        let wrappers: Result<Vec<serde_json::Value>, MigrationError> = data
945            .into_iter()
946            .map(|item| {
947                let data_value = serde_json::to_value(&item).map_err(|e| {
948                    MigrationError::SerializationError(format!("Failed to serialize item: {}", e))
949                })?;
950
951                let mut map = serde_json::Map::new();
952                map.insert(
953                    version_key.to_string(),
954                    serde_json::Value::String(T::VERSION.to_string()),
955                );
956                map.insert(data_key.to_string(), data_value);
957
958                Ok(serde_json::Value::Object(map))
959            })
960            .collect();
961
962        serde_json::to_string(&wrappers?).map_err(|e| {
963            MigrationError::SerializationError(format!("Failed to serialize data array: {}", e))
964        })
965    }
966
967    /// Saves multiple versioned entities to a JSON array string in flat format.
968    ///
969    /// This method serializes each item with the version field at the same level
970    /// as the data fields, not wrapped in a separate object.
971    ///
972    /// # Arguments
973    ///
974    /// * `data` - Vector of versioned data to save
975    ///
976    /// # Returns
977    ///
978    /// A JSON array string where each element has the format: `{"version":"x.y.z","field1":"value1",...}`
979    ///
980    /// # Errors
981    ///
982    /// Returns `SerializationError` if the data cannot be serialized to JSON.
983    ///
984    /// # Example
985    ///
986    /// ```ignore
987    /// let tasks = vec![
988    ///     TaskV1_0_0 {
989    ///         id: "task-1".to_string(),
990    ///         title: "Task 1".to_string(),
991    ///     },
992    ///     TaskV1_0_0 {
993    ///         id: "task-2".to_string(),
994    ///         title: "Task 2".to_string(),
995    ///     },
996    /// ];
997    ///
998    /// let migrator = Migrator::new();
999    /// let json = migrator.save_vec_flat(tasks)?;
1000    /// // json: [{"version":"1.0.0","id":"task-1",...}, ...]
1001    /// ```
1002    pub fn save_vec_flat<T: Versioned + Serialize>(
1003        &self,
1004        data: Vec<T>,
1005    ) -> Result<String, MigrationError> {
1006        let version_key = T::VERSION_KEY;
1007
1008        let flat_items: Result<Vec<serde_json::Value>, MigrationError> = data
1009            .into_iter()
1010            .map(|item| {
1011                let mut data_value = serde_json::to_value(&item).map_err(|e| {
1012                    MigrationError::SerializationError(format!("Failed to serialize item: {}", e))
1013                })?;
1014
1015                let obj = data_value.as_object_mut().ok_or_else(|| {
1016                    MigrationError::SerializationError(
1017                        "Data must serialize to a JSON object for flat format".to_string(),
1018                    )
1019                })?;
1020
1021                // Add version field at the same level
1022                obj.insert(
1023                    version_key.to_string(),
1024                    serde_json::Value::String(T::VERSION.to_string()),
1025                );
1026
1027                Ok(serde_json::Value::Object(obj.clone()))
1028            })
1029            .collect();
1030
1031        serde_json::to_string(&flat_items?).map_err(|e| {
1032            MigrationError::SerializationError(format!(
1033                "Failed to serialize flat data array: {}",
1034                e
1035            ))
1036        })
1037    }
1038
1039    /// Saves a domain entity to a JSON string using its latest versioned format.
1040    ///
1041    /// This method automatically converts the domain entity to its latest version
1042    /// and saves it with version information.
1043    ///
1044    /// # Arguments
1045    ///
1046    /// * `entity` - The domain entity to save (must implement `LatestVersioned`)
1047    ///
1048    /// # Returns
1049    ///
1050    /// A JSON string with the format: `{"version":"x.y.z","data":{...}}`
1051    ///
1052    /// # Errors
1053    ///
1054    /// Returns `SerializationError` if the entity cannot be serialized to JSON.
1055    ///
1056    /// # Example
1057    ///
1058    /// ```ignore
1059    /// #[version_migrate(entity = "task", latest = TaskV1_1_0)]
1060    /// struct TaskEntity {
1061    ///     id: String,
1062    ///     title: String,
1063    ///     description: Option<String>,
1064    /// }
1065    ///
1066    /// let entity = TaskEntity {
1067    ///     id: "task-1".to_string(),
1068    ///     title: "My Task".to_string(),
1069    ///     description: Some("Description".to_string()),
1070    /// };
1071    ///
1072    /// let migrator = Migrator::new();
1073    /// let json = migrator.save_entity(entity)?;
1074    /// // Automatically saved with latest version (1.1.0)
1075    /// ```
1076    pub fn save_entity<E: crate::LatestVersioned>(
1077        &self,
1078        entity: E,
1079    ) -> Result<String, MigrationError> {
1080        let latest = entity.to_latest();
1081        self.save(latest)
1082    }
1083
1084    /// Saves a domain entity to a JSON string in flat format using its latest versioned format.
1085    ///
1086    /// This method automatically converts the domain entity to its latest version
1087    /// and saves it with the version field at the same level as data fields.
1088    ///
1089    /// # Arguments
1090    ///
1091    /// * `entity` - The domain entity to save (must implement `LatestVersioned`)
1092    ///
1093    /// # Returns
1094    ///
1095    /// A JSON string with the format: `{"version":"x.y.z","field1":"value1",...}`
1096    ///
1097    /// # Errors
1098    ///
1099    /// Returns `SerializationError` if the entity cannot be serialized to JSON.
1100    ///
1101    /// # Example
1102    ///
1103    /// ```ignore
1104    /// #[version_migrate(entity = "task", latest = TaskV1_1_0)]
1105    /// struct TaskEntity {
1106    ///     id: String,
1107    ///     title: String,
1108    ///     description: Option<String>,
1109    /// }
1110    ///
1111    /// let entity = TaskEntity {
1112    ///     id: "task-1".to_string(),
1113    ///     title: "My Task".to_string(),
1114    ///     description: Some("Description".to_string()),
1115    /// };
1116    ///
1117    /// let migrator = Migrator::new();
1118    /// let json = migrator.save_entity_flat(entity)?;
1119    /// // json: {"version":"1.1.0","id":"task-1","title":"My Task",...}
1120    /// ```
1121    pub fn save_entity_flat<E: crate::LatestVersioned>(
1122        &self,
1123        entity: E,
1124    ) -> Result<String, MigrationError> {
1125        let latest = entity.to_latest();
1126        self.save_flat(latest)
1127    }
1128
1129    /// Saves multiple domain entities to a JSON array string using their latest versioned format.
1130    ///
1131    /// This method automatically converts each domain entity to its latest version
1132    /// and saves them as a JSON array.
1133    ///
1134    /// # Arguments
1135    ///
1136    /// * `entities` - Vector of domain entities to save
1137    ///
1138    /// # Returns
1139    ///
1140    /// A JSON array string where each element has the format: `{"version":"x.y.z","data":{...}}`
1141    ///
1142    /// # Errors
1143    ///
1144    /// Returns `SerializationError` if the entities cannot be serialized to JSON.
1145    ///
1146    /// # Example
1147    ///
1148    /// ```ignore
1149    /// let entities = vec![
1150    ///     TaskEntity { id: "1".into(), title: "Task 1".into(), description: None },
1151    ///     TaskEntity { id: "2".into(), title: "Task 2".into(), description: None },
1152    /// ];
1153    ///
1154    /// let json = migrator.save_entity_vec(entities)?;
1155    /// ```
1156    pub fn save_entity_vec<E: crate::LatestVersioned>(
1157        &self,
1158        entities: Vec<E>,
1159    ) -> Result<String, MigrationError> {
1160        let versioned: Vec<E::Latest> = entities.into_iter().map(|e| e.to_latest()).collect();
1161        self.save_vec(versioned)
1162    }
1163
1164    /// Saves multiple domain entities to a JSON array string in flat format using their latest versioned format.
1165    ///
1166    /// This method automatically converts each domain entity to its latest version
1167    /// and saves them with version fields at the same level as data fields.
1168    ///
1169    /// # Arguments
1170    ///
1171    /// * `entities` - Vector of domain entities to save
1172    ///
1173    /// # Returns
1174    ///
1175    /// A JSON array string where each element has the format: `{"version":"x.y.z","field1":"value1",...}`
1176    ///
1177    /// # Errors
1178    ///
1179    /// Returns `SerializationError` if the entities cannot be serialized to JSON.
1180    ///
1181    /// # Example
1182    ///
1183    /// ```ignore
1184    /// let entities = vec![
1185    ///     TaskEntity { id: "1".into(), title: "Task 1".into(), description: None },
1186    ///     TaskEntity { id: "2".into(), title: "Task 2".into(), description: None },
1187    /// ];
1188    ///
1189    /// let json = migrator.save_entity_vec_flat(entities)?;
1190    /// ```
1191    pub fn save_entity_vec_flat<E: crate::LatestVersioned>(
1192        &self,
1193        entities: Vec<E>,
1194    ) -> Result<String, MigrationError> {
1195        let versioned: Vec<E::Latest> = entities.into_iter().map(|e| e.to_latest()).collect();
1196        self.save_vec_flat(versioned)
1197    }
1198
1199    /// Saves a domain entity to a JSON string using its latest versioned format, by entity name.
1200    ///
1201    /// This method works without requiring the `VersionMigrate` macro on the entity type.
1202    /// Instead, it uses the save function registered during `register()` via `into_with_save()`.
1203    ///
1204    /// # Arguments
1205    ///
1206    /// * `entity_name` - The entity name used when registering the migration path
1207    /// * `entity` - The domain entity to save (must be Serialize)
1208    ///
1209    /// # Returns
1210    ///
1211    /// A JSON string with the format: `{"version":"x.y.z","data":{...}}`
1212    ///
1213    /// # Errors
1214    ///
1215    /// Returns `MigrationPathNotDefined` if the entity is not registered with save support.
1216    /// Returns `SerializationError` if the entity cannot be serialized.
1217    ///
1218    /// # Example
1219    ///
1220    /// ```ignore
1221    /// impl FromDomain<TaskEntity> for TaskV1_1_0 {
1222    ///     fn from_domain(entity: TaskEntity) -> Self { ... }
1223    /// }
1224    ///
1225    /// let path = Migrator::define("task")
1226    ///     .from::<TaskV1_0_0>()
1227    ///     .step::<TaskV1_1_0>()
1228    ///     .into_with_save::<TaskEntity>();
1229    ///
1230    /// migrator.register(path)?;
1231    ///
1232    /// let entity = TaskEntity { ... };
1233    /// let json = migrator.save_domain("task", entity)?;
1234    /// // → {"version":"1.1.0","data":{"id":"1","title":"My Task",...}}
1235    /// ```
1236    pub fn save_domain<T: Serialize>(
1237        &self,
1238        entity_name: &str,
1239        entity: T,
1240    ) -> Result<String, MigrationError> {
1241        let saver = self.domain_savers.get(entity_name).ok_or_else(|| {
1242            MigrationError::EntityNotFound(format!(
1243                "Entity '{}' is not registered with domain save support. Use into_with_save() when defining the migration path.",
1244                entity_name
1245            ))
1246        })?;
1247
1248        // Get version/data keys from registered path
1249        let path = self.paths.get(entity_name).ok_or_else(|| {
1250            MigrationError::EntityNotFound(format!("Entity '{}' is not registered", entity_name))
1251        })?;
1252
1253        let domain_value = serde_json::to_value(entity).map_err(|e| {
1254            MigrationError::SerializationError(format!("Failed to serialize entity: {}", e))
1255        })?;
1256
1257        (saver.save_fn)(domain_value, &path.version_key, &path.data_key)
1258    }
1259
1260    /// Saves a domain entity to a JSON string in flat format using its latest versioned format, by entity name.
1261    ///
1262    /// This method works without requiring the `VersionMigrate` macro on the entity type.
1263    /// The version field is placed at the same level as data fields.
1264    ///
1265    /// # Arguments
1266    ///
1267    /// * `entity_name` - The entity name used when registering the migration path
1268    /// * `entity` - The domain entity to save (must be Serialize)
1269    ///
1270    /// # Returns
1271    ///
1272    /// A JSON string with the format: `{"version":"x.y.z","field1":"value1",...}`
1273    ///
1274    /// # Errors
1275    ///
1276    /// Returns `MigrationPathNotDefined` if the entity is not registered with save support.
1277    /// Returns `SerializationError` if the entity cannot be serialized.
1278    ///
1279    /// # Example
1280    ///
1281    /// ```ignore
1282    /// let json = migrator.save_domain_flat("task", entity)?;
1283    /// // → {"version":"1.1.0","id":"1","title":"My Task",...}
1284    /// ```
1285    pub fn save_domain_flat<T: Serialize>(
1286        &self,
1287        entity_name: &str,
1288        entity: T,
1289    ) -> Result<String, MigrationError> {
1290        let saver = self.domain_savers.get(entity_name).ok_or_else(|| {
1291            MigrationError::EntityNotFound(format!(
1292                "Entity '{}' is not registered with domain save support. Use into_with_save() when defining the migration path.",
1293                entity_name
1294            ))
1295        })?;
1296
1297        // Get version key from registered path
1298        let path = self.paths.get(entity_name).ok_or_else(|| {
1299            MigrationError::EntityNotFound(format!("Entity '{}' is not registered", entity_name))
1300        })?;
1301
1302        let domain_value = serde_json::to_value(entity).map_err(|e| {
1303            MigrationError::SerializationError(format!("Failed to serialize entity: {}", e))
1304        })?;
1305
1306        (saver.save_flat_fn)(domain_value, &path.version_key)
1307    }
1308}
1309
1310impl Default for Migrator {
1311    fn default() -> Self {
1312        Self::new()
1313    }
1314}
1315
1316/// Builder for configuring a `Migrator` with default settings.
1317pub struct MigratorBuilder {
1318    default_version_key: Option<String>,
1319    default_data_key: Option<String>,
1320}
1321
1322impl MigratorBuilder {
1323    pub(crate) fn new() -> Self {
1324        Self {
1325            default_version_key: None,
1326            default_data_key: None,
1327        }
1328    }
1329
1330    /// Sets the default version key for all entities.
1331    ///
1332    /// This key will be used unless overridden by:
1333    /// - The entity's `MigrationPath` via `with_keys()`
1334    /// - The type's `Versioned` trait constants
1335    pub fn default_version_key(mut self, key: impl Into<String>) -> Self {
1336        self.default_version_key = Some(key.into());
1337        self
1338    }
1339
1340    /// Sets the default data key for all entities.
1341    ///
1342    /// This key will be used unless overridden by:
1343    /// - The entity's `MigrationPath` via `with_keys()`
1344    /// - The type's `Versioned` trait constants
1345    pub fn default_data_key(mut self, key: impl Into<String>) -> Self {
1346        self.default_data_key = Some(key.into());
1347        self
1348    }
1349
1350    /// Builds the `Migrator` with the configured defaults.
1351    pub fn build(self) -> Migrator {
1352        Migrator {
1353            paths: HashMap::new(),
1354            default_version_key: self.default_version_key,
1355            default_data_key: self.default_data_key,
1356            domain_savers: HashMap::new(),
1357        }
1358    }
1359}
1360
1361/// Marker type for builder state: start
1362pub struct Start;
1363
1364/// Marker type for builder state: has a starting version
1365pub struct HasFrom<V>(PhantomData<V>);
1366
1367/// Marker type for builder state: has intermediate steps
1368pub struct HasSteps<V>(PhantomData<V>);
1369
1370/// Builder for defining migration paths.
1371pub struct MigrationPathBuilder<State> {
1372    entity: String,
1373    steps: HashMap<String, MigrationFn>,
1374    versions: Vec<String>,
1375    version_key: String,
1376    data_key: String,
1377    custom_version_key: Option<String>,
1378    custom_data_key: Option<String>,
1379    _state: PhantomData<State>,
1380}
1381
1382impl MigrationPathBuilder<Start> {
1383    fn new(entity: String) -> Self {
1384        Self {
1385            entity,
1386            steps: HashMap::new(),
1387            versions: Vec::new(),
1388            version_key: String::from("version"),
1389            data_key: String::from("data"),
1390            custom_version_key: None,
1391            custom_data_key: None,
1392            _state: PhantomData,
1393        }
1394    }
1395
1396    /// Overrides the version and data keys for this migration path.
1397    ///
1398    /// This takes precedence over both the Migrator's defaults and the type's trait constants.
1399    ///
1400    /// # Example
1401    ///
1402    /// ```ignore
1403    /// Migrator::define("task")
1404    ///     .with_keys("custom_version", "custom_data")
1405    ///     .from::<TaskV1>()
1406    ///     .into::<TaskDomain>();
1407    /// ```
1408    pub fn with_keys(
1409        mut self,
1410        version_key: impl Into<String>,
1411        data_key: impl Into<String>,
1412    ) -> Self {
1413        self.custom_version_key = Some(version_key.into());
1414        self.custom_data_key = Some(data_key.into());
1415        self
1416    }
1417
1418    /// Sets the starting version for migrations.
1419    pub fn from<V: Versioned + DeserializeOwned>(self) -> MigrationPathBuilder<HasFrom<V>> {
1420        let mut versions = self.versions;
1421        versions.push(V::VERSION.to_string());
1422
1423        MigrationPathBuilder {
1424            entity: self.entity,
1425            steps: self.steps,
1426            versions,
1427            version_key: V::VERSION_KEY.to_string(),
1428            data_key: V::DATA_KEY.to_string(),
1429            custom_version_key: self.custom_version_key,
1430            custom_data_key: self.custom_data_key,
1431            _state: PhantomData,
1432        }
1433    }
1434}
1435
1436impl<V> MigrationPathBuilder<HasFrom<V>>
1437where
1438    V: Versioned + DeserializeOwned,
1439{
1440    /// Adds a migration step to the next version.
1441    pub fn step<Next>(mut self) -> MigrationPathBuilder<HasSteps<Next>>
1442    where
1443        V: MigratesTo<Next>,
1444        Next: Versioned + DeserializeOwned + Serialize,
1445    {
1446        let from_version = V::VERSION.to_string();
1447        let migration_fn: MigrationFn = Box::new(move |value| {
1448            let from_value: V = serde_json::from_value(value).map_err(|e| {
1449                MigrationError::DeserializationError(format!(
1450                    "Failed to deserialize version {}: {}",
1451                    V::VERSION,
1452                    e
1453                ))
1454            })?;
1455
1456            let to_value = from_value.migrate();
1457
1458            // Return the raw migrated value without wrapping
1459            serde_json::to_value(&to_value).map_err(|e| MigrationError::MigrationStepFailed {
1460                from: V::VERSION.to_string(),
1461                to: Next::VERSION.to_string(),
1462                error: e.to_string(),
1463            })
1464        });
1465
1466        self.steps.insert(from_version, migration_fn);
1467        self.versions.push(Next::VERSION.to_string());
1468
1469        MigrationPathBuilder {
1470            entity: self.entity,
1471            steps: self.steps,
1472            versions: self.versions,
1473            version_key: self.version_key,
1474            data_key: self.data_key,
1475            custom_version_key: self.custom_version_key,
1476            custom_data_key: self.custom_data_key,
1477            _state: PhantomData,
1478        }
1479    }
1480
1481    /// Finalizes the migration path with conversion to domain model.
1482    pub fn into<D: DeserializeOwned + Serialize>(self) -> MigrationPath<D>
1483    where
1484        V: IntoDomain<D>,
1485    {
1486        let finalize: Box<
1487            dyn Fn(serde_json::Value) -> Result<serde_json::Value, MigrationError> + Send + Sync,
1488        > = Box::new(move |value| {
1489            let versioned: V = serde_json::from_value(value).map_err(|e| {
1490                MigrationError::DeserializationError(format!(
1491                    "Failed to deserialize final version: {}",
1492                    e
1493                ))
1494            })?;
1495
1496            let domain = versioned.into_domain();
1497
1498            serde_json::to_value(domain).map_err(|e| MigrationError::MigrationStepFailed {
1499                from: V::VERSION.to_string(),
1500                to: "domain".to_string(),
1501                error: e.to_string(),
1502            })
1503        });
1504
1505        MigrationPath {
1506            entity: self.entity,
1507            inner: EntityMigrationPath {
1508                steps: self.steps,
1509                finalize,
1510                versions: self.versions.clone(),
1511                version_key: self.version_key,
1512                data_key: self.data_key,
1513            },
1514            versions: self.versions,
1515            custom_version_key: self.custom_version_key,
1516            custom_data_key: self.custom_data_key,
1517            save_fn: None,
1518            save_flat_fn: None,
1519            _phantom: PhantomData,
1520        }
1521    }
1522
1523    /// Finalizes the migration path with conversion to domain model and enables domain entity saving.
1524    ///
1525    /// This variant registers save functions that allow saving domain entities directly by entity name,
1526    /// without needing the `VersionMigrate` macro on the entity type.
1527    ///
1528    /// # Requirements
1529    ///
1530    /// The latest versioned type (V) must implement `FromDomain<D>` to convert domain entities
1531    /// back to the versioned format for saving.
1532    ///
1533    /// # Example
1534    ///
1535    /// ```ignore
1536    /// impl FromDomain<TaskEntity> for TaskV1_1_0 {
1537    ///     fn from_domain(entity: TaskEntity) -> Self {
1538    ///         TaskV1_1_0 {
1539    ///             id: entity.id,
1540    ///             title: entity.title,
1541    ///             description: entity.description,
1542    ///         }
1543    ///     }
1544    /// }
1545    ///
1546    /// let path = Migrator::define("task")
1547    ///     .from::<TaskV1_0_0>()
1548    ///     .step::<TaskV1_1_0>()
1549    ///     .into_with_save::<TaskEntity>();
1550    ///
1551    /// migrator.register(path)?;
1552    ///
1553    /// // Now you can save by entity name
1554    /// let entity = TaskEntity { ... };
1555    /// let json = migrator.save_domain("task", entity)?;
1556    /// ```
1557    pub fn into_with_save<D: DeserializeOwned + Serialize>(self) -> MigrationPath<D>
1558    where
1559        V: IntoDomain<D> + crate::FromDomain<D>,
1560    {
1561        let finalize: Box<
1562            dyn Fn(serde_json::Value) -> Result<serde_json::Value, MigrationError> + Send + Sync,
1563        > = Box::new(move |value| {
1564            let versioned: V = serde_json::from_value(value).map_err(|e| {
1565                MigrationError::DeserializationError(format!(
1566                    "Failed to deserialize final version: {}",
1567                    e
1568                ))
1569            })?;
1570
1571            let domain = versioned.into_domain();
1572
1573            serde_json::to_value(domain).map_err(|e| MigrationError::MigrationStepFailed {
1574                from: V::VERSION.to_string(),
1575                to: "domain".to_string(),
1576                error: e.to_string(),
1577            })
1578        });
1579
1580        // Create save function for domain entities
1581        let version = V::VERSION;
1582
1583        let save_fn: DomainSaveFn = Box::new(move |domain_value, vkey, dkey| {
1584            let domain: D = serde_json::from_value(domain_value).map_err(|e| {
1585                MigrationError::DeserializationError(format!("Failed to deserialize domain: {}", e))
1586            })?;
1587
1588            let latest = V::from_domain(domain);
1589            let data_value = serde_json::to_value(&latest).map_err(|e| {
1590                MigrationError::SerializationError(format!("Failed to serialize latest: {}", e))
1591            })?;
1592
1593            let mut map = serde_json::Map::new();
1594            map.insert(
1595                vkey.to_string(),
1596                serde_json::Value::String(version.to_string()),
1597            );
1598            map.insert(dkey.to_string(), data_value);
1599
1600            serde_json::to_string(&map).map_err(|e| {
1601                MigrationError::SerializationError(format!("Failed to serialize wrapper: {}", e))
1602            })
1603        });
1604
1605        let save_flat_fn: DomainSaveFlatFn = Box::new(move |domain_value, vkey| {
1606            let domain: D = serde_json::from_value(domain_value).map_err(|e| {
1607                MigrationError::DeserializationError(format!("Failed to deserialize domain: {}", e))
1608            })?;
1609
1610            let latest = V::from_domain(domain);
1611            let mut data_value = serde_json::to_value(&latest).map_err(|e| {
1612                MigrationError::SerializationError(format!("Failed to serialize latest: {}", e))
1613            })?;
1614
1615            let obj = data_value.as_object_mut().ok_or_else(|| {
1616                MigrationError::SerializationError(
1617                    "Data must serialize to a JSON object for flat format".to_string(),
1618                )
1619            })?;
1620
1621            obj.insert(
1622                vkey.to_string(),
1623                serde_json::Value::String(version.to_string()),
1624            );
1625
1626            serde_json::to_string(&obj).map_err(|e| {
1627                MigrationError::SerializationError(format!(
1628                    "Failed to serialize flat format: {}",
1629                    e
1630                ))
1631            })
1632        });
1633
1634        MigrationPath {
1635            entity: self.entity,
1636            inner: EntityMigrationPath {
1637                steps: self.steps,
1638                finalize,
1639                versions: self.versions.clone(),
1640                version_key: self.version_key,
1641                data_key: self.data_key,
1642            },
1643            versions: self.versions,
1644            custom_version_key: self.custom_version_key,
1645            custom_data_key: self.custom_data_key,
1646            save_fn: Some(save_fn),
1647            save_flat_fn: Some(save_flat_fn),
1648            _phantom: PhantomData,
1649        }
1650    }
1651}
1652
1653impl<V> MigrationPathBuilder<HasSteps<V>>
1654where
1655    V: Versioned + DeserializeOwned,
1656{
1657    /// Adds another migration step.
1658    pub fn step<Next>(mut self) -> MigrationPathBuilder<HasSteps<Next>>
1659    where
1660        V: MigratesTo<Next>,
1661        Next: Versioned + DeserializeOwned + Serialize,
1662    {
1663        let from_version = V::VERSION.to_string();
1664        let migration_fn: MigrationFn = Box::new(move |value| {
1665            let from_value: V = serde_json::from_value(value).map_err(|e| {
1666                MigrationError::DeserializationError(format!(
1667                    "Failed to deserialize version {}: {}",
1668                    V::VERSION,
1669                    e
1670                ))
1671            })?;
1672
1673            let to_value = from_value.migrate();
1674
1675            // Return the raw migrated value without wrapping
1676            serde_json::to_value(&to_value).map_err(|e| MigrationError::MigrationStepFailed {
1677                from: V::VERSION.to_string(),
1678                to: Next::VERSION.to_string(),
1679                error: e.to_string(),
1680            })
1681        });
1682
1683        self.steps.insert(from_version, migration_fn);
1684        self.versions.push(Next::VERSION.to_string());
1685
1686        MigrationPathBuilder {
1687            entity: self.entity,
1688            steps: self.steps,
1689            versions: self.versions,
1690            version_key: self.version_key,
1691            data_key: self.data_key,
1692            custom_version_key: self.custom_version_key,
1693            custom_data_key: self.custom_data_key,
1694            _state: PhantomData,
1695        }
1696    }
1697
1698    /// Finalizes the migration path with conversion to domain model.
1699    pub fn into<D: DeserializeOwned + Serialize>(self) -> MigrationPath<D>
1700    where
1701        V: IntoDomain<D>,
1702    {
1703        let finalize: Box<
1704            dyn Fn(serde_json::Value) -> Result<serde_json::Value, MigrationError> + Send + Sync,
1705        > = Box::new(move |value| {
1706            let versioned: V = serde_json::from_value(value).map_err(|e| {
1707                MigrationError::DeserializationError(format!(
1708                    "Failed to deserialize final version: {}",
1709                    e
1710                ))
1711            })?;
1712
1713            let domain = versioned.into_domain();
1714
1715            serde_json::to_value(domain).map_err(|e| MigrationError::MigrationStepFailed {
1716                from: V::VERSION.to_string(),
1717                to: "domain".to_string(),
1718                error: e.to_string(),
1719            })
1720        });
1721
1722        MigrationPath {
1723            entity: self.entity,
1724            inner: EntityMigrationPath {
1725                steps: self.steps,
1726                finalize,
1727                versions: self.versions.clone(),
1728                version_key: self.version_key,
1729                data_key: self.data_key,
1730            },
1731            versions: self.versions,
1732            custom_version_key: self.custom_version_key,
1733            custom_data_key: self.custom_data_key,
1734            save_fn: None,
1735            save_flat_fn: None,
1736            _phantom: PhantomData,
1737        }
1738    }
1739
1740    /// Finalizes the migration path with conversion to domain model and enables domain entity saving.
1741    ///
1742    /// See `MigrationPathBuilder<HasFrom<V>>::into_with_save` for details.
1743    pub fn into_with_save<D: DeserializeOwned + Serialize>(self) -> MigrationPath<D>
1744    where
1745        V: IntoDomain<D> + crate::FromDomain<D>,
1746    {
1747        let finalize: Box<
1748            dyn Fn(serde_json::Value) -> Result<serde_json::Value, MigrationError> + Send + Sync,
1749        > = Box::new(move |value| {
1750            let versioned: V = serde_json::from_value(value).map_err(|e| {
1751                MigrationError::DeserializationError(format!(
1752                    "Failed to deserialize final version: {}",
1753                    e
1754                ))
1755            })?;
1756
1757            let domain = versioned.into_domain();
1758
1759            serde_json::to_value(domain).map_err(|e| MigrationError::MigrationStepFailed {
1760                from: V::VERSION.to_string(),
1761                to: "domain".to_string(),
1762                error: e.to_string(),
1763            })
1764        });
1765
1766        // Create save function for domain entities
1767        let version = V::VERSION;
1768
1769        let save_fn: DomainSaveFn = Box::new(move |domain_value, vkey, dkey| {
1770            let domain: D = serde_json::from_value(domain_value).map_err(|e| {
1771                MigrationError::DeserializationError(format!("Failed to deserialize domain: {}", e))
1772            })?;
1773
1774            let latest = V::from_domain(domain);
1775            let data_value = serde_json::to_value(&latest).map_err(|e| {
1776                MigrationError::SerializationError(format!("Failed to serialize latest: {}", e))
1777            })?;
1778
1779            let mut map = serde_json::Map::new();
1780            map.insert(
1781                vkey.to_string(),
1782                serde_json::Value::String(version.to_string()),
1783            );
1784            map.insert(dkey.to_string(), data_value);
1785
1786            serde_json::to_string(&map).map_err(|e| {
1787                MigrationError::SerializationError(format!("Failed to serialize wrapper: {}", e))
1788            })
1789        });
1790
1791        let save_flat_fn: DomainSaveFlatFn = Box::new(move |domain_value, vkey| {
1792            let domain: D = serde_json::from_value(domain_value).map_err(|e| {
1793                MigrationError::DeserializationError(format!("Failed to deserialize domain: {}", e))
1794            })?;
1795
1796            let latest = V::from_domain(domain);
1797            let mut data_value = serde_json::to_value(&latest).map_err(|e| {
1798                MigrationError::SerializationError(format!("Failed to serialize latest: {}", e))
1799            })?;
1800
1801            let obj = data_value.as_object_mut().ok_or_else(|| {
1802                MigrationError::SerializationError(
1803                    "Data must serialize to a JSON object for flat format".to_string(),
1804                )
1805            })?;
1806
1807            obj.insert(
1808                vkey.to_string(),
1809                serde_json::Value::String(version.to_string()),
1810            );
1811
1812            serde_json::to_string(&obj).map_err(|e| {
1813                MigrationError::SerializationError(format!(
1814                    "Failed to serialize flat format: {}",
1815                    e
1816                ))
1817            })
1818        });
1819
1820        MigrationPath {
1821            entity: self.entity,
1822            inner: EntityMigrationPath {
1823                steps: self.steps,
1824                finalize,
1825                versions: self.versions.clone(),
1826                version_key: self.version_key,
1827                data_key: self.data_key,
1828            },
1829            versions: self.versions,
1830            custom_version_key: self.custom_version_key,
1831            custom_data_key: self.custom_data_key,
1832            save_fn: Some(save_fn),
1833            save_flat_fn: Some(save_flat_fn),
1834            _phantom: PhantomData,
1835        }
1836    }
1837}
1838
1839/// A complete migration path from versioned DTOs to a domain model.
1840pub struct MigrationPath<D> {
1841    entity: String,
1842    inner: EntityMigrationPath,
1843    /// List of versions in the migration path for validation
1844    versions: Vec<String>,
1845    /// Custom version key override (takes precedence over Migrator defaults)
1846    custom_version_key: Option<String>,
1847    /// Custom data key override (takes precedence over Migrator defaults)
1848    custom_data_key: Option<String>,
1849    /// Function to save domain entities (if FromDomain is implemented)
1850    save_fn: Option<DomainSaveFn>,
1851    /// Function to save domain entities in flat format (if FromDomain is implemented)
1852    save_flat_fn: Option<DomainSaveFlatFn>,
1853    _phantom: PhantomData<D>,
1854}
1855
1856/// A wrapper around JSON data that provides convenient query and update methods
1857/// for partial updates with automatic migration.
1858///
1859/// `ConfigMigrator` holds a JSON object and allows you to query specific keys,
1860/// automatically migrating versioned data to domain entities, and update them
1861/// with the latest version.
1862///
1863/// # Example
1864///
1865/// ```ignore
1866/// // config.json:
1867/// // {
1868/// //   "app_name": "MyApp",
1869/// //   "tasks": [
1870/// //     {"version": "1.0.0", "id": "1", "title": "Task 1"},
1871/// //     {"version": "2.0.0", "id": "2", "title": "Task 2", "description": "New"}
1872/// //   ]
1873/// // }
1874///
1875/// let config_json = fs::read_to_string("config.json")?;
1876/// let mut config = ConfigMigrator::from(&config_json, migrator)?;
1877///
1878/// // Query tasks (automatically migrates all versions to TaskEntity)
1879/// let mut tasks: Vec<TaskEntity> = config.query("tasks")?;
1880///
1881/// // Update tasks
1882/// tasks[0].title = "Updated Task".to_string();
1883///
1884/// // Save back with latest version
1885/// config.update("tasks", tasks)?;
1886///
1887/// // Write to file
1888/// fs::write("config.json", config.to_string()?)?;
1889/// ```
1890pub struct ConfigMigrator {
1891    root: serde_json::Value,
1892    migrator: Migrator,
1893}
1894
1895impl ConfigMigrator {
1896    /// Creates a new `ConfigMigrator` from a JSON string and a `Migrator`.
1897    ///
1898    /// # Errors
1899    ///
1900    /// Returns `MigrationError::DeserializationError` if the JSON is invalid.
1901    pub fn from(json: &str, migrator: Migrator) -> Result<Self, MigrationError> {
1902        let root = serde_json::from_str(json)
1903            .map_err(|e| MigrationError::DeserializationError(e.to_string()))?;
1904        Ok(Self { root, migrator })
1905    }
1906
1907    /// Queries a specific key from the JSON object and returns the data as domain entities.
1908    ///
1909    /// This method automatically migrates all versioned data to the latest version
1910    /// and converts them to domain entities.
1911    ///
1912    /// # Type Parameters
1913    ///
1914    /// - `T`: Must implement `Queryable` to provide the entity name, and `Deserialize` for deserialization.
1915    ///
1916    /// # Errors
1917    ///
1918    /// - Returns `MigrationError::DeserializationError` if the key doesn't contain a valid array.
1919    /// - Returns migration errors if the data cannot be migrated.
1920    ///
1921    /// # Example
1922    ///
1923    /// ```ignore
1924    /// let tasks: Vec<TaskEntity> = config.query("tasks")?;
1925    /// ```
1926    pub fn query<T>(&self, key: &str) -> Result<Vec<T>, MigrationError>
1927    where
1928        T: crate::Queryable + for<'de> serde::Deserialize<'de>,
1929    {
1930        let value = &self.root[key];
1931        if value.is_null() {
1932            return Ok(Vec::new());
1933        }
1934
1935        if !value.is_array() {
1936            return Err(MigrationError::DeserializationError(format!(
1937                "Key '{}' does not contain an array",
1938                key
1939            )));
1940        }
1941
1942        let array = value.as_array().unwrap(); // Safe because we checked is_array()
1943        self.migrator
1944            .load_vec_flat_from(T::ENTITY_NAME, array.to_vec())
1945    }
1946
1947    /// Updates a specific key in the JSON object with new domain entities.
1948    ///
1949    /// This method serializes the entities with the latest version (automatically
1950    /// determined from the `Queryable` trait) and updates the JSON object in place.
1951    ///
1952    /// # Type Parameters
1953    ///
1954    /// - `T`: Must implement `Serialize` and `Queryable`.
1955    ///
1956    /// # Errors
1957    ///
1958    /// - Returns `MigrationError::EntityNotFound` if the entity is not registered.
1959    /// - Returns serialization errors if the data cannot be serialized.
1960    ///
1961    /// # Example
1962    ///
1963    /// ```ignore
1964    /// // Version is automatically determined from the entity's migration path
1965    /// config.update("tasks", updated_tasks)?;
1966    /// ```
1967    pub fn update<T>(&mut self, key: &str, data: Vec<T>) -> Result<(), MigrationError>
1968    where
1969        T: serde::Serialize + crate::Queryable,
1970    {
1971        let entity_name = T::ENTITY_NAME;
1972        let latest_version = self
1973            .migrator
1974            .get_latest_version(entity_name)
1975            .ok_or_else(|| MigrationError::EntityNotFound(entity_name.to_string()))?;
1976
1977        // Serialize each item with version field
1978        let items: Vec<serde_json::Value> = data
1979            .into_iter()
1980            .map(|item| {
1981                let mut obj = serde_json::to_value(&item)
1982                    .map_err(|e| MigrationError::SerializationError(e.to_string()))?;
1983
1984                if let Some(obj_map) = obj.as_object_mut() {
1985                    obj_map.insert(
1986                        "version".to_string(),
1987                        serde_json::Value::String(latest_version.to_string()),
1988                    );
1989                }
1990
1991                Ok(obj)
1992            })
1993            .collect::<Result<Vec<_>, MigrationError>>()?;
1994
1995        self.root[key] = serde_json::Value::Array(items);
1996        Ok(())
1997    }
1998
1999    /// Converts the entire JSON object back to a pretty-printed string.
2000    ///
2001    /// # Errors
2002    ///
2003    /// Returns `MigrationError::SerializationError` if serialization fails.
2004    pub fn to_string(&self) -> Result<String, MigrationError> {
2005        serde_json::to_string_pretty(&self.root)
2006            .map_err(|e| MigrationError::SerializationError(e.to_string()))
2007    }
2008
2009    /// Converts the entire JSON object to a compact string.
2010    ///
2011    /// # Errors
2012    ///
2013    /// Returns `MigrationError::SerializationError` if serialization fails.
2014    pub fn to_string_compact(&self) -> Result<String, MigrationError> {
2015        serde_json::to_string(&self.root)
2016            .map_err(|e| MigrationError::SerializationError(e.to_string()))
2017    }
2018
2019    /// Returns a reference to the underlying JSON value.
2020    pub fn as_value(&self) -> &serde_json::Value {
2021        &self.root
2022    }
2023}
2024
2025#[cfg(test)]
2026mod tests {
2027    use super::*;
2028    use crate::{IntoDomain, MigratesTo, Versioned, VersionedWrapper};
2029    use serde::{Deserialize, Serialize};
2030
2031    // Test data structures
2032    #[derive(Serialize, Deserialize, Debug, PartialEq)]
2033    struct V1 {
2034        value: String,
2035    }
2036
2037    impl Versioned for V1 {
2038        const VERSION: &'static str = "1.0.0";
2039    }
2040
2041    #[derive(Serialize, Deserialize, Debug, PartialEq)]
2042    struct V2 {
2043        value: String,
2044        count: u32,
2045    }
2046
2047    impl Versioned for V2 {
2048        const VERSION: &'static str = "2.0.0";
2049    }
2050
2051    #[derive(Serialize, Deserialize, Debug, PartialEq)]
2052    struct V3 {
2053        value: String,
2054        count: u32,
2055        enabled: bool,
2056    }
2057
2058    impl Versioned for V3 {
2059        const VERSION: &'static str = "3.0.0";
2060    }
2061
2062    #[derive(Serialize, Deserialize, Debug, PartialEq)]
2063    struct Domain {
2064        value: String,
2065        count: u32,
2066        enabled: bool,
2067    }
2068
2069    impl MigratesTo<V2> for V1 {
2070        fn migrate(self) -> V2 {
2071            V2 {
2072                value: self.value,
2073                count: 0,
2074            }
2075        }
2076    }
2077
2078    impl MigratesTo<V3> for V2 {
2079        fn migrate(self) -> V3 {
2080            V3 {
2081                value: self.value,
2082                count: self.count,
2083                enabled: true,
2084            }
2085        }
2086    }
2087
2088    impl IntoDomain<Domain> for V3 {
2089        fn into_domain(self) -> Domain {
2090            Domain {
2091                value: self.value,
2092                count: self.count,
2093                enabled: self.enabled,
2094            }
2095        }
2096    }
2097
2098    #[test]
2099    fn test_migrator_new() {
2100        let migrator = Migrator::new();
2101        assert_eq!(migrator.paths.len(), 0);
2102    }
2103
2104    #[test]
2105    fn test_migrator_default() {
2106        let migrator = Migrator::default();
2107        assert_eq!(migrator.paths.len(), 0);
2108    }
2109
2110    #[test]
2111    fn test_single_step_migration() {
2112        let path = Migrator::define("test")
2113            .from::<V2>()
2114            .step::<V3>()
2115            .into::<Domain>();
2116
2117        let mut migrator = Migrator::new();
2118        migrator.register(path).unwrap();
2119
2120        let v2 = V2 {
2121            value: "test".to_string(),
2122            count: 42,
2123        };
2124        let wrapper = VersionedWrapper::from_versioned(v2);
2125        let json = serde_json::to_string(&wrapper).unwrap();
2126
2127        let result: Domain = migrator.load("test", &json).unwrap();
2128        assert_eq!(result.value, "test");
2129        assert_eq!(result.count, 42);
2130        assert!(result.enabled);
2131    }
2132
2133    #[test]
2134    fn test_multi_step_migration() {
2135        let path = Migrator::define("test")
2136            .from::<V1>()
2137            .step::<V2>()
2138            .step::<V3>()
2139            .into::<Domain>();
2140
2141        let mut migrator = Migrator::new();
2142        migrator.register(path).unwrap();
2143
2144        let v1 = V1 {
2145            value: "multi_step".to_string(),
2146        };
2147        let wrapper = VersionedWrapper::from_versioned(v1);
2148        let json = serde_json::to_string(&wrapper).unwrap();
2149
2150        let result: Domain = migrator.load("test", &json).unwrap();
2151        assert_eq!(result.value, "multi_step");
2152        assert_eq!(result.count, 0);
2153        assert!(result.enabled);
2154    }
2155
2156    #[test]
2157    fn test_no_migration_needed() {
2158        let path = Migrator::define("test").from::<V3>().into::<Domain>();
2159
2160        let mut migrator = Migrator::new();
2161        migrator.register(path).unwrap();
2162
2163        let v3 = V3 {
2164            value: "latest".to_string(),
2165            count: 100,
2166            enabled: false,
2167        };
2168        let wrapper = VersionedWrapper::from_versioned(v3);
2169        let json = serde_json::to_string(&wrapper).unwrap();
2170
2171        let result: Domain = migrator.load("test", &json).unwrap();
2172        assert_eq!(result.value, "latest");
2173        assert_eq!(result.count, 100);
2174        assert!(!result.enabled);
2175    }
2176
2177    #[test]
2178    fn test_entity_not_found() {
2179        let migrator = Migrator::new();
2180
2181        let v1 = V1 {
2182            value: "test".to_string(),
2183        };
2184        let wrapper = VersionedWrapper::from_versioned(v1);
2185        let json = serde_json::to_string(&wrapper).unwrap();
2186
2187        let result: Result<Domain, MigrationError> = migrator.load("unknown", &json);
2188        assert!(matches!(result, Err(MigrationError::EntityNotFound(_))));
2189
2190        if let Err(MigrationError::EntityNotFound(entity)) = result {
2191            assert_eq!(entity, "unknown");
2192        }
2193    }
2194
2195    #[test]
2196    fn test_invalid_json() {
2197        let path = Migrator::define("test").from::<V3>().into::<Domain>();
2198
2199        let mut migrator = Migrator::new();
2200        migrator.register(path).unwrap();
2201
2202        let invalid_json = "{ invalid json }";
2203        let result: Result<Domain, MigrationError> = migrator.load("test", invalid_json);
2204
2205        assert!(matches!(
2206            result,
2207            Err(MigrationError::DeserializationError(_))
2208        ));
2209    }
2210
2211    #[test]
2212    fn test_multiple_entities() {
2213        #[derive(Serialize, Deserialize, Debug, PartialEq)]
2214        struct OtherDomain {
2215            value: String,
2216        }
2217
2218        impl IntoDomain<OtherDomain> for V1 {
2219            fn into_domain(self) -> OtherDomain {
2220                OtherDomain { value: self.value }
2221            }
2222        }
2223
2224        let path1 = Migrator::define("entity1")
2225            .from::<V1>()
2226            .step::<V2>()
2227            .step::<V3>()
2228            .into::<Domain>();
2229
2230        let path2 = Migrator::define("entity2")
2231            .from::<V1>()
2232            .into::<OtherDomain>();
2233
2234        let mut migrator = Migrator::new();
2235        migrator.register(path1).unwrap();
2236        migrator.register(path2).unwrap();
2237
2238        // Test entity1
2239        let v1 = V1 {
2240            value: "entity1".to_string(),
2241        };
2242        let wrapper = VersionedWrapper::from_versioned(v1);
2243        let json = serde_json::to_string(&wrapper).unwrap();
2244        let result: Domain = migrator.load("entity1", &json).unwrap();
2245        assert_eq!(result.value, "entity1");
2246
2247        // Test entity2
2248        let v1 = V1 {
2249            value: "entity2".to_string(),
2250        };
2251        let wrapper = VersionedWrapper::from_versioned(v1);
2252        let json = serde_json::to_string(&wrapper).unwrap();
2253        let result: OtherDomain = migrator.load("entity2", &json).unwrap();
2254        assert_eq!(result.value, "entity2");
2255    }
2256
2257    #[test]
2258    fn test_save() {
2259        let migrator = Migrator::new();
2260
2261        let v1 = V1 {
2262            value: "test_save".to_string(),
2263        };
2264
2265        let json = migrator.save(v1).unwrap();
2266
2267        // Verify JSON contains version and data
2268        assert!(json.contains("\"version\""));
2269        assert!(json.contains("\"1.0.0\""));
2270        assert!(json.contains("\"data\""));
2271        assert!(json.contains("\"test_save\""));
2272
2273        // Verify it can be parsed back
2274        let parsed: VersionedWrapper<serde_json::Value> = serde_json::from_str(&json).unwrap();
2275        assert_eq!(parsed.version, "1.0.0");
2276    }
2277
2278    #[test]
2279    fn test_save_and_load_roundtrip() {
2280        let path = Migrator::define("test")
2281            .from::<V1>()
2282            .step::<V2>()
2283            .step::<V3>()
2284            .into::<Domain>();
2285
2286        let mut migrator = Migrator::new();
2287        migrator.register(path).unwrap();
2288
2289        // Save V1 data
2290        let v1 = V1 {
2291            value: "roundtrip".to_string(),
2292        };
2293        let json = migrator.save(v1).unwrap();
2294
2295        // Load and migrate to Domain
2296        let domain: Domain = migrator.load("test", &json).unwrap();
2297
2298        assert_eq!(domain.value, "roundtrip");
2299        assert_eq!(domain.count, 0); // Default from V1->V2 migration
2300        assert!(domain.enabled); // Default from V2->V3 migration
2301    }
2302
2303    #[test]
2304    fn test_save_latest_version() {
2305        let migrator = Migrator::new();
2306
2307        let v3 = V3 {
2308            value: "latest".to_string(),
2309            count: 42,
2310            enabled: false,
2311        };
2312
2313        let json = migrator.save(v3).unwrap();
2314
2315        // Verify the JSON structure
2316        assert!(json.contains("\"version\":\"3.0.0\""));
2317        assert!(json.contains("\"value\":\"latest\""));
2318        assert!(json.contains("\"count\":42"));
2319        assert!(json.contains("\"enabled\":false"));
2320    }
2321
2322    #[test]
2323    fn test_save_pretty() {
2324        let migrator = Migrator::new();
2325
2326        let v2 = V2 {
2327            value: "pretty".to_string(),
2328            count: 10,
2329        };
2330
2331        let json = migrator.save(v2).unwrap();
2332
2333        // Should be compact JSON (not pretty-printed)
2334        assert!(!json.contains('\n'));
2335        assert!(json.contains("\"version\":\"2.0.0\""));
2336    }
2337
2338    #[test]
2339    fn test_validation_invalid_version_order() {
2340        // Manually construct a path with invalid version ordering
2341        let entity = "test".to_string();
2342        let versions = vec!["2.0.0".to_string(), "1.0.0".to_string()]; // Wrong order
2343
2344        let result = Migrator::validate_migration_path(&entity, &versions);
2345        assert!(matches!(
2346            result,
2347            Err(MigrationError::InvalidVersionOrder { .. })
2348        ));
2349
2350        if let Err(MigrationError::InvalidVersionOrder {
2351            entity: e,
2352            from,
2353            to,
2354        }) = result
2355        {
2356            assert_eq!(e, "test");
2357            assert_eq!(from, "2.0.0");
2358            assert_eq!(to, "1.0.0");
2359        }
2360    }
2361
2362    #[test]
2363    fn test_validation_circular_path() {
2364        // Manually construct a path with circular reference
2365        let entity = "test".to_string();
2366        let versions = vec![
2367            "1.0.0".to_string(),
2368            "2.0.0".to_string(),
2369            "1.0.0".to_string(), // Circular!
2370        ];
2371
2372        let result = Migrator::validate_migration_path(&entity, &versions);
2373        assert!(matches!(
2374            result,
2375            Err(MigrationError::CircularMigrationPath { .. })
2376        ));
2377
2378        if let Err(MigrationError::CircularMigrationPath { entity: e, path }) = result {
2379            assert_eq!(e, "test");
2380            assert!(path.contains("1.0.0"));
2381            assert!(path.contains("2.0.0"));
2382        }
2383    }
2384
2385    #[test]
2386    fn test_validation_valid_path() {
2387        // Valid migration path
2388        let entity = "test".to_string();
2389        let versions = vec![
2390            "1.0.0".to_string(),
2391            "1.1.0".to_string(),
2392            "2.0.0".to_string(),
2393        ];
2394
2395        let result = Migrator::validate_migration_path(&entity, &versions);
2396        assert!(result.is_ok());
2397    }
2398
2399    #[test]
2400    fn test_validation_empty_path() {
2401        // Empty path should be valid
2402        let entity = "test".to_string();
2403        let versions = vec![];
2404
2405        let result = Migrator::validate_migration_path(&entity, &versions);
2406        assert!(result.is_ok());
2407    }
2408
2409    #[test]
2410    fn test_validation_single_version() {
2411        // Single version path should be valid (no steps, just final conversion)
2412        let entity = "test".to_string();
2413        let versions = vec!["1.0.0".to_string()];
2414
2415        let result = Migrator::validate_migration_path(&entity, &versions);
2416        assert!(result.is_ok());
2417    }
2418
2419    // Tests for Vec operations
2420    #[test]
2421    fn test_save_vec_and_load_vec() {
2422        let migrator = Migrator::new();
2423
2424        // Save multiple V1 items
2425        let items = vec![
2426            V1 {
2427                value: "item1".to_string(),
2428            },
2429            V1 {
2430                value: "item2".to_string(),
2431            },
2432            V1 {
2433                value: "item3".to_string(),
2434            },
2435        ];
2436
2437        let json = migrator.save_vec(items).unwrap();
2438
2439        // Verify JSON array format
2440        assert!(json.starts_with('['));
2441        assert!(json.ends_with(']'));
2442        assert!(json.contains("\"version\":\"1.0.0\""));
2443        assert!(json.contains("item1"));
2444        assert!(json.contains("item2"));
2445        assert!(json.contains("item3"));
2446
2447        // Setup migration path
2448        let path = Migrator::define("test")
2449            .from::<V1>()
2450            .step::<V2>()
2451            .step::<V3>()
2452            .into::<Domain>();
2453
2454        let mut migrator = Migrator::new();
2455        migrator.register(path).unwrap();
2456
2457        // Load and migrate the array
2458        let domains: Vec<Domain> = migrator.load_vec("test", &json).unwrap();
2459
2460        assert_eq!(domains.len(), 3);
2461        assert_eq!(domains[0].value, "item1");
2462        assert_eq!(domains[1].value, "item2");
2463        assert_eq!(domains[2].value, "item3");
2464
2465        // All should have default values from migration
2466        for domain in &domains {
2467            assert_eq!(domain.count, 0);
2468            assert!(domain.enabled);
2469        }
2470    }
2471
2472    #[test]
2473    fn test_load_vec_empty_array() {
2474        let path = Migrator::define("test")
2475            .from::<V1>()
2476            .step::<V2>()
2477            .step::<V3>()
2478            .into::<Domain>();
2479
2480        let mut migrator = Migrator::new();
2481        migrator.register(path).unwrap();
2482
2483        let json = "[]";
2484        let domains: Vec<Domain> = migrator.load_vec("test", json).unwrap();
2485
2486        assert_eq!(domains.len(), 0);
2487    }
2488
2489    #[test]
2490    fn test_load_vec_mixed_versions() {
2491        // Setup migration path
2492        let path = Migrator::define("test")
2493            .from::<V1>()
2494            .step::<V2>()
2495            .step::<V3>()
2496            .into::<Domain>();
2497
2498        let mut migrator = Migrator::new();
2499        migrator.register(path).unwrap();
2500
2501        // JSON with mixed versions
2502        let json = r#"[
2503            {"version":"1.0.0","data":{"value":"v1-item"}},
2504            {"version":"2.0.0","data":{"value":"v2-item","count":42}},
2505            {"version":"3.0.0","data":{"value":"v3-item","count":99,"enabled":false}}
2506        ]"#;
2507
2508        let domains: Vec<Domain> = migrator.load_vec("test", json).unwrap();
2509
2510        assert_eq!(domains.len(), 3);
2511
2512        // V1 item migrated to domain
2513        assert_eq!(domains[0].value, "v1-item");
2514        assert_eq!(domains[0].count, 0);
2515        assert!(domains[0].enabled);
2516
2517        // V2 item migrated to domain
2518        assert_eq!(domains[1].value, "v2-item");
2519        assert_eq!(domains[1].count, 42);
2520        assert!(domains[1].enabled);
2521
2522        // V3 item converted to domain
2523        assert_eq!(domains[2].value, "v3-item");
2524        assert_eq!(domains[2].count, 99);
2525        assert!(!domains[2].enabled);
2526    }
2527
2528    #[test]
2529    fn test_load_vec_from_json_values() {
2530        let path = Migrator::define("test")
2531            .from::<V1>()
2532            .step::<V2>()
2533            .step::<V3>()
2534            .into::<Domain>();
2535
2536        let mut migrator = Migrator::new();
2537        migrator.register(path).unwrap();
2538
2539        // Create Vec<serde_json::Value> directly
2540        let values: Vec<serde_json::Value> = vec![
2541            serde_json::json!({"version":"1.0.0","data":{"value":"direct1"}}),
2542            serde_json::json!({"version":"1.0.0","data":{"value":"direct2"}}),
2543        ];
2544
2545        let domains: Vec<Domain> = migrator.load_vec_from("test", values).unwrap();
2546
2547        assert_eq!(domains.len(), 2);
2548        assert_eq!(domains[0].value, "direct1");
2549        assert_eq!(domains[1].value, "direct2");
2550    }
2551
2552    #[test]
2553    fn test_save_vec_empty() {
2554        let migrator = Migrator::new();
2555        let empty: Vec<V1> = vec![];
2556
2557        let json = migrator.save_vec(empty).unwrap();
2558
2559        assert_eq!(json, "[]");
2560    }
2561
2562    #[test]
2563    fn test_load_vec_invalid_json() {
2564        let path = Migrator::define("test")
2565            .from::<V1>()
2566            .step::<V2>()
2567            .step::<V3>()
2568            .into::<Domain>();
2569
2570        let mut migrator = Migrator::new();
2571        migrator.register(path).unwrap();
2572
2573        let invalid_json = "{ not an array }";
2574        let result: Result<Vec<Domain>, MigrationError> = migrator.load_vec("test", invalid_json);
2575
2576        assert!(matches!(
2577            result,
2578            Err(MigrationError::DeserializationError(_))
2579        ));
2580    }
2581
2582    #[test]
2583    fn test_load_vec_entity_not_found() {
2584        let migrator = Migrator::new();
2585
2586        let json = r#"[{"version":"1.0.0","data":{"value":"test"}}]"#;
2587        let result: Result<Vec<Domain>, MigrationError> = migrator.load_vec("unknown", json);
2588
2589        assert!(matches!(result, Err(MigrationError::EntityNotFound(_))));
2590    }
2591
2592    #[test]
2593    fn test_save_vec_latest_version() {
2594        let migrator = Migrator::new();
2595
2596        let items = vec![
2597            V3 {
2598                value: "latest1".to_string(),
2599                count: 10,
2600                enabled: true,
2601            },
2602            V3 {
2603                value: "latest2".to_string(),
2604                count: 20,
2605                enabled: false,
2606            },
2607        ];
2608
2609        let json = migrator.save_vec(items).unwrap();
2610
2611        // Verify structure
2612        assert!(json.contains("\"version\":\"3.0.0\""));
2613        assert!(json.contains("latest1"));
2614        assert!(json.contains("latest2"));
2615        assert!(json.contains("\"count\":10"));
2616        assert!(json.contains("\"count\":20"));
2617    }
2618
2619    #[test]
2620    fn test_load_with_fallback_versioned_data() {
2621        let path = Migrator::define("test")
2622            .from::<V1>()
2623            .step::<V2>()
2624            .step::<V3>()
2625            .into::<Domain>();
2626
2627        let mut migrator = Migrator::new();
2628        migrator.register(path).unwrap();
2629
2630        // Test with properly versioned data
2631        let versioned_json = r#"{"version":"1.0.0","data":{"value":"versioned_test"}}"#;
2632        let result: Domain = migrator.load_with_fallback("test", versioned_json).unwrap();
2633
2634        assert_eq!(result.value, "versioned_test");
2635        assert_eq!(result.count, 0); // Default from V1->V2 migration
2636        assert!(result.enabled); // Default from V2->V3 migration
2637    }
2638
2639    #[test]
2640    fn test_load_with_fallback_legacy_data() {
2641        let path = Migrator::define("test")
2642            .from::<V1>()
2643            .step::<V2>()
2644            .step::<V3>()
2645            .into::<Domain>();
2646
2647        let mut migrator = Migrator::new();
2648        migrator.register(path).unwrap();
2649
2650        // Test with legacy data (no version field) - should treat as V1
2651        let legacy_json = r#"{"value":"legacy_test"}"#;
2652        let result: Domain = migrator.load_with_fallback("test", legacy_json).unwrap();
2653
2654        assert_eq!(result.value, "legacy_test");
2655        assert_eq!(result.count, 0); // Default from V1->V2 migration
2656        assert!(result.enabled); // Default from V2->V3 migration
2657    }
2658
2659    #[test]
2660    fn test_load_with_fallback_mixed_formats() {
2661        let path = Migrator::define("test")
2662            .from::<V2>()
2663            .step::<V3>()
2664            .into::<Domain>();
2665
2666        let mut migrator = Migrator::new();
2667        migrator.register(path).unwrap();
2668
2669        // Test with legacy data that matches V2 format
2670        let legacy_v2_json = r#"{"value":"legacy_v2","count":42}"#;
2671        let result: Domain = migrator.load_with_fallback("test", legacy_v2_json).unwrap();
2672
2673        assert_eq!(result.value, "legacy_v2");
2674        assert_eq!(result.count, 42);
2675        assert!(result.enabled); // Default from V2->V3 migration
2676
2677        // Test with versioned data
2678        let versioned_json = r#"{"version":"2.0.0","data":{"value":"versioned_v2","count":99}}"#;
2679        let result2: Domain = migrator.load_with_fallback("test", versioned_json).unwrap();
2680
2681        assert_eq!(result2.value, "versioned_v2");
2682        assert_eq!(result2.count, 99);
2683        assert!(result2.enabled);
2684    }
2685
2686    #[test]
2687    fn test_load_from_with_fallback_toml_value() {
2688        let path = Migrator::define("test")
2689            .from::<V1>()
2690            .step::<V2>()
2691            .step::<V3>()
2692            .into::<Domain>();
2693
2694        let mut migrator = Migrator::new();
2695        migrator.register(path).unwrap();
2696
2697        // Simulate loading from TOML or other format
2698        let data = serde_json::json!({"value": "from_toml"});
2699        let result: Domain = migrator.load_from_with_fallback("test", data).unwrap();
2700
2701        assert_eq!(result.value, "from_toml");
2702        assert_eq!(result.count, 0);
2703        assert!(result.enabled);
2704    }
2705
2706    #[test]
2707    fn test_load_with_fallback_no_migration_path() {
2708        let migrator = Migrator::new();
2709
2710        let legacy_json = r#"{"value":"test"}"#;
2711        let result: Result<Domain, MigrationError> =
2712            migrator.load_with_fallback("unknown", legacy_json);
2713
2714        assert!(matches!(result, Err(MigrationError::EntityNotFound(_))));
2715    }
2716
2717    #[test]
2718    fn test_load_with_fallback_empty_migration_path() {
2719        // Register entity with no migration steps (direct conversion)
2720        let path = Migrator::define("test").from::<V3>().into::<Domain>();
2721
2722        let mut migrator = Migrator::new();
2723        migrator.register(path).unwrap();
2724
2725        // Legacy data should still work with direct conversion
2726        let legacy_json = r#"{"value":"direct","count":10,"enabled":false}"#;
2727        let result: Domain = migrator.load_with_fallback("test", legacy_json).unwrap();
2728
2729        assert_eq!(result.value, "direct");
2730        assert_eq!(result.count, 10);
2731        assert!(!result.enabled);
2732    }
2733
2734    #[test]
2735    fn test_load_with_fallback_invalid_json() {
2736        let path = Migrator::define("test")
2737            .from::<V1>()
2738            .step::<V2>()
2739            .step::<V3>()
2740            .into::<Domain>();
2741
2742        let mut migrator = Migrator::new();
2743        migrator.register(path).unwrap();
2744
2745        let invalid_json = "{ invalid json }";
2746        let result: Result<Domain, MigrationError> =
2747            migrator.load_with_fallback("test", invalid_json);
2748
2749        assert!(matches!(
2750            result,
2751            Err(MigrationError::DeserializationError(_))
2752        ));
2753    }
2754
2755    #[test]
2756    fn test_load_with_fallback_non_object_data() {
2757        let path = Migrator::define("test")
2758            .from::<V1>()
2759            .step::<V2>()
2760            .step::<V3>()
2761            .into::<Domain>();
2762
2763        let mut migrator = Migrator::new();
2764        migrator.register(path).unwrap();
2765
2766        // Array instead of object
2767        let array_json = r#"["not", "an", "object"]"#;
2768        let result: Result<Domain, MigrationError> =
2769            migrator.load_with_fallback("test", array_json);
2770
2771        assert!(matches!(
2772            result,
2773            Err(MigrationError::DeserializationError(_))
2774        ));
2775    }
2776}