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