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/// A registered migration path for a specific entity type.
13struct EntityMigrationPath {
14    /// Maps version -> migration function to next version
15    steps: HashMap<String, MigrationFn>,
16    /// The final conversion to domain model
17    finalize: Box<dyn Fn(serde_json::Value) -> Result<serde_json::Value, MigrationError>>,
18    /// Ordered list of versions in the migration path
19    versions: Vec<String>,
20    /// The key name for version field in serialized data
21    version_key: String,
22    /// The key name for data field in serialized data
23    data_key: String,
24}
25
26/// The migration manager that orchestrates all migrations.
27pub struct Migrator {
28    paths: HashMap<String, EntityMigrationPath>,
29    default_version_key: Option<String>,
30    default_data_key: Option<String>,
31}
32
33impl Migrator {
34    /// Creates a new, empty migrator.
35    pub fn new() -> Self {
36        Self {
37            paths: HashMap::new(),
38            default_version_key: None,
39            default_data_key: None,
40        }
41    }
42
43    /// Creates a builder for configuring the migrator.
44    ///
45    /// # Example
46    ///
47    /// ```ignore
48    /// let migrator = Migrator::builder()
49    ///     .default_version_key("schema_version")
50    ///     .default_data_key("payload")
51    ///     .build();
52    /// ```
53    pub fn builder() -> MigratorBuilder {
54        MigratorBuilder::new()
55    }
56
57    /// Starts defining a migration path for an entity.
58    pub fn define(entity: &str) -> MigrationPathBuilder<Start> {
59        MigrationPathBuilder::new(entity.to_string())
60    }
61
62    /// Registers a migration path with validation.
63    ///
64    /// This method validates the migration path before registering it:
65    /// - Checks for circular migration paths
66    /// - Validates version ordering follows semver rules
67    ///
68    /// # Errors
69    ///
70    /// Returns an error if validation fails.
71    pub fn register<D>(&mut self, path: MigrationPath<D>) -> Result<(), MigrationError> {
72        Self::validate_migration_path(&path.entity, &path.versions)?;
73
74        // Resolve key priority: Path custom > Migrator default > EntityPath (trait constants)
75        let version_key = path
76            .custom_version_key
77            .or_else(|| self.default_version_key.clone())
78            .unwrap_or_else(|| path.inner.version_key.clone());
79
80        let data_key = path
81            .custom_data_key
82            .or_else(|| self.default_data_key.clone())
83            .unwrap_or_else(|| path.inner.data_key.clone());
84
85        let final_path = EntityMigrationPath {
86            steps: path.inner.steps,
87            finalize: path.inner.finalize,
88            versions: path.versions,
89            version_key,
90            data_key,
91        };
92
93        self.paths.insert(path.entity, final_path);
94        Ok(())
95    }
96
97    /// Validates a migration path for correctness.
98    fn validate_migration_path(entity: &str, versions: &[String]) -> Result<(), MigrationError> {
99        // Check for circular paths
100        Self::check_circular_path(entity, versions)?;
101
102        // Check version ordering
103        Self::check_version_ordering(entity, versions)?;
104
105        Ok(())
106    }
107
108    /// Checks if there are any circular dependencies in the migration path.
109    fn check_circular_path(entity: &str, versions: &[String]) -> Result<(), MigrationError> {
110        let mut seen = std::collections::HashSet::new();
111
112        for version in versions {
113            if !seen.insert(version) {
114                // Found a duplicate - circular path detected
115                let path = versions.join(" -> ");
116                return Err(MigrationError::CircularMigrationPath {
117                    entity: entity.to_string(),
118                    path,
119                });
120            }
121        }
122
123        Ok(())
124    }
125
126    /// Checks if versions are ordered according to semver rules.
127    fn check_version_ordering(entity: &str, versions: &[String]) -> Result<(), MigrationError> {
128        for i in 0..versions.len().saturating_sub(1) {
129            let current = &versions[i];
130            let next = &versions[i + 1];
131
132            // Parse versions
133            let current_ver = semver::Version::parse(current).map_err(|e| {
134                MigrationError::DeserializationError(format!("Invalid semver '{}': {}", current, e))
135            })?;
136
137            let next_ver = semver::Version::parse(next).map_err(|e| {
138                MigrationError::DeserializationError(format!("Invalid semver '{}': {}", next, e))
139            })?;
140
141            // Check that next version is greater than current
142            if next_ver <= current_ver {
143                return Err(MigrationError::InvalidVersionOrder {
144                    entity: entity.to_string(),
145                    from: current.clone(),
146                    to: next.clone(),
147                });
148            }
149        }
150
151        Ok(())
152    }
153
154    /// Loads and migrates data from any serde-compatible format.
155    ///
156    /// This is the generic version that accepts any type implementing `Serialize`.
157    /// For JSON strings, use the convenience method `load` instead.
158    ///
159    /// # Arguments
160    ///
161    /// * `entity` - The entity name used when registering the migration path
162    /// * `data` - Versioned data in any serde-compatible format (e.g., `toml::Value`, `serde_json::Value`)
163    ///
164    /// # Returns
165    ///
166    /// The migrated data as the domain model type
167    ///
168    /// # Errors
169    ///
170    /// Returns an error if:
171    /// - The data cannot be converted to the internal format
172    /// - The entity is not registered
173    /// - A migration step fails
174    ///
175    /// # Example
176    ///
177    /// ```ignore
178    /// // Load from TOML
179    /// let toml_data: toml::Value = toml::from_str(toml_str)?;
180    /// let domain: TaskEntity = migrator.load_from("task", toml_data)?;
181    ///
182    /// // Load from JSON Value
183    /// let json_data: serde_json::Value = serde_json::from_str(json_str)?;
184    /// let domain: TaskEntity = migrator.load_from("task", json_data)?;
185    /// ```
186    pub fn load_from<D, T>(&self, entity: &str, data: T) -> Result<D, MigrationError>
187    where
188        D: DeserializeOwned,
189        T: Serialize,
190    {
191        // Convert the input data to serde_json::Value for internal processing
192        let value = serde_json::to_value(data).map_err(|e| {
193            MigrationError::DeserializationError(format!(
194                "Failed to convert input data to internal format: {}",
195                e
196            ))
197        })?;
198
199        // Get the migration path for this entity
200        let path = self
201            .paths
202            .get(entity)
203            .ok_or_else(|| MigrationError::EntityNotFound(entity.to_string()))?;
204
205        let version_key = &path.version_key;
206        let data_key = &path.data_key;
207
208        // Extract version and data using custom keys
209        let obj = value.as_object().ok_or_else(|| {
210            MigrationError::DeserializationError(
211                "Expected object with version and data fields".to_string(),
212            )
213        })?;
214
215        let current_version = obj
216            .get(version_key)
217            .and_then(|v| v.as_str())
218            .ok_or_else(|| {
219                MigrationError::DeserializationError(format!(
220                    "Missing or invalid '{}' field",
221                    version_key
222                ))
223            })?
224            .to_string();
225
226        let mut current_data = obj
227            .get(data_key)
228            .ok_or_else(|| {
229                MigrationError::DeserializationError(format!("Missing '{}' field", data_key))
230            })?
231            .clone();
232
233        let mut current_version = current_version;
234
235        // Apply migration steps until we reach a version with no further steps
236        while let Some(migrate_fn) = path.steps.get(&current_version) {
237            // Migration function returns raw value, no wrapping
238            current_data = migrate_fn(current_data.clone())?;
239
240            // Update version to the next step
241            // Find the next version in the path
242            match path.versions.iter().position(|v| v == &current_version) {
243                Some(idx) if idx + 1 < path.versions.len() => {
244                    current_version = path.versions[idx + 1].clone();
245                }
246                _ => break,
247            }
248        }
249
250        // Finalize into domain model
251        let domain_value = (path.finalize)(current_data)?;
252
253        serde_json::from_value(domain_value).map_err(|e| {
254            MigrationError::DeserializationError(format!("Failed to convert to domain: {}", e))
255        })
256    }
257
258    /// Loads and migrates data from a JSON string.
259    ///
260    /// This is a convenience method for the common case of loading from JSON.
261    /// For other formats, use `load_from` instead.
262    ///
263    /// # Arguments
264    ///
265    /// * `entity` - The entity name used when registering the migration path
266    /// * `json` - A JSON string containing versioned data
267    ///
268    /// # Returns
269    ///
270    /// The migrated data as the domain model type
271    ///
272    /// # Errors
273    ///
274    /// Returns an error if:
275    /// - The JSON cannot be parsed
276    /// - The entity is not registered
277    /// - A migration step fails
278    ///
279    /// # Example
280    ///
281    /// ```ignore
282    /// let json = r#"{"version":"1.0.0","data":{"id":"task-1","title":"My Task"}}"#;
283    /// let domain: TaskEntity = migrator.load("task", json)?;
284    /// ```
285    pub fn load<D: DeserializeOwned>(&self, entity: &str, json: &str) -> Result<D, MigrationError> {
286        let data: serde_json::Value = serde_json::from_str(json).map_err(|e| {
287            MigrationError::DeserializationError(format!("Failed to parse JSON: {}", e))
288        })?;
289        self.load_from(entity, data)
290    }
291
292    /// Loads and migrates data from a flat format JSON string.
293    ///
294    /// This is a convenience method for loading from flat format JSON where the version
295    /// field is at the same level as the data fields.
296    ///
297    /// # Arguments
298    ///
299    /// * `entity` - The entity name used when registering the migration path
300    /// * `json` - A JSON string containing versioned data in flat format
301    ///
302    /// # Returns
303    ///
304    /// The migrated data as the domain model type
305    ///
306    /// # Errors
307    ///
308    /// Returns an error if:
309    /// - The JSON cannot be parsed
310    /// - The entity is not registered
311    /// - A migration step fails
312    ///
313    /// # Example
314    ///
315    /// ```ignore
316    /// let json = r#"{"version":"1.0.0","id":"task-1","title":"My Task"}"#;
317    /// let domain: TaskEntity = migrator.load_flat("task", json)?;
318    /// ```
319    pub fn load_flat<D: DeserializeOwned>(
320        &self,
321        entity: &str,
322        json: &str,
323    ) -> Result<D, MigrationError> {
324        let data: serde_json::Value = serde_json::from_str(json).map_err(|e| {
325            MigrationError::DeserializationError(format!("Failed to parse JSON: {}", e))
326        })?;
327        self.load_flat_from(entity, data)
328    }
329
330    /// Loads and migrates data from any serde-compatible format in flat format.
331    ///
332    /// This method expects the version field to be at the same level as the data fields.
333    /// It uses the registered migration path's runtime-configured keys (respecting the
334    /// Path > Migrator > Trait priority).
335    ///
336    /// # Arguments
337    ///
338    /// * `entity` - The entity name used when registering the migration path
339    /// * `value` - A serde-compatible value 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 entity is not registered
349    /// - The data format is invalid
350    /// - A migration step fails
351    ///
352    /// # Example
353    ///
354    /// ```ignore
355    /// let toml_value: toml::Value = toml::from_str(toml_str)?;
356    /// let domain: TaskEntity = migrator.load_flat_from("task", toml_value)?;
357    /// ```
358    pub fn load_flat_from<D, T>(&self, entity: &str, value: T) -> Result<D, MigrationError>
359    where
360        D: DeserializeOwned,
361        T: Serialize,
362    {
363        let path = self
364            .paths
365            .get(entity)
366            .ok_or_else(|| MigrationError::EntityNotFound(entity.to_string()))?;
367
368        let version_key = &path.version_key;
369
370        // Convert to serde_json::Value for manipulation
371        let mut value = serde_json::to_value(value).map_err(|e| {
372            MigrationError::SerializationError(format!("Failed to convert input: {}", e))
373        })?;
374
375        // Extract version from the flat structure
376        let obj = value.as_object_mut().ok_or_else(|| {
377            MigrationError::DeserializationError(
378                "Expected object with version field at top level".to_string(),
379            )
380        })?;
381
382        let current_version = obj
383            .remove(version_key)
384            .ok_or_else(|| {
385                MigrationError::DeserializationError(format!(
386                    "Missing '{}' field in flat format",
387                    version_key
388                ))
389            })?
390            .as_str()
391            .ok_or_else(|| {
392                MigrationError::DeserializationError(format!(
393                    "Invalid '{}' field type",
394                    version_key
395                ))
396            })?
397            .to_string();
398
399        // Now obj contains only data fields (version has been removed)
400        let mut current_data = serde_json::Value::Object(obj.clone());
401        let mut current_version = current_version;
402
403        // Apply migration steps until we reach a version with no further steps
404        while let Some(migrate_fn) = path.steps.get(&current_version) {
405            // Migration function returns raw value, no wrapping
406            current_data = migrate_fn(current_data.clone())?;
407
408            // Update version to the next step
409            match path.versions.iter().position(|v| v == &current_version) {
410                Some(idx) if idx + 1 < path.versions.len() => {
411                    current_version = path.versions[idx + 1].clone();
412                }
413                _ => break,
414            }
415        }
416
417        // Finalize into domain model
418        let domain_value = (path.finalize)(current_data)?;
419
420        serde_json::from_value(domain_value).map_err(|e| {
421            MigrationError::DeserializationError(format!("Failed to convert to domain: {}", e))
422        })
423    }
424
425    /// Saves versioned data to a JSON string.
426    ///
427    /// This method wraps the provided data with its version information and serializes
428    /// it to JSON format. The resulting JSON can later be loaded and migrated using
429    /// the `load` method.
430    ///
431    /// # Arguments
432    ///
433    /// * `data` - The versioned data to save
434    ///
435    /// # Returns
436    ///
437    /// A JSON string with the format: `{"version":"x.y.z","data":{...}}`
438    ///
439    /// # Errors
440    ///
441    /// Returns `SerializationError` if the data cannot be serialized to JSON.
442    ///
443    /// # Example
444    ///
445    /// ```ignore
446    /// let task = TaskV1_0_0 {
447    ///     id: "task-1".to_string(),
448    ///     title: "My Task".to_string(),
449    /// };
450    ///
451    /// let migrator = Migrator::new();
452    /// let json = migrator.save(task)?;
453    /// // json: {"version":"1.0.0","data":{"id":"task-1","title":"My Task"}}
454    /// ```
455    pub fn save<T: Versioned + Serialize>(&self, data: T) -> Result<String, MigrationError> {
456        // Use custom keys from the type's Versioned trait
457        let version_key = T::VERSION_KEY;
458        let data_key = T::DATA_KEY;
459
460        // Serialize the data
461        let data_value = serde_json::to_value(&data).map_err(|e| {
462            MigrationError::SerializationError(format!("Failed to serialize data: {}", e))
463        })?;
464
465        // Build the wrapper with custom keys
466        let mut map = serde_json::Map::new();
467        map.insert(
468            version_key.to_string(),
469            serde_json::Value::String(T::VERSION.to_string()),
470        );
471        map.insert(data_key.to_string(), data_value);
472
473        serde_json::to_string(&map).map_err(|e| {
474            MigrationError::SerializationError(format!("Failed to serialize wrapper: {}", e))
475        })
476    }
477
478    /// Saves versioned data to a JSON string in flat format.
479    ///
480    /// Unlike `save()`, this method produces a flat JSON structure where the version
481    /// field is at the same level as the data fields, not wrapped in a separate object.
482    ///
483    /// # Arguments
484    ///
485    /// * `data` - The versioned data to save
486    ///
487    /// # Returns
488    ///
489    /// A JSON string with the format: `{"version":"x.y.z","field1":"value1",...}`
490    ///
491    /// # Errors
492    ///
493    /// Returns `SerializationError` if the data cannot be serialized to JSON.
494    ///
495    /// # Example
496    ///
497    /// ```ignore
498    /// let task = TaskV1_0_0 {
499    ///     id: "task-1".to_string(),
500    ///     title: "My Task".to_string(),
501    /// };
502    ///
503    /// let migrator = Migrator::new();
504    /// let json = migrator.save_flat(task)?;
505    /// // json: {"version":"1.0.0","id":"task-1","title":"My Task"}
506    /// ```
507    pub fn save_flat<T: Versioned + Serialize>(&self, data: T) -> Result<String, MigrationError> {
508        let version_key = T::VERSION_KEY;
509
510        // Serialize the data to a JSON object
511        let mut data_value = serde_json::to_value(&data).map_err(|e| {
512            MigrationError::SerializationError(format!("Failed to serialize data: {}", e))
513        })?;
514
515        // Ensure it's an object so we can add the version field
516        let obj = data_value.as_object_mut().ok_or_else(|| {
517            MigrationError::SerializationError(
518                "Data must serialize to a JSON object for flat format".to_string(),
519            )
520        })?;
521
522        // Add the version field to the same level as data fields
523        obj.insert(
524            version_key.to_string(),
525            serde_json::Value::String(T::VERSION.to_string()),
526        );
527
528        serde_json::to_string(&obj).map_err(|e| {
529            MigrationError::SerializationError(format!("Failed to serialize flat format: {}", e))
530        })
531    }
532
533    /// Loads and migrates multiple entities from any serde-compatible format.
534    ///
535    /// This is the generic version that accepts any type implementing `Serialize`.
536    /// For JSON arrays, use the convenience method `load_vec` instead.
537    ///
538    /// # Arguments
539    ///
540    /// * `entity` - The entity name used when registering the migration path
541    /// * `data` - Array of versioned data in any serde-compatible format
542    ///
543    /// # Returns
544    ///
545    /// A vector of migrated data as domain model types
546    ///
547    /// # Errors
548    ///
549    /// Returns an error if:
550    /// - The data cannot be converted to the internal format
551    /// - The entity is not registered
552    /// - Any migration step fails
553    ///
554    /// # Example
555    ///
556    /// ```ignore
557    /// // Load from TOML array
558    /// let toml_array: Vec<toml::Value> = /* ... */;
559    /// let domains: Vec<TaskEntity> = migrator.load_vec_from("task", toml_array)?;
560    ///
561    /// // Load from JSON Value array
562    /// let json_array: Vec<serde_json::Value> = /* ... */;
563    /// let domains: Vec<TaskEntity> = migrator.load_vec_from("task", json_array)?;
564    /// ```
565    pub fn load_vec_from<D, T>(&self, entity: &str, data: Vec<T>) -> Result<Vec<D>, MigrationError>
566    where
567        D: DeserializeOwned,
568        T: Serialize,
569    {
570        data.into_iter()
571            .map(|item| self.load_from(entity, item))
572            .collect()
573    }
574
575    /// Loads and migrates multiple entities from a JSON array string.
576    ///
577    /// This is a convenience method for the common case of loading from a JSON array.
578    /// For other formats, use `load_vec_from` instead.
579    ///
580    /// # Arguments
581    ///
582    /// * `entity` - The entity name used when registering the migration path
583    /// * `json` - A JSON array string containing versioned data
584    ///
585    /// # Returns
586    ///
587    /// A vector of migrated data as domain model types
588    ///
589    /// # Errors
590    ///
591    /// Returns an error if:
592    /// - The JSON cannot be parsed
593    /// - The entity is not registered
594    /// - Any migration step fails
595    ///
596    /// # Example
597    ///
598    /// ```ignore
599    /// let json = r#"[
600    ///     {"version":"1.0.0","data":{"id":"task-1","title":"Task 1"}},
601    ///     {"version":"1.0.0","data":{"id":"task-2","title":"Task 2"}}
602    /// ]"#;
603    /// let domains: Vec<TaskEntity> = migrator.load_vec("task", json)?;
604    /// ```
605    pub fn load_vec<D: DeserializeOwned>(
606        &self,
607        entity: &str,
608        json: &str,
609    ) -> Result<Vec<D>, MigrationError> {
610        let data: Vec<serde_json::Value> = serde_json::from_str(json).map_err(|e| {
611            MigrationError::DeserializationError(format!("Failed to parse JSON array: {}", e))
612        })?;
613        self.load_vec_from(entity, data)
614    }
615
616    /// Loads and migrates multiple entities from a flat format JSON array string.
617    ///
618    /// This is a convenience method for loading from a JSON array where each element
619    /// has the version field at the same level as the data fields.
620    ///
621    /// # Arguments
622    ///
623    /// * `entity` - The entity name used when registering the migration path
624    /// * `json` - A JSON array string containing versioned data in flat format
625    ///
626    /// # Returns
627    ///
628    /// A vector of migrated data as domain model types
629    ///
630    /// # Errors
631    ///
632    /// Returns an error if:
633    /// - The JSON cannot be parsed
634    /// - The entity is not registered
635    /// - Any migration step fails
636    ///
637    /// # Example
638    ///
639    /// ```ignore
640    /// let json = r#"[
641    ///     {"version":"1.0.0","id":"task-1","title":"Task 1"},
642    ///     {"version":"1.0.0","id":"task-2","title":"Task 2"}
643    /// ]"#;
644    /// let domains: Vec<TaskEntity> = migrator.load_vec_flat("task", json)?;
645    /// ```
646    pub fn load_vec_flat<D: DeserializeOwned>(
647        &self,
648        entity: &str,
649        json: &str,
650    ) -> Result<Vec<D>, MigrationError> {
651        let data: Vec<serde_json::Value> = serde_json::from_str(json).map_err(|e| {
652            MigrationError::DeserializationError(format!("Failed to parse JSON array: {}", e))
653        })?;
654        self.load_vec_flat_from(entity, data)
655    }
656
657    /// Loads and migrates multiple entities from any serde-compatible format in flat format.
658    ///
659    /// This method expects each element to have the version field at the same level
660    /// as the data fields. It uses the registered migration path's runtime-configured
661    /// keys (respecting the Path > Migrator > Trait priority).
662    ///
663    /// # Arguments
664    ///
665    /// * `entity` - The entity name used when registering the migration path
666    /// * `data` - Vector of serde-compatible values in flat format
667    ///
668    /// # Returns
669    ///
670    /// A vector of migrated data as domain model types
671    ///
672    /// # Errors
673    ///
674    /// Returns an error if:
675    /// - The entity is not registered
676    /// - The data format is invalid
677    /// - Any migration step fails
678    ///
679    /// # Example
680    ///
681    /// ```ignore
682    /// let toml_array: Vec<toml::Value> = /* ... */;
683    /// let domains: Vec<TaskEntity> = migrator.load_vec_flat_from("task", toml_array)?;
684    /// ```
685    pub fn load_vec_flat_from<D, T>(
686        &self,
687        entity: &str,
688        data: Vec<T>,
689    ) -> Result<Vec<D>, MigrationError>
690    where
691        D: DeserializeOwned,
692        T: Serialize,
693    {
694        data.into_iter()
695            .map(|item| self.load_flat_from(entity, item))
696            .collect()
697    }
698
699    /// Saves multiple versioned entities to a JSON array string.
700    ///
701    /// This method wraps each item with its version information and serializes
702    /// them as a JSON array. The resulting JSON can later be loaded and migrated
703    /// using the `load_vec` method.
704    ///
705    /// # Arguments
706    ///
707    /// * `data` - Vector of versioned data to save
708    ///
709    /// # Returns
710    ///
711    /// A JSON array string where each element has the format: `{"version":"x.y.z","data":{...}}`
712    ///
713    /// # Errors
714    ///
715    /// Returns `SerializationError` if the data cannot be serialized to JSON.
716    ///
717    /// # Example
718    ///
719    /// ```ignore
720    /// let tasks = vec![
721    ///     TaskV1_0_0 {
722    ///         id: "task-1".to_string(),
723    ///         title: "Task 1".to_string(),
724    ///     },
725    ///     TaskV1_0_0 {
726    ///         id: "task-2".to_string(),
727    ///         title: "Task 2".to_string(),
728    ///     },
729    /// ];
730    ///
731    /// let migrator = Migrator::new();
732    /// let json = migrator.save_vec(tasks)?;
733    /// // json: [{"version":"1.0.0","data":{"id":"task-1",...}}, ...]
734    /// ```
735    pub fn save_vec<T: Versioned + Serialize>(
736        &self,
737        data: Vec<T>,
738    ) -> Result<String, MigrationError> {
739        let version_key = T::VERSION_KEY;
740        let data_key = T::DATA_KEY;
741
742        let wrappers: Result<Vec<serde_json::Value>, MigrationError> = data
743            .into_iter()
744            .map(|item| {
745                let data_value = serde_json::to_value(&item).map_err(|e| {
746                    MigrationError::SerializationError(format!("Failed to serialize item: {}", e))
747                })?;
748
749                let mut map = serde_json::Map::new();
750                map.insert(
751                    version_key.to_string(),
752                    serde_json::Value::String(T::VERSION.to_string()),
753                );
754                map.insert(data_key.to_string(), data_value);
755
756                Ok(serde_json::Value::Object(map))
757            })
758            .collect();
759
760        serde_json::to_string(&wrappers?).map_err(|e| {
761            MigrationError::SerializationError(format!("Failed to serialize data array: {}", e))
762        })
763    }
764
765    /// Saves multiple versioned entities to a JSON array string in flat format.
766    ///
767    /// This method serializes each item with the version field at the same level
768    /// as the data fields, not wrapped in a separate object.
769    ///
770    /// # Arguments
771    ///
772    /// * `data` - Vector of versioned data to save
773    ///
774    /// # Returns
775    ///
776    /// A JSON array string where each element has the format: `{"version":"x.y.z","field1":"value1",...}`
777    ///
778    /// # Errors
779    ///
780    /// Returns `SerializationError` if the data cannot be serialized to JSON.
781    ///
782    /// # Example
783    ///
784    /// ```ignore
785    /// let tasks = vec![
786    ///     TaskV1_0_0 {
787    ///         id: "task-1".to_string(),
788    ///         title: "Task 1".to_string(),
789    ///     },
790    ///     TaskV1_0_0 {
791    ///         id: "task-2".to_string(),
792    ///         title: "Task 2".to_string(),
793    ///     },
794    /// ];
795    ///
796    /// let migrator = Migrator::new();
797    /// let json = migrator.save_vec_flat(tasks)?;
798    /// // json: [{"version":"1.0.0","id":"task-1",...}, ...]
799    /// ```
800    pub fn save_vec_flat<T: Versioned + Serialize>(
801        &self,
802        data: Vec<T>,
803    ) -> Result<String, MigrationError> {
804        let version_key = T::VERSION_KEY;
805
806        let flat_items: Result<Vec<serde_json::Value>, MigrationError> = data
807            .into_iter()
808            .map(|item| {
809                let mut data_value = serde_json::to_value(&item).map_err(|e| {
810                    MigrationError::SerializationError(format!("Failed to serialize item: {}", e))
811                })?;
812
813                let obj = data_value.as_object_mut().ok_or_else(|| {
814                    MigrationError::SerializationError(
815                        "Data must serialize to a JSON object for flat format".to_string(),
816                    )
817                })?;
818
819                // Add version field at the same level
820                obj.insert(
821                    version_key.to_string(),
822                    serde_json::Value::String(T::VERSION.to_string()),
823                );
824
825                Ok(serde_json::Value::Object(obj.clone()))
826            })
827            .collect();
828
829        serde_json::to_string(&flat_items?).map_err(|e| {
830            MigrationError::SerializationError(format!(
831                "Failed to serialize flat data array: {}",
832                e
833            ))
834        })
835    }
836}
837
838impl Default for Migrator {
839    fn default() -> Self {
840        Self::new()
841    }
842}
843
844/// Builder for configuring a `Migrator` with default settings.
845pub struct MigratorBuilder {
846    default_version_key: Option<String>,
847    default_data_key: Option<String>,
848}
849
850impl MigratorBuilder {
851    pub(crate) fn new() -> Self {
852        Self {
853            default_version_key: None,
854            default_data_key: None,
855        }
856    }
857
858    /// Sets the default version key for all entities.
859    ///
860    /// This key will be used unless overridden by:
861    /// - The entity's `MigrationPath` via `with_keys()`
862    /// - The type's `Versioned` trait constants
863    pub fn default_version_key(mut self, key: impl Into<String>) -> Self {
864        self.default_version_key = Some(key.into());
865        self
866    }
867
868    /// Sets the default data key for all entities.
869    ///
870    /// This key will be used unless overridden by:
871    /// - The entity's `MigrationPath` via `with_keys()`
872    /// - The type's `Versioned` trait constants
873    pub fn default_data_key(mut self, key: impl Into<String>) -> Self {
874        self.default_data_key = Some(key.into());
875        self
876    }
877
878    /// Builds the `Migrator` with the configured defaults.
879    pub fn build(self) -> Migrator {
880        Migrator {
881            paths: HashMap::new(),
882            default_version_key: self.default_version_key,
883            default_data_key: self.default_data_key,
884        }
885    }
886}
887
888/// Marker type for builder state: start
889pub struct Start;
890
891/// Marker type for builder state: has a starting version
892pub struct HasFrom<V>(PhantomData<V>);
893
894/// Marker type for builder state: has intermediate steps
895pub struct HasSteps<V>(PhantomData<V>);
896
897/// Builder for defining migration paths.
898pub struct MigrationPathBuilder<State> {
899    entity: String,
900    steps: HashMap<String, MigrationFn>,
901    versions: Vec<String>,
902    version_key: String,
903    data_key: String,
904    custom_version_key: Option<String>,
905    custom_data_key: Option<String>,
906    _state: PhantomData<State>,
907}
908
909impl MigrationPathBuilder<Start> {
910    fn new(entity: String) -> Self {
911        Self {
912            entity,
913            steps: HashMap::new(),
914            versions: Vec::new(),
915            version_key: String::from("version"),
916            data_key: String::from("data"),
917            custom_version_key: None,
918            custom_data_key: None,
919            _state: PhantomData,
920        }
921    }
922
923    /// Overrides the version and data keys for this migration path.
924    ///
925    /// This takes precedence over both the Migrator's defaults and the type's trait constants.
926    ///
927    /// # Example
928    ///
929    /// ```ignore
930    /// Migrator::define("task")
931    ///     .with_keys("custom_version", "custom_data")
932    ///     .from::<TaskV1>()
933    ///     .into::<TaskDomain>();
934    /// ```
935    pub fn with_keys(
936        mut self,
937        version_key: impl Into<String>,
938        data_key: impl Into<String>,
939    ) -> Self {
940        self.custom_version_key = Some(version_key.into());
941        self.custom_data_key = Some(data_key.into());
942        self
943    }
944
945    /// Sets the starting version for migrations.
946    pub fn from<V: Versioned + DeserializeOwned>(self) -> MigrationPathBuilder<HasFrom<V>> {
947        let mut versions = self.versions;
948        versions.push(V::VERSION.to_string());
949
950        MigrationPathBuilder {
951            entity: self.entity,
952            steps: self.steps,
953            versions,
954            version_key: V::VERSION_KEY.to_string(),
955            data_key: V::DATA_KEY.to_string(),
956            custom_version_key: self.custom_version_key,
957            custom_data_key: self.custom_data_key,
958            _state: PhantomData,
959        }
960    }
961}
962
963impl<V> MigrationPathBuilder<HasFrom<V>>
964where
965    V: Versioned + DeserializeOwned,
966{
967    /// Adds a migration step to the next version.
968    pub fn step<Next>(mut self) -> MigrationPathBuilder<HasSteps<Next>>
969    where
970        V: MigratesTo<Next>,
971        Next: Versioned + DeserializeOwned + Serialize,
972    {
973        let from_version = V::VERSION.to_string();
974        let migration_fn: MigrationFn = Box::new(move |value| {
975            let from_value: V = serde_json::from_value(value).map_err(|e| {
976                MigrationError::DeserializationError(format!(
977                    "Failed to deserialize version {}: {}",
978                    V::VERSION,
979                    e
980                ))
981            })?;
982
983            let to_value = from_value.migrate();
984
985            // Return the raw migrated value without wrapping
986            serde_json::to_value(&to_value).map_err(|e| MigrationError::MigrationStepFailed {
987                from: V::VERSION.to_string(),
988                to: Next::VERSION.to_string(),
989                error: e.to_string(),
990            })
991        });
992
993        self.steps.insert(from_version, migration_fn);
994        self.versions.push(Next::VERSION.to_string());
995
996        MigrationPathBuilder {
997            entity: self.entity,
998            steps: self.steps,
999            versions: self.versions,
1000            version_key: self.version_key,
1001            data_key: self.data_key,
1002            custom_version_key: self.custom_version_key,
1003            custom_data_key: self.custom_data_key,
1004            _state: PhantomData,
1005        }
1006    }
1007
1008    /// Finalizes the migration path with conversion to domain model.
1009    pub fn into<D: DeserializeOwned + Serialize>(self) -> MigrationPath<D>
1010    where
1011        V: IntoDomain<D>,
1012    {
1013        let finalize: Box<dyn Fn(serde_json::Value) -> Result<serde_json::Value, MigrationError>> =
1014            Box::new(move |value| {
1015                let versioned: V = serde_json::from_value(value).map_err(|e| {
1016                    MigrationError::DeserializationError(format!(
1017                        "Failed to deserialize final version: {}",
1018                        e
1019                    ))
1020                })?;
1021
1022                let domain = versioned.into_domain();
1023
1024                serde_json::to_value(domain).map_err(|e| MigrationError::MigrationStepFailed {
1025                    from: V::VERSION.to_string(),
1026                    to: "domain".to_string(),
1027                    error: e.to_string(),
1028                })
1029            });
1030
1031        MigrationPath {
1032            entity: self.entity,
1033            inner: EntityMigrationPath {
1034                steps: self.steps,
1035                finalize,
1036                versions: self.versions.clone(),
1037                version_key: self.version_key,
1038                data_key: self.data_key,
1039            },
1040            versions: self.versions,
1041            custom_version_key: self.custom_version_key,
1042            custom_data_key: self.custom_data_key,
1043            _phantom: PhantomData,
1044        }
1045    }
1046}
1047
1048impl<V> MigrationPathBuilder<HasSteps<V>>
1049where
1050    V: Versioned + DeserializeOwned,
1051{
1052    /// Adds another migration step.
1053    pub fn step<Next>(mut self) -> MigrationPathBuilder<HasSteps<Next>>
1054    where
1055        V: MigratesTo<Next>,
1056        Next: Versioned + DeserializeOwned + Serialize,
1057    {
1058        let from_version = V::VERSION.to_string();
1059        let migration_fn: MigrationFn = Box::new(move |value| {
1060            let from_value: V = serde_json::from_value(value).map_err(|e| {
1061                MigrationError::DeserializationError(format!(
1062                    "Failed to deserialize version {}: {}",
1063                    V::VERSION,
1064                    e
1065                ))
1066            })?;
1067
1068            let to_value = from_value.migrate();
1069
1070            // Return the raw migrated value without wrapping
1071            serde_json::to_value(&to_value).map_err(|e| MigrationError::MigrationStepFailed {
1072                from: V::VERSION.to_string(),
1073                to: Next::VERSION.to_string(),
1074                error: e.to_string(),
1075            })
1076        });
1077
1078        self.steps.insert(from_version, migration_fn);
1079        self.versions.push(Next::VERSION.to_string());
1080
1081        MigrationPathBuilder {
1082            entity: self.entity,
1083            steps: self.steps,
1084            versions: self.versions,
1085            version_key: self.version_key,
1086            data_key: self.data_key,
1087            custom_version_key: self.custom_version_key,
1088            custom_data_key: self.custom_data_key,
1089            _state: PhantomData,
1090        }
1091    }
1092
1093    /// Finalizes the migration path with conversion to domain model.
1094    pub fn into<D: DeserializeOwned + Serialize>(self) -> MigrationPath<D>
1095    where
1096        V: IntoDomain<D>,
1097    {
1098        let finalize: Box<dyn Fn(serde_json::Value) -> Result<serde_json::Value, MigrationError>> =
1099            Box::new(move |value| {
1100                let versioned: V = serde_json::from_value(value).map_err(|e| {
1101                    MigrationError::DeserializationError(format!(
1102                        "Failed to deserialize final version: {}",
1103                        e
1104                    ))
1105                })?;
1106
1107                let domain = versioned.into_domain();
1108
1109                serde_json::to_value(domain).map_err(|e| MigrationError::MigrationStepFailed {
1110                    from: V::VERSION.to_string(),
1111                    to: "domain".to_string(),
1112                    error: e.to_string(),
1113                })
1114            });
1115
1116        MigrationPath {
1117            entity: self.entity,
1118            inner: EntityMigrationPath {
1119                steps: self.steps,
1120                finalize,
1121                versions: self.versions.clone(),
1122                version_key: self.version_key,
1123                data_key: self.data_key,
1124            },
1125            versions: self.versions,
1126            custom_version_key: self.custom_version_key,
1127            custom_data_key: self.custom_data_key,
1128            _phantom: PhantomData,
1129        }
1130    }
1131}
1132
1133/// A complete migration path from versioned DTOs to a domain model.
1134pub struct MigrationPath<D> {
1135    entity: String,
1136    inner: EntityMigrationPath,
1137    /// List of versions in the migration path for validation
1138    versions: Vec<String>,
1139    /// Custom version key override (takes precedence over Migrator defaults)
1140    custom_version_key: Option<String>,
1141    /// Custom data key override (takes precedence over Migrator defaults)
1142    custom_data_key: Option<String>,
1143    _phantom: PhantomData<D>,
1144}
1145
1146/// A wrapper around JSON data that provides convenient query and update methods
1147/// for partial updates with automatic migration.
1148///
1149/// `ConfigMigrator` holds a JSON object and allows you to query specific keys,
1150/// automatically migrating versioned data to domain entities, and update them
1151/// with the latest version.
1152///
1153/// # Example
1154///
1155/// ```ignore
1156/// // config.json:
1157/// // {
1158/// //   "app_name": "MyApp",
1159/// //   "tasks": [
1160/// //     {"version": "1.0.0", "id": "1", "title": "Task 1"},
1161/// //     {"version": "2.0.0", "id": "2", "title": "Task 2", "description": "New"}
1162/// //   ]
1163/// // }
1164///
1165/// let config_json = fs::read_to_string("config.json")?;
1166/// let mut config = ConfigMigrator::from(&config_json, migrator)?;
1167///
1168/// // Query tasks (automatically migrates all versions to TaskEntity)
1169/// let mut tasks: Vec<TaskEntity> = config.query("tasks")?;
1170///
1171/// // Update tasks
1172/// tasks[0].title = "Updated Task".to_string();
1173///
1174/// // Save back with latest version
1175/// config.update("tasks", tasks)?;
1176///
1177/// // Write to file
1178/// fs::write("config.json", config.to_string()?)?;
1179/// ```
1180pub struct ConfigMigrator {
1181    root: serde_json::Value,
1182    migrator: Migrator,
1183}
1184
1185impl ConfigMigrator {
1186    /// Creates a new `ConfigMigrator` from a JSON string and a `Migrator`.
1187    ///
1188    /// # Errors
1189    ///
1190    /// Returns `MigrationError::DeserializationError` if the JSON is invalid.
1191    pub fn from(json: &str, migrator: Migrator) -> Result<Self, MigrationError> {
1192        let root = serde_json::from_str(json)
1193            .map_err(|e| MigrationError::DeserializationError(e.to_string()))?;
1194        Ok(Self { root, migrator })
1195    }
1196
1197    /// Queries a specific key from the JSON object and returns the data as domain entities.
1198    ///
1199    /// This method automatically migrates all versioned data to the latest version
1200    /// and converts them to domain entities.
1201    ///
1202    /// # Type Parameters
1203    ///
1204    /// - `T`: Must implement `Queryable` to provide the entity name, and `Deserialize` for deserialization.
1205    ///
1206    /// # Errors
1207    ///
1208    /// - Returns `MigrationError::DeserializationError` if the key doesn't contain a valid array.
1209    /// - Returns migration errors if the data cannot be migrated.
1210    ///
1211    /// # Example
1212    ///
1213    /// ```ignore
1214    /// let tasks: Vec<TaskEntity> = config.query("tasks")?;
1215    /// ```
1216    pub fn query<T>(&self, key: &str) -> Result<Vec<T>, MigrationError>
1217    where
1218        T: crate::Queryable + for<'de> serde::Deserialize<'de>,
1219    {
1220        let value = &self.root[key];
1221        if value.is_null() {
1222            return Ok(Vec::new());
1223        }
1224
1225        if !value.is_array() {
1226            return Err(MigrationError::DeserializationError(format!(
1227                "Key '{}' does not contain an array",
1228                key
1229            )));
1230        }
1231
1232        let array = value.as_array().unwrap(); // Safe because we checked is_array()
1233        self.migrator
1234            .load_vec_flat_from(T::ENTITY_NAME, array.to_vec())
1235    }
1236
1237    /// Updates a specific key in the JSON object with new domain entities.
1238    ///
1239    /// This method serializes the entities with the latest version and updates
1240    /// the JSON object in place.
1241    ///
1242    /// # Type Parameters
1243    ///
1244    /// - `T`: Must implement `Queryable`, `Versioned`, and `Serialize`.
1245    ///
1246    /// # Errors
1247    ///
1248    /// - Returns serialization errors if the data cannot be serialized.
1249    ///
1250    /// # Example
1251    ///
1252    /// ```ignore
1253    /// config.update("tasks", updated_tasks)?;
1254    /// ```
1255    pub fn update<T>(&mut self, key: &str, data: Vec<T>) -> Result<(), MigrationError>
1256    where
1257        T: crate::Queryable + crate::Versioned + serde::Serialize,
1258    {
1259        let json = self.migrator.save_vec_flat(data)?;
1260        let value: serde_json::Value = serde_json::from_str(&json)
1261            .map_err(|e| MigrationError::SerializationError(e.to_string()))?;
1262        self.root[key] = value;
1263        Ok(())
1264    }
1265
1266    /// Converts the entire JSON object back to a pretty-printed string.
1267    ///
1268    /// # Errors
1269    ///
1270    /// Returns `MigrationError::SerializationError` if serialization fails.
1271    pub fn to_string(&self) -> Result<String, MigrationError> {
1272        serde_json::to_string_pretty(&self.root)
1273            .map_err(|e| MigrationError::SerializationError(e.to_string()))
1274    }
1275
1276    /// Converts the entire JSON object to a compact string.
1277    ///
1278    /// # Errors
1279    ///
1280    /// Returns `MigrationError::SerializationError` if serialization fails.
1281    pub fn to_string_compact(&self) -> Result<String, MigrationError> {
1282        serde_json::to_string(&self.root)
1283            .map_err(|e| MigrationError::SerializationError(e.to_string()))
1284    }
1285
1286    /// Returns a reference to the underlying JSON value.
1287    pub fn as_value(&self) -> &serde_json::Value {
1288        &self.root
1289    }
1290}
1291
1292#[cfg(test)]
1293mod tests {
1294    use super::*;
1295    use crate::{IntoDomain, MigratesTo, Versioned, VersionedWrapper};
1296    use serde::{Deserialize, Serialize};
1297
1298    // Test data structures
1299    #[derive(Serialize, Deserialize, Debug, PartialEq)]
1300    struct V1 {
1301        value: String,
1302    }
1303
1304    impl Versioned for V1 {
1305        const VERSION: &'static str = "1.0.0";
1306    }
1307
1308    #[derive(Serialize, Deserialize, Debug, PartialEq)]
1309    struct V2 {
1310        value: String,
1311        count: u32,
1312    }
1313
1314    impl Versioned for V2 {
1315        const VERSION: &'static str = "2.0.0";
1316    }
1317
1318    #[derive(Serialize, Deserialize, Debug, PartialEq)]
1319    struct V3 {
1320        value: String,
1321        count: u32,
1322        enabled: bool,
1323    }
1324
1325    impl Versioned for V3 {
1326        const VERSION: &'static str = "3.0.0";
1327    }
1328
1329    #[derive(Serialize, Deserialize, Debug, PartialEq)]
1330    struct Domain {
1331        value: String,
1332        count: u32,
1333        enabled: bool,
1334    }
1335
1336    impl MigratesTo<V2> for V1 {
1337        fn migrate(self) -> V2 {
1338            V2 {
1339                value: self.value,
1340                count: 0,
1341            }
1342        }
1343    }
1344
1345    impl MigratesTo<V3> for V2 {
1346        fn migrate(self) -> V3 {
1347            V3 {
1348                value: self.value,
1349                count: self.count,
1350                enabled: true,
1351            }
1352        }
1353    }
1354
1355    impl IntoDomain<Domain> for V3 {
1356        fn into_domain(self) -> Domain {
1357            Domain {
1358                value: self.value,
1359                count: self.count,
1360                enabled: self.enabled,
1361            }
1362        }
1363    }
1364
1365    #[test]
1366    fn test_migrator_new() {
1367        let migrator = Migrator::new();
1368        assert_eq!(migrator.paths.len(), 0);
1369    }
1370
1371    #[test]
1372    fn test_migrator_default() {
1373        let migrator = Migrator::default();
1374        assert_eq!(migrator.paths.len(), 0);
1375    }
1376
1377    #[test]
1378    fn test_single_step_migration() {
1379        let path = Migrator::define("test")
1380            .from::<V2>()
1381            .step::<V3>()
1382            .into::<Domain>();
1383
1384        let mut migrator = Migrator::new();
1385        migrator.register(path).unwrap();
1386
1387        let v2 = V2 {
1388            value: "test".to_string(),
1389            count: 42,
1390        };
1391        let wrapper = VersionedWrapper::from_versioned(v2);
1392        let json = serde_json::to_string(&wrapper).unwrap();
1393
1394        let result: Domain = migrator.load("test", &json).unwrap();
1395        assert_eq!(result.value, "test");
1396        assert_eq!(result.count, 42);
1397        assert!(result.enabled);
1398    }
1399
1400    #[test]
1401    fn test_multi_step_migration() {
1402        let path = Migrator::define("test")
1403            .from::<V1>()
1404            .step::<V2>()
1405            .step::<V3>()
1406            .into::<Domain>();
1407
1408        let mut migrator = Migrator::new();
1409        migrator.register(path).unwrap();
1410
1411        let v1 = V1 {
1412            value: "multi_step".to_string(),
1413        };
1414        let wrapper = VersionedWrapper::from_versioned(v1);
1415        let json = serde_json::to_string(&wrapper).unwrap();
1416
1417        let result: Domain = migrator.load("test", &json).unwrap();
1418        assert_eq!(result.value, "multi_step");
1419        assert_eq!(result.count, 0);
1420        assert!(result.enabled);
1421    }
1422
1423    #[test]
1424    fn test_no_migration_needed() {
1425        let path = Migrator::define("test").from::<V3>().into::<Domain>();
1426
1427        let mut migrator = Migrator::new();
1428        migrator.register(path).unwrap();
1429
1430        let v3 = V3 {
1431            value: "latest".to_string(),
1432            count: 100,
1433            enabled: false,
1434        };
1435        let wrapper = VersionedWrapper::from_versioned(v3);
1436        let json = serde_json::to_string(&wrapper).unwrap();
1437
1438        let result: Domain = migrator.load("test", &json).unwrap();
1439        assert_eq!(result.value, "latest");
1440        assert_eq!(result.count, 100);
1441        assert!(!result.enabled);
1442    }
1443
1444    #[test]
1445    fn test_entity_not_found() {
1446        let migrator = Migrator::new();
1447
1448        let v1 = V1 {
1449            value: "test".to_string(),
1450        };
1451        let wrapper = VersionedWrapper::from_versioned(v1);
1452        let json = serde_json::to_string(&wrapper).unwrap();
1453
1454        let result: Result<Domain, MigrationError> = migrator.load("unknown", &json);
1455        assert!(matches!(result, Err(MigrationError::EntityNotFound(_))));
1456
1457        if let Err(MigrationError::EntityNotFound(entity)) = result {
1458            assert_eq!(entity, "unknown");
1459        }
1460    }
1461
1462    #[test]
1463    fn test_invalid_json() {
1464        let path = Migrator::define("test").from::<V3>().into::<Domain>();
1465
1466        let mut migrator = Migrator::new();
1467        migrator.register(path).unwrap();
1468
1469        let invalid_json = "{ invalid json }";
1470        let result: Result<Domain, MigrationError> = migrator.load("test", invalid_json);
1471
1472        assert!(matches!(
1473            result,
1474            Err(MigrationError::DeserializationError(_))
1475        ));
1476    }
1477
1478    #[test]
1479    fn test_multiple_entities() {
1480        #[derive(Serialize, Deserialize, Debug, PartialEq)]
1481        struct OtherDomain {
1482            value: String,
1483        }
1484
1485        impl IntoDomain<OtherDomain> for V1 {
1486            fn into_domain(self) -> OtherDomain {
1487                OtherDomain { value: self.value }
1488            }
1489        }
1490
1491        let path1 = Migrator::define("entity1")
1492            .from::<V1>()
1493            .step::<V2>()
1494            .step::<V3>()
1495            .into::<Domain>();
1496
1497        let path2 = Migrator::define("entity2")
1498            .from::<V1>()
1499            .into::<OtherDomain>();
1500
1501        let mut migrator = Migrator::new();
1502        migrator.register(path1).unwrap();
1503        migrator.register(path2).unwrap();
1504
1505        // Test entity1
1506        let v1 = V1 {
1507            value: "entity1".to_string(),
1508        };
1509        let wrapper = VersionedWrapper::from_versioned(v1);
1510        let json = serde_json::to_string(&wrapper).unwrap();
1511        let result: Domain = migrator.load("entity1", &json).unwrap();
1512        assert_eq!(result.value, "entity1");
1513
1514        // Test entity2
1515        let v1 = V1 {
1516            value: "entity2".to_string(),
1517        };
1518        let wrapper = VersionedWrapper::from_versioned(v1);
1519        let json = serde_json::to_string(&wrapper).unwrap();
1520        let result: OtherDomain = migrator.load("entity2", &json).unwrap();
1521        assert_eq!(result.value, "entity2");
1522    }
1523
1524    #[test]
1525    fn test_save() {
1526        let migrator = Migrator::new();
1527
1528        let v1 = V1 {
1529            value: "test_save".to_string(),
1530        };
1531
1532        let json = migrator.save(v1).unwrap();
1533
1534        // Verify JSON contains version and data
1535        assert!(json.contains("\"version\""));
1536        assert!(json.contains("\"1.0.0\""));
1537        assert!(json.contains("\"data\""));
1538        assert!(json.contains("\"test_save\""));
1539
1540        // Verify it can be parsed back
1541        let parsed: VersionedWrapper<serde_json::Value> = serde_json::from_str(&json).unwrap();
1542        assert_eq!(parsed.version, "1.0.0");
1543    }
1544
1545    #[test]
1546    fn test_save_and_load_roundtrip() {
1547        let path = Migrator::define("test")
1548            .from::<V1>()
1549            .step::<V2>()
1550            .step::<V3>()
1551            .into::<Domain>();
1552
1553        let mut migrator = Migrator::new();
1554        migrator.register(path).unwrap();
1555
1556        // Save V1 data
1557        let v1 = V1 {
1558            value: "roundtrip".to_string(),
1559        };
1560        let json = migrator.save(v1).unwrap();
1561
1562        // Load and migrate to Domain
1563        let domain: Domain = migrator.load("test", &json).unwrap();
1564
1565        assert_eq!(domain.value, "roundtrip");
1566        assert_eq!(domain.count, 0); // Default from V1->V2 migration
1567        assert!(domain.enabled); // Default from V2->V3 migration
1568    }
1569
1570    #[test]
1571    fn test_save_latest_version() {
1572        let migrator = Migrator::new();
1573
1574        let v3 = V3 {
1575            value: "latest".to_string(),
1576            count: 42,
1577            enabled: false,
1578        };
1579
1580        let json = migrator.save(v3).unwrap();
1581
1582        // Verify the JSON structure
1583        assert!(json.contains("\"version\":\"3.0.0\""));
1584        assert!(json.contains("\"value\":\"latest\""));
1585        assert!(json.contains("\"count\":42"));
1586        assert!(json.contains("\"enabled\":false"));
1587    }
1588
1589    #[test]
1590    fn test_save_pretty() {
1591        let migrator = Migrator::new();
1592
1593        let v2 = V2 {
1594            value: "pretty".to_string(),
1595            count: 10,
1596        };
1597
1598        let json = migrator.save(v2).unwrap();
1599
1600        // Should be compact JSON (not pretty-printed)
1601        assert!(!json.contains('\n'));
1602        assert!(json.contains("\"version\":\"2.0.0\""));
1603    }
1604
1605    #[test]
1606    fn test_validation_invalid_version_order() {
1607        // Manually construct a path with invalid version ordering
1608        let entity = "test".to_string();
1609        let versions = vec!["2.0.0".to_string(), "1.0.0".to_string()]; // Wrong order
1610
1611        let result = Migrator::validate_migration_path(&entity, &versions);
1612        assert!(matches!(
1613            result,
1614            Err(MigrationError::InvalidVersionOrder { .. })
1615        ));
1616
1617        if let Err(MigrationError::InvalidVersionOrder {
1618            entity: e,
1619            from,
1620            to,
1621        }) = result
1622        {
1623            assert_eq!(e, "test");
1624            assert_eq!(from, "2.0.0");
1625            assert_eq!(to, "1.0.0");
1626        }
1627    }
1628
1629    #[test]
1630    fn test_validation_circular_path() {
1631        // Manually construct a path with circular reference
1632        let entity = "test".to_string();
1633        let versions = vec![
1634            "1.0.0".to_string(),
1635            "2.0.0".to_string(),
1636            "1.0.0".to_string(), // Circular!
1637        ];
1638
1639        let result = Migrator::validate_migration_path(&entity, &versions);
1640        assert!(matches!(
1641            result,
1642            Err(MigrationError::CircularMigrationPath { .. })
1643        ));
1644
1645        if let Err(MigrationError::CircularMigrationPath { entity: e, path }) = result {
1646            assert_eq!(e, "test");
1647            assert!(path.contains("1.0.0"));
1648            assert!(path.contains("2.0.0"));
1649        }
1650    }
1651
1652    #[test]
1653    fn test_validation_valid_path() {
1654        // Valid migration path
1655        let entity = "test".to_string();
1656        let versions = vec![
1657            "1.0.0".to_string(),
1658            "1.1.0".to_string(),
1659            "2.0.0".to_string(),
1660        ];
1661
1662        let result = Migrator::validate_migration_path(&entity, &versions);
1663        assert!(result.is_ok());
1664    }
1665
1666    #[test]
1667    fn test_validation_empty_path() {
1668        // Empty path should be valid
1669        let entity = "test".to_string();
1670        let versions = vec![];
1671
1672        let result = Migrator::validate_migration_path(&entity, &versions);
1673        assert!(result.is_ok());
1674    }
1675
1676    #[test]
1677    fn test_validation_single_version() {
1678        // Single version path should be valid (no steps, just final conversion)
1679        let entity = "test".to_string();
1680        let versions = vec!["1.0.0".to_string()];
1681
1682        let result = Migrator::validate_migration_path(&entity, &versions);
1683        assert!(result.is_ok());
1684    }
1685
1686    // Tests for Vec operations
1687    #[test]
1688    fn test_save_vec_and_load_vec() {
1689        let migrator = Migrator::new();
1690
1691        // Save multiple V1 items
1692        let items = vec![
1693            V1 {
1694                value: "item1".to_string(),
1695            },
1696            V1 {
1697                value: "item2".to_string(),
1698            },
1699            V1 {
1700                value: "item3".to_string(),
1701            },
1702        ];
1703
1704        let json = migrator.save_vec(items).unwrap();
1705
1706        // Verify JSON array format
1707        assert!(json.starts_with('['));
1708        assert!(json.ends_with(']'));
1709        assert!(json.contains("\"version\":\"1.0.0\""));
1710        assert!(json.contains("item1"));
1711        assert!(json.contains("item2"));
1712        assert!(json.contains("item3"));
1713
1714        // Setup migration path
1715        let path = Migrator::define("test")
1716            .from::<V1>()
1717            .step::<V2>()
1718            .step::<V3>()
1719            .into::<Domain>();
1720
1721        let mut migrator = Migrator::new();
1722        migrator.register(path).unwrap();
1723
1724        // Load and migrate the array
1725        let domains: Vec<Domain> = migrator.load_vec("test", &json).unwrap();
1726
1727        assert_eq!(domains.len(), 3);
1728        assert_eq!(domains[0].value, "item1");
1729        assert_eq!(domains[1].value, "item2");
1730        assert_eq!(domains[2].value, "item3");
1731
1732        // All should have default values from migration
1733        for domain in &domains {
1734            assert_eq!(domain.count, 0);
1735            assert!(domain.enabled);
1736        }
1737    }
1738
1739    #[test]
1740    fn test_load_vec_empty_array() {
1741        let path = Migrator::define("test")
1742            .from::<V1>()
1743            .step::<V2>()
1744            .step::<V3>()
1745            .into::<Domain>();
1746
1747        let mut migrator = Migrator::new();
1748        migrator.register(path).unwrap();
1749
1750        let json = "[]";
1751        let domains: Vec<Domain> = migrator.load_vec("test", json).unwrap();
1752
1753        assert_eq!(domains.len(), 0);
1754    }
1755
1756    #[test]
1757    fn test_load_vec_mixed_versions() {
1758        // Setup migration path
1759        let path = Migrator::define("test")
1760            .from::<V1>()
1761            .step::<V2>()
1762            .step::<V3>()
1763            .into::<Domain>();
1764
1765        let mut migrator = Migrator::new();
1766        migrator.register(path).unwrap();
1767
1768        // JSON with mixed versions
1769        let json = r#"[
1770            {"version":"1.0.0","data":{"value":"v1-item"}},
1771            {"version":"2.0.0","data":{"value":"v2-item","count":42}},
1772            {"version":"3.0.0","data":{"value":"v3-item","count":99,"enabled":false}}
1773        ]"#;
1774
1775        let domains: Vec<Domain> = migrator.load_vec("test", json).unwrap();
1776
1777        assert_eq!(domains.len(), 3);
1778
1779        // V1 item migrated to domain
1780        assert_eq!(domains[0].value, "v1-item");
1781        assert_eq!(domains[0].count, 0);
1782        assert!(domains[0].enabled);
1783
1784        // V2 item migrated to domain
1785        assert_eq!(domains[1].value, "v2-item");
1786        assert_eq!(domains[1].count, 42);
1787        assert!(domains[1].enabled);
1788
1789        // V3 item converted to domain
1790        assert_eq!(domains[2].value, "v3-item");
1791        assert_eq!(domains[2].count, 99);
1792        assert!(!domains[2].enabled);
1793    }
1794
1795    #[test]
1796    fn test_load_vec_from_json_values() {
1797        let path = Migrator::define("test")
1798            .from::<V1>()
1799            .step::<V2>()
1800            .step::<V3>()
1801            .into::<Domain>();
1802
1803        let mut migrator = Migrator::new();
1804        migrator.register(path).unwrap();
1805
1806        // Create Vec<serde_json::Value> directly
1807        let values: Vec<serde_json::Value> = vec![
1808            serde_json::json!({"version":"1.0.0","data":{"value":"direct1"}}),
1809            serde_json::json!({"version":"1.0.0","data":{"value":"direct2"}}),
1810        ];
1811
1812        let domains: Vec<Domain> = migrator.load_vec_from("test", values).unwrap();
1813
1814        assert_eq!(domains.len(), 2);
1815        assert_eq!(domains[0].value, "direct1");
1816        assert_eq!(domains[1].value, "direct2");
1817    }
1818
1819    #[test]
1820    fn test_save_vec_empty() {
1821        let migrator = Migrator::new();
1822        let empty: Vec<V1> = vec![];
1823
1824        let json = migrator.save_vec(empty).unwrap();
1825
1826        assert_eq!(json, "[]");
1827    }
1828
1829    #[test]
1830    fn test_load_vec_invalid_json() {
1831        let path = Migrator::define("test")
1832            .from::<V1>()
1833            .step::<V2>()
1834            .step::<V3>()
1835            .into::<Domain>();
1836
1837        let mut migrator = Migrator::new();
1838        migrator.register(path).unwrap();
1839
1840        let invalid_json = "{ not an array }";
1841        let result: Result<Vec<Domain>, MigrationError> = migrator.load_vec("test", invalid_json);
1842
1843        assert!(matches!(
1844            result,
1845            Err(MigrationError::DeserializationError(_))
1846        ));
1847    }
1848
1849    #[test]
1850    fn test_load_vec_entity_not_found() {
1851        let migrator = Migrator::new();
1852
1853        let json = r#"[{"version":"1.0.0","data":{"value":"test"}}]"#;
1854        let result: Result<Vec<Domain>, MigrationError> = migrator.load_vec("unknown", json);
1855
1856        assert!(matches!(result, Err(MigrationError::EntityNotFound(_))));
1857    }
1858
1859    #[test]
1860    fn test_save_vec_latest_version() {
1861        let migrator = Migrator::new();
1862
1863        let items = vec![
1864            V3 {
1865                value: "latest1".to_string(),
1866                count: 10,
1867                enabled: true,
1868            },
1869            V3 {
1870                value: "latest2".to_string(),
1871                count: 20,
1872                enabled: false,
1873            },
1874        ];
1875
1876        let json = migrator.save_vec(items).unwrap();
1877
1878        // Verify structure
1879        assert!(json.contains("\"version\":\"3.0.0\""));
1880        assert!(json.contains("latest1"));
1881        assert!(json.contains("latest2"));
1882        assert!(json.contains("\"count\":10"));
1883        assert!(json.contains("\"count\":20"));
1884    }
1885}