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