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 a flat format JSON string.
334    ///
335    /// This is a convenience method for loading from flat format JSON where the version
336    /// field is at the same level as the data fields.
337    ///
338    /// # Arguments
339    ///
340    /// * `entity` - The entity name used when registering the migration path
341    /// * `json` - A JSON string containing versioned data in flat format
342    ///
343    /// # Returns
344    ///
345    /// The migrated data as the domain model type
346    ///
347    /// # Errors
348    ///
349    /// Returns an error if:
350    /// - The JSON cannot be parsed
351    /// - The entity is not registered
352    /// - A migration step fails
353    ///
354    /// # Example
355    ///
356    /// ```ignore
357    /// let json = r#"{"version":"1.0.0","id":"task-1","title":"My Task"}"#;
358    /// let domain: TaskEntity = migrator.load_flat("task", json)?;
359    /// ```
360    pub fn load_flat<D: DeserializeOwned>(
361        &self,
362        entity: &str,
363        json: &str,
364    ) -> Result<D, MigrationError> {
365        let data: serde_json::Value = serde_json::from_str(json).map_err(|e| {
366            MigrationError::DeserializationError(format!("Failed to parse JSON: {}", e))
367        })?;
368        self.load_flat_from(entity, data)
369    }
370
371    /// Loads and migrates data from any serde-compatible format in flat format.
372    ///
373    /// This method expects the version field to be at the same level as the data fields.
374    /// It uses the registered migration path's runtime-configured keys (respecting the
375    /// Path > Migrator > Trait priority).
376    ///
377    /// # Arguments
378    ///
379    /// * `entity` - The entity name used when registering the migration path
380    /// * `value` - A serde-compatible value containing versioned data in flat format
381    ///
382    /// # Returns
383    ///
384    /// The migrated data as the domain model type
385    ///
386    /// # Errors
387    ///
388    /// Returns an error if:
389    /// - The entity is not registered
390    /// - The data format is invalid
391    /// - A migration step fails
392    ///
393    /// # Example
394    ///
395    /// ```ignore
396    /// let toml_value: toml::Value = toml::from_str(toml_str)?;
397    /// let domain: TaskEntity = migrator.load_flat_from("task", toml_value)?;
398    /// ```
399    pub fn load_flat_from<D, T>(&self, entity: &str, value: T) -> Result<D, MigrationError>
400    where
401        D: DeserializeOwned,
402        T: Serialize,
403    {
404        let path = self
405            .paths
406            .get(entity)
407            .ok_or_else(|| MigrationError::EntityNotFound(entity.to_string()))?;
408
409        let version_key = &path.version_key;
410
411        // Convert to serde_json::Value for manipulation
412        let mut value = serde_json::to_value(value).map_err(|e| {
413            MigrationError::SerializationError(format!("Failed to convert input: {}", e))
414        })?;
415
416        // Extract version from the flat structure
417        let obj = value.as_object_mut().ok_or_else(|| {
418            MigrationError::DeserializationError(
419                "Expected object with version field at top level".to_string(),
420            )
421        })?;
422
423        let current_version = obj
424            .remove(version_key)
425            .ok_or_else(|| {
426                MigrationError::DeserializationError(format!(
427                    "Missing '{}' field in flat format",
428                    version_key
429                ))
430            })?
431            .as_str()
432            .ok_or_else(|| {
433                MigrationError::DeserializationError(format!(
434                    "Invalid '{}' field type",
435                    version_key
436                ))
437            })?
438            .to_string();
439
440        // Now obj contains only data fields (version has been removed)
441        let mut current_data = serde_json::Value::Object(obj.clone());
442        let mut current_version = current_version;
443
444        // Apply migration steps until we reach a version with no further steps
445        while let Some(migrate_fn) = path.steps.get(&current_version) {
446            // Migration function returns raw value, no wrapping
447            current_data = migrate_fn(current_data.clone())?;
448
449            // Update version to the next step
450            match path.versions.iter().position(|v| v == &current_version) {
451                Some(idx) if idx + 1 < path.versions.len() => {
452                    current_version = path.versions[idx + 1].clone();
453                }
454                _ => break,
455            }
456        }
457
458        // Finalize into domain model
459        let domain_value = (path.finalize)(current_data)?;
460
461        serde_json::from_value(domain_value).map_err(|e| {
462            MigrationError::DeserializationError(format!("Failed to convert to domain: {}", e))
463        })
464    }
465
466    /// Saves versioned data to a JSON string.
467    ///
468    /// This method wraps the provided data with its version information and serializes
469    /// it to JSON format. The resulting JSON can later be loaded and migrated using
470    /// the `load` method.
471    ///
472    /// # Arguments
473    ///
474    /// * `data` - The versioned data to save
475    ///
476    /// # Returns
477    ///
478    /// A JSON string with the format: `{"version":"x.y.z","data":{...}}`
479    ///
480    /// # Errors
481    ///
482    /// Returns `SerializationError` if the data cannot be serialized to JSON.
483    ///
484    /// # Example
485    ///
486    /// ```ignore
487    /// let task = TaskV1_0_0 {
488    ///     id: "task-1".to_string(),
489    ///     title: "My Task".to_string(),
490    /// };
491    ///
492    /// let migrator = Migrator::new();
493    /// let json = migrator.save(task)?;
494    /// // json: {"version":"1.0.0","data":{"id":"task-1","title":"My Task"}}
495    /// ```
496    pub fn save<T: Versioned + Serialize>(&self, data: T) -> Result<String, MigrationError> {
497        // Use custom keys from the type's Versioned trait
498        let version_key = T::VERSION_KEY;
499        let data_key = T::DATA_KEY;
500
501        // Serialize the data
502        let data_value = serde_json::to_value(&data).map_err(|e| {
503            MigrationError::SerializationError(format!("Failed to serialize data: {}", e))
504        })?;
505
506        // Build the wrapper with custom keys
507        let mut map = serde_json::Map::new();
508        map.insert(
509            version_key.to_string(),
510            serde_json::Value::String(T::VERSION.to_string()),
511        );
512        map.insert(data_key.to_string(), data_value);
513
514        serde_json::to_string(&map).map_err(|e| {
515            MigrationError::SerializationError(format!("Failed to serialize wrapper: {}", e))
516        })
517    }
518
519    /// Saves versioned data to a JSON string in flat format.
520    ///
521    /// Unlike `save()`, this method produces a flat JSON structure where the version
522    /// field is at the same level as the data fields, not wrapped in a separate object.
523    ///
524    /// # Arguments
525    ///
526    /// * `data` - The versioned data to save
527    ///
528    /// # Returns
529    ///
530    /// A JSON string with the format: `{"version":"x.y.z","field1":"value1",...}`
531    ///
532    /// # Errors
533    ///
534    /// Returns `SerializationError` if the data cannot be serialized to JSON.
535    ///
536    /// # Example
537    ///
538    /// ```ignore
539    /// let task = TaskV1_0_0 {
540    ///     id: "task-1".to_string(),
541    ///     title: "My Task".to_string(),
542    /// };
543    ///
544    /// let migrator = Migrator::new();
545    /// let json = migrator.save_flat(task)?;
546    /// // json: {"version":"1.0.0","id":"task-1","title":"My Task"}
547    /// ```
548    pub fn save_flat<T: Versioned + Serialize>(&self, data: T) -> Result<String, MigrationError> {
549        let version_key = T::VERSION_KEY;
550
551        // Serialize the data to a JSON object
552        let mut data_value = serde_json::to_value(&data).map_err(|e| {
553            MigrationError::SerializationError(format!("Failed to serialize data: {}", e))
554        })?;
555
556        // Ensure it's an object so we can add the version field
557        let obj = data_value.as_object_mut().ok_or_else(|| {
558            MigrationError::SerializationError(
559                "Data must serialize to a JSON object for flat format".to_string(),
560            )
561        })?;
562
563        // Add the version field to the same level as data fields
564        obj.insert(
565            version_key.to_string(),
566            serde_json::Value::String(T::VERSION.to_string()),
567        );
568
569        serde_json::to_string(&obj).map_err(|e| {
570            MigrationError::SerializationError(format!("Failed to serialize flat format: {}", e))
571        })
572    }
573
574    /// Loads and migrates multiple entities from any serde-compatible format.
575    ///
576    /// This is the generic version that accepts any type implementing `Serialize`.
577    /// For JSON arrays, use the convenience method `load_vec` instead.
578    ///
579    /// # Arguments
580    ///
581    /// * `entity` - The entity name used when registering the migration path
582    /// * `data` - Array of versioned data in any serde-compatible format
583    ///
584    /// # Returns
585    ///
586    /// A vector of migrated data as domain model types
587    ///
588    /// # Errors
589    ///
590    /// Returns an error if:
591    /// - The data cannot be converted to the internal format
592    /// - The entity is not registered
593    /// - Any migration step fails
594    ///
595    /// # Example
596    ///
597    /// ```ignore
598    /// // Load from TOML array
599    /// let toml_array: Vec<toml::Value> = /* ... */;
600    /// let domains: Vec<TaskEntity> = migrator.load_vec_from("task", toml_array)?;
601    ///
602    /// // Load from JSON Value array
603    /// let json_array: Vec<serde_json::Value> = /* ... */;
604    /// let domains: Vec<TaskEntity> = migrator.load_vec_from("task", json_array)?;
605    /// ```
606    pub fn load_vec_from<D, T>(&self, entity: &str, data: Vec<T>) -> Result<Vec<D>, MigrationError>
607    where
608        D: DeserializeOwned,
609        T: Serialize,
610    {
611        data.into_iter()
612            .map(|item| self.load_from(entity, item))
613            .collect()
614    }
615
616    /// Loads and migrates multiple entities from a JSON array string.
617    ///
618    /// This is a convenience method for the common case of loading from a JSON array.
619    /// For other formats, use `load_vec_from` instead.
620    ///
621    /// # Arguments
622    ///
623    /// * `entity` - The entity name used when registering the migration path
624    /// * `json` - A JSON array string containing versioned data
625    ///
626    /// # Returns
627    ///
628    /// A vector of migrated data as domain model types
629    ///
630    /// # Errors
631    ///
632    /// Returns an error if:
633    /// - The JSON cannot be parsed
634    /// - The entity is not registered
635    /// - Any migration step fails
636    ///
637    /// # Example
638    ///
639    /// ```ignore
640    /// let json = r#"[
641    ///     {"version":"1.0.0","data":{"id":"task-1","title":"Task 1"}},
642    ///     {"version":"1.0.0","data":{"id":"task-2","title":"Task 2"}}
643    /// ]"#;
644    /// let domains: Vec<TaskEntity> = migrator.load_vec("task", json)?;
645    /// ```
646    pub fn load_vec<D: DeserializeOwned>(
647        &self,
648        entity: &str,
649        json: &str,
650    ) -> Result<Vec<D>, MigrationError> {
651        let data: Vec<serde_json::Value> = serde_json::from_str(json).map_err(|e| {
652            MigrationError::DeserializationError(format!("Failed to parse JSON array: {}", e))
653        })?;
654        self.load_vec_from(entity, data)
655    }
656
657    /// Loads and migrates multiple entities from a flat format JSON array string.
658    ///
659    /// This is a convenience method for loading from a JSON array where each element
660    /// has the version field at the same level as the data fields.
661    ///
662    /// # Arguments
663    ///
664    /// * `entity` - The entity name used when registering the migration path
665    /// * `json` - A JSON array string containing versioned data in flat format
666    ///
667    /// # Returns
668    ///
669    /// A vector of migrated data as domain model types
670    ///
671    /// # Errors
672    ///
673    /// Returns an error if:
674    /// - The JSON cannot be parsed
675    /// - The entity is not registered
676    /// - Any migration step fails
677    ///
678    /// # Example
679    ///
680    /// ```ignore
681    /// let json = r#"[
682    ///     {"version":"1.0.0","id":"task-1","title":"Task 1"},
683    ///     {"version":"1.0.0","id":"task-2","title":"Task 2"}
684    /// ]"#;
685    /// let domains: Vec<TaskEntity> = migrator.load_vec_flat("task", json)?;
686    /// ```
687    pub fn load_vec_flat<D: DeserializeOwned>(
688        &self,
689        entity: &str,
690        json: &str,
691    ) -> Result<Vec<D>, MigrationError> {
692        let data: Vec<serde_json::Value> = serde_json::from_str(json).map_err(|e| {
693            MigrationError::DeserializationError(format!("Failed to parse JSON array: {}", e))
694        })?;
695        self.load_vec_flat_from(entity, data)
696    }
697
698    /// Loads and migrates multiple entities from any serde-compatible format in flat format.
699    ///
700    /// This method expects each element to have the version field at the same level
701    /// as the data fields. It uses the registered migration path's runtime-configured
702    /// keys (respecting the Path > Migrator > Trait priority).
703    ///
704    /// # Arguments
705    ///
706    /// * `entity` - The entity name used when registering the migration path
707    /// * `data` - Vector of serde-compatible values in flat format
708    ///
709    /// # Returns
710    ///
711    /// A vector of migrated data as domain model types
712    ///
713    /// # Errors
714    ///
715    /// Returns an error if:
716    /// - The entity is not registered
717    /// - The data format is invalid
718    /// - Any migration step fails
719    ///
720    /// # Example
721    ///
722    /// ```ignore
723    /// let toml_array: Vec<toml::Value> = /* ... */;
724    /// let domains: Vec<TaskEntity> = migrator.load_vec_flat_from("task", toml_array)?;
725    /// ```
726    pub fn load_vec_flat_from<D, T>(
727        &self,
728        entity: &str,
729        data: Vec<T>,
730    ) -> Result<Vec<D>, MigrationError>
731    where
732        D: DeserializeOwned,
733        T: Serialize,
734    {
735        data.into_iter()
736            .map(|item| self.load_flat_from(entity, item))
737            .collect()
738    }
739
740    /// Saves multiple versioned entities to a JSON array string.
741    ///
742    /// This method wraps each item with its version information and serializes
743    /// them as a JSON array. The resulting JSON can later be loaded and migrated
744    /// using the `load_vec` method.
745    ///
746    /// # Arguments
747    ///
748    /// * `data` - Vector of versioned data to save
749    ///
750    /// # Returns
751    ///
752    /// A JSON array string where each element has the format: `{"version":"x.y.z","data":{...}}`
753    ///
754    /// # Errors
755    ///
756    /// Returns `SerializationError` if the data cannot be serialized to JSON.
757    ///
758    /// # Example
759    ///
760    /// ```ignore
761    /// let tasks = vec![
762    ///     TaskV1_0_0 {
763    ///         id: "task-1".to_string(),
764    ///         title: "Task 1".to_string(),
765    ///     },
766    ///     TaskV1_0_0 {
767    ///         id: "task-2".to_string(),
768    ///         title: "Task 2".to_string(),
769    ///     },
770    /// ];
771    ///
772    /// let migrator = Migrator::new();
773    /// let json = migrator.save_vec(tasks)?;
774    /// // json: [{"version":"1.0.0","data":{"id":"task-1",...}}, ...]
775    /// ```
776    pub fn save_vec<T: Versioned + Serialize>(
777        &self,
778        data: Vec<T>,
779    ) -> Result<String, MigrationError> {
780        let version_key = T::VERSION_KEY;
781        let data_key = T::DATA_KEY;
782
783        let wrappers: Result<Vec<serde_json::Value>, MigrationError> = data
784            .into_iter()
785            .map(|item| {
786                let data_value = serde_json::to_value(&item).map_err(|e| {
787                    MigrationError::SerializationError(format!("Failed to serialize item: {}", e))
788                })?;
789
790                let mut map = serde_json::Map::new();
791                map.insert(
792                    version_key.to_string(),
793                    serde_json::Value::String(T::VERSION.to_string()),
794                );
795                map.insert(data_key.to_string(), data_value);
796
797                Ok(serde_json::Value::Object(map))
798            })
799            .collect();
800
801        serde_json::to_string(&wrappers?).map_err(|e| {
802            MigrationError::SerializationError(format!("Failed to serialize data array: {}", e))
803        })
804    }
805
806    /// Saves multiple versioned entities to a JSON array string in flat format.
807    ///
808    /// This method serializes each item with the version field at the same level
809    /// as the data fields, not wrapped in a separate object.
810    ///
811    /// # Arguments
812    ///
813    /// * `data` - Vector of versioned data to save
814    ///
815    /// # Returns
816    ///
817    /// A JSON array string where each element has the format: `{"version":"x.y.z","field1":"value1",...}`
818    ///
819    /// # Errors
820    ///
821    /// Returns `SerializationError` if the data cannot be serialized to JSON.
822    ///
823    /// # Example
824    ///
825    /// ```ignore
826    /// let tasks = vec![
827    ///     TaskV1_0_0 {
828    ///         id: "task-1".to_string(),
829    ///         title: "Task 1".to_string(),
830    ///     },
831    ///     TaskV1_0_0 {
832    ///         id: "task-2".to_string(),
833    ///         title: "Task 2".to_string(),
834    ///     },
835    /// ];
836    ///
837    /// let migrator = Migrator::new();
838    /// let json = migrator.save_vec_flat(tasks)?;
839    /// // json: [{"version":"1.0.0","id":"task-1",...}, ...]
840    /// ```
841    pub fn save_vec_flat<T: Versioned + Serialize>(
842        &self,
843        data: Vec<T>,
844    ) -> Result<String, MigrationError> {
845        let version_key = T::VERSION_KEY;
846
847        let flat_items: Result<Vec<serde_json::Value>, MigrationError> = data
848            .into_iter()
849            .map(|item| {
850                let mut data_value = serde_json::to_value(&item).map_err(|e| {
851                    MigrationError::SerializationError(format!("Failed to serialize item: {}", e))
852                })?;
853
854                let obj = data_value.as_object_mut().ok_or_else(|| {
855                    MigrationError::SerializationError(
856                        "Data must serialize to a JSON object for flat format".to_string(),
857                    )
858                })?;
859
860                // Add version field at the same level
861                obj.insert(
862                    version_key.to_string(),
863                    serde_json::Value::String(T::VERSION.to_string()),
864                );
865
866                Ok(serde_json::Value::Object(obj.clone()))
867            })
868            .collect();
869
870        serde_json::to_string(&flat_items?).map_err(|e| {
871            MigrationError::SerializationError(format!(
872                "Failed to serialize flat data array: {}",
873                e
874            ))
875        })
876    }
877
878    /// Saves a domain entity to a JSON string using its latest versioned format.
879    ///
880    /// This method automatically converts the domain entity to its latest version
881    /// and saves it with version information.
882    ///
883    /// # Arguments
884    ///
885    /// * `entity` - The domain entity to save (must implement `LatestVersioned`)
886    ///
887    /// # Returns
888    ///
889    /// A JSON string with the format: `{"version":"x.y.z","data":{...}}`
890    ///
891    /// # Errors
892    ///
893    /// Returns `SerializationError` if the entity cannot be serialized to JSON.
894    ///
895    /// # Example
896    ///
897    /// ```ignore
898    /// #[version_migrate(entity = "task", latest = TaskV1_1_0)]
899    /// struct TaskEntity {
900    ///     id: String,
901    ///     title: String,
902    ///     description: Option<String>,
903    /// }
904    ///
905    /// let entity = TaskEntity {
906    ///     id: "task-1".to_string(),
907    ///     title: "My Task".to_string(),
908    ///     description: Some("Description".to_string()),
909    /// };
910    ///
911    /// let migrator = Migrator::new();
912    /// let json = migrator.save_entity(entity)?;
913    /// // Automatically saved with latest version (1.1.0)
914    /// ```
915    pub fn save_entity<E: crate::LatestVersioned>(
916        &self,
917        entity: E,
918    ) -> Result<String, MigrationError> {
919        let latest = entity.to_latest();
920        self.save(latest)
921    }
922
923    /// Saves a domain entity to a JSON string in flat format using its latest versioned format.
924    ///
925    /// This method automatically converts the domain entity to its latest version
926    /// and saves it with the version field at the same level as data fields.
927    ///
928    /// # Arguments
929    ///
930    /// * `entity` - The domain entity to save (must implement `LatestVersioned`)
931    ///
932    /// # Returns
933    ///
934    /// A JSON string with the format: `{"version":"x.y.z","field1":"value1",...}`
935    ///
936    /// # Errors
937    ///
938    /// Returns `SerializationError` if the entity cannot be serialized to JSON.
939    ///
940    /// # Example
941    ///
942    /// ```ignore
943    /// #[version_migrate(entity = "task", latest = TaskV1_1_0)]
944    /// struct TaskEntity {
945    ///     id: String,
946    ///     title: String,
947    ///     description: Option<String>,
948    /// }
949    ///
950    /// let entity = TaskEntity {
951    ///     id: "task-1".to_string(),
952    ///     title: "My Task".to_string(),
953    ///     description: Some("Description".to_string()),
954    /// };
955    ///
956    /// let migrator = Migrator::new();
957    /// let json = migrator.save_entity_flat(entity)?;
958    /// // json: {"version":"1.1.0","id":"task-1","title":"My Task",...}
959    /// ```
960    pub fn save_entity_flat<E: crate::LatestVersioned>(
961        &self,
962        entity: E,
963    ) -> Result<String, MigrationError> {
964        let latest = entity.to_latest();
965        self.save_flat(latest)
966    }
967
968    /// Saves multiple domain entities to a JSON array string using their latest versioned format.
969    ///
970    /// This method automatically converts each domain entity to its latest version
971    /// and saves them as a JSON array.
972    ///
973    /// # Arguments
974    ///
975    /// * `entities` - Vector of domain entities to save
976    ///
977    /// # Returns
978    ///
979    /// A JSON array string where each element has the format: `{"version":"x.y.z","data":{...}}`
980    ///
981    /// # Errors
982    ///
983    /// Returns `SerializationError` if the entities cannot be serialized to JSON.
984    ///
985    /// # Example
986    ///
987    /// ```ignore
988    /// let entities = vec![
989    ///     TaskEntity { id: "1".into(), title: "Task 1".into(), description: None },
990    ///     TaskEntity { id: "2".into(), title: "Task 2".into(), description: None },
991    /// ];
992    ///
993    /// let json = migrator.save_entity_vec(entities)?;
994    /// ```
995    pub fn save_entity_vec<E: crate::LatestVersioned>(
996        &self,
997        entities: Vec<E>,
998    ) -> Result<String, MigrationError> {
999        let versioned: Vec<E::Latest> = entities.into_iter().map(|e| e.to_latest()).collect();
1000        self.save_vec(versioned)
1001    }
1002
1003    /// Saves multiple domain entities to a JSON array string in flat format using their latest versioned format.
1004    ///
1005    /// This method automatically converts each domain entity to its latest version
1006    /// and saves them with version fields at the same level as data fields.
1007    ///
1008    /// # Arguments
1009    ///
1010    /// * `entities` - Vector of domain entities to save
1011    ///
1012    /// # Returns
1013    ///
1014    /// A JSON array string where each element has the format: `{"version":"x.y.z","field1":"value1",...}`
1015    ///
1016    /// # Errors
1017    ///
1018    /// Returns `SerializationError` if the entities cannot be serialized to JSON.
1019    ///
1020    /// # Example
1021    ///
1022    /// ```ignore
1023    /// let entities = vec![
1024    ///     TaskEntity { id: "1".into(), title: "Task 1".into(), description: None },
1025    ///     TaskEntity { id: "2".into(), title: "Task 2".into(), description: None },
1026    /// ];
1027    ///
1028    /// let json = migrator.save_entity_vec_flat(entities)?;
1029    /// ```
1030    pub fn save_entity_vec_flat<E: crate::LatestVersioned>(
1031        &self,
1032        entities: Vec<E>,
1033    ) -> Result<String, MigrationError> {
1034        let versioned: Vec<E::Latest> = entities.into_iter().map(|e| e.to_latest()).collect();
1035        self.save_vec_flat(versioned)
1036    }
1037
1038    /// Saves a domain entity to a JSON string using its latest versioned format, by entity name.
1039    ///
1040    /// This method works without requiring the `VersionMigrate` macro on the entity type.
1041    /// Instead, it uses the save function registered during `register()` via `into_with_save()`.
1042    ///
1043    /// # Arguments
1044    ///
1045    /// * `entity_name` - The entity name used when registering the migration path
1046    /// * `entity` - The domain entity to save (must be Serialize)
1047    ///
1048    /// # Returns
1049    ///
1050    /// A JSON string with the format: `{"version":"x.y.z","data":{...}}`
1051    ///
1052    /// # Errors
1053    ///
1054    /// Returns `MigrationPathNotDefined` if the entity is not registered with save support.
1055    /// Returns `SerializationError` if the entity cannot be serialized.
1056    ///
1057    /// # Example
1058    ///
1059    /// ```ignore
1060    /// impl FromDomain<TaskEntity> for TaskV1_1_0 {
1061    ///     fn from_domain(entity: TaskEntity) -> Self { ... }
1062    /// }
1063    ///
1064    /// let path = Migrator::define("task")
1065    ///     .from::<TaskV1_0_0>()
1066    ///     .step::<TaskV1_1_0>()
1067    ///     .into_with_save::<TaskEntity>();
1068    ///
1069    /// migrator.register(path)?;
1070    ///
1071    /// let entity = TaskEntity { ... };
1072    /// let json = migrator.save_domain("task", entity)?;
1073    /// // → {"version":"1.1.0","data":{"id":"1","title":"My Task",...}}
1074    /// ```
1075    pub fn save_domain<T: Serialize>(
1076        &self,
1077        entity_name: &str,
1078        entity: T,
1079    ) -> Result<String, MigrationError> {
1080        let saver = self.domain_savers.get(entity_name).ok_or_else(|| {
1081            MigrationError::EntityNotFound(format!(
1082                "Entity '{}' is not registered with domain save support. Use into_with_save() when defining the migration path.",
1083                entity_name
1084            ))
1085        })?;
1086
1087        // Get version/data keys from registered path
1088        let path = self.paths.get(entity_name).ok_or_else(|| {
1089            MigrationError::EntityNotFound(format!("Entity '{}' is not registered", entity_name))
1090        })?;
1091
1092        let domain_value = serde_json::to_value(entity).map_err(|e| {
1093            MigrationError::SerializationError(format!("Failed to serialize entity: {}", e))
1094        })?;
1095
1096        (saver.save_fn)(domain_value, &path.version_key, &path.data_key)
1097    }
1098
1099    /// Saves a domain entity to a JSON string in flat format using its latest versioned format, by entity name.
1100    ///
1101    /// This method works without requiring the `VersionMigrate` macro on the entity type.
1102    /// The version field is placed at the same level as data fields.
1103    ///
1104    /// # Arguments
1105    ///
1106    /// * `entity_name` - The entity name used when registering the migration path
1107    /// * `entity` - The domain entity to save (must be Serialize)
1108    ///
1109    /// # Returns
1110    ///
1111    /// A JSON string with the format: `{"version":"x.y.z","field1":"value1",...}`
1112    ///
1113    /// # Errors
1114    ///
1115    /// Returns `MigrationPathNotDefined` if the entity is not registered with save support.
1116    /// Returns `SerializationError` if the entity cannot be serialized.
1117    ///
1118    /// # Example
1119    ///
1120    /// ```ignore
1121    /// let json = migrator.save_domain_flat("task", entity)?;
1122    /// // → {"version":"1.1.0","id":"1","title":"My Task",...}
1123    /// ```
1124    pub fn save_domain_flat<T: Serialize>(
1125        &self,
1126        entity_name: &str,
1127        entity: T,
1128    ) -> Result<String, MigrationError> {
1129        let saver = self.domain_savers.get(entity_name).ok_or_else(|| {
1130            MigrationError::EntityNotFound(format!(
1131                "Entity '{}' is not registered with domain save support. Use into_with_save() when defining the migration path.",
1132                entity_name
1133            ))
1134        })?;
1135
1136        // Get version key from registered path
1137        let path = self.paths.get(entity_name).ok_or_else(|| {
1138            MigrationError::EntityNotFound(format!("Entity '{}' is not registered", entity_name))
1139        })?;
1140
1141        let domain_value = serde_json::to_value(entity).map_err(|e| {
1142            MigrationError::SerializationError(format!("Failed to serialize entity: {}", e))
1143        })?;
1144
1145        (saver.save_flat_fn)(domain_value, &path.version_key)
1146    }
1147}
1148
1149impl Default for Migrator {
1150    fn default() -> Self {
1151        Self::new()
1152    }
1153}
1154
1155/// Builder for configuring a `Migrator` with default settings.
1156pub struct MigratorBuilder {
1157    default_version_key: Option<String>,
1158    default_data_key: Option<String>,
1159}
1160
1161impl MigratorBuilder {
1162    pub(crate) fn new() -> Self {
1163        Self {
1164            default_version_key: None,
1165            default_data_key: None,
1166        }
1167    }
1168
1169    /// Sets the default version key for all entities.
1170    ///
1171    /// This key will be used unless overridden by:
1172    /// - The entity's `MigrationPath` via `with_keys()`
1173    /// - The type's `Versioned` trait constants
1174    pub fn default_version_key(mut self, key: impl Into<String>) -> Self {
1175        self.default_version_key = Some(key.into());
1176        self
1177    }
1178
1179    /// Sets the default data key for all entities.
1180    ///
1181    /// This key will be used unless overridden by:
1182    /// - The entity's `MigrationPath` via `with_keys()`
1183    /// - The type's `Versioned` trait constants
1184    pub fn default_data_key(mut self, key: impl Into<String>) -> Self {
1185        self.default_data_key = Some(key.into());
1186        self
1187    }
1188
1189    /// Builds the `Migrator` with the configured defaults.
1190    pub fn build(self) -> Migrator {
1191        Migrator {
1192            paths: HashMap::new(),
1193            default_version_key: self.default_version_key,
1194            default_data_key: self.default_data_key,
1195            domain_savers: HashMap::new(),
1196        }
1197    }
1198}
1199
1200/// Marker type for builder state: start
1201pub struct Start;
1202
1203/// Marker type for builder state: has a starting version
1204pub struct HasFrom<V>(PhantomData<V>);
1205
1206/// Marker type for builder state: has intermediate steps
1207pub struct HasSteps<V>(PhantomData<V>);
1208
1209/// Builder for defining migration paths.
1210pub struct MigrationPathBuilder<State> {
1211    entity: String,
1212    steps: HashMap<String, MigrationFn>,
1213    versions: Vec<String>,
1214    version_key: String,
1215    data_key: String,
1216    custom_version_key: Option<String>,
1217    custom_data_key: Option<String>,
1218    _state: PhantomData<State>,
1219}
1220
1221impl MigrationPathBuilder<Start> {
1222    fn new(entity: String) -> Self {
1223        Self {
1224            entity,
1225            steps: HashMap::new(),
1226            versions: Vec::new(),
1227            version_key: String::from("version"),
1228            data_key: String::from("data"),
1229            custom_version_key: None,
1230            custom_data_key: None,
1231            _state: PhantomData,
1232        }
1233    }
1234
1235    /// Overrides the version and data keys for this migration path.
1236    ///
1237    /// This takes precedence over both the Migrator's defaults and the type's trait constants.
1238    ///
1239    /// # Example
1240    ///
1241    /// ```ignore
1242    /// Migrator::define("task")
1243    ///     .with_keys("custom_version", "custom_data")
1244    ///     .from::<TaskV1>()
1245    ///     .into::<TaskDomain>();
1246    /// ```
1247    pub fn with_keys(
1248        mut self,
1249        version_key: impl Into<String>,
1250        data_key: impl Into<String>,
1251    ) -> Self {
1252        self.custom_version_key = Some(version_key.into());
1253        self.custom_data_key = Some(data_key.into());
1254        self
1255    }
1256
1257    /// Sets the starting version for migrations.
1258    pub fn from<V: Versioned + DeserializeOwned>(self) -> MigrationPathBuilder<HasFrom<V>> {
1259        let mut versions = self.versions;
1260        versions.push(V::VERSION.to_string());
1261
1262        MigrationPathBuilder {
1263            entity: self.entity,
1264            steps: self.steps,
1265            versions,
1266            version_key: V::VERSION_KEY.to_string(),
1267            data_key: V::DATA_KEY.to_string(),
1268            custom_version_key: self.custom_version_key,
1269            custom_data_key: self.custom_data_key,
1270            _state: PhantomData,
1271        }
1272    }
1273}
1274
1275impl<V> MigrationPathBuilder<HasFrom<V>>
1276where
1277    V: Versioned + DeserializeOwned,
1278{
1279    /// Adds a migration step to the next version.
1280    pub fn step<Next>(mut self) -> MigrationPathBuilder<HasSteps<Next>>
1281    where
1282        V: MigratesTo<Next>,
1283        Next: Versioned + DeserializeOwned + Serialize,
1284    {
1285        let from_version = V::VERSION.to_string();
1286        let migration_fn: MigrationFn = Box::new(move |value| {
1287            let from_value: V = serde_json::from_value(value).map_err(|e| {
1288                MigrationError::DeserializationError(format!(
1289                    "Failed to deserialize version {}: {}",
1290                    V::VERSION,
1291                    e
1292                ))
1293            })?;
1294
1295            let to_value = from_value.migrate();
1296
1297            // Return the raw migrated value without wrapping
1298            serde_json::to_value(&to_value).map_err(|e| MigrationError::MigrationStepFailed {
1299                from: V::VERSION.to_string(),
1300                to: Next::VERSION.to_string(),
1301                error: e.to_string(),
1302            })
1303        });
1304
1305        self.steps.insert(from_version, migration_fn);
1306        self.versions.push(Next::VERSION.to_string());
1307
1308        MigrationPathBuilder {
1309            entity: self.entity,
1310            steps: self.steps,
1311            versions: self.versions,
1312            version_key: self.version_key,
1313            data_key: self.data_key,
1314            custom_version_key: self.custom_version_key,
1315            custom_data_key: self.custom_data_key,
1316            _state: PhantomData,
1317        }
1318    }
1319
1320    /// Finalizes the migration path with conversion to domain model.
1321    pub fn into<D: DeserializeOwned + Serialize>(self) -> MigrationPath<D>
1322    where
1323        V: IntoDomain<D>,
1324    {
1325        let finalize: Box<
1326            dyn Fn(serde_json::Value) -> Result<serde_json::Value, MigrationError> + Send + Sync,
1327        > = Box::new(move |value| {
1328            let versioned: V = serde_json::from_value(value).map_err(|e| {
1329                MigrationError::DeserializationError(format!(
1330                    "Failed to deserialize final version: {}",
1331                    e
1332                ))
1333            })?;
1334
1335            let domain = versioned.into_domain();
1336
1337            serde_json::to_value(domain).map_err(|e| MigrationError::MigrationStepFailed {
1338                from: V::VERSION.to_string(),
1339                to: "domain".to_string(),
1340                error: e.to_string(),
1341            })
1342        });
1343
1344        MigrationPath {
1345            entity: self.entity,
1346            inner: EntityMigrationPath {
1347                steps: self.steps,
1348                finalize,
1349                versions: self.versions.clone(),
1350                version_key: self.version_key,
1351                data_key: self.data_key,
1352            },
1353            versions: self.versions,
1354            custom_version_key: self.custom_version_key,
1355            custom_data_key: self.custom_data_key,
1356            save_fn: None,
1357            save_flat_fn: None,
1358            _phantom: PhantomData,
1359        }
1360    }
1361
1362    /// Finalizes the migration path with conversion to domain model and enables domain entity saving.
1363    ///
1364    /// This variant registers save functions that allow saving domain entities directly by entity name,
1365    /// without needing the `VersionMigrate` macro on the entity type.
1366    ///
1367    /// # Requirements
1368    ///
1369    /// The latest versioned type (V) must implement `FromDomain<D>` to convert domain entities
1370    /// back to the versioned format for saving.
1371    ///
1372    /// # Example
1373    ///
1374    /// ```ignore
1375    /// impl FromDomain<TaskEntity> for TaskV1_1_0 {
1376    ///     fn from_domain(entity: TaskEntity) -> Self {
1377    ///         TaskV1_1_0 {
1378    ///             id: entity.id,
1379    ///             title: entity.title,
1380    ///             description: entity.description,
1381    ///         }
1382    ///     }
1383    /// }
1384    ///
1385    /// let path = Migrator::define("task")
1386    ///     .from::<TaskV1_0_0>()
1387    ///     .step::<TaskV1_1_0>()
1388    ///     .into_with_save::<TaskEntity>();
1389    ///
1390    /// migrator.register(path)?;
1391    ///
1392    /// // Now you can save by entity name
1393    /// let entity = TaskEntity { ... };
1394    /// let json = migrator.save_domain("task", entity)?;
1395    /// ```
1396    pub fn into_with_save<D: DeserializeOwned + Serialize>(self) -> MigrationPath<D>
1397    where
1398        V: IntoDomain<D> + crate::FromDomain<D>,
1399    {
1400        let finalize: Box<
1401            dyn Fn(serde_json::Value) -> Result<serde_json::Value, MigrationError> + Send + Sync,
1402        > = Box::new(move |value| {
1403            let versioned: V = serde_json::from_value(value).map_err(|e| {
1404                MigrationError::DeserializationError(format!(
1405                    "Failed to deserialize final version: {}",
1406                    e
1407                ))
1408            })?;
1409
1410            let domain = versioned.into_domain();
1411
1412            serde_json::to_value(domain).map_err(|e| MigrationError::MigrationStepFailed {
1413                from: V::VERSION.to_string(),
1414                to: "domain".to_string(),
1415                error: e.to_string(),
1416            })
1417        });
1418
1419        // Create save function for domain entities
1420        let version = V::VERSION;
1421
1422        let save_fn: DomainSaveFn = Box::new(move |domain_value, vkey, dkey| {
1423            let domain: D = serde_json::from_value(domain_value).map_err(|e| {
1424                MigrationError::DeserializationError(format!("Failed to deserialize domain: {}", e))
1425            })?;
1426
1427            let latest = V::from_domain(domain);
1428            let data_value = serde_json::to_value(&latest).map_err(|e| {
1429                MigrationError::SerializationError(format!("Failed to serialize latest: {}", e))
1430            })?;
1431
1432            let mut map = serde_json::Map::new();
1433            map.insert(
1434                vkey.to_string(),
1435                serde_json::Value::String(version.to_string()),
1436            );
1437            map.insert(dkey.to_string(), data_value);
1438
1439            serde_json::to_string(&map).map_err(|e| {
1440                MigrationError::SerializationError(format!("Failed to serialize wrapper: {}", e))
1441            })
1442        });
1443
1444        let save_flat_fn: DomainSaveFlatFn = Box::new(move |domain_value, vkey| {
1445            let domain: D = serde_json::from_value(domain_value).map_err(|e| {
1446                MigrationError::DeserializationError(format!("Failed to deserialize domain: {}", e))
1447            })?;
1448
1449            let latest = V::from_domain(domain);
1450            let mut data_value = serde_json::to_value(&latest).map_err(|e| {
1451                MigrationError::SerializationError(format!("Failed to serialize latest: {}", e))
1452            })?;
1453
1454            let obj = data_value.as_object_mut().ok_or_else(|| {
1455                MigrationError::SerializationError(
1456                    "Data must serialize to a JSON object for flat format".to_string(),
1457                )
1458            })?;
1459
1460            obj.insert(
1461                vkey.to_string(),
1462                serde_json::Value::String(version.to_string()),
1463            );
1464
1465            serde_json::to_string(&obj).map_err(|e| {
1466                MigrationError::SerializationError(format!(
1467                    "Failed to serialize flat format: {}",
1468                    e
1469                ))
1470            })
1471        });
1472
1473        MigrationPath {
1474            entity: self.entity,
1475            inner: EntityMigrationPath {
1476                steps: self.steps,
1477                finalize,
1478                versions: self.versions.clone(),
1479                version_key: self.version_key,
1480                data_key: self.data_key,
1481            },
1482            versions: self.versions,
1483            custom_version_key: self.custom_version_key,
1484            custom_data_key: self.custom_data_key,
1485            save_fn: Some(save_fn),
1486            save_flat_fn: Some(save_flat_fn),
1487            _phantom: PhantomData,
1488        }
1489    }
1490}
1491
1492impl<V> MigrationPathBuilder<HasSteps<V>>
1493where
1494    V: Versioned + DeserializeOwned,
1495{
1496    /// Adds another migration step.
1497    pub fn step<Next>(mut self) -> MigrationPathBuilder<HasSteps<Next>>
1498    where
1499        V: MigratesTo<Next>,
1500        Next: Versioned + DeserializeOwned + Serialize,
1501    {
1502        let from_version = V::VERSION.to_string();
1503        let migration_fn: MigrationFn = Box::new(move |value| {
1504            let from_value: V = serde_json::from_value(value).map_err(|e| {
1505                MigrationError::DeserializationError(format!(
1506                    "Failed to deserialize version {}: {}",
1507                    V::VERSION,
1508                    e
1509                ))
1510            })?;
1511
1512            let to_value = from_value.migrate();
1513
1514            // Return the raw migrated value without wrapping
1515            serde_json::to_value(&to_value).map_err(|e| MigrationError::MigrationStepFailed {
1516                from: V::VERSION.to_string(),
1517                to: Next::VERSION.to_string(),
1518                error: e.to_string(),
1519            })
1520        });
1521
1522        self.steps.insert(from_version, migration_fn);
1523        self.versions.push(Next::VERSION.to_string());
1524
1525        MigrationPathBuilder {
1526            entity: self.entity,
1527            steps: self.steps,
1528            versions: self.versions,
1529            version_key: self.version_key,
1530            data_key: self.data_key,
1531            custom_version_key: self.custom_version_key,
1532            custom_data_key: self.custom_data_key,
1533            _state: PhantomData,
1534        }
1535    }
1536
1537    /// Finalizes the migration path with conversion to domain model.
1538    pub fn into<D: DeserializeOwned + Serialize>(self) -> MigrationPath<D>
1539    where
1540        V: IntoDomain<D>,
1541    {
1542        let finalize: Box<
1543            dyn Fn(serde_json::Value) -> Result<serde_json::Value, MigrationError> + Send + Sync,
1544        > = Box::new(move |value| {
1545            let versioned: V = serde_json::from_value(value).map_err(|e| {
1546                MigrationError::DeserializationError(format!(
1547                    "Failed to deserialize final version: {}",
1548                    e
1549                ))
1550            })?;
1551
1552            let domain = versioned.into_domain();
1553
1554            serde_json::to_value(domain).map_err(|e| MigrationError::MigrationStepFailed {
1555                from: V::VERSION.to_string(),
1556                to: "domain".to_string(),
1557                error: e.to_string(),
1558            })
1559        });
1560
1561        MigrationPath {
1562            entity: self.entity,
1563            inner: EntityMigrationPath {
1564                steps: self.steps,
1565                finalize,
1566                versions: self.versions.clone(),
1567                version_key: self.version_key,
1568                data_key: self.data_key,
1569            },
1570            versions: self.versions,
1571            custom_version_key: self.custom_version_key,
1572            custom_data_key: self.custom_data_key,
1573            save_fn: None,
1574            save_flat_fn: None,
1575            _phantom: PhantomData,
1576        }
1577    }
1578
1579    /// Finalizes the migration path with conversion to domain model and enables domain entity saving.
1580    ///
1581    /// See `MigrationPathBuilder<HasFrom<V>>::into_with_save` for details.
1582    pub fn into_with_save<D: DeserializeOwned + Serialize>(self) -> MigrationPath<D>
1583    where
1584        V: IntoDomain<D> + crate::FromDomain<D>,
1585    {
1586        let finalize: Box<
1587            dyn Fn(serde_json::Value) -> Result<serde_json::Value, MigrationError> + Send + Sync,
1588        > = Box::new(move |value| {
1589            let versioned: V = serde_json::from_value(value).map_err(|e| {
1590                MigrationError::DeserializationError(format!(
1591                    "Failed to deserialize final version: {}",
1592                    e
1593                ))
1594            })?;
1595
1596            let domain = versioned.into_domain();
1597
1598            serde_json::to_value(domain).map_err(|e| MigrationError::MigrationStepFailed {
1599                from: V::VERSION.to_string(),
1600                to: "domain".to_string(),
1601                error: e.to_string(),
1602            })
1603        });
1604
1605        // Create save function for domain entities
1606        let version = V::VERSION;
1607
1608        let save_fn: DomainSaveFn = Box::new(move |domain_value, vkey, dkey| {
1609            let domain: D = serde_json::from_value(domain_value).map_err(|e| {
1610                MigrationError::DeserializationError(format!("Failed to deserialize domain: {}", e))
1611            })?;
1612
1613            let latest = V::from_domain(domain);
1614            let data_value = serde_json::to_value(&latest).map_err(|e| {
1615                MigrationError::SerializationError(format!("Failed to serialize latest: {}", e))
1616            })?;
1617
1618            let mut map = serde_json::Map::new();
1619            map.insert(
1620                vkey.to_string(),
1621                serde_json::Value::String(version.to_string()),
1622            );
1623            map.insert(dkey.to_string(), data_value);
1624
1625            serde_json::to_string(&map).map_err(|e| {
1626                MigrationError::SerializationError(format!("Failed to serialize wrapper: {}", e))
1627            })
1628        });
1629
1630        let save_flat_fn: DomainSaveFlatFn = Box::new(move |domain_value, vkey| {
1631            let domain: D = serde_json::from_value(domain_value).map_err(|e| {
1632                MigrationError::DeserializationError(format!("Failed to deserialize domain: {}", e))
1633            })?;
1634
1635            let latest = V::from_domain(domain);
1636            let mut data_value = serde_json::to_value(&latest).map_err(|e| {
1637                MigrationError::SerializationError(format!("Failed to serialize latest: {}", e))
1638            })?;
1639
1640            let obj = data_value.as_object_mut().ok_or_else(|| {
1641                MigrationError::SerializationError(
1642                    "Data must serialize to a JSON object for flat format".to_string(),
1643                )
1644            })?;
1645
1646            obj.insert(
1647                vkey.to_string(),
1648                serde_json::Value::String(version.to_string()),
1649            );
1650
1651            serde_json::to_string(&obj).map_err(|e| {
1652                MigrationError::SerializationError(format!(
1653                    "Failed to serialize flat format: {}",
1654                    e
1655                ))
1656            })
1657        });
1658
1659        MigrationPath {
1660            entity: self.entity,
1661            inner: EntityMigrationPath {
1662                steps: self.steps,
1663                finalize,
1664                versions: self.versions.clone(),
1665                version_key: self.version_key,
1666                data_key: self.data_key,
1667            },
1668            versions: self.versions,
1669            custom_version_key: self.custom_version_key,
1670            custom_data_key: self.custom_data_key,
1671            save_fn: Some(save_fn),
1672            save_flat_fn: Some(save_flat_fn),
1673            _phantom: PhantomData,
1674        }
1675    }
1676}
1677
1678/// A complete migration path from versioned DTOs to a domain model.
1679pub struct MigrationPath<D> {
1680    entity: String,
1681    inner: EntityMigrationPath,
1682    /// List of versions in the migration path for validation
1683    versions: Vec<String>,
1684    /// Custom version key override (takes precedence over Migrator defaults)
1685    custom_version_key: Option<String>,
1686    /// Custom data key override (takes precedence over Migrator defaults)
1687    custom_data_key: Option<String>,
1688    /// Function to save domain entities (if FromDomain is implemented)
1689    save_fn: Option<DomainSaveFn>,
1690    /// Function to save domain entities in flat format (if FromDomain is implemented)
1691    save_flat_fn: Option<DomainSaveFlatFn>,
1692    _phantom: PhantomData<D>,
1693}
1694
1695/// A wrapper around JSON data that provides convenient query and update methods
1696/// for partial updates with automatic migration.
1697///
1698/// `ConfigMigrator` holds a JSON object and allows you to query specific keys,
1699/// automatically migrating versioned data to domain entities, and update them
1700/// with the latest version.
1701///
1702/// # Example
1703///
1704/// ```ignore
1705/// // config.json:
1706/// // {
1707/// //   "app_name": "MyApp",
1708/// //   "tasks": [
1709/// //     {"version": "1.0.0", "id": "1", "title": "Task 1"},
1710/// //     {"version": "2.0.0", "id": "2", "title": "Task 2", "description": "New"}
1711/// //   ]
1712/// // }
1713///
1714/// let config_json = fs::read_to_string("config.json")?;
1715/// let mut config = ConfigMigrator::from(&config_json, migrator)?;
1716///
1717/// // Query tasks (automatically migrates all versions to TaskEntity)
1718/// let mut tasks: Vec<TaskEntity> = config.query("tasks")?;
1719///
1720/// // Update tasks
1721/// tasks[0].title = "Updated Task".to_string();
1722///
1723/// // Save back with latest version
1724/// config.update("tasks", tasks)?;
1725///
1726/// // Write to file
1727/// fs::write("config.json", config.to_string()?)?;
1728/// ```
1729pub struct ConfigMigrator {
1730    root: serde_json::Value,
1731    migrator: Migrator,
1732}
1733
1734impl ConfigMigrator {
1735    /// Creates a new `ConfigMigrator` from a JSON string and a `Migrator`.
1736    ///
1737    /// # Errors
1738    ///
1739    /// Returns `MigrationError::DeserializationError` if the JSON is invalid.
1740    pub fn from(json: &str, migrator: Migrator) -> Result<Self, MigrationError> {
1741        let root = serde_json::from_str(json)
1742            .map_err(|e| MigrationError::DeserializationError(e.to_string()))?;
1743        Ok(Self { root, migrator })
1744    }
1745
1746    /// Queries a specific key from the JSON object and returns the data as domain entities.
1747    ///
1748    /// This method automatically migrates all versioned data to the latest version
1749    /// and converts them to domain entities.
1750    ///
1751    /// # Type Parameters
1752    ///
1753    /// - `T`: Must implement `Queryable` to provide the entity name, and `Deserialize` for deserialization.
1754    ///
1755    /// # Errors
1756    ///
1757    /// - Returns `MigrationError::DeserializationError` if the key doesn't contain a valid array.
1758    /// - Returns migration errors if the data cannot be migrated.
1759    ///
1760    /// # Example
1761    ///
1762    /// ```ignore
1763    /// let tasks: Vec<TaskEntity> = config.query("tasks")?;
1764    /// ```
1765    pub fn query<T>(&self, key: &str) -> Result<Vec<T>, MigrationError>
1766    where
1767        T: crate::Queryable + for<'de> serde::Deserialize<'de>,
1768    {
1769        let value = &self.root[key];
1770        if value.is_null() {
1771            return Ok(Vec::new());
1772        }
1773
1774        if !value.is_array() {
1775            return Err(MigrationError::DeserializationError(format!(
1776                "Key '{}' does not contain an array",
1777                key
1778            )));
1779        }
1780
1781        let array = value.as_array().unwrap(); // Safe because we checked is_array()
1782        self.migrator
1783            .load_vec_flat_from(T::ENTITY_NAME, array.to_vec())
1784    }
1785
1786    /// Updates a specific key in the JSON object with new domain entities.
1787    ///
1788    /// This method serializes the entities with the latest version (automatically
1789    /// determined from the `Queryable` trait) and updates the JSON object in place.
1790    ///
1791    /// # Type Parameters
1792    ///
1793    /// - `T`: Must implement `Serialize` and `Queryable`.
1794    ///
1795    /// # Errors
1796    ///
1797    /// - Returns `MigrationError::EntityNotFound` if the entity is not registered.
1798    /// - Returns serialization errors if the data cannot be serialized.
1799    ///
1800    /// # Example
1801    ///
1802    /// ```ignore
1803    /// // Version is automatically determined from the entity's migration path
1804    /// config.update("tasks", updated_tasks)?;
1805    /// ```
1806    pub fn update<T>(&mut self, key: &str, data: Vec<T>) -> Result<(), MigrationError>
1807    where
1808        T: serde::Serialize + crate::Queryable,
1809    {
1810        let entity_name = T::ENTITY_NAME;
1811        let latest_version = self
1812            .migrator
1813            .get_latest_version(entity_name)
1814            .ok_or_else(|| MigrationError::EntityNotFound(entity_name.to_string()))?;
1815
1816        // Serialize each item with version field
1817        let items: Vec<serde_json::Value> = data
1818            .into_iter()
1819            .map(|item| {
1820                let mut obj = serde_json::to_value(&item)
1821                    .map_err(|e| MigrationError::SerializationError(e.to_string()))?;
1822
1823                if let Some(obj_map) = obj.as_object_mut() {
1824                    obj_map.insert(
1825                        "version".to_string(),
1826                        serde_json::Value::String(latest_version.to_string()),
1827                    );
1828                }
1829
1830                Ok(obj)
1831            })
1832            .collect::<Result<Vec<_>, MigrationError>>()?;
1833
1834        self.root[key] = serde_json::Value::Array(items);
1835        Ok(())
1836    }
1837
1838    /// Converts the entire JSON object back to a pretty-printed string.
1839    ///
1840    /// # Errors
1841    ///
1842    /// Returns `MigrationError::SerializationError` if serialization fails.
1843    pub fn to_string(&self) -> Result<String, MigrationError> {
1844        serde_json::to_string_pretty(&self.root)
1845            .map_err(|e| MigrationError::SerializationError(e.to_string()))
1846    }
1847
1848    /// Converts the entire JSON object to a compact string.
1849    ///
1850    /// # Errors
1851    ///
1852    /// Returns `MigrationError::SerializationError` if serialization fails.
1853    pub fn to_string_compact(&self) -> Result<String, MigrationError> {
1854        serde_json::to_string(&self.root)
1855            .map_err(|e| MigrationError::SerializationError(e.to_string()))
1856    }
1857
1858    /// Returns a reference to the underlying JSON value.
1859    pub fn as_value(&self) -> &serde_json::Value {
1860        &self.root
1861    }
1862}
1863
1864#[cfg(test)]
1865mod tests {
1866    use super::*;
1867    use crate::{IntoDomain, MigratesTo, Versioned, VersionedWrapper};
1868    use serde::{Deserialize, Serialize};
1869
1870    // Test data structures
1871    #[derive(Serialize, Deserialize, Debug, PartialEq)]
1872    struct V1 {
1873        value: String,
1874    }
1875
1876    impl Versioned for V1 {
1877        const VERSION: &'static str = "1.0.0";
1878    }
1879
1880    #[derive(Serialize, Deserialize, Debug, PartialEq)]
1881    struct V2 {
1882        value: String,
1883        count: u32,
1884    }
1885
1886    impl Versioned for V2 {
1887        const VERSION: &'static str = "2.0.0";
1888    }
1889
1890    #[derive(Serialize, Deserialize, Debug, PartialEq)]
1891    struct V3 {
1892        value: String,
1893        count: u32,
1894        enabled: bool,
1895    }
1896
1897    impl Versioned for V3 {
1898        const VERSION: &'static str = "3.0.0";
1899    }
1900
1901    #[derive(Serialize, Deserialize, Debug, PartialEq)]
1902    struct Domain {
1903        value: String,
1904        count: u32,
1905        enabled: bool,
1906    }
1907
1908    impl MigratesTo<V2> for V1 {
1909        fn migrate(self) -> V2 {
1910            V2 {
1911                value: self.value,
1912                count: 0,
1913            }
1914        }
1915    }
1916
1917    impl MigratesTo<V3> for V2 {
1918        fn migrate(self) -> V3 {
1919            V3 {
1920                value: self.value,
1921                count: self.count,
1922                enabled: true,
1923            }
1924        }
1925    }
1926
1927    impl IntoDomain<Domain> for V3 {
1928        fn into_domain(self) -> Domain {
1929            Domain {
1930                value: self.value,
1931                count: self.count,
1932                enabled: self.enabled,
1933            }
1934        }
1935    }
1936
1937    #[test]
1938    fn test_migrator_new() {
1939        let migrator = Migrator::new();
1940        assert_eq!(migrator.paths.len(), 0);
1941    }
1942
1943    #[test]
1944    fn test_migrator_default() {
1945        let migrator = Migrator::default();
1946        assert_eq!(migrator.paths.len(), 0);
1947    }
1948
1949    #[test]
1950    fn test_single_step_migration() {
1951        let path = Migrator::define("test")
1952            .from::<V2>()
1953            .step::<V3>()
1954            .into::<Domain>();
1955
1956        let mut migrator = Migrator::new();
1957        migrator.register(path).unwrap();
1958
1959        let v2 = V2 {
1960            value: "test".to_string(),
1961            count: 42,
1962        };
1963        let wrapper = VersionedWrapper::from_versioned(v2);
1964        let json = serde_json::to_string(&wrapper).unwrap();
1965
1966        let result: Domain = migrator.load("test", &json).unwrap();
1967        assert_eq!(result.value, "test");
1968        assert_eq!(result.count, 42);
1969        assert!(result.enabled);
1970    }
1971
1972    #[test]
1973    fn test_multi_step_migration() {
1974        let path = Migrator::define("test")
1975            .from::<V1>()
1976            .step::<V2>()
1977            .step::<V3>()
1978            .into::<Domain>();
1979
1980        let mut migrator = Migrator::new();
1981        migrator.register(path).unwrap();
1982
1983        let v1 = V1 {
1984            value: "multi_step".to_string(),
1985        };
1986        let wrapper = VersionedWrapper::from_versioned(v1);
1987        let json = serde_json::to_string(&wrapper).unwrap();
1988
1989        let result: Domain = migrator.load("test", &json).unwrap();
1990        assert_eq!(result.value, "multi_step");
1991        assert_eq!(result.count, 0);
1992        assert!(result.enabled);
1993    }
1994
1995    #[test]
1996    fn test_no_migration_needed() {
1997        let path = Migrator::define("test").from::<V3>().into::<Domain>();
1998
1999        let mut migrator = Migrator::new();
2000        migrator.register(path).unwrap();
2001
2002        let v3 = V3 {
2003            value: "latest".to_string(),
2004            count: 100,
2005            enabled: false,
2006        };
2007        let wrapper = VersionedWrapper::from_versioned(v3);
2008        let json = serde_json::to_string(&wrapper).unwrap();
2009
2010        let result: Domain = migrator.load("test", &json).unwrap();
2011        assert_eq!(result.value, "latest");
2012        assert_eq!(result.count, 100);
2013        assert!(!result.enabled);
2014    }
2015
2016    #[test]
2017    fn test_entity_not_found() {
2018        let migrator = Migrator::new();
2019
2020        let v1 = V1 {
2021            value: "test".to_string(),
2022        };
2023        let wrapper = VersionedWrapper::from_versioned(v1);
2024        let json = serde_json::to_string(&wrapper).unwrap();
2025
2026        let result: Result<Domain, MigrationError> = migrator.load("unknown", &json);
2027        assert!(matches!(result, Err(MigrationError::EntityNotFound(_))));
2028
2029        if let Err(MigrationError::EntityNotFound(entity)) = result {
2030            assert_eq!(entity, "unknown");
2031        }
2032    }
2033
2034    #[test]
2035    fn test_invalid_json() {
2036        let path = Migrator::define("test").from::<V3>().into::<Domain>();
2037
2038        let mut migrator = Migrator::new();
2039        migrator.register(path).unwrap();
2040
2041        let invalid_json = "{ invalid json }";
2042        let result: Result<Domain, MigrationError> = migrator.load("test", invalid_json);
2043
2044        assert!(matches!(
2045            result,
2046            Err(MigrationError::DeserializationError(_))
2047        ));
2048    }
2049
2050    #[test]
2051    fn test_multiple_entities() {
2052        #[derive(Serialize, Deserialize, Debug, PartialEq)]
2053        struct OtherDomain {
2054            value: String,
2055        }
2056
2057        impl IntoDomain<OtherDomain> for V1 {
2058            fn into_domain(self) -> OtherDomain {
2059                OtherDomain { value: self.value }
2060            }
2061        }
2062
2063        let path1 = Migrator::define("entity1")
2064            .from::<V1>()
2065            .step::<V2>()
2066            .step::<V3>()
2067            .into::<Domain>();
2068
2069        let path2 = Migrator::define("entity2")
2070            .from::<V1>()
2071            .into::<OtherDomain>();
2072
2073        let mut migrator = Migrator::new();
2074        migrator.register(path1).unwrap();
2075        migrator.register(path2).unwrap();
2076
2077        // Test entity1
2078        let v1 = V1 {
2079            value: "entity1".to_string(),
2080        };
2081        let wrapper = VersionedWrapper::from_versioned(v1);
2082        let json = serde_json::to_string(&wrapper).unwrap();
2083        let result: Domain = migrator.load("entity1", &json).unwrap();
2084        assert_eq!(result.value, "entity1");
2085
2086        // Test entity2
2087        let v1 = V1 {
2088            value: "entity2".to_string(),
2089        };
2090        let wrapper = VersionedWrapper::from_versioned(v1);
2091        let json = serde_json::to_string(&wrapper).unwrap();
2092        let result: OtherDomain = migrator.load("entity2", &json).unwrap();
2093        assert_eq!(result.value, "entity2");
2094    }
2095
2096    #[test]
2097    fn test_save() {
2098        let migrator = Migrator::new();
2099
2100        let v1 = V1 {
2101            value: "test_save".to_string(),
2102        };
2103
2104        let json = migrator.save(v1).unwrap();
2105
2106        // Verify JSON contains version and data
2107        assert!(json.contains("\"version\""));
2108        assert!(json.contains("\"1.0.0\""));
2109        assert!(json.contains("\"data\""));
2110        assert!(json.contains("\"test_save\""));
2111
2112        // Verify it can be parsed back
2113        let parsed: VersionedWrapper<serde_json::Value> = serde_json::from_str(&json).unwrap();
2114        assert_eq!(parsed.version, "1.0.0");
2115    }
2116
2117    #[test]
2118    fn test_save_and_load_roundtrip() {
2119        let path = Migrator::define("test")
2120            .from::<V1>()
2121            .step::<V2>()
2122            .step::<V3>()
2123            .into::<Domain>();
2124
2125        let mut migrator = Migrator::new();
2126        migrator.register(path).unwrap();
2127
2128        // Save V1 data
2129        let v1 = V1 {
2130            value: "roundtrip".to_string(),
2131        };
2132        let json = migrator.save(v1).unwrap();
2133
2134        // Load and migrate to Domain
2135        let domain: Domain = migrator.load("test", &json).unwrap();
2136
2137        assert_eq!(domain.value, "roundtrip");
2138        assert_eq!(domain.count, 0); // Default from V1->V2 migration
2139        assert!(domain.enabled); // Default from V2->V3 migration
2140    }
2141
2142    #[test]
2143    fn test_save_latest_version() {
2144        let migrator = Migrator::new();
2145
2146        let v3 = V3 {
2147            value: "latest".to_string(),
2148            count: 42,
2149            enabled: false,
2150        };
2151
2152        let json = migrator.save(v3).unwrap();
2153
2154        // Verify the JSON structure
2155        assert!(json.contains("\"version\":\"3.0.0\""));
2156        assert!(json.contains("\"value\":\"latest\""));
2157        assert!(json.contains("\"count\":42"));
2158        assert!(json.contains("\"enabled\":false"));
2159    }
2160
2161    #[test]
2162    fn test_save_pretty() {
2163        let migrator = Migrator::new();
2164
2165        let v2 = V2 {
2166            value: "pretty".to_string(),
2167            count: 10,
2168        };
2169
2170        let json = migrator.save(v2).unwrap();
2171
2172        // Should be compact JSON (not pretty-printed)
2173        assert!(!json.contains('\n'));
2174        assert!(json.contains("\"version\":\"2.0.0\""));
2175    }
2176
2177    #[test]
2178    fn test_validation_invalid_version_order() {
2179        // Manually construct a path with invalid version ordering
2180        let entity = "test".to_string();
2181        let versions = vec!["2.0.0".to_string(), "1.0.0".to_string()]; // Wrong order
2182
2183        let result = Migrator::validate_migration_path(&entity, &versions);
2184        assert!(matches!(
2185            result,
2186            Err(MigrationError::InvalidVersionOrder { .. })
2187        ));
2188
2189        if let Err(MigrationError::InvalidVersionOrder {
2190            entity: e,
2191            from,
2192            to,
2193        }) = result
2194        {
2195            assert_eq!(e, "test");
2196            assert_eq!(from, "2.0.0");
2197            assert_eq!(to, "1.0.0");
2198        }
2199    }
2200
2201    #[test]
2202    fn test_validation_circular_path() {
2203        // Manually construct a path with circular reference
2204        let entity = "test".to_string();
2205        let versions = vec![
2206            "1.0.0".to_string(),
2207            "2.0.0".to_string(),
2208            "1.0.0".to_string(), // Circular!
2209        ];
2210
2211        let result = Migrator::validate_migration_path(&entity, &versions);
2212        assert!(matches!(
2213            result,
2214            Err(MigrationError::CircularMigrationPath { .. })
2215        ));
2216
2217        if let Err(MigrationError::CircularMigrationPath { entity: e, path }) = result {
2218            assert_eq!(e, "test");
2219            assert!(path.contains("1.0.0"));
2220            assert!(path.contains("2.0.0"));
2221        }
2222    }
2223
2224    #[test]
2225    fn test_validation_valid_path() {
2226        // Valid migration path
2227        let entity = "test".to_string();
2228        let versions = vec![
2229            "1.0.0".to_string(),
2230            "1.1.0".to_string(),
2231            "2.0.0".to_string(),
2232        ];
2233
2234        let result = Migrator::validate_migration_path(&entity, &versions);
2235        assert!(result.is_ok());
2236    }
2237
2238    #[test]
2239    fn test_validation_empty_path() {
2240        // Empty path should be valid
2241        let entity = "test".to_string();
2242        let versions = vec![];
2243
2244        let result = Migrator::validate_migration_path(&entity, &versions);
2245        assert!(result.is_ok());
2246    }
2247
2248    #[test]
2249    fn test_validation_single_version() {
2250        // Single version path should be valid (no steps, just final conversion)
2251        let entity = "test".to_string();
2252        let versions = vec!["1.0.0".to_string()];
2253
2254        let result = Migrator::validate_migration_path(&entity, &versions);
2255        assert!(result.is_ok());
2256    }
2257
2258    // Tests for Vec operations
2259    #[test]
2260    fn test_save_vec_and_load_vec() {
2261        let migrator = Migrator::new();
2262
2263        // Save multiple V1 items
2264        let items = vec![
2265            V1 {
2266                value: "item1".to_string(),
2267            },
2268            V1 {
2269                value: "item2".to_string(),
2270            },
2271            V1 {
2272                value: "item3".to_string(),
2273            },
2274        ];
2275
2276        let json = migrator.save_vec(items).unwrap();
2277
2278        // Verify JSON array format
2279        assert!(json.starts_with('['));
2280        assert!(json.ends_with(']'));
2281        assert!(json.contains("\"version\":\"1.0.0\""));
2282        assert!(json.contains("item1"));
2283        assert!(json.contains("item2"));
2284        assert!(json.contains("item3"));
2285
2286        // Setup migration path
2287        let path = Migrator::define("test")
2288            .from::<V1>()
2289            .step::<V2>()
2290            .step::<V3>()
2291            .into::<Domain>();
2292
2293        let mut migrator = Migrator::new();
2294        migrator.register(path).unwrap();
2295
2296        // Load and migrate the array
2297        let domains: Vec<Domain> = migrator.load_vec("test", &json).unwrap();
2298
2299        assert_eq!(domains.len(), 3);
2300        assert_eq!(domains[0].value, "item1");
2301        assert_eq!(domains[1].value, "item2");
2302        assert_eq!(domains[2].value, "item3");
2303
2304        // All should have default values from migration
2305        for domain in &domains {
2306            assert_eq!(domain.count, 0);
2307            assert!(domain.enabled);
2308        }
2309    }
2310
2311    #[test]
2312    fn test_load_vec_empty_array() {
2313        let path = Migrator::define("test")
2314            .from::<V1>()
2315            .step::<V2>()
2316            .step::<V3>()
2317            .into::<Domain>();
2318
2319        let mut migrator = Migrator::new();
2320        migrator.register(path).unwrap();
2321
2322        let json = "[]";
2323        let domains: Vec<Domain> = migrator.load_vec("test", json).unwrap();
2324
2325        assert_eq!(domains.len(), 0);
2326    }
2327
2328    #[test]
2329    fn test_load_vec_mixed_versions() {
2330        // Setup migration path
2331        let path = Migrator::define("test")
2332            .from::<V1>()
2333            .step::<V2>()
2334            .step::<V3>()
2335            .into::<Domain>();
2336
2337        let mut migrator = Migrator::new();
2338        migrator.register(path).unwrap();
2339
2340        // JSON with mixed versions
2341        let json = r#"[
2342            {"version":"1.0.0","data":{"value":"v1-item"}},
2343            {"version":"2.0.0","data":{"value":"v2-item","count":42}},
2344            {"version":"3.0.0","data":{"value":"v3-item","count":99,"enabled":false}}
2345        ]"#;
2346
2347        let domains: Vec<Domain> = migrator.load_vec("test", json).unwrap();
2348
2349        assert_eq!(domains.len(), 3);
2350
2351        // V1 item migrated to domain
2352        assert_eq!(domains[0].value, "v1-item");
2353        assert_eq!(domains[0].count, 0);
2354        assert!(domains[0].enabled);
2355
2356        // V2 item migrated to domain
2357        assert_eq!(domains[1].value, "v2-item");
2358        assert_eq!(domains[1].count, 42);
2359        assert!(domains[1].enabled);
2360
2361        // V3 item converted to domain
2362        assert_eq!(domains[2].value, "v3-item");
2363        assert_eq!(domains[2].count, 99);
2364        assert!(!domains[2].enabled);
2365    }
2366
2367    #[test]
2368    fn test_load_vec_from_json_values() {
2369        let path = Migrator::define("test")
2370            .from::<V1>()
2371            .step::<V2>()
2372            .step::<V3>()
2373            .into::<Domain>();
2374
2375        let mut migrator = Migrator::new();
2376        migrator.register(path).unwrap();
2377
2378        // Create Vec<serde_json::Value> directly
2379        let values: Vec<serde_json::Value> = vec![
2380            serde_json::json!({"version":"1.0.0","data":{"value":"direct1"}}),
2381            serde_json::json!({"version":"1.0.0","data":{"value":"direct2"}}),
2382        ];
2383
2384        let domains: Vec<Domain> = migrator.load_vec_from("test", values).unwrap();
2385
2386        assert_eq!(domains.len(), 2);
2387        assert_eq!(domains[0].value, "direct1");
2388        assert_eq!(domains[1].value, "direct2");
2389    }
2390
2391    #[test]
2392    fn test_save_vec_empty() {
2393        let migrator = Migrator::new();
2394        let empty: Vec<V1> = vec![];
2395
2396        let json = migrator.save_vec(empty).unwrap();
2397
2398        assert_eq!(json, "[]");
2399    }
2400
2401    #[test]
2402    fn test_load_vec_invalid_json() {
2403        let path = Migrator::define("test")
2404            .from::<V1>()
2405            .step::<V2>()
2406            .step::<V3>()
2407            .into::<Domain>();
2408
2409        let mut migrator = Migrator::new();
2410        migrator.register(path).unwrap();
2411
2412        let invalid_json = "{ not an array }";
2413        let result: Result<Vec<Domain>, MigrationError> = migrator.load_vec("test", invalid_json);
2414
2415        assert!(matches!(
2416            result,
2417            Err(MigrationError::DeserializationError(_))
2418        ));
2419    }
2420
2421    #[test]
2422    fn test_load_vec_entity_not_found() {
2423        let migrator = Migrator::new();
2424
2425        let json = r#"[{"version":"1.0.0","data":{"value":"test"}}]"#;
2426        let result: Result<Vec<Domain>, MigrationError> = migrator.load_vec("unknown", json);
2427
2428        assert!(matches!(result, Err(MigrationError::EntityNotFound(_))));
2429    }
2430
2431    #[test]
2432    fn test_save_vec_latest_version() {
2433        let migrator = Migrator::new();
2434
2435        let items = vec![
2436            V3 {
2437                value: "latest1".to_string(),
2438                count: 10,
2439                enabled: true,
2440            },
2441            V3 {
2442                value: "latest2".to_string(),
2443                count: 20,
2444                enabled: false,
2445            },
2446        ];
2447
2448        let json = migrator.save_vec(items).unwrap();
2449
2450        // Verify structure
2451        assert!(json.contains("\"version\":\"3.0.0\""));
2452        assert!(json.contains("latest1"));
2453        assert!(json.contains("latest2"));
2454        assert!(json.contains("\"count\":10"));
2455        assert!(json.contains("\"count\":20"));
2456    }
2457}