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