Skip to main content

version_migrate/
migrator.rs

1//! Migration manager and builder pattern for defining type-safe migration paths.
2
3use crate::errors::MigrationError;
4use crate::forward::{ForwardContext, Forwardable};
5use crate::{IntoDomain, MigratesTo, Versioned};
6use serde::de::DeserializeOwned;
7use serde::Serialize;
8use std::collections::HashMap;
9use std::marker::PhantomData;
10
11type MigrationFn =
12    Box<dyn Fn(serde_json::Value) -> Result<serde_json::Value, MigrationError> + Send + Sync>;
13
14/// Type-erased function for saving domain entities
15type DomainSaveFn =
16    Box<dyn Fn(serde_json::Value, &str, &str) -> Result<String, MigrationError> + Send + Sync>;
17type DomainSaveFlatFn =
18    Box<dyn Fn(serde_json::Value, &str) -> Result<String, MigrationError> + Send + Sync>;
19
20/// A registered migration path for a specific entity type.
21struct EntityMigrationPath {
22    /// Maps version -> migration function to next version
23    steps: HashMap<String, MigrationFn>,
24    /// The final conversion to domain model
25    finalize:
26        Box<dyn Fn(serde_json::Value) -> Result<serde_json::Value, MigrationError> + Send + Sync>,
27    /// Ordered list of versions in the migration path
28    versions: Vec<String>,
29    /// The key name for version field in serialized data
30    version_key: String,
31    /// The key name for data field in serialized data
32    data_key: String,
33}
34
35/// Type-erased functions for saving domain entities by entity name
36struct DomainSavers {
37    save_fn: DomainSaveFn,
38    save_flat_fn: DomainSaveFlatFn,
39}
40
41/// The migration manager that orchestrates all migrations.
42pub struct Migrator {
43    paths: HashMap<String, EntityMigrationPath>,
44    default_version_key: Option<String>,
45    default_data_key: Option<String>,
46    domain_savers: HashMap<String, DomainSavers>,
47}
48
49impl Migrator {
50    /// Creates a new, empty migrator.
51    pub fn new() -> Self {
52        Self {
53            paths: HashMap::new(),
54            default_version_key: None,
55            default_data_key: None,
56            domain_savers: HashMap::new(),
57        }
58    }
59
60    /// Gets the latest version for a given entity.
61    ///
62    /// # Returns
63    ///
64    /// The latest version string if the entity is registered, `None` otherwise.
65    pub fn get_latest_version(&self, entity: &str) -> Option<&str> {
66        self.paths
67            .get(entity)
68            .and_then(|path| path.versions.last())
69            .map(|v| v.as_str())
70    }
71
72    /// Creates a builder for configuring the migrator.
73    ///
74    /// # Example
75    ///
76    /// ```ignore
77    /// let migrator = Migrator::builder()
78    ///     .default_version_key("schema_version")
79    ///     .default_data_key("payload")
80    ///     .build();
81    /// ```
82    pub fn builder() -> MigratorBuilder {
83        MigratorBuilder::new()
84    }
85
86    /// Starts defining a migration path for an entity.
87    pub fn define(entity: &str) -> MigrationPathBuilder<Start> {
88        MigrationPathBuilder::new(entity.to_string())
89    }
90
91    /// Registers a migration path with validation.
92    ///
93    /// This method validates the migration path before registering it:
94    /// - Checks for circular migration paths
95    /// - Validates version ordering follows semver rules
96    ///
97    /// # Errors
98    ///
99    /// Returns an error if validation fails.
100    pub fn register<D>(&mut self, path: MigrationPath<D>) -> Result<(), MigrationError> {
101        Self::validate_migration_path(&path.entity, &path.versions)?;
102
103        // Resolve key priority: Path custom > Migrator default > EntityPath (trait constants)
104        let version_key = path
105            .custom_version_key
106            .or_else(|| self.default_version_key.clone())
107            .unwrap_or_else(|| path.inner.version_key.clone());
108
109        let data_key = path
110            .custom_data_key
111            .or_else(|| self.default_data_key.clone())
112            .unwrap_or_else(|| path.inner.data_key.clone());
113
114        let entity_name = path.entity.clone();
115        let final_path = EntityMigrationPath {
116            steps: path.inner.steps,
117            finalize: path.inner.finalize,
118            versions: path.versions,
119            version_key,
120            data_key,
121        };
122
123        self.paths.insert(path.entity, final_path);
124
125        // Register domain savers if available
126        if let (Some(save_fn), Some(save_flat_fn)) = (path.save_fn, path.save_flat_fn) {
127            self.domain_savers.insert(
128                entity_name,
129                DomainSavers {
130                    save_fn,
131                    save_flat_fn,
132                },
133            );
134        }
135
136        Ok(())
137    }
138
139    /// Validates a migration path for correctness.
140    fn validate_migration_path(entity: &str, versions: &[String]) -> Result<(), MigrationError> {
141        // Check for circular paths
142        Self::check_circular_path(entity, versions)?;
143
144        // Check version ordering
145        Self::check_version_ordering(entity, versions)?;
146
147        Ok(())
148    }
149
150    /// Checks if there are any circular dependencies in the migration path.
151    fn check_circular_path(entity: &str, versions: &[String]) -> Result<(), MigrationError> {
152        let mut seen = std::collections::HashSet::new();
153
154        for version in versions {
155            if !seen.insert(version) {
156                // Found a duplicate - circular path detected
157                let path = versions.join(" -> ");
158                return Err(MigrationError::CircularMigrationPath {
159                    entity: entity.to_string(),
160                    path,
161                });
162            }
163        }
164
165        Ok(())
166    }
167
168    /// Checks if versions are ordered according to semver rules.
169    fn check_version_ordering(entity: &str, versions: &[String]) -> Result<(), MigrationError> {
170        for i in 0..versions.len().saturating_sub(1) {
171            let current = &versions[i];
172            let next = &versions[i + 1];
173
174            // Parse versions
175            let current_ver = semver::Version::parse(current).map_err(|e| {
176                MigrationError::DeserializationError(format!("Invalid semver '{}': {}", current, e))
177            })?;
178
179            let next_ver = semver::Version::parse(next).map_err(|e| {
180                MigrationError::DeserializationError(format!("Invalid semver '{}': {}", next, e))
181            })?;
182
183            // Check that next version is greater than current
184            if next_ver <= current_ver {
185                return Err(MigrationError::InvalidVersionOrder {
186                    entity: entity.to_string(),
187                    from: current.clone(),
188                    to: next.clone(),
189                });
190            }
191        }
192
193        Ok(())
194    }
195
196    /// Loads and migrates data from any serde-compatible format.
197    ///
198    /// This is the generic version that accepts any type implementing `Serialize`.
199    /// For JSON strings, use the convenience method `load` instead.
200    ///
201    /// # Arguments
202    ///
203    /// * `entity` - The entity name used when registering the migration path
204    /// * `data` - Versioned data in any serde-compatible format (e.g., `toml::Value`, `serde_json::Value`)
205    ///
206    /// # Returns
207    ///
208    /// The migrated data as the domain model type
209    ///
210    /// # Errors
211    ///
212    /// Returns an error if:
213    /// - The data cannot be converted to the internal format
214    /// - The entity is not registered
215    /// - A migration step fails
216    ///
217    /// # Example
218    ///
219    /// ```ignore
220    /// // Load from TOML
221    /// let toml_data: toml::Value = toml::from_str(toml_str)?;
222    /// let domain: TaskEntity = migrator.load_from("task", toml_data)?;
223    ///
224    /// // Load from JSON Value
225    /// let json_data: serde_json::Value = serde_json::from_str(json_str)?;
226    /// let domain: TaskEntity = migrator.load_from("task", json_data)?;
227    /// ```
228    pub fn load_from<D, T>(&self, entity: &str, data: T) -> Result<D, MigrationError>
229    where
230        D: DeserializeOwned,
231        T: Serialize,
232    {
233        // Convert the input data to serde_json::Value for internal processing
234        let value = serde_json::to_value(data).map_err(|e| {
235            MigrationError::DeserializationError(format!(
236                "Failed to convert input data to internal format: {}",
237                e
238            ))
239        })?;
240
241        // Get the migration path for this entity
242        let path = self
243            .paths
244            .get(entity)
245            .ok_or_else(|| MigrationError::EntityNotFound(entity.to_string()))?;
246
247        let version_key = &path.version_key;
248        let data_key = &path.data_key;
249
250        // Extract version and data using custom keys
251        let obj = value.as_object().ok_or_else(|| {
252            MigrationError::DeserializationError(
253                "Expected object with version and data fields".to_string(),
254            )
255        })?;
256
257        let current_version = obj
258            .get(version_key)
259            .and_then(|v| v.as_str())
260            .ok_or_else(|| {
261                MigrationError::DeserializationError(format!(
262                    "Missing or invalid '{}' field",
263                    version_key
264                ))
265            })?
266            .to_string();
267
268        let mut current_data = obj
269            .get(data_key)
270            .ok_or_else(|| {
271                MigrationError::DeserializationError(format!("Missing '{}' field", data_key))
272            })?
273            .clone();
274
275        let mut current_version = current_version;
276
277        // Apply migration steps until we reach a version with no further steps
278        while let Some(migrate_fn) = path.steps.get(&current_version) {
279            // Migration function returns raw value, no wrapping
280            current_data = migrate_fn(current_data.clone())?;
281
282            // Update version to the next step
283            // Find the next version in the path
284            match path.versions.iter().position(|v| v == &current_version) {
285                Some(idx) if idx + 1 < path.versions.len() => {
286                    current_version = path.versions[idx + 1].clone();
287                }
288                _ => break,
289            }
290        }
291
292        // Finalize into domain model
293        let domain_value = (path.finalize)(current_data)?;
294
295        serde_json::from_value(domain_value).map_err(|e| {
296            MigrationError::DeserializationError(format!("Failed to convert to domain: {}", e))
297        })
298    }
299
300    /// Loads and migrates data from a JSON string.
301    ///
302    /// This is a convenience method for the common case of loading from JSON.
303    /// For other formats, use `load_from` instead.
304    ///
305    /// # Arguments
306    ///
307    /// * `entity` - The entity name used when registering the migration path
308    /// * `json` - A JSON string containing versioned data
309    ///
310    /// # Returns
311    ///
312    /// The migrated data as the domain model type
313    ///
314    /// # Errors
315    ///
316    /// Returns an error if:
317    /// - The JSON cannot be parsed
318    /// - The entity is not registered
319    /// - A migration step fails
320    ///
321    /// # Example
322    ///
323    /// ```ignore
324    /// let json = r#"{"version":"1.0.0","data":{"id":"task-1","title":"My Task"}}"#;
325    /// let domain: TaskEntity = migrator.load("task", json)?;
326    /// ```
327    pub fn load<D: DeserializeOwned>(&self, entity: &str, json: &str) -> Result<D, MigrationError> {
328        let data: serde_json::Value = serde_json::from_str(json).map_err(|e| {
329            MigrationError::DeserializationError(format!("Failed to parse JSON: {}", e))
330        })?;
331        self.load_from(entity, data)
332    }
333
334    /// Loads and migrates data from any serde-compatible format with fallback for legacy data.
335    ///
336    /// This method attempts to load data as versioned first. If version field is missing,
337    /// it treats the data as version 0 (the first version in the migration chain).
338    ///
339    /// # Arguments
340    ///
341    /// * `entity` - The entity name used when registering the migration path
342    /// * `data` - Data that may or may not have version information
343    ///
344    /// # Returns
345    ///
346    /// The migrated data as the domain model type
347    ///
348    /// # Errors
349    ///
350    /// Returns an error if:
351    /// - The data cannot be converted to the internal format
352    /// - The entity is not registered
353    /// - A migration step fails
354    ///
355    /// # Example
356    ///
357    /// ```ignore
358    /// // Works with versioned data
359    /// let versioned_json = r#"{"version":"1.0.0","data":{"id":"task-1","title":"My Task"}}"#;
360    /// let domain: TaskEntity = migrator.load_with_fallback("task", versioned_json)?;
361    ///
362    /// // Works with legacy data (no version field) - treats as V1
363    /// let legacy_json = r#"{"id":"task-1","title":"My Task"}"#;
364    /// let domain: TaskEntity = migrator.load_with_fallback("task", legacy_json)?;
365    /// ```
366    pub fn load_from_with_fallback<D, T>(&self, entity: &str, data: T) -> Result<D, MigrationError>
367    where
368        D: DeserializeOwned,
369        T: Serialize,
370    {
371        // Convert the input data to serde_json::Value for internal processing
372        let value = serde_json::to_value(data).map_err(|e| {
373            MigrationError::DeserializationError(format!(
374                "Failed to convert input data to internal format: {}",
375                e
376            ))
377        })?;
378
379        // Get the migration path for this entity
380        let path = self
381            .paths
382            .get(entity)
383            .ok_or_else(|| MigrationError::EntityNotFound(entity.to_string()))?;
384
385        let version_key = &path.version_key;
386        let data_key = &path.data_key;
387
388        // Try to extract version and data using custom keys
389        let (current_version, current_data) = if let Some(obj) = value.as_object() {
390            if let Some(version_value) = obj.get(version_key) {
391                if let Some(version_str) = version_value.as_str() {
392                    // Versioned data format
393                    let data = obj
394                        .get(data_key)
395                        .ok_or_else(|| {
396                            MigrationError::DeserializationError(format!(
397                                "Missing '{}' field",
398                                data_key
399                            ))
400                        })?
401                        .clone();
402                    (version_str.to_string(), data)
403                } else {
404                    // Version field exists but is not a string - fallback to first version
405                    if path.versions.is_empty() {
406                        return Err(MigrationError::DeserializationError(
407                            "No migration versions defined for fallback".to_string(),
408                        ));
409                    }
410                    (path.versions[0].clone(), value)
411                }
412            } else {
413                // No version field - fallback to first version
414                if path.versions.is_empty() {
415                    return Err(MigrationError::DeserializationError(
416                        "No migration versions defined for fallback".to_string(),
417                    ));
418                }
419                (path.versions[0].clone(), value)
420            }
421        } else {
422            return Err(MigrationError::DeserializationError(
423                "Expected object format for versioned data".to_string(),
424            ));
425        };
426
427        let mut current_version = current_version;
428        let mut current_data = current_data;
429
430        // Apply migration steps until we reach a version with no further steps
431        while let Some(migrate_fn) = path.steps.get(&current_version) {
432            // Migration function returns raw value, no wrapping
433            current_data = migrate_fn(current_data.clone())?;
434
435            // Update version to the next step
436            // Find the next version in the path
437            match path.versions.iter().position(|v| v == &current_version) {
438                Some(idx) if idx + 1 < path.versions.len() => {
439                    current_version = path.versions[idx + 1].clone();
440                }
441                _ => break,
442            }
443        }
444
445        // Finalize into domain model
446        let domain_value = (path.finalize)(current_data)?;
447
448        serde_json::from_value(domain_value).map_err(|e| {
449            MigrationError::DeserializationError(format!("Failed to convert to domain: {}", e))
450        })
451    }
452
453    /// Loads and migrates data from a JSON string with fallback for legacy data.
454    ///
455    /// This is a convenience method for `load_from_with_fallback` that accepts JSON strings.
456    ///
457    /// # Arguments
458    ///
459    /// * `entity` - The entity name used when registering the migration path
460    /// * `json` - A JSON string that may or may not contain version information
461    ///
462    /// # Returns
463    ///
464    /// The migrated data as the domain model type
465    ///
466    /// # Errors
467    ///
468    /// Returns an error if:
469    /// - The JSON cannot be parsed
470    /// - The entity is not registered
471    /// - A migration step fails
472    ///
473    /// # Example
474    ///
475    /// ```ignore
476    /// // Works with versioned data
477    /// let versioned_json = r#"{"version":"1.0.0","data":{"id":"task-1","title":"My Task"}}"#;
478    /// let domain: TaskEntity = migrator.load_with_fallback("task", versioned_json)?;
479    ///
480    /// // Works with legacy data (no version field) - treats as V1
481    /// let legacy_json = r#"{"id":"task-1","title":"My Task"}"#;
482    /// let domain: TaskEntity = migrator.load_with_fallback("task", legacy_json)?;
483    /// ```
484    pub fn load_with_fallback<D: DeserializeOwned>(
485        &self,
486        entity: &str,
487        json: &str,
488    ) -> Result<D, MigrationError> {
489        let data: serde_json::Value = serde_json::from_str(json).map_err(|e| {
490            MigrationError::DeserializationError(format!("Failed to parse JSON: {}", e))
491        })?;
492        self.load_from_with_fallback(entity, data)
493    }
494
495    /// Loads and migrates data from a flat format JSON string.
496    ///
497    /// This is a convenience method for loading from flat format JSON where the version
498    /// field is at the same level as the data fields.
499    ///
500    /// # Arguments
501    ///
502    /// * `entity` - The entity name used when registering the migration path
503    /// * `json` - A JSON string containing versioned data in flat format
504    ///
505    /// # Returns
506    ///
507    /// The migrated data as the domain model type
508    ///
509    /// # Errors
510    ///
511    /// Returns an error if:
512    /// - The JSON cannot be parsed
513    /// - The entity is not registered
514    /// - A migration step fails
515    ///
516    /// # Example
517    ///
518    /// ```ignore
519    /// let json = r#"{"version":"1.0.0","id":"task-1","title":"My Task"}"#;
520    /// let domain: TaskEntity = migrator.load_flat("task", json)?;
521    /// ```
522    pub fn load_flat<D: DeserializeOwned>(
523        &self,
524        entity: &str,
525        json: &str,
526    ) -> Result<D, MigrationError> {
527        let data: serde_json::Value = serde_json::from_str(json).map_err(|e| {
528            MigrationError::DeserializationError(format!("Failed to parse JSON: {}", e))
529        })?;
530        self.load_flat_from(entity, data)
531    }
532
533    /// Loads and migrates data from any serde-compatible format in flat format.
534    ///
535    /// This method expects the version field to be at the same level as the data fields.
536    /// It uses the registered migration path's runtime-configured keys (respecting the
537    /// Path > Migrator > Trait priority).
538    ///
539    /// # Arguments
540    ///
541    /// * `entity` - The entity name used when registering the migration path
542    /// * `value` - A serde-compatible value containing versioned data in flat format
543    ///
544    /// # Returns
545    ///
546    /// The migrated data as the domain model type
547    ///
548    /// # Errors
549    ///
550    /// Returns an error if:
551    /// - The entity is not registered
552    /// - The data format is invalid
553    /// - A migration step fails
554    ///
555    /// # Example
556    ///
557    /// ```ignore
558    /// let toml_value: toml::Value = toml::from_str(toml_str)?;
559    /// let domain: TaskEntity = migrator.load_flat_from("task", toml_value)?;
560    /// ```
561    pub fn load_flat_from<D, T>(&self, entity: &str, value: T) -> Result<D, MigrationError>
562    where
563        D: DeserializeOwned,
564        T: Serialize,
565    {
566        let path = self
567            .paths
568            .get(entity)
569            .ok_or_else(|| MigrationError::EntityNotFound(entity.to_string()))?;
570
571        let version_key = &path.version_key;
572
573        // Convert to serde_json::Value for manipulation
574        let mut value = serde_json::to_value(value).map_err(|e| {
575            MigrationError::SerializationError(format!("Failed to convert input: {}", e))
576        })?;
577
578        // Extract version from the flat structure
579        let obj = value.as_object_mut().ok_or_else(|| {
580            MigrationError::DeserializationError(
581                "Expected object with version field at top level".to_string(),
582            )
583        })?;
584
585        let current_version = obj
586            .remove(version_key)
587            .ok_or_else(|| {
588                MigrationError::DeserializationError(format!(
589                    "Missing '{}' field in flat format",
590                    version_key
591                ))
592            })?
593            .as_str()
594            .ok_or_else(|| {
595                MigrationError::DeserializationError(format!(
596                    "Invalid '{}' field type",
597                    version_key
598                ))
599            })?
600            .to_string();
601
602        // Now obj contains only data fields (version has been removed)
603        let mut current_data = serde_json::Value::Object(obj.clone());
604        let mut current_version = current_version;
605
606        // Apply migration steps until we reach a version with no further steps
607        while let Some(migrate_fn) = path.steps.get(&current_version) {
608            // Migration function returns raw value, no wrapping
609            current_data = migrate_fn(current_data.clone())?;
610
611            // Update version to the next step
612            match path.versions.iter().position(|v| v == &current_version) {
613                Some(idx) if idx + 1 < path.versions.len() => {
614                    current_version = path.versions[idx + 1].clone();
615                }
616                _ => break,
617            }
618        }
619
620        // Finalize into domain model
621        let domain_value = (path.finalize)(current_data)?;
622
623        serde_json::from_value(domain_value).map_err(|e| {
624            MigrationError::DeserializationError(format!("Failed to convert to domain: {}", e))
625        })
626    }
627
628    /// Saves versioned data to a JSON string.
629    ///
630    /// This method wraps the provided data with its version information and serializes
631    /// it to JSON format. The resulting JSON can later be loaded and migrated using
632    /// the `load` method.
633    ///
634    /// # Arguments
635    ///
636    /// * `data` - The versioned data to save
637    ///
638    /// # Returns
639    ///
640    /// A JSON string with the format: `{"version":"x.y.z","data":{...}}`
641    ///
642    /// # Errors
643    ///
644    /// Returns `SerializationError` if the data cannot be serialized to JSON.
645    ///
646    /// # Example
647    ///
648    /// ```ignore
649    /// let task = TaskV1_0_0 {
650    ///     id: "task-1".to_string(),
651    ///     title: "My Task".to_string(),
652    /// };
653    ///
654    /// let migrator = Migrator::new();
655    /// let json = migrator.save(task)?;
656    /// // json: {"version":"1.0.0","data":{"id":"task-1","title":"My Task"}}
657    /// ```
658    pub fn save<T: Versioned + Serialize>(&self, data: T) -> Result<String, MigrationError> {
659        // Use custom keys from the type's Versioned trait
660        let version_key = T::VERSION_KEY;
661        let data_key = T::DATA_KEY;
662
663        // Serialize the data
664        let data_value = serde_json::to_value(&data).map_err(|e| {
665            MigrationError::SerializationError(format!("Failed to serialize data: {}", e))
666        })?;
667
668        // Build the wrapper with custom keys
669        let mut map = serde_json::Map::new();
670        map.insert(
671            version_key.to_string(),
672            serde_json::Value::String(T::VERSION.to_string()),
673        );
674        map.insert(data_key.to_string(), data_value);
675
676        serde_json::to_string(&map).map_err(|e| {
677            MigrationError::SerializationError(format!("Failed to serialize wrapper: {}", e))
678        })
679    }
680
681    /// Saves versioned data to a JSON string in flat format.
682    ///
683    /// Unlike `save()`, this method produces a flat JSON structure where the version
684    /// field is at the same level as the data fields, not wrapped in a separate object.
685    ///
686    /// # Arguments
687    ///
688    /// * `data` - The versioned data to save
689    ///
690    /// # Returns
691    ///
692    /// A JSON string with the format: `{"version":"x.y.z","field1":"value1",...}`
693    ///
694    /// # Errors
695    ///
696    /// Returns `SerializationError` if the data cannot be serialized to JSON.
697    ///
698    /// # Example
699    ///
700    /// ```ignore
701    /// let task = TaskV1_0_0 {
702    ///     id: "task-1".to_string(),
703    ///     title: "My Task".to_string(),
704    /// };
705    ///
706    /// let migrator = Migrator::new();
707    /// let json = migrator.save_flat(task)?;
708    /// // json: {"version":"1.0.0","id":"task-1","title":"My Task"}
709    /// ```
710    pub fn save_flat<T: Versioned + Serialize>(&self, data: T) -> Result<String, MigrationError> {
711        let version_key = T::VERSION_KEY;
712
713        // Serialize the data to a JSON object
714        let mut data_value = serde_json::to_value(&data).map_err(|e| {
715            MigrationError::SerializationError(format!("Failed to serialize data: {}", e))
716        })?;
717
718        // Ensure it's an object so we can add the version field
719        let obj = data_value.as_object_mut().ok_or_else(|| {
720            MigrationError::SerializationError(
721                "Data must serialize to a JSON object for flat format".to_string(),
722            )
723        })?;
724
725        // Add the version field to the same level as data fields
726        obj.insert(
727            version_key.to_string(),
728            serde_json::Value::String(T::VERSION.to_string()),
729        );
730
731        serde_json::to_string(&obj).map_err(|e| {
732            MigrationError::SerializationError(format!("Failed to serialize flat format: {}", e))
733        })
734    }
735
736    /// Loads and migrates multiple entities from any serde-compatible format.
737    ///
738    /// This is the generic version that accepts any type implementing `Serialize`.
739    /// For JSON arrays, use the convenience method `load_vec` instead.
740    ///
741    /// # Arguments
742    ///
743    /// * `entity` - The entity name used when registering the migration path
744    /// * `data` - Array of versioned data in any serde-compatible format
745    ///
746    /// # Returns
747    ///
748    /// A vector of migrated data as domain model types
749    ///
750    /// # Errors
751    ///
752    /// Returns an error if:
753    /// - The data cannot be converted to the internal format
754    /// - The entity is not registered
755    /// - Any migration step fails
756    ///
757    /// # Example
758    ///
759    /// ```ignore
760    /// // Load from TOML array
761    /// let toml_array: Vec<toml::Value> = /* ... */;
762    /// let domains: Vec<TaskEntity> = migrator.load_vec_from("task", toml_array)?;
763    ///
764    /// // Load from JSON Value array
765    /// let json_array: Vec<serde_json::Value> = /* ... */;
766    /// let domains: Vec<TaskEntity> = migrator.load_vec_from("task", json_array)?;
767    /// ```
768    pub fn load_vec_from<D, T>(&self, entity: &str, data: Vec<T>) -> Result<Vec<D>, MigrationError>
769    where
770        D: DeserializeOwned,
771        T: Serialize,
772    {
773        data.into_iter()
774            .map(|item| self.load_from(entity, item))
775            .collect()
776    }
777
778    /// Loads and migrates multiple entities from a JSON array string.
779    ///
780    /// This is a convenience method for the common case of loading from a JSON array.
781    /// For other formats, use `load_vec_from` instead.
782    ///
783    /// # Arguments
784    ///
785    /// * `entity` - The entity name used when registering the migration path
786    /// * `json` - A JSON array string containing versioned data
787    ///
788    /// # Returns
789    ///
790    /// A vector of migrated data as domain model types
791    ///
792    /// # Errors
793    ///
794    /// Returns an error if:
795    /// - The JSON cannot be parsed
796    /// - The entity is not registered
797    /// - Any migration step fails
798    ///
799    /// # Example
800    ///
801    /// ```ignore
802    /// let json = r#"[
803    ///     {"version":"1.0.0","data":{"id":"task-1","title":"Task 1"}},
804    ///     {"version":"1.0.0","data":{"id":"task-2","title":"Task 2"}}
805    /// ]"#;
806    /// let domains: Vec<TaskEntity> = migrator.load_vec("task", json)?;
807    /// ```
808    pub fn load_vec<D: DeserializeOwned>(
809        &self,
810        entity: &str,
811        json: &str,
812    ) -> Result<Vec<D>, MigrationError> {
813        let data: Vec<serde_json::Value> = serde_json::from_str(json).map_err(|e| {
814            MigrationError::DeserializationError(format!("Failed to parse JSON array: {}", e))
815        })?;
816        self.load_vec_from(entity, data)
817    }
818
819    /// Loads and migrates multiple entities from a flat format JSON array string.
820    ///
821    /// This is a convenience method for loading from a JSON array where each element
822    /// has the version field at the same level as the data fields.
823    ///
824    /// # Arguments
825    ///
826    /// * `entity` - The entity name used when registering the migration path
827    /// * `json` - A JSON array string containing versioned data in flat format
828    ///
829    /// # Returns
830    ///
831    /// A vector of migrated data as domain model types
832    ///
833    /// # Errors
834    ///
835    /// Returns an error if:
836    /// - The JSON cannot be parsed
837    /// - The entity is not registered
838    /// - Any migration step fails
839    ///
840    /// # Example
841    ///
842    /// ```ignore
843    /// let json = r#"[
844    ///     {"version":"1.0.0","id":"task-1","title":"Task 1"},
845    ///     {"version":"1.0.0","id":"task-2","title":"Task 2"}
846    /// ]"#;
847    /// let domains: Vec<TaskEntity> = migrator.load_vec_flat("task", json)?;
848    /// ```
849    pub fn load_vec_flat<D: DeserializeOwned>(
850        &self,
851        entity: &str,
852        json: &str,
853    ) -> Result<Vec<D>, MigrationError> {
854        let data: Vec<serde_json::Value> = serde_json::from_str(json).map_err(|e| {
855            MigrationError::DeserializationError(format!("Failed to parse JSON array: {}", e))
856        })?;
857        self.load_vec_flat_from(entity, data)
858    }
859
860    /// Loads and migrates multiple entities from any serde-compatible format in flat format.
861    ///
862    /// This method expects each element to have the version field at the same level
863    /// as the data fields. It uses the registered migration path's runtime-configured
864    /// keys (respecting the Path > Migrator > Trait priority).
865    ///
866    /// # Arguments
867    ///
868    /// * `entity` - The entity name used when registering the migration path
869    /// * `data` - Vector of serde-compatible values in flat format
870    ///
871    /// # Returns
872    ///
873    /// A vector of migrated data as domain model types
874    ///
875    /// # Errors
876    ///
877    /// Returns an error if:
878    /// - The entity is not registered
879    /// - The data format is invalid
880    /// - Any migration step fails
881    ///
882    /// # Example
883    ///
884    /// ```ignore
885    /// let toml_array: Vec<toml::Value> = /* ... */;
886    /// let domains: Vec<TaskEntity> = migrator.load_vec_flat_from("task", toml_array)?;
887    /// ```
888    pub fn load_vec_flat_from<D, T>(
889        &self,
890        entity: &str,
891        data: Vec<T>,
892    ) -> Result<Vec<D>, MigrationError>
893    where
894        D: DeserializeOwned,
895        T: Serialize,
896    {
897        data.into_iter()
898            .map(|item| self.load_flat_from(entity, item))
899            .collect()
900    }
901
902    /// Saves multiple versioned entities to a JSON array string.
903    ///
904    /// This method wraps each item with its version information and serializes
905    /// them as a JSON array. The resulting JSON can later be loaded and migrated
906    /// using the `load_vec` method.
907    ///
908    /// # Arguments
909    ///
910    /// * `data` - Vector of versioned data to save
911    ///
912    /// # Returns
913    ///
914    /// A JSON array string where each element has the format: `{"version":"x.y.z","data":{...}}`
915    ///
916    /// # Errors
917    ///
918    /// Returns `SerializationError` if the data cannot be serialized to JSON.
919    ///
920    /// # Example
921    ///
922    /// ```ignore
923    /// let tasks = vec![
924    ///     TaskV1_0_0 {
925    ///         id: "task-1".to_string(),
926    ///         title: "Task 1".to_string(),
927    ///     },
928    ///     TaskV1_0_0 {
929    ///         id: "task-2".to_string(),
930    ///         title: "Task 2".to_string(),
931    ///     },
932    /// ];
933    ///
934    /// let migrator = Migrator::new();
935    /// let json = migrator.save_vec(tasks)?;
936    /// // json: [{"version":"1.0.0","data":{"id":"task-1",...}}, ...]
937    /// ```
938    pub fn save_vec<T: Versioned + Serialize>(
939        &self,
940        data: Vec<T>,
941    ) -> Result<String, MigrationError> {
942        let version_key = T::VERSION_KEY;
943        let data_key = T::DATA_KEY;
944
945        let wrappers: Result<Vec<serde_json::Value>, MigrationError> = data
946            .into_iter()
947            .map(|item| {
948                let data_value = serde_json::to_value(&item).map_err(|e| {
949                    MigrationError::SerializationError(format!("Failed to serialize item: {}", e))
950                })?;
951
952                let mut map = serde_json::Map::new();
953                map.insert(
954                    version_key.to_string(),
955                    serde_json::Value::String(T::VERSION.to_string()),
956                );
957                map.insert(data_key.to_string(), data_value);
958
959                Ok(serde_json::Value::Object(map))
960            })
961            .collect();
962
963        serde_json::to_string(&wrappers?).map_err(|e| {
964            MigrationError::SerializationError(format!("Failed to serialize data array: {}", e))
965        })
966    }
967
968    /// Saves multiple versioned entities to a JSON array string in flat format.
969    ///
970    /// This method serializes each item with the version field at the same level
971    /// as the data fields, not wrapped in a separate object.
972    ///
973    /// # Arguments
974    ///
975    /// * `data` - Vector of versioned data to save
976    ///
977    /// # Returns
978    ///
979    /// A JSON array string where each element has the format: `{"version":"x.y.z","field1":"value1",...}`
980    ///
981    /// # Errors
982    ///
983    /// Returns `SerializationError` if the data cannot be serialized to JSON.
984    ///
985    /// # Example
986    ///
987    /// ```ignore
988    /// let tasks = vec![
989    ///     TaskV1_0_0 {
990    ///         id: "task-1".to_string(),
991    ///         title: "Task 1".to_string(),
992    ///     },
993    ///     TaskV1_0_0 {
994    ///         id: "task-2".to_string(),
995    ///         title: "Task 2".to_string(),
996    ///     },
997    /// ];
998    ///
999    /// let migrator = Migrator::new();
1000    /// let json = migrator.save_vec_flat(tasks)?;
1001    /// // json: [{"version":"1.0.0","id":"task-1",...}, ...]
1002    /// ```
1003    pub fn save_vec_flat<T: Versioned + Serialize>(
1004        &self,
1005        data: Vec<T>,
1006    ) -> Result<String, MigrationError> {
1007        let version_key = T::VERSION_KEY;
1008
1009        let flat_items: Result<Vec<serde_json::Value>, MigrationError> = data
1010            .into_iter()
1011            .map(|item| {
1012                let mut data_value = serde_json::to_value(&item).map_err(|e| {
1013                    MigrationError::SerializationError(format!("Failed to serialize item: {}", e))
1014                })?;
1015
1016                let obj = data_value.as_object_mut().ok_or_else(|| {
1017                    MigrationError::SerializationError(
1018                        "Data must serialize to a JSON object for flat format".to_string(),
1019                    )
1020                })?;
1021
1022                // Add version field at the same level
1023                obj.insert(
1024                    version_key.to_string(),
1025                    serde_json::Value::String(T::VERSION.to_string()),
1026                );
1027
1028                Ok(serde_json::Value::Object(obj.clone()))
1029            })
1030            .collect();
1031
1032        serde_json::to_string(&flat_items?).map_err(|e| {
1033            MigrationError::SerializationError(format!(
1034                "Failed to serialize flat data array: {}",
1035                e
1036            ))
1037        })
1038    }
1039
1040    // =========================================================================
1041    // Forward Compatibility API
1042    // =========================================================================
1043
1044    /// Loads data with forward compatibility support.
1045    ///
1046    /// This method allows loading data from versions that don't exist in the current
1047    /// codebase yet. Unknown versions are deserialized using the latest known schema,
1048    /// and unknown fields are preserved for later saving.
1049    ///
1050    /// # ⚠️ Requirements
1051    ///
1052    /// Forward compatibility assumes **additive-only schema changes**:
1053    ///
1054    /// - ✅ Field additions (future version has new fields) → OK, unknown fields preserved
1055    /// - ❌ Field deletions → Deserialization will fail
1056    /// - ❌ Field type changes → Data corruption
1057    /// - ❌ Field semantic changes → Logic bugs
1058    ///
1059    /// # Arguments
1060    ///
1061    /// * `entity` - The entity name used when registering the migration path
1062    /// * `json` - A JSON string containing versioned data
1063    ///
1064    /// # Returns
1065    ///
1066    /// A `Forwardable<D>` wrapper containing the domain model and context for preserving
1067    /// unknown fields when saving.
1068    ///
1069    /// # Example
1070    ///
1071    /// ```ignore
1072    /// // Data from a future version (2.0.0) that doesn't exist in code yet
1073    /// let json = r#"{"version":"2.0.0","data":{"id":"1","title":"Task","new_field":"value"}}"#;
1074    ///
1075    /// // Load with forward compatibility (V1 is the latest known version)
1076    /// let mut task: Forwardable<TaskEntity> = migrator.load_forward("task", json)?;
1077    ///
1078    /// // Check if it was a lossy load
1079    /// if task.was_lossy() {
1080    ///     eprintln!("Warning: Loaded from unknown version {}", task.original_version());
1081    /// }
1082    ///
1083    /// // Modify the data
1084    /// task.title = "Updated".to_string();
1085    ///
1086    /// // Save preserving unknown fields and original version
1087    /// let saved = migrator.save_forward(&task)?;
1088    /// // → {"version":"2.0.0","data":{"id":"1","title":"Updated","new_field":"value"}}
1089    /// ```
1090    pub fn load_forward<D: DeserializeOwned>(
1091        &self,
1092        entity: &str,
1093        json: &str,
1094    ) -> Result<Forwardable<D>, MigrationError> {
1095        let value: serde_json::Value = serde_json::from_str(json).map_err(|e| {
1096            MigrationError::DeserializationError(format!("Failed to parse JSON: {}", e))
1097        })?;
1098        self.load_forward_from(entity, value, false)
1099    }
1100
1101    /// Loads data with forward compatibility support from flat format.
1102    ///
1103    /// Same as `load_forward` but expects flat format where version is at the same
1104    /// level as data fields.
1105    ///
1106    /// # Example
1107    ///
1108    /// ```ignore
1109    /// let json = r#"{"version":"2.0.0","id":"1","title":"Task","new_field":"value"}"#;
1110    /// let task: Forwardable<TaskEntity> = migrator.load_forward_flat("task", json)?;
1111    /// ```
1112    pub fn load_forward_flat<D: DeserializeOwned>(
1113        &self,
1114        entity: &str,
1115        json: &str,
1116    ) -> Result<Forwardable<D>, MigrationError> {
1117        let value: serde_json::Value = serde_json::from_str(json).map_err(|e| {
1118            MigrationError::DeserializationError(format!("Failed to parse JSON: {}", e))
1119        })?;
1120        self.load_forward_from(entity, value, true)
1121    }
1122
1123    /// Internal implementation for forward-compatible loading.
1124    fn load_forward_from<D: DeserializeOwned>(
1125        &self,
1126        entity: &str,
1127        value: serde_json::Value,
1128        is_flat: bool,
1129    ) -> Result<Forwardable<D>, MigrationError> {
1130        let path = self
1131            .paths
1132            .get(entity)
1133            .ok_or_else(|| MigrationError::EntityNotFound(entity.to_string()))?;
1134
1135        let version_key = &path.version_key;
1136        let data_key = &path.data_key;
1137
1138        let obj = value.as_object().ok_or_else(|| {
1139            MigrationError::DeserializationError("Expected JSON object".to_string())
1140        })?;
1141
1142        // Extract version
1143        let original_version = obj
1144            .get(version_key)
1145            .and_then(|v| v.as_str())
1146            .ok_or_else(|| {
1147                MigrationError::DeserializationError(format!(
1148                    "Missing or invalid '{}' field",
1149                    version_key
1150                ))
1151            })?
1152            .to_string();
1153
1154        // Extract data (handling both wrapped and flat formats)
1155        let (data_value, all_fields) = if is_flat {
1156            // Flat format: data fields are at the same level as version
1157            let mut fields = obj.clone();
1158            fields.remove(version_key);
1159            (serde_json::Value::Object(fields.clone()), fields)
1160        } else {
1161            // Wrapped format: data is in a separate field
1162            let data = obj
1163                .get(data_key)
1164                .ok_or_else(|| {
1165                    MigrationError::DeserializationError(format!("Missing '{}' field", data_key))
1166                })?
1167                .clone();
1168            let fields = data
1169                .as_object()
1170                .cloned()
1171                .unwrap_or_else(serde_json::Map::new);
1172            (data, fields)
1173        };
1174
1175        // Check if version is known
1176        let is_known_version = path.versions.contains(&original_version);
1177        let was_lossy = !is_known_version;
1178
1179        // Determine which version to use for deserialization
1180        let target_version = if is_known_version {
1181            original_version.clone()
1182        } else {
1183            // Use the latest known version for unknown versions
1184            path.versions.last().cloned().ok_or_else(|| {
1185                MigrationError::DeserializationError(
1186                    "No versions registered for entity".to_string(),
1187                )
1188            })?
1189        };
1190
1191        // Apply migrations if needed (from known version to latest)
1192        let mut current_data = data_value.clone();
1193        let mut current_version = if is_known_version {
1194            original_version.clone()
1195        } else {
1196            // For unknown versions, skip migration and deserialize directly
1197            target_version.clone()
1198        };
1199
1200        if is_known_version {
1201            // Apply migration steps
1202            while let Some(migrate_fn) = path.steps.get(&current_version) {
1203                current_data = migrate_fn(current_data)?;
1204                match path.versions.iter().position(|v| v == &current_version) {
1205                    Some(idx) if idx + 1 < path.versions.len() => {
1206                        current_version = path.versions[idx + 1].clone();
1207                    }
1208                    _ => break,
1209                }
1210            }
1211        }
1212
1213        // Finalize into domain model
1214        let domain_value = (path.finalize)(current_data)?;
1215
1216        // Deserialize to domain type
1217        let domain: D = serde_json::from_value(domain_value.clone()).map_err(|e| {
1218            MigrationError::DeserializationError(format!("Failed to deserialize domain: {}", e))
1219        })?;
1220
1221        // Calculate unknown fields (fields in original data but not in domain)
1222        let domain_obj = domain_value
1223            .as_object()
1224            .cloned()
1225            .unwrap_or_else(serde_json::Map::new);
1226        let mut unknown_fields = serde_json::Map::new();
1227        for (key, value) in all_fields {
1228            if !domain_obj.contains_key(&key) {
1229                unknown_fields.insert(key, value);
1230            }
1231        }
1232
1233        let ctx = ForwardContext::new(
1234            original_version,
1235            unknown_fields,
1236            was_lossy,
1237            version_key.clone(),
1238            data_key.clone(),
1239            is_flat,
1240        );
1241
1242        Ok(Forwardable::new(domain, ctx))
1243    }
1244
1245    /// Saves data preserving forward compatibility information.
1246    ///
1247    /// This method saves the domain data while preserving unknown fields from the
1248    /// original data and maintaining the original version number.
1249    ///
1250    /// # Arguments
1251    ///
1252    /// * `data` - A `Forwardable<D>` wrapper containing domain data and context
1253    ///
1254    /// # Returns
1255    ///
1256    /// A JSON string with preserved unknown fields and original version.
1257    ///
1258    /// # Example
1259    ///
1260    /// ```ignore
1261    /// let mut task: Forwardable<TaskEntity> = migrator.load_forward("task", json)?;
1262    /// task.title = "Updated".to_string();
1263    /// let saved = migrator.save_forward(&task)?;
1264    /// ```
1265    pub fn save_forward<D: Serialize>(
1266        &self,
1267        data: &Forwardable<D>,
1268    ) -> Result<String, MigrationError> {
1269        let ctx = data.context();
1270
1271        // Serialize the domain data
1272        let mut domain_value = serde_json::to_value(&data.inner).map_err(|e| {
1273            MigrationError::SerializationError(format!("Failed to serialize domain: {}", e))
1274        })?;
1275
1276        // Merge unknown fields back
1277        if let Some(obj) = domain_value.as_object_mut() {
1278            for (key, value) in ctx.unknown_fields.iter() {
1279                if !obj.contains_key(key) {
1280                    obj.insert(key.clone(), value.clone());
1281                }
1282            }
1283        }
1284
1285        // Build output based on original format
1286        if ctx.was_flat {
1287            // Flat format
1288            let obj = domain_value.as_object_mut().ok_or_else(|| {
1289                MigrationError::SerializationError(
1290                    "Domain must serialize to object for flat format".to_string(),
1291                )
1292            })?;
1293            obj.insert(
1294                ctx.version_key.clone(),
1295                serde_json::Value::String(ctx.original_version.clone()),
1296            );
1297            serde_json::to_string(&obj).map_err(|e| {
1298                MigrationError::SerializationError(format!("Failed to serialize: {}", e))
1299            })
1300        } else {
1301            // Wrapped format
1302            let mut wrapper = serde_json::Map::new();
1303            wrapper.insert(
1304                ctx.version_key.clone(),
1305                serde_json::Value::String(ctx.original_version.clone()),
1306            );
1307            wrapper.insert(ctx.data_key.clone(), domain_value);
1308            serde_json::to_string(&wrapper).map_err(|e| {
1309                MigrationError::SerializationError(format!("Failed to serialize: {}", e))
1310            })
1311        }
1312    }
1313
1314    /// Saves a domain entity to a JSON string using its latest versioned format.
1315    ///
1316    /// This method automatically converts the domain entity to its latest version
1317    /// and saves it with version information.
1318    ///
1319    /// # Arguments
1320    ///
1321    /// * `entity` - The domain entity to save (must implement `LatestVersioned`)
1322    ///
1323    /// # Returns
1324    ///
1325    /// A JSON string with the format: `{"version":"x.y.z","data":{...}}`
1326    ///
1327    /// # Errors
1328    ///
1329    /// Returns `SerializationError` if the entity cannot be serialized to JSON.
1330    ///
1331    /// # Example
1332    ///
1333    /// ```ignore
1334    /// #[version_migrate(entity = "task", latest = TaskV1_1_0)]
1335    /// struct TaskEntity {
1336    ///     id: String,
1337    ///     title: String,
1338    ///     description: Option<String>,
1339    /// }
1340    ///
1341    /// let entity = TaskEntity {
1342    ///     id: "task-1".to_string(),
1343    ///     title: "My Task".to_string(),
1344    ///     description: Some("Description".to_string()),
1345    /// };
1346    ///
1347    /// let migrator = Migrator::new();
1348    /// let json = migrator.save_entity(entity)?;
1349    /// // Automatically saved with latest version (1.1.0)
1350    /// ```
1351    pub fn save_entity<E: crate::LatestVersioned>(
1352        &self,
1353        entity: E,
1354    ) -> Result<String, MigrationError> {
1355        let latest = entity.to_latest();
1356        self.save(latest)
1357    }
1358
1359    /// Saves a domain entity to a JSON string in flat format using its latest versioned format.
1360    ///
1361    /// This method automatically converts the domain entity to its latest version
1362    /// and saves it with the version field at the same level as data fields.
1363    ///
1364    /// # Arguments
1365    ///
1366    /// * `entity` - The domain entity to save (must implement `LatestVersioned`)
1367    ///
1368    /// # Returns
1369    ///
1370    /// A JSON string with the format: `{"version":"x.y.z","field1":"value1",...}`
1371    ///
1372    /// # Errors
1373    ///
1374    /// Returns `SerializationError` if the entity cannot be serialized to JSON.
1375    ///
1376    /// # Example
1377    ///
1378    /// ```ignore
1379    /// #[version_migrate(entity = "task", latest = TaskV1_1_0)]
1380    /// struct TaskEntity {
1381    ///     id: String,
1382    ///     title: String,
1383    ///     description: Option<String>,
1384    /// }
1385    ///
1386    /// let entity = TaskEntity {
1387    ///     id: "task-1".to_string(),
1388    ///     title: "My Task".to_string(),
1389    ///     description: Some("Description".to_string()),
1390    /// };
1391    ///
1392    /// let migrator = Migrator::new();
1393    /// let json = migrator.save_entity_flat(entity)?;
1394    /// // json: {"version":"1.1.0","id":"task-1","title":"My Task",...}
1395    /// ```
1396    pub fn save_entity_flat<E: crate::LatestVersioned>(
1397        &self,
1398        entity: E,
1399    ) -> Result<String, MigrationError> {
1400        let latest = entity.to_latest();
1401        self.save_flat(latest)
1402    }
1403
1404    /// Saves multiple domain entities to a JSON array string using their latest versioned format.
1405    ///
1406    /// This method automatically converts each domain entity to its latest version
1407    /// and saves them as a JSON array.
1408    ///
1409    /// # Arguments
1410    ///
1411    /// * `entities` - Vector of domain entities to save
1412    ///
1413    /// # Returns
1414    ///
1415    /// A JSON array string where each element has the format: `{"version":"x.y.z","data":{...}}`
1416    ///
1417    /// # Errors
1418    ///
1419    /// Returns `SerializationError` if the entities cannot be serialized to JSON.
1420    ///
1421    /// # Example
1422    ///
1423    /// ```ignore
1424    /// let entities = vec![
1425    ///     TaskEntity { id: "1".into(), title: "Task 1".into(), description: None },
1426    ///     TaskEntity { id: "2".into(), title: "Task 2".into(), description: None },
1427    /// ];
1428    ///
1429    /// let json = migrator.save_entity_vec(entities)?;
1430    /// ```
1431    pub fn save_entity_vec<E: crate::LatestVersioned>(
1432        &self,
1433        entities: Vec<E>,
1434    ) -> Result<String, MigrationError> {
1435        let versioned: Vec<E::Latest> = entities.into_iter().map(|e| e.to_latest()).collect();
1436        self.save_vec(versioned)
1437    }
1438
1439    /// Saves multiple domain entities to a JSON array string in flat format using their latest versioned format.
1440    ///
1441    /// This method automatically converts each domain entity to its latest version
1442    /// and saves them with version fields at the same level as data fields.
1443    ///
1444    /// # Arguments
1445    ///
1446    /// * `entities` - Vector of domain entities to save
1447    ///
1448    /// # Returns
1449    ///
1450    /// A JSON array string where each element has the format: `{"version":"x.y.z","field1":"value1",...}`
1451    ///
1452    /// # Errors
1453    ///
1454    /// Returns `SerializationError` if the entities cannot be serialized to JSON.
1455    ///
1456    /// # Example
1457    ///
1458    /// ```ignore
1459    /// let entities = vec![
1460    ///     TaskEntity { id: "1".into(), title: "Task 1".into(), description: None },
1461    ///     TaskEntity { id: "2".into(), title: "Task 2".into(), description: None },
1462    /// ];
1463    ///
1464    /// let json = migrator.save_entity_vec_flat(entities)?;
1465    /// ```
1466    pub fn save_entity_vec_flat<E: crate::LatestVersioned>(
1467        &self,
1468        entities: Vec<E>,
1469    ) -> Result<String, MigrationError> {
1470        let versioned: Vec<E::Latest> = entities.into_iter().map(|e| e.to_latest()).collect();
1471        self.save_vec_flat(versioned)
1472    }
1473
1474    /// Saves a domain entity to a JSON string using its latest versioned format, by entity name.
1475    ///
1476    /// This method works without requiring the `VersionMigrate` macro on the entity type.
1477    /// Instead, it uses the save function registered during `register()` via `into_with_save()`.
1478    ///
1479    /// # Arguments
1480    ///
1481    /// * `entity_name` - The entity name used when registering the migration path
1482    /// * `entity` - The domain entity to save (must be Serialize)
1483    ///
1484    /// # Returns
1485    ///
1486    /// A JSON string with the format: `{"version":"x.y.z","data":{...}}`
1487    ///
1488    /// # Errors
1489    ///
1490    /// Returns `MigrationPathNotDefined` if the entity is not registered with save support.
1491    /// Returns `SerializationError` if the entity cannot be serialized.
1492    ///
1493    /// # Example
1494    ///
1495    /// ```ignore
1496    /// impl FromDomain<TaskEntity> for TaskV1_1_0 {
1497    ///     fn from_domain(entity: TaskEntity) -> Self { ... }
1498    /// }
1499    ///
1500    /// let path = Migrator::define("task")
1501    ///     .from::<TaskV1_0_0>()
1502    ///     .step::<TaskV1_1_0>()
1503    ///     .into_with_save::<TaskEntity>();
1504    ///
1505    /// migrator.register(path)?;
1506    ///
1507    /// let entity = TaskEntity { ... };
1508    /// let json = migrator.save_domain("task", entity)?;
1509    /// // → {"version":"1.1.0","data":{"id":"1","title":"My Task",...}}
1510    /// ```
1511    pub fn save_domain<T: Serialize>(
1512        &self,
1513        entity_name: &str,
1514        entity: T,
1515    ) -> Result<String, MigrationError> {
1516        let saver = self.domain_savers.get(entity_name).ok_or_else(|| {
1517            MigrationError::EntityNotFound(format!(
1518                "Entity '{}' is not registered with domain save support. Use into_with_save() when defining the migration path.",
1519                entity_name
1520            ))
1521        })?;
1522
1523        // Get version/data keys from registered path
1524        let path = self.paths.get(entity_name).ok_or_else(|| {
1525            MigrationError::EntityNotFound(format!("Entity '{}' is not registered", entity_name))
1526        })?;
1527
1528        let domain_value = serde_json::to_value(entity).map_err(|e| {
1529            MigrationError::SerializationError(format!("Failed to serialize entity: {}", e))
1530        })?;
1531
1532        (saver.save_fn)(domain_value, &path.version_key, &path.data_key)
1533    }
1534
1535    /// Saves a domain entity to a JSON string in flat format using its latest versioned format, by entity name.
1536    ///
1537    /// This method works without requiring the `VersionMigrate` macro on the entity type.
1538    /// The version field is placed at the same level as data fields.
1539    ///
1540    /// # Arguments
1541    ///
1542    /// * `entity_name` - The entity name used when registering the migration path
1543    /// * `entity` - The domain entity to save (must be Serialize)
1544    ///
1545    /// # Returns
1546    ///
1547    /// A JSON string with the format: `{"version":"x.y.z","field1":"value1",...}`
1548    ///
1549    /// # Errors
1550    ///
1551    /// Returns `MigrationPathNotDefined` if the entity is not registered with save support.
1552    /// Returns `SerializationError` if the entity cannot be serialized.
1553    ///
1554    /// # Example
1555    ///
1556    /// ```ignore
1557    /// let json = migrator.save_domain_flat("task", entity)?;
1558    /// // → {"version":"1.1.0","id":"1","title":"My Task",...}
1559    /// ```
1560    pub fn save_domain_flat<T: Serialize>(
1561        &self,
1562        entity_name: &str,
1563        entity: T,
1564    ) -> Result<String, MigrationError> {
1565        let saver = self.domain_savers.get(entity_name).ok_or_else(|| {
1566            MigrationError::EntityNotFound(format!(
1567                "Entity '{}' is not registered with domain save support. Use into_with_save() when defining the migration path.",
1568                entity_name
1569            ))
1570        })?;
1571
1572        // Get version key from registered path
1573        let path = self.paths.get(entity_name).ok_or_else(|| {
1574            MigrationError::EntityNotFound(format!("Entity '{}' is not registered", entity_name))
1575        })?;
1576
1577        let domain_value = serde_json::to_value(entity).map_err(|e| {
1578            MigrationError::SerializationError(format!("Failed to serialize entity: {}", e))
1579        })?;
1580
1581        (saver.save_flat_fn)(domain_value, &path.version_key)
1582    }
1583}
1584
1585impl Default for Migrator {
1586    fn default() -> Self {
1587        Self::new()
1588    }
1589}
1590
1591/// Builder for configuring a `Migrator` with default settings.
1592pub struct MigratorBuilder {
1593    default_version_key: Option<String>,
1594    default_data_key: Option<String>,
1595}
1596
1597impl MigratorBuilder {
1598    pub(crate) fn new() -> Self {
1599        Self {
1600            default_version_key: None,
1601            default_data_key: None,
1602        }
1603    }
1604
1605    /// Sets the default version key for all entities.
1606    ///
1607    /// This key will be used unless overridden by:
1608    /// - The entity's `MigrationPath` via `with_keys()`
1609    /// - The type's `Versioned` trait constants
1610    pub fn default_version_key(mut self, key: impl Into<String>) -> Self {
1611        self.default_version_key = Some(key.into());
1612        self
1613    }
1614
1615    /// Sets the default data key for all entities.
1616    ///
1617    /// This key will be used unless overridden by:
1618    /// - The entity's `MigrationPath` via `with_keys()`
1619    /// - The type's `Versioned` trait constants
1620    pub fn default_data_key(mut self, key: impl Into<String>) -> Self {
1621        self.default_data_key = Some(key.into());
1622        self
1623    }
1624
1625    /// Builds the `Migrator` with the configured defaults.
1626    pub fn build(self) -> Migrator {
1627        Migrator {
1628            paths: HashMap::new(),
1629            default_version_key: self.default_version_key,
1630            default_data_key: self.default_data_key,
1631            domain_savers: HashMap::new(),
1632        }
1633    }
1634}
1635
1636/// Marker type for builder state: start
1637pub struct Start;
1638
1639/// Marker type for builder state: has a starting version
1640pub struct HasFrom<V>(PhantomData<V>);
1641
1642/// Marker type for builder state: has intermediate steps
1643pub struct HasSteps<V>(PhantomData<V>);
1644
1645/// Builder for defining migration paths.
1646pub struct MigrationPathBuilder<State> {
1647    entity: String,
1648    steps: HashMap<String, MigrationFn>,
1649    versions: Vec<String>,
1650    version_key: String,
1651    data_key: String,
1652    custom_version_key: Option<String>,
1653    custom_data_key: Option<String>,
1654    _state: PhantomData<State>,
1655}
1656
1657impl MigrationPathBuilder<Start> {
1658    fn new(entity: String) -> Self {
1659        Self {
1660            entity,
1661            steps: HashMap::new(),
1662            versions: Vec::new(),
1663            version_key: String::from("version"),
1664            data_key: String::from("data"),
1665            custom_version_key: None,
1666            custom_data_key: None,
1667            _state: PhantomData,
1668        }
1669    }
1670
1671    /// Overrides the version and data keys for this migration path.
1672    ///
1673    /// This takes precedence over both the Migrator's defaults and the type's trait constants.
1674    ///
1675    /// # Example
1676    ///
1677    /// ```ignore
1678    /// Migrator::define("task")
1679    ///     .with_keys("custom_version", "custom_data")
1680    ///     .from::<TaskV1>()
1681    ///     .into::<TaskDomain>();
1682    /// ```
1683    pub fn with_keys(
1684        mut self,
1685        version_key: impl Into<String>,
1686        data_key: impl Into<String>,
1687    ) -> Self {
1688        self.custom_version_key = Some(version_key.into());
1689        self.custom_data_key = Some(data_key.into());
1690        self
1691    }
1692
1693    /// Sets the starting version for migrations.
1694    pub fn from<V: Versioned + DeserializeOwned>(self) -> MigrationPathBuilder<HasFrom<V>> {
1695        let mut versions = self.versions;
1696        versions.push(V::VERSION.to_string());
1697
1698        MigrationPathBuilder {
1699            entity: self.entity,
1700            steps: self.steps,
1701            versions,
1702            version_key: V::VERSION_KEY.to_string(),
1703            data_key: V::DATA_KEY.to_string(),
1704            custom_version_key: self.custom_version_key,
1705            custom_data_key: self.custom_data_key,
1706            _state: PhantomData,
1707        }
1708    }
1709}
1710
1711impl<V> MigrationPathBuilder<HasFrom<V>>
1712where
1713    V: Versioned + DeserializeOwned,
1714{
1715    /// Adds a migration step to the next version.
1716    pub fn step<Next>(mut self) -> MigrationPathBuilder<HasSteps<Next>>
1717    where
1718        V: MigratesTo<Next>,
1719        Next: Versioned + DeserializeOwned + Serialize,
1720    {
1721        let from_version = V::VERSION.to_string();
1722        let migration_fn: MigrationFn = Box::new(move |value| {
1723            let from_value: V = serde_json::from_value(value).map_err(|e| {
1724                MigrationError::DeserializationError(format!(
1725                    "Failed to deserialize version {}: {}",
1726                    V::VERSION,
1727                    e
1728                ))
1729            })?;
1730
1731            let to_value = from_value.migrate();
1732
1733            // Return the raw migrated value without wrapping
1734            serde_json::to_value(&to_value).map_err(|e| MigrationError::MigrationStepFailed {
1735                from: V::VERSION.to_string(),
1736                to: Next::VERSION.to_string(),
1737                error: e.to_string(),
1738            })
1739        });
1740
1741        self.steps.insert(from_version, migration_fn);
1742        self.versions.push(Next::VERSION.to_string());
1743
1744        MigrationPathBuilder {
1745            entity: self.entity,
1746            steps: self.steps,
1747            versions: self.versions,
1748            version_key: self.version_key,
1749            data_key: self.data_key,
1750            custom_version_key: self.custom_version_key,
1751            custom_data_key: self.custom_data_key,
1752            _state: PhantomData,
1753        }
1754    }
1755
1756    /// Finalizes the migration path with conversion to domain model.
1757    pub fn into<D: DeserializeOwned + Serialize>(self) -> MigrationPath<D>
1758    where
1759        V: IntoDomain<D>,
1760    {
1761        let finalize: Box<
1762            dyn Fn(serde_json::Value) -> Result<serde_json::Value, MigrationError> + Send + Sync,
1763        > = Box::new(move |value| {
1764            let versioned: V = serde_json::from_value(value).map_err(|e| {
1765                MigrationError::DeserializationError(format!(
1766                    "Failed to deserialize final version: {}",
1767                    e
1768                ))
1769            })?;
1770
1771            let domain = versioned.into_domain();
1772
1773            serde_json::to_value(domain).map_err(|e| MigrationError::MigrationStepFailed {
1774                from: V::VERSION.to_string(),
1775                to: "domain".to_string(),
1776                error: e.to_string(),
1777            })
1778        });
1779
1780        MigrationPath {
1781            entity: self.entity,
1782            inner: EntityMigrationPath {
1783                steps: self.steps,
1784                finalize,
1785                versions: self.versions.clone(),
1786                version_key: self.version_key,
1787                data_key: self.data_key,
1788            },
1789            versions: self.versions,
1790            custom_version_key: self.custom_version_key,
1791            custom_data_key: self.custom_data_key,
1792            save_fn: None,
1793            save_flat_fn: None,
1794            _phantom: PhantomData,
1795        }
1796    }
1797
1798    /// Finalizes the migration path with conversion to domain model and enables domain entity saving.
1799    ///
1800    /// This variant registers save functions that allow saving domain entities directly by entity name,
1801    /// without needing the `VersionMigrate` macro on the entity type.
1802    ///
1803    /// # Requirements
1804    ///
1805    /// The latest versioned type (V) must implement `FromDomain<D>` to convert domain entities
1806    /// back to the versioned format for saving.
1807    ///
1808    /// # Example
1809    ///
1810    /// ```ignore
1811    /// impl FromDomain<TaskEntity> for TaskV1_1_0 {
1812    ///     fn from_domain(entity: TaskEntity) -> Self {
1813    ///         TaskV1_1_0 {
1814    ///             id: entity.id,
1815    ///             title: entity.title,
1816    ///             description: entity.description,
1817    ///         }
1818    ///     }
1819    /// }
1820    ///
1821    /// let path = Migrator::define("task")
1822    ///     .from::<TaskV1_0_0>()
1823    ///     .step::<TaskV1_1_0>()
1824    ///     .into_with_save::<TaskEntity>();
1825    ///
1826    /// migrator.register(path)?;
1827    ///
1828    /// // Now you can save by entity name
1829    /// let entity = TaskEntity { ... };
1830    /// let json = migrator.save_domain("task", entity)?;
1831    /// ```
1832    pub fn into_with_save<D: DeserializeOwned + Serialize>(self) -> MigrationPath<D>
1833    where
1834        V: IntoDomain<D> + crate::FromDomain<D>,
1835    {
1836        let finalize: Box<
1837            dyn Fn(serde_json::Value) -> Result<serde_json::Value, MigrationError> + Send + Sync,
1838        > = Box::new(move |value| {
1839            let versioned: V = serde_json::from_value(value).map_err(|e| {
1840                MigrationError::DeserializationError(format!(
1841                    "Failed to deserialize final version: {}",
1842                    e
1843                ))
1844            })?;
1845
1846            let domain = versioned.into_domain();
1847
1848            serde_json::to_value(domain).map_err(|e| MigrationError::MigrationStepFailed {
1849                from: V::VERSION.to_string(),
1850                to: "domain".to_string(),
1851                error: e.to_string(),
1852            })
1853        });
1854
1855        // Create save function for domain entities
1856        let version = V::VERSION;
1857
1858        let save_fn: DomainSaveFn = Box::new(move |domain_value, vkey, dkey| {
1859            let domain: D = serde_json::from_value(domain_value).map_err(|e| {
1860                MigrationError::DeserializationError(format!("Failed to deserialize domain: {}", e))
1861            })?;
1862
1863            let latest = V::from_domain(domain);
1864            let data_value = serde_json::to_value(&latest).map_err(|e| {
1865                MigrationError::SerializationError(format!("Failed to serialize latest: {}", e))
1866            })?;
1867
1868            let mut map = serde_json::Map::new();
1869            map.insert(
1870                vkey.to_string(),
1871                serde_json::Value::String(version.to_string()),
1872            );
1873            map.insert(dkey.to_string(), data_value);
1874
1875            serde_json::to_string(&map).map_err(|e| {
1876                MigrationError::SerializationError(format!("Failed to serialize wrapper: {}", e))
1877            })
1878        });
1879
1880        let save_flat_fn: DomainSaveFlatFn = Box::new(move |domain_value, vkey| {
1881            let domain: D = serde_json::from_value(domain_value).map_err(|e| {
1882                MigrationError::DeserializationError(format!("Failed to deserialize domain: {}", e))
1883            })?;
1884
1885            let latest = V::from_domain(domain);
1886            let mut data_value = serde_json::to_value(&latest).map_err(|e| {
1887                MigrationError::SerializationError(format!("Failed to serialize latest: {}", e))
1888            })?;
1889
1890            let obj = data_value.as_object_mut().ok_or_else(|| {
1891                MigrationError::SerializationError(
1892                    "Data must serialize to a JSON object for flat format".to_string(),
1893                )
1894            })?;
1895
1896            obj.insert(
1897                vkey.to_string(),
1898                serde_json::Value::String(version.to_string()),
1899            );
1900
1901            serde_json::to_string(&obj).map_err(|e| {
1902                MigrationError::SerializationError(format!(
1903                    "Failed to serialize flat format: {}",
1904                    e
1905                ))
1906            })
1907        });
1908
1909        MigrationPath {
1910            entity: self.entity,
1911            inner: EntityMigrationPath {
1912                steps: self.steps,
1913                finalize,
1914                versions: self.versions.clone(),
1915                version_key: self.version_key,
1916                data_key: self.data_key,
1917            },
1918            versions: self.versions,
1919            custom_version_key: self.custom_version_key,
1920            custom_data_key: self.custom_data_key,
1921            save_fn: Some(save_fn),
1922            save_flat_fn: Some(save_flat_fn),
1923            _phantom: PhantomData,
1924        }
1925    }
1926}
1927
1928impl<V> MigrationPathBuilder<HasSteps<V>>
1929where
1930    V: Versioned + DeserializeOwned,
1931{
1932    /// Adds another migration step.
1933    pub fn step<Next>(mut self) -> MigrationPathBuilder<HasSteps<Next>>
1934    where
1935        V: MigratesTo<Next>,
1936        Next: Versioned + DeserializeOwned + Serialize,
1937    {
1938        let from_version = V::VERSION.to_string();
1939        let migration_fn: MigrationFn = Box::new(move |value| {
1940            let from_value: V = serde_json::from_value(value).map_err(|e| {
1941                MigrationError::DeserializationError(format!(
1942                    "Failed to deserialize version {}: {}",
1943                    V::VERSION,
1944                    e
1945                ))
1946            })?;
1947
1948            let to_value = from_value.migrate();
1949
1950            // Return the raw migrated value without wrapping
1951            serde_json::to_value(&to_value).map_err(|e| MigrationError::MigrationStepFailed {
1952                from: V::VERSION.to_string(),
1953                to: Next::VERSION.to_string(),
1954                error: e.to_string(),
1955            })
1956        });
1957
1958        self.steps.insert(from_version, migration_fn);
1959        self.versions.push(Next::VERSION.to_string());
1960
1961        MigrationPathBuilder {
1962            entity: self.entity,
1963            steps: self.steps,
1964            versions: self.versions,
1965            version_key: self.version_key,
1966            data_key: self.data_key,
1967            custom_version_key: self.custom_version_key,
1968            custom_data_key: self.custom_data_key,
1969            _state: PhantomData,
1970        }
1971    }
1972
1973    /// Finalizes the migration path with conversion to domain model.
1974    pub fn into<D: DeserializeOwned + Serialize>(self) -> MigrationPath<D>
1975    where
1976        V: IntoDomain<D>,
1977    {
1978        let finalize: Box<
1979            dyn Fn(serde_json::Value) -> Result<serde_json::Value, MigrationError> + Send + Sync,
1980        > = Box::new(move |value| {
1981            let versioned: V = serde_json::from_value(value).map_err(|e| {
1982                MigrationError::DeserializationError(format!(
1983                    "Failed to deserialize final version: {}",
1984                    e
1985                ))
1986            })?;
1987
1988            let domain = versioned.into_domain();
1989
1990            serde_json::to_value(domain).map_err(|e| MigrationError::MigrationStepFailed {
1991                from: V::VERSION.to_string(),
1992                to: "domain".to_string(),
1993                error: e.to_string(),
1994            })
1995        });
1996
1997        MigrationPath {
1998            entity: self.entity,
1999            inner: EntityMigrationPath {
2000                steps: self.steps,
2001                finalize,
2002                versions: self.versions.clone(),
2003                version_key: self.version_key,
2004                data_key: self.data_key,
2005            },
2006            versions: self.versions,
2007            custom_version_key: self.custom_version_key,
2008            custom_data_key: self.custom_data_key,
2009            save_fn: None,
2010            save_flat_fn: None,
2011            _phantom: PhantomData,
2012        }
2013    }
2014
2015    /// Finalizes the migration path with conversion to domain model and enables domain entity saving.
2016    ///
2017    /// See `MigrationPathBuilder<HasFrom<V>>::into_with_save` for details.
2018    pub fn into_with_save<D: DeserializeOwned + Serialize>(self) -> MigrationPath<D>
2019    where
2020        V: IntoDomain<D> + crate::FromDomain<D>,
2021    {
2022        let finalize: Box<
2023            dyn Fn(serde_json::Value) -> Result<serde_json::Value, MigrationError> + Send + Sync,
2024        > = Box::new(move |value| {
2025            let versioned: V = serde_json::from_value(value).map_err(|e| {
2026                MigrationError::DeserializationError(format!(
2027                    "Failed to deserialize final version: {}",
2028                    e
2029                ))
2030            })?;
2031
2032            let domain = versioned.into_domain();
2033
2034            serde_json::to_value(domain).map_err(|e| MigrationError::MigrationStepFailed {
2035                from: V::VERSION.to_string(),
2036                to: "domain".to_string(),
2037                error: e.to_string(),
2038            })
2039        });
2040
2041        // Create save function for domain entities
2042        let version = V::VERSION;
2043
2044        let save_fn: DomainSaveFn = Box::new(move |domain_value, vkey, dkey| {
2045            let domain: D = serde_json::from_value(domain_value).map_err(|e| {
2046                MigrationError::DeserializationError(format!("Failed to deserialize domain: {}", e))
2047            })?;
2048
2049            let latest = V::from_domain(domain);
2050            let data_value = serde_json::to_value(&latest).map_err(|e| {
2051                MigrationError::SerializationError(format!("Failed to serialize latest: {}", e))
2052            })?;
2053
2054            let mut map = serde_json::Map::new();
2055            map.insert(
2056                vkey.to_string(),
2057                serde_json::Value::String(version.to_string()),
2058            );
2059            map.insert(dkey.to_string(), data_value);
2060
2061            serde_json::to_string(&map).map_err(|e| {
2062                MigrationError::SerializationError(format!("Failed to serialize wrapper: {}", e))
2063            })
2064        });
2065
2066        let save_flat_fn: DomainSaveFlatFn = Box::new(move |domain_value, vkey| {
2067            let domain: D = serde_json::from_value(domain_value).map_err(|e| {
2068                MigrationError::DeserializationError(format!("Failed to deserialize domain: {}", e))
2069            })?;
2070
2071            let latest = V::from_domain(domain);
2072            let mut data_value = serde_json::to_value(&latest).map_err(|e| {
2073                MigrationError::SerializationError(format!("Failed to serialize latest: {}", e))
2074            })?;
2075
2076            let obj = data_value.as_object_mut().ok_or_else(|| {
2077                MigrationError::SerializationError(
2078                    "Data must serialize to a JSON object for flat format".to_string(),
2079                )
2080            })?;
2081
2082            obj.insert(
2083                vkey.to_string(),
2084                serde_json::Value::String(version.to_string()),
2085            );
2086
2087            serde_json::to_string(&obj).map_err(|e| {
2088                MigrationError::SerializationError(format!(
2089                    "Failed to serialize flat format: {}",
2090                    e
2091                ))
2092            })
2093        });
2094
2095        MigrationPath {
2096            entity: self.entity,
2097            inner: EntityMigrationPath {
2098                steps: self.steps,
2099                finalize,
2100                versions: self.versions.clone(),
2101                version_key: self.version_key,
2102                data_key: self.data_key,
2103            },
2104            versions: self.versions,
2105            custom_version_key: self.custom_version_key,
2106            custom_data_key: self.custom_data_key,
2107            save_fn: Some(save_fn),
2108            save_flat_fn: Some(save_flat_fn),
2109            _phantom: PhantomData,
2110        }
2111    }
2112}
2113
2114/// A complete migration path from versioned DTOs to a domain model.
2115pub struct MigrationPath<D> {
2116    entity: String,
2117    inner: EntityMigrationPath,
2118    /// List of versions in the migration path for validation
2119    versions: Vec<String>,
2120    /// Custom version key override (takes precedence over Migrator defaults)
2121    custom_version_key: Option<String>,
2122    /// Custom data key override (takes precedence over Migrator defaults)
2123    custom_data_key: Option<String>,
2124    /// Function to save domain entities (if FromDomain is implemented)
2125    save_fn: Option<DomainSaveFn>,
2126    /// Function to save domain entities in flat format (if FromDomain is implemented)
2127    save_flat_fn: Option<DomainSaveFlatFn>,
2128    _phantom: PhantomData<D>,
2129}
2130
2131/// A wrapper around JSON data that provides convenient query and update methods
2132/// for partial updates with automatic migration.
2133///
2134/// `ConfigMigrator` holds a JSON object and allows you to query specific keys,
2135/// automatically migrating versioned data to domain entities, and update them
2136/// with the latest version.
2137///
2138/// # Example
2139///
2140/// ```ignore
2141/// // config.json:
2142/// // {
2143/// //   "app_name": "MyApp",
2144/// //   "tasks": [
2145/// //     {"version": "1.0.0", "id": "1", "title": "Task 1"},
2146/// //     {"version": "2.0.0", "id": "2", "title": "Task 2", "description": "New"}
2147/// //   ]
2148/// // }
2149///
2150/// let config_json = fs::read_to_string("config.json")?;
2151/// let mut config = ConfigMigrator::from(&config_json, migrator)?;
2152///
2153/// // Query tasks (automatically migrates all versions to TaskEntity)
2154/// let mut tasks: Vec<TaskEntity> = config.query("tasks")?;
2155///
2156/// // Update tasks
2157/// tasks[0].title = "Updated Task".to_string();
2158///
2159/// // Save back with latest version
2160/// config.update("tasks", tasks)?;
2161///
2162/// // Write to file
2163/// fs::write("config.json", config.to_string()?)?;
2164/// ```
2165pub struct ConfigMigrator {
2166    root: serde_json::Value,
2167    migrator: Migrator,
2168}
2169
2170impl ConfigMigrator {
2171    /// Creates a new `ConfigMigrator` from a JSON string and a `Migrator`.
2172    ///
2173    /// # Errors
2174    ///
2175    /// Returns `MigrationError::DeserializationError` if the JSON is invalid.
2176    pub fn from(json: &str, migrator: Migrator) -> Result<Self, MigrationError> {
2177        let root = serde_json::from_str(json)
2178            .map_err(|e| MigrationError::DeserializationError(e.to_string()))?;
2179        Ok(Self { root, migrator })
2180    }
2181
2182    /// Queries a specific key from the JSON object and returns the data as domain entities.
2183    ///
2184    /// This method automatically migrates all versioned data to the latest version
2185    /// and converts them to domain entities.
2186    ///
2187    /// # Type Parameters
2188    ///
2189    /// - `T`: Must implement `Queryable` to provide the entity name, and `Deserialize` for deserialization.
2190    ///
2191    /// # Errors
2192    ///
2193    /// - Returns `MigrationError::DeserializationError` if the key doesn't contain a valid array.
2194    /// - Returns migration errors if the data cannot be migrated.
2195    ///
2196    /// # Example
2197    ///
2198    /// ```ignore
2199    /// let tasks: Vec<TaskEntity> = config.query("tasks")?;
2200    /// ```
2201    pub fn query<T>(&self, key: &str) -> Result<Vec<T>, MigrationError>
2202    where
2203        T: crate::Queryable + for<'de> serde::Deserialize<'de>,
2204    {
2205        let value = &self.root[key];
2206        if value.is_null() {
2207            return Ok(Vec::new());
2208        }
2209
2210        if !value.is_array() {
2211            return Err(MigrationError::DeserializationError(format!(
2212                "Key '{}' does not contain an array",
2213                key
2214            )));
2215        }
2216
2217        match value.as_array() {
2218            Some(array) => self
2219                .migrator
2220                .load_vec_flat_from(T::ENTITY_NAME, array.to_vec()),
2221            None => Err(MigrationError::DeserializationError(format!(
2222                "Key '{}' is not an array",
2223                key
2224            ))),
2225        }
2226    }
2227
2228    /// Updates a specific key in the JSON object with new domain entities.
2229    ///
2230    /// This method serializes the entities with the latest version (automatically
2231    /// determined from the `Queryable` trait) and updates the JSON object in place.
2232    ///
2233    /// # Type Parameters
2234    ///
2235    /// - `T`: Must implement `Serialize` and `Queryable`.
2236    ///
2237    /// # Errors
2238    ///
2239    /// - Returns `MigrationError::EntityNotFound` if the entity is not registered.
2240    /// - Returns serialization errors if the data cannot be serialized.
2241    ///
2242    /// # Example
2243    ///
2244    /// ```ignore
2245    /// // Version is automatically determined from the entity's migration path
2246    /// config.update("tasks", updated_tasks)?;
2247    /// ```
2248    pub fn update<T>(&mut self, key: &str, data: Vec<T>) -> Result<(), MigrationError>
2249    where
2250        T: serde::Serialize + crate::Queryable,
2251    {
2252        let entity_name = T::ENTITY_NAME;
2253        let latest_version = self
2254            .migrator
2255            .get_latest_version(entity_name)
2256            .ok_or_else(|| MigrationError::EntityNotFound(entity_name.to_string()))?;
2257
2258        // Serialize each item with version field
2259        let items: Vec<serde_json::Value> = data
2260            .into_iter()
2261            .map(|item| {
2262                let mut obj = serde_json::to_value(&item)
2263                    .map_err(|e| MigrationError::SerializationError(e.to_string()))?;
2264
2265                if let Some(obj_map) = obj.as_object_mut() {
2266                    obj_map.insert(
2267                        "version".to_string(),
2268                        serde_json::Value::String(latest_version.to_string()),
2269                    );
2270                }
2271
2272                Ok(obj)
2273            })
2274            .collect::<Result<Vec<_>, MigrationError>>()?;
2275
2276        self.root[key] = serde_json::Value::Array(items);
2277        Ok(())
2278    }
2279
2280    /// Converts the entire JSON object back to a pretty-printed string.
2281    ///
2282    /// # Errors
2283    ///
2284    /// Returns `MigrationError::SerializationError` if serialization fails.
2285    pub fn to_string(&self) -> Result<String, MigrationError> {
2286        serde_json::to_string_pretty(&self.root)
2287            .map_err(|e| MigrationError::SerializationError(e.to_string()))
2288    }
2289
2290    /// Converts the entire JSON object to a compact string.
2291    ///
2292    /// # Errors
2293    ///
2294    /// Returns `MigrationError::SerializationError` if serialization fails.
2295    pub fn to_string_compact(&self) -> Result<String, MigrationError> {
2296        serde_json::to_string(&self.root)
2297            .map_err(|e| MigrationError::SerializationError(e.to_string()))
2298    }
2299
2300    /// Returns a reference to the underlying JSON value.
2301    pub fn as_value(&self) -> &serde_json::Value {
2302        &self.root
2303    }
2304}
2305
2306#[cfg(test)]
2307mod tests {
2308    use super::*;
2309    use crate::{IntoDomain, MigratesTo, Versioned, VersionedWrapper};
2310    use serde::{Deserialize, Serialize};
2311
2312    // Test data structures
2313    #[derive(Serialize, Deserialize, Debug, PartialEq)]
2314    struct V1 {
2315        value: String,
2316    }
2317
2318    impl Versioned for V1 {
2319        const VERSION: &'static str = "1.0.0";
2320    }
2321
2322    #[derive(Serialize, Deserialize, Debug, PartialEq)]
2323    struct V2 {
2324        value: String,
2325        count: u32,
2326    }
2327
2328    impl Versioned for V2 {
2329        const VERSION: &'static str = "2.0.0";
2330    }
2331
2332    #[derive(Serialize, Deserialize, Debug, PartialEq)]
2333    struct V3 {
2334        value: String,
2335        count: u32,
2336        enabled: bool,
2337    }
2338
2339    impl Versioned for V3 {
2340        const VERSION: &'static str = "3.0.0";
2341    }
2342
2343    #[derive(Serialize, Deserialize, Debug, PartialEq)]
2344    struct Domain {
2345        value: String,
2346        count: u32,
2347        enabled: bool,
2348    }
2349
2350    impl MigratesTo<V2> for V1 {
2351        fn migrate(self) -> V2 {
2352            V2 {
2353                value: self.value,
2354                count: 0,
2355            }
2356        }
2357    }
2358
2359    impl MigratesTo<V3> for V2 {
2360        fn migrate(self) -> V3 {
2361            V3 {
2362                value: self.value,
2363                count: self.count,
2364                enabled: true,
2365            }
2366        }
2367    }
2368
2369    impl IntoDomain<Domain> for V3 {
2370        fn into_domain(self) -> Domain {
2371            Domain {
2372                value: self.value,
2373                count: self.count,
2374                enabled: self.enabled,
2375            }
2376        }
2377    }
2378
2379    #[test]
2380    fn test_migrator_new() {
2381        let migrator = Migrator::new();
2382        assert_eq!(migrator.paths.len(), 0);
2383    }
2384
2385    #[test]
2386    fn test_migrator_default() {
2387        let migrator = Migrator::default();
2388        assert_eq!(migrator.paths.len(), 0);
2389    }
2390
2391    #[test]
2392    fn test_single_step_migration() {
2393        let path = Migrator::define("test")
2394            .from::<V2>()
2395            .step::<V3>()
2396            .into::<Domain>();
2397
2398        let mut migrator = Migrator::new();
2399        migrator.register(path).unwrap();
2400
2401        let v2 = V2 {
2402            value: "test".to_string(),
2403            count: 42,
2404        };
2405        let wrapper = VersionedWrapper::from_versioned(v2);
2406        let json = serde_json::to_string(&wrapper).unwrap();
2407
2408        let result: Domain = migrator.load("test", &json).unwrap();
2409        assert_eq!(result.value, "test");
2410        assert_eq!(result.count, 42);
2411        assert!(result.enabled);
2412    }
2413
2414    #[test]
2415    fn test_multi_step_migration() {
2416        let path = Migrator::define("test")
2417            .from::<V1>()
2418            .step::<V2>()
2419            .step::<V3>()
2420            .into::<Domain>();
2421
2422        let mut migrator = Migrator::new();
2423        migrator.register(path).unwrap();
2424
2425        let v1 = V1 {
2426            value: "multi_step".to_string(),
2427        };
2428        let wrapper = VersionedWrapper::from_versioned(v1);
2429        let json = serde_json::to_string(&wrapper).unwrap();
2430
2431        let result: Domain = migrator.load("test", &json).unwrap();
2432        assert_eq!(result.value, "multi_step");
2433        assert_eq!(result.count, 0);
2434        assert!(result.enabled);
2435    }
2436
2437    #[test]
2438    fn test_no_migration_needed() {
2439        let path = Migrator::define("test").from::<V3>().into::<Domain>();
2440
2441        let mut migrator = Migrator::new();
2442        migrator.register(path).unwrap();
2443
2444        let v3 = V3 {
2445            value: "latest".to_string(),
2446            count: 100,
2447            enabled: false,
2448        };
2449        let wrapper = VersionedWrapper::from_versioned(v3);
2450        let json = serde_json::to_string(&wrapper).unwrap();
2451
2452        let result: Domain = migrator.load("test", &json).unwrap();
2453        assert_eq!(result.value, "latest");
2454        assert_eq!(result.count, 100);
2455        assert!(!result.enabled);
2456    }
2457
2458    #[test]
2459    fn test_entity_not_found() {
2460        let migrator = Migrator::new();
2461
2462        let v1 = V1 {
2463            value: "test".to_string(),
2464        };
2465        let wrapper = VersionedWrapper::from_versioned(v1);
2466        let json = serde_json::to_string(&wrapper).unwrap();
2467
2468        let result: Result<Domain, MigrationError> = migrator.load("unknown", &json);
2469        assert!(matches!(result, Err(MigrationError::EntityNotFound(_))));
2470
2471        if let Err(MigrationError::EntityNotFound(entity)) = result {
2472            assert_eq!(entity, "unknown");
2473        }
2474    }
2475
2476    #[test]
2477    fn test_invalid_json() {
2478        let path = Migrator::define("test").from::<V3>().into::<Domain>();
2479
2480        let mut migrator = Migrator::new();
2481        migrator.register(path).unwrap();
2482
2483        let invalid_json = "{ invalid json }";
2484        let result: Result<Domain, MigrationError> = migrator.load("test", invalid_json);
2485
2486        assert!(matches!(
2487            result,
2488            Err(MigrationError::DeserializationError(_))
2489        ));
2490    }
2491
2492    #[test]
2493    fn test_multiple_entities() {
2494        #[derive(Serialize, Deserialize, Debug, PartialEq)]
2495        struct OtherDomain {
2496            value: String,
2497        }
2498
2499        impl IntoDomain<OtherDomain> for V1 {
2500            fn into_domain(self) -> OtherDomain {
2501                OtherDomain { value: self.value }
2502            }
2503        }
2504
2505        let path1 = Migrator::define("entity1")
2506            .from::<V1>()
2507            .step::<V2>()
2508            .step::<V3>()
2509            .into::<Domain>();
2510
2511        let path2 = Migrator::define("entity2")
2512            .from::<V1>()
2513            .into::<OtherDomain>();
2514
2515        let mut migrator = Migrator::new();
2516        migrator.register(path1).unwrap();
2517        migrator.register(path2).unwrap();
2518
2519        // Test entity1
2520        let v1 = V1 {
2521            value: "entity1".to_string(),
2522        };
2523        let wrapper = VersionedWrapper::from_versioned(v1);
2524        let json = serde_json::to_string(&wrapper).unwrap();
2525        let result: Domain = migrator.load("entity1", &json).unwrap();
2526        assert_eq!(result.value, "entity1");
2527
2528        // Test entity2
2529        let v1 = V1 {
2530            value: "entity2".to_string(),
2531        };
2532        let wrapper = VersionedWrapper::from_versioned(v1);
2533        let json = serde_json::to_string(&wrapper).unwrap();
2534        let result: OtherDomain = migrator.load("entity2", &json).unwrap();
2535        assert_eq!(result.value, "entity2");
2536    }
2537
2538    #[test]
2539    fn test_save() {
2540        let migrator = Migrator::new();
2541
2542        let v1 = V1 {
2543            value: "test_save".to_string(),
2544        };
2545
2546        let json = migrator.save(v1).unwrap();
2547
2548        // Verify JSON contains version and data
2549        assert!(json.contains("\"version\""));
2550        assert!(json.contains("\"1.0.0\""));
2551        assert!(json.contains("\"data\""));
2552        assert!(json.contains("\"test_save\""));
2553
2554        // Verify it can be parsed back
2555        let parsed: VersionedWrapper<serde_json::Value> = serde_json::from_str(&json).unwrap();
2556        assert_eq!(parsed.version, "1.0.0");
2557    }
2558
2559    #[test]
2560    fn test_save_and_load_roundtrip() {
2561        let path = Migrator::define("test")
2562            .from::<V1>()
2563            .step::<V2>()
2564            .step::<V3>()
2565            .into::<Domain>();
2566
2567        let mut migrator = Migrator::new();
2568        migrator.register(path).unwrap();
2569
2570        // Save V1 data
2571        let v1 = V1 {
2572            value: "roundtrip".to_string(),
2573        };
2574        let json = migrator.save(v1).unwrap();
2575
2576        // Load and migrate to Domain
2577        let domain: Domain = migrator.load("test", &json).unwrap();
2578
2579        assert_eq!(domain.value, "roundtrip");
2580        assert_eq!(domain.count, 0); // Default from V1->V2 migration
2581        assert!(domain.enabled); // Default from V2->V3 migration
2582    }
2583
2584    #[test]
2585    fn test_save_latest_version() {
2586        let migrator = Migrator::new();
2587
2588        let v3 = V3 {
2589            value: "latest".to_string(),
2590            count: 42,
2591            enabled: false,
2592        };
2593
2594        let json = migrator.save(v3).unwrap();
2595
2596        // Verify the JSON structure
2597        assert!(json.contains("\"version\":\"3.0.0\""));
2598        assert!(json.contains("\"value\":\"latest\""));
2599        assert!(json.contains("\"count\":42"));
2600        assert!(json.contains("\"enabled\":false"));
2601    }
2602
2603    #[test]
2604    fn test_save_pretty() {
2605        let migrator = Migrator::new();
2606
2607        let v2 = V2 {
2608            value: "pretty".to_string(),
2609            count: 10,
2610        };
2611
2612        let json = migrator.save(v2).unwrap();
2613
2614        // Should be compact JSON (not pretty-printed)
2615        assert!(!json.contains('\n'));
2616        assert!(json.contains("\"version\":\"2.0.0\""));
2617    }
2618
2619    #[test]
2620    fn test_validation_invalid_version_order() {
2621        // Manually construct a path with invalid version ordering
2622        let entity = "test".to_string();
2623        let versions = vec!["2.0.0".to_string(), "1.0.0".to_string()]; // Wrong order
2624
2625        let result = Migrator::validate_migration_path(&entity, &versions);
2626        assert!(matches!(
2627            result,
2628            Err(MigrationError::InvalidVersionOrder { .. })
2629        ));
2630
2631        if let Err(MigrationError::InvalidVersionOrder {
2632            entity: e,
2633            from,
2634            to,
2635        }) = result
2636        {
2637            assert_eq!(e, "test");
2638            assert_eq!(from, "2.0.0");
2639            assert_eq!(to, "1.0.0");
2640        }
2641    }
2642
2643    #[test]
2644    fn test_validation_circular_path() {
2645        // Manually construct a path with circular reference
2646        let entity = "test".to_string();
2647        let versions = vec![
2648            "1.0.0".to_string(),
2649            "2.0.0".to_string(),
2650            "1.0.0".to_string(), // Circular!
2651        ];
2652
2653        let result = Migrator::validate_migration_path(&entity, &versions);
2654        assert!(matches!(
2655            result,
2656            Err(MigrationError::CircularMigrationPath { .. })
2657        ));
2658
2659        if let Err(MigrationError::CircularMigrationPath { entity: e, path }) = result {
2660            assert_eq!(e, "test");
2661            assert!(path.contains("1.0.0"));
2662            assert!(path.contains("2.0.0"));
2663        }
2664    }
2665
2666    #[test]
2667    fn test_validation_valid_path() {
2668        // Valid migration path
2669        let entity = "test".to_string();
2670        let versions = vec![
2671            "1.0.0".to_string(),
2672            "1.1.0".to_string(),
2673            "2.0.0".to_string(),
2674        ];
2675
2676        let result = Migrator::validate_migration_path(&entity, &versions);
2677        assert!(result.is_ok());
2678    }
2679
2680    #[test]
2681    fn test_validation_empty_path() {
2682        // Empty path should be valid
2683        let entity = "test".to_string();
2684        let versions = vec![];
2685
2686        let result = Migrator::validate_migration_path(&entity, &versions);
2687        assert!(result.is_ok());
2688    }
2689
2690    #[test]
2691    fn test_validation_single_version() {
2692        // Single version path should be valid (no steps, just final conversion)
2693        let entity = "test".to_string();
2694        let versions = vec!["1.0.0".to_string()];
2695
2696        let result = Migrator::validate_migration_path(&entity, &versions);
2697        assert!(result.is_ok());
2698    }
2699
2700    // Tests for Vec operations
2701    #[test]
2702    fn test_save_vec_and_load_vec() {
2703        let migrator = Migrator::new();
2704
2705        // Save multiple V1 items
2706        let items = vec![
2707            V1 {
2708                value: "item1".to_string(),
2709            },
2710            V1 {
2711                value: "item2".to_string(),
2712            },
2713            V1 {
2714                value: "item3".to_string(),
2715            },
2716        ];
2717
2718        let json = migrator.save_vec(items).unwrap();
2719
2720        // Verify JSON array format
2721        assert!(json.starts_with('['));
2722        assert!(json.ends_with(']'));
2723        assert!(json.contains("\"version\":\"1.0.0\""));
2724        assert!(json.contains("item1"));
2725        assert!(json.contains("item2"));
2726        assert!(json.contains("item3"));
2727
2728        // Setup migration path
2729        let path = Migrator::define("test")
2730            .from::<V1>()
2731            .step::<V2>()
2732            .step::<V3>()
2733            .into::<Domain>();
2734
2735        let mut migrator = Migrator::new();
2736        migrator.register(path).unwrap();
2737
2738        // Load and migrate the array
2739        let domains: Vec<Domain> = migrator.load_vec("test", &json).unwrap();
2740
2741        assert_eq!(domains.len(), 3);
2742        assert_eq!(domains[0].value, "item1");
2743        assert_eq!(domains[1].value, "item2");
2744        assert_eq!(domains[2].value, "item3");
2745
2746        // All should have default values from migration
2747        for domain in &domains {
2748            assert_eq!(domain.count, 0);
2749            assert!(domain.enabled);
2750        }
2751    }
2752
2753    #[test]
2754    fn test_load_vec_empty_array() {
2755        let path = Migrator::define("test")
2756            .from::<V1>()
2757            .step::<V2>()
2758            .step::<V3>()
2759            .into::<Domain>();
2760
2761        let mut migrator = Migrator::new();
2762        migrator.register(path).unwrap();
2763
2764        let json = "[]";
2765        let domains: Vec<Domain> = migrator.load_vec("test", json).unwrap();
2766
2767        assert_eq!(domains.len(), 0);
2768    }
2769
2770    #[test]
2771    fn test_load_vec_mixed_versions() {
2772        // Setup migration path
2773        let path = Migrator::define("test")
2774            .from::<V1>()
2775            .step::<V2>()
2776            .step::<V3>()
2777            .into::<Domain>();
2778
2779        let mut migrator = Migrator::new();
2780        migrator.register(path).unwrap();
2781
2782        // JSON with mixed versions
2783        let json = r#"[
2784            {"version":"1.0.0","data":{"value":"v1-item"}},
2785            {"version":"2.0.0","data":{"value":"v2-item","count":42}},
2786            {"version":"3.0.0","data":{"value":"v3-item","count":99,"enabled":false}}
2787        ]"#;
2788
2789        let domains: Vec<Domain> = migrator.load_vec("test", json).unwrap();
2790
2791        assert_eq!(domains.len(), 3);
2792
2793        // V1 item migrated to domain
2794        assert_eq!(domains[0].value, "v1-item");
2795        assert_eq!(domains[0].count, 0);
2796        assert!(domains[0].enabled);
2797
2798        // V2 item migrated to domain
2799        assert_eq!(domains[1].value, "v2-item");
2800        assert_eq!(domains[1].count, 42);
2801        assert!(domains[1].enabled);
2802
2803        // V3 item converted to domain
2804        assert_eq!(domains[2].value, "v3-item");
2805        assert_eq!(domains[2].count, 99);
2806        assert!(!domains[2].enabled);
2807    }
2808
2809    #[test]
2810    fn test_load_vec_from_json_values() {
2811        let path = Migrator::define("test")
2812            .from::<V1>()
2813            .step::<V2>()
2814            .step::<V3>()
2815            .into::<Domain>();
2816
2817        let mut migrator = Migrator::new();
2818        migrator.register(path).unwrap();
2819
2820        // Create Vec<serde_json::Value> directly
2821        let values: Vec<serde_json::Value> = vec![
2822            serde_json::json!({"version":"1.0.0","data":{"value":"direct1"}}),
2823            serde_json::json!({"version":"1.0.0","data":{"value":"direct2"}}),
2824        ];
2825
2826        let domains: Vec<Domain> = migrator.load_vec_from("test", values).unwrap();
2827
2828        assert_eq!(domains.len(), 2);
2829        assert_eq!(domains[0].value, "direct1");
2830        assert_eq!(domains[1].value, "direct2");
2831    }
2832
2833    #[test]
2834    fn test_save_vec_empty() {
2835        let migrator = Migrator::new();
2836        let empty: Vec<V1> = vec![];
2837
2838        let json = migrator.save_vec(empty).unwrap();
2839
2840        assert_eq!(json, "[]");
2841    }
2842
2843    #[test]
2844    fn test_load_vec_invalid_json() {
2845        let path = Migrator::define("test")
2846            .from::<V1>()
2847            .step::<V2>()
2848            .step::<V3>()
2849            .into::<Domain>();
2850
2851        let mut migrator = Migrator::new();
2852        migrator.register(path).unwrap();
2853
2854        let invalid_json = "{ not an array }";
2855        let result: Result<Vec<Domain>, MigrationError> = migrator.load_vec("test", invalid_json);
2856
2857        assert!(matches!(
2858            result,
2859            Err(MigrationError::DeserializationError(_))
2860        ));
2861    }
2862
2863    #[test]
2864    fn test_load_vec_entity_not_found() {
2865        let migrator = Migrator::new();
2866
2867        let json = r#"[{"version":"1.0.0","data":{"value":"test"}}]"#;
2868        let result: Result<Vec<Domain>, MigrationError> = migrator.load_vec("unknown", json);
2869
2870        assert!(matches!(result, Err(MigrationError::EntityNotFound(_))));
2871    }
2872
2873    #[test]
2874    fn test_save_vec_latest_version() {
2875        let migrator = Migrator::new();
2876
2877        let items = vec![
2878            V3 {
2879                value: "latest1".to_string(),
2880                count: 10,
2881                enabled: true,
2882            },
2883            V3 {
2884                value: "latest2".to_string(),
2885                count: 20,
2886                enabled: false,
2887            },
2888        ];
2889
2890        let json = migrator.save_vec(items).unwrap();
2891
2892        // Verify structure
2893        assert!(json.contains("\"version\":\"3.0.0\""));
2894        assert!(json.contains("latest1"));
2895        assert!(json.contains("latest2"));
2896        assert!(json.contains("\"count\":10"));
2897        assert!(json.contains("\"count\":20"));
2898    }
2899
2900    #[test]
2901    fn test_load_with_fallback_versioned_data() {
2902        let path = Migrator::define("test")
2903            .from::<V1>()
2904            .step::<V2>()
2905            .step::<V3>()
2906            .into::<Domain>();
2907
2908        let mut migrator = Migrator::new();
2909        migrator.register(path).unwrap();
2910
2911        // Test with properly versioned data
2912        let versioned_json = r#"{"version":"1.0.0","data":{"value":"versioned_test"}}"#;
2913        let result: Domain = migrator.load_with_fallback("test", versioned_json).unwrap();
2914
2915        assert_eq!(result.value, "versioned_test");
2916        assert_eq!(result.count, 0); // Default from V1->V2 migration
2917        assert!(result.enabled); // Default from V2->V3 migration
2918    }
2919
2920    #[test]
2921    fn test_load_with_fallback_legacy_data() {
2922        let path = Migrator::define("test")
2923            .from::<V1>()
2924            .step::<V2>()
2925            .step::<V3>()
2926            .into::<Domain>();
2927
2928        let mut migrator = Migrator::new();
2929        migrator.register(path).unwrap();
2930
2931        // Test with legacy data (no version field) - should treat as V1
2932        let legacy_json = r#"{"value":"legacy_test"}"#;
2933        let result: Domain = migrator.load_with_fallback("test", legacy_json).unwrap();
2934
2935        assert_eq!(result.value, "legacy_test");
2936        assert_eq!(result.count, 0); // Default from V1->V2 migration
2937        assert!(result.enabled); // Default from V2->V3 migration
2938    }
2939
2940    #[test]
2941    fn test_load_with_fallback_mixed_formats() {
2942        let path = Migrator::define("test")
2943            .from::<V2>()
2944            .step::<V3>()
2945            .into::<Domain>();
2946
2947        let mut migrator = Migrator::new();
2948        migrator.register(path).unwrap();
2949
2950        // Test with legacy data that matches V2 format
2951        let legacy_v2_json = r#"{"value":"legacy_v2","count":42}"#;
2952        let result: Domain = migrator.load_with_fallback("test", legacy_v2_json).unwrap();
2953
2954        assert_eq!(result.value, "legacy_v2");
2955        assert_eq!(result.count, 42);
2956        assert!(result.enabled); // Default from V2->V3 migration
2957
2958        // Test with versioned data
2959        let versioned_json = r#"{"version":"2.0.0","data":{"value":"versioned_v2","count":99}}"#;
2960        let result2: Domain = migrator.load_with_fallback("test", versioned_json).unwrap();
2961
2962        assert_eq!(result2.value, "versioned_v2");
2963        assert_eq!(result2.count, 99);
2964        assert!(result2.enabled);
2965    }
2966
2967    #[test]
2968    fn test_load_from_with_fallback_toml_value() {
2969        let path = Migrator::define("test")
2970            .from::<V1>()
2971            .step::<V2>()
2972            .step::<V3>()
2973            .into::<Domain>();
2974
2975        let mut migrator = Migrator::new();
2976        migrator.register(path).unwrap();
2977
2978        // Simulate loading from TOML or other format
2979        let data = serde_json::json!({"value": "from_toml"});
2980        let result: Domain = migrator.load_from_with_fallback("test", data).unwrap();
2981
2982        assert_eq!(result.value, "from_toml");
2983        assert_eq!(result.count, 0);
2984        assert!(result.enabled);
2985    }
2986
2987    #[test]
2988    fn test_load_with_fallback_no_migration_path() {
2989        let migrator = Migrator::new();
2990
2991        let legacy_json = r#"{"value":"test"}"#;
2992        let result: Result<Domain, MigrationError> =
2993            migrator.load_with_fallback("unknown", legacy_json);
2994
2995        assert!(matches!(result, Err(MigrationError::EntityNotFound(_))));
2996    }
2997
2998    #[test]
2999    fn test_load_with_fallback_empty_migration_path() {
3000        // Register entity with no migration steps (direct conversion)
3001        let path = Migrator::define("test").from::<V3>().into::<Domain>();
3002
3003        let mut migrator = Migrator::new();
3004        migrator.register(path).unwrap();
3005
3006        // Legacy data should still work with direct conversion
3007        let legacy_json = r#"{"value":"direct","count":10,"enabled":false}"#;
3008        let result: Domain = migrator.load_with_fallback("test", legacy_json).unwrap();
3009
3010        assert_eq!(result.value, "direct");
3011        assert_eq!(result.count, 10);
3012        assert!(!result.enabled);
3013    }
3014
3015    #[test]
3016    fn test_load_with_fallback_invalid_json() {
3017        let path = Migrator::define("test")
3018            .from::<V1>()
3019            .step::<V2>()
3020            .step::<V3>()
3021            .into::<Domain>();
3022
3023        let mut migrator = Migrator::new();
3024        migrator.register(path).unwrap();
3025
3026        let invalid_json = "{ invalid json }";
3027        let result: Result<Domain, MigrationError> =
3028            migrator.load_with_fallback("test", invalid_json);
3029
3030        assert!(matches!(
3031            result,
3032            Err(MigrationError::DeserializationError(_))
3033        ));
3034    }
3035
3036    #[test]
3037    fn test_load_with_fallback_non_object_data() {
3038        let path = Migrator::define("test")
3039            .from::<V1>()
3040            .step::<V2>()
3041            .step::<V3>()
3042            .into::<Domain>();
3043
3044        let mut migrator = Migrator::new();
3045        migrator.register(path).unwrap();
3046
3047        // Array instead of object
3048        let array_json = r#"["not", "an", "object"]"#;
3049        let result: Result<Domain, MigrationError> =
3050            migrator.load_with_fallback("test", array_json);
3051
3052        assert!(matches!(
3053            result,
3054            Err(MigrationError::DeserializationError(_))
3055        ));
3056    }
3057
3058    #[test]
3059    fn test_invalid_semver_version() {
3060        // Test with invalid semver format in a migration chain
3061        // Note: Validation only runs when there are 2+ versions to compare
3062        #[derive(Serialize, Deserialize, Debug)]
3063        struct BadV1 {
3064            value: String,
3065        }
3066
3067        impl Versioned for BadV1 {
3068            const VERSION: &'static str = "not-a-semver";
3069        }
3070
3071        impl MigratesTo<V2> for BadV1 {
3072            fn migrate(self) -> V2 {
3073                V2 {
3074                    value: self.value,
3075                    count: 0,
3076                }
3077            }
3078        }
3079
3080        // Two versions triggers version ordering check
3081        let path = Migrator::define("bad")
3082            .from::<BadV1>()
3083            .step::<V2>()
3084            .step::<V3>()
3085            .into::<Domain>();
3086
3087        let mut migrator = Migrator::new();
3088        let result = migrator.register(path);
3089
3090        // Should fail due to invalid semver in version ordering check
3091        assert!(matches!(
3092            result,
3093            Err(MigrationError::DeserializationError(_))
3094        ));
3095    }
3096
3097    #[test]
3098    fn test_load_from_with_toml_value() {
3099        let path = Migrator::define("test")
3100            .from::<V1>()
3101            .step::<V2>()
3102            .step::<V3>()
3103            .into::<Domain>();
3104
3105        let mut migrator = Migrator::new();
3106        migrator.register(path).unwrap();
3107
3108        // Create a toml::Value directly
3109        let toml_str = r#"
3110            version = "1.0.0"
3111            [data]
3112            value = "from_toml"
3113        "#;
3114        let toml_value: toml::Value = toml::from_str(toml_str).unwrap();
3115
3116        let result: Domain = migrator.load_from("test", toml_value).unwrap();
3117        assert_eq!(result.value, "from_toml");
3118        assert_eq!(result.count, 0);
3119        assert!(result.enabled);
3120    }
3121
3122    #[test]
3123    fn test_load_from_missing_version_field() {
3124        let path = Migrator::define("test").from::<V3>().into::<Domain>();
3125
3126        let mut migrator = Migrator::new();
3127        migrator.register(path).unwrap();
3128
3129        // JSON object without version field
3130        let json_value: serde_json::Value = serde_json::json!({
3131            "data": {"value": "test", "count": 1, "enabled": true}
3132        });
3133
3134        let result: Result<Domain, MigrationError> = migrator.load_from("test", json_value);
3135        assert!(matches!(
3136            result,
3137            Err(MigrationError::DeserializationError(_))
3138        ));
3139    }
3140
3141    #[test]
3142    fn test_load_from_missing_data_field() {
3143        let path = Migrator::define("test").from::<V3>().into::<Domain>();
3144
3145        let mut migrator = Migrator::new();
3146        migrator.register(path).unwrap();
3147
3148        // JSON object without data field
3149        let json_value: serde_json::Value = serde_json::json!({
3150            "version": "3.0.0"
3151        });
3152
3153        let result: Result<Domain, MigrationError> = migrator.load_from("test", json_value);
3154        assert!(matches!(
3155            result,
3156            Err(MigrationError::DeserializationError(_))
3157        ));
3158    }
3159
3160    #[test]
3161    fn test_load_from_non_object() {
3162        let path = Migrator::define("test").from::<V3>().into::<Domain>();
3163
3164        let mut migrator = Migrator::new();
3165        migrator.register(path).unwrap();
3166
3167        // Not an object
3168        let json_value: serde_json::Value = serde_json::json!("just a string");
3169
3170        let result: Result<Domain, MigrationError> = migrator.load_from("test", json_value);
3171        assert!(matches!(
3172            result,
3173            Err(MigrationError::DeserializationError(_))
3174        ));
3175    }
3176
3177    #[test]
3178    fn test_load_from_invalid_domain_conversion() {
3179        #[derive(Serialize, Deserialize, Debug)]
3180        struct StrictDomain {
3181            required_field: String,
3182        }
3183
3184        impl IntoDomain<StrictDomain> for V3 {
3185            fn into_domain(self) -> StrictDomain {
3186                StrictDomain {
3187                    required_field: self.value,
3188                }
3189            }
3190        }
3191
3192        let path = Migrator::define("strict")
3193            .from::<V3>()
3194            .into::<StrictDomain>();
3195
3196        let mut migrator = Migrator::new();
3197        migrator.register(path).unwrap();
3198
3199        // V3 data that will fail to convert to StrictDomain due to field name mismatch
3200        // The finalize function creates StrictDomain but serde expects different field
3201        let json = r#"{"version":"3.0.0","data":{"value":"test","count":1,"enabled":true}}"#;
3202
3203        // This should work because IntoDomain converts correctly
3204        let result: Result<StrictDomain, MigrationError> = migrator.load("strict", json);
3205        assert!(result.is_ok());
3206        assert_eq!(result.unwrap().required_field, "test");
3207    }
3208
3209    #[test]
3210    fn test_save_flat_and_load_flat() {
3211        let path = Migrator::define("test").from::<V3>().into::<Domain>();
3212
3213        let mut migrator = Migrator::new();
3214        migrator.register(path).unwrap();
3215
3216        let v3 = V3 {
3217            value: "flat_test".to_string(),
3218            count: 99,
3219            enabled: true,
3220        };
3221
3222        let json = migrator.save_flat(v3).unwrap();
3223
3224        // Verify flat format (version at same level as data)
3225        assert!(json.contains("\"version\":\"3.0.0\""));
3226        assert!(json.contains("\"value\":\"flat_test\""));
3227        assert!(!json.contains("\"data\":"));
3228
3229        // Load back
3230        let result: Domain = migrator.load_flat("test", &json).unwrap();
3231        assert_eq!(result.value, "flat_test");
3232        assert_eq!(result.count, 99);
3233        assert!(result.enabled);
3234    }
3235
3236    #[test]
3237    fn test_load_from_with_fallback_version_not_string() {
3238        let path = Migrator::define("test")
3239            .from::<V1>()
3240            .step::<V2>()
3241            .step::<V3>()
3242            .into::<Domain>();
3243
3244        let mut migrator = Migrator::new();
3245        migrator.register(path).unwrap();
3246
3247        // Version field is a number, not a string - should fallback to V1
3248        let json_value: serde_json::Value = serde_json::json!({
3249            "version": 100,
3250            "value": "fallback_test"
3251        });
3252
3253        let result: Domain = migrator
3254            .load_from_with_fallback("test", json_value)
3255            .unwrap();
3256        assert_eq!(result.value, "fallback_test");
3257        assert_eq!(result.count, 0); // Default from V1->V2 migration
3258        assert!(result.enabled); // Default from V2->V3 migration
3259    }
3260
3261    #[test]
3262    fn test_load_from_with_fallback_missing_data_field() {
3263        let path = Migrator::define("test")
3264            .from::<V1>()
3265            .step::<V2>()
3266            .step::<V3>()
3267            .into::<Domain>();
3268
3269        let mut migrator = Migrator::new();
3270        migrator.register(path).unwrap();
3271
3272        // Has version string but missing data field
3273        let json_value: serde_json::Value = serde_json::json!({
3274            "version": "1.0.0"
3275        });
3276
3277        let result: Result<Domain, MigrationError> =
3278            migrator.load_from_with_fallback("test", json_value);
3279        assert!(result.is_err());
3280        let err = result.unwrap_err();
3281        assert!(
3282            matches!(err, MigrationError::DeserializationError(ref msg) if msg.contains("data")),
3283            "Expected DeserializationError about missing data field, got: {:?}",
3284            err
3285        );
3286    }
3287
3288    #[test]
3289    fn test_register_second_version_invalid_semver() {
3290        // BadV2 has invalid semver format
3291        #[derive(Serialize, Deserialize, Debug)]
3292        struct BadV2 {
3293            value: String,
3294            count: u32,
3295        }
3296
3297        impl Versioned for BadV2 {
3298            const VERSION: &'static str = "not-a-version"; // Invalid semver
3299        }
3300
3301        impl MigratesTo<BadV2> for V1 {
3302            fn migrate(self) -> BadV2 {
3303                BadV2 {
3304                    value: self.value,
3305                    count: 0,
3306                }
3307            }
3308        }
3309
3310        impl IntoDomain<Domain> for BadV2 {
3311            fn into_domain(self) -> Domain {
3312                Domain {
3313                    value: self.value,
3314                    count: self.count,
3315                    enabled: false,
3316                }
3317            }
3318        }
3319
3320        let path = Migrator::define("test")
3321            .from::<V1>()
3322            .step::<BadV2>()
3323            .into::<Domain>();
3324
3325        let mut migrator = Migrator::new();
3326        let result = migrator.register(path);
3327
3328        assert!(result.is_err());
3329        let err = result.unwrap_err();
3330        assert!(
3331            matches!(err, MigrationError::DeserializationError(ref msg) if msg.contains("not-a-version")),
3332            "Expected error about invalid semver 'not-a-version', got: {:?}",
3333            err
3334        );
3335    }
3336
3337    #[test]
3338    fn test_load_from_with_fallback_non_object() {
3339        let path = Migrator::define("test").from::<V3>().into::<Domain>();
3340
3341        let mut migrator = Migrator::new();
3342        migrator.register(path).unwrap();
3343
3344        // Array instead of object
3345        let json_value: serde_json::Value = serde_json::json!([1, 2, 3]);
3346
3347        let result: Result<Domain, MigrationError> =
3348            migrator.load_from_with_fallback("test", json_value);
3349        assert!(result.is_err());
3350        let err = result.unwrap_err();
3351        assert!(
3352            matches!(err, MigrationError::DeserializationError(ref msg) if msg.contains("object")),
3353            "Expected DeserializationError about object format, got: {:?}",
3354            err
3355        );
3356    }
3357}