1use crate::errors::MigrationError;
4use crate::{IntoDomain, MigratesTo, Versioned};
5use serde::de::DeserializeOwned;
6use serde::Serialize;
7use std::collections::HashMap;
8use std::marker::PhantomData;
9
10type MigrationFn = Box<dyn Fn(serde_json::Value) -> Result<serde_json::Value, MigrationError>>;
11
12struct EntityMigrationPath {
14 steps: HashMap<String, MigrationFn>,
16 finalize: Box<dyn Fn(serde_json::Value) -> Result<serde_json::Value, MigrationError>>,
18 versions: Vec<String>,
20 version_key: String,
22 data_key: String,
24}
25
26pub struct Migrator {
28 paths: HashMap<String, EntityMigrationPath>,
29 default_version_key: Option<String>,
30 default_data_key: Option<String>,
31}
32
33impl Migrator {
34 pub fn new() -> Self {
36 Self {
37 paths: HashMap::new(),
38 default_version_key: None,
39 default_data_key: None,
40 }
41 }
42
43 pub fn builder() -> MigratorBuilder {
54 MigratorBuilder::new()
55 }
56
57 pub fn define(entity: &str) -> MigrationPathBuilder<Start> {
59 MigrationPathBuilder::new(entity.to_string())
60 }
61
62 pub fn register<D>(&mut self, path: MigrationPath<D>) -> Result<(), MigrationError> {
72 Self::validate_migration_path(&path.entity, &path.versions)?;
73
74 let version_key = path
76 .custom_version_key
77 .or_else(|| self.default_version_key.clone())
78 .unwrap_or_else(|| path.inner.version_key.clone());
79
80 let data_key = path
81 .custom_data_key
82 .or_else(|| self.default_data_key.clone())
83 .unwrap_or_else(|| path.inner.data_key.clone());
84
85 let final_path = EntityMigrationPath {
86 steps: path.inner.steps,
87 finalize: path.inner.finalize,
88 versions: path.versions,
89 version_key,
90 data_key,
91 };
92
93 self.paths.insert(path.entity, final_path);
94 Ok(())
95 }
96
97 fn validate_migration_path(entity: &str, versions: &[String]) -> Result<(), MigrationError> {
99 Self::check_circular_path(entity, versions)?;
101
102 Self::check_version_ordering(entity, versions)?;
104
105 Ok(())
106 }
107
108 fn check_circular_path(entity: &str, versions: &[String]) -> Result<(), MigrationError> {
110 let mut seen = std::collections::HashSet::new();
111
112 for version in versions {
113 if !seen.insert(version) {
114 let path = versions.join(" -> ");
116 return Err(MigrationError::CircularMigrationPath {
117 entity: entity.to_string(),
118 path,
119 });
120 }
121 }
122
123 Ok(())
124 }
125
126 fn check_version_ordering(entity: &str, versions: &[String]) -> Result<(), MigrationError> {
128 for i in 0..versions.len().saturating_sub(1) {
129 let current = &versions[i];
130 let next = &versions[i + 1];
131
132 let current_ver = semver::Version::parse(current).map_err(|e| {
134 MigrationError::DeserializationError(format!("Invalid semver '{}': {}", current, e))
135 })?;
136
137 let next_ver = semver::Version::parse(next).map_err(|e| {
138 MigrationError::DeserializationError(format!("Invalid semver '{}': {}", next, e))
139 })?;
140
141 if next_ver <= current_ver {
143 return Err(MigrationError::InvalidVersionOrder {
144 entity: entity.to_string(),
145 from: current.clone(),
146 to: next.clone(),
147 });
148 }
149 }
150
151 Ok(())
152 }
153
154 pub fn load_from<D, T>(&self, entity: &str, data: T) -> Result<D, MigrationError>
187 where
188 D: DeserializeOwned,
189 T: Serialize,
190 {
191 let value = serde_json::to_value(data).map_err(|e| {
193 MigrationError::DeserializationError(format!(
194 "Failed to convert input data to internal format: {}",
195 e
196 ))
197 })?;
198
199 let path = self
201 .paths
202 .get(entity)
203 .ok_or_else(|| MigrationError::EntityNotFound(entity.to_string()))?;
204
205 let version_key = &path.version_key;
206 let data_key = &path.data_key;
207
208 let obj = value.as_object().ok_or_else(|| {
210 MigrationError::DeserializationError(
211 "Expected object with version and data fields".to_string(),
212 )
213 })?;
214
215 let current_version = obj
216 .get(version_key)
217 .and_then(|v| v.as_str())
218 .ok_or_else(|| {
219 MigrationError::DeserializationError(format!(
220 "Missing or invalid '{}' field",
221 version_key
222 ))
223 })?
224 .to_string();
225
226 let mut current_data = obj
227 .get(data_key)
228 .ok_or_else(|| {
229 MigrationError::DeserializationError(format!("Missing '{}' field", data_key))
230 })?
231 .clone();
232
233 let mut current_version = current_version;
234
235 while let Some(migrate_fn) = path.steps.get(¤t_version) {
237 current_data = migrate_fn(current_data.clone())?;
239
240 match path.versions.iter().position(|v| v == ¤t_version) {
243 Some(idx) if idx + 1 < path.versions.len() => {
244 current_version = path.versions[idx + 1].clone();
245 }
246 _ => break,
247 }
248 }
249
250 let domain_value = (path.finalize)(current_data)?;
252
253 serde_json::from_value(domain_value).map_err(|e| {
254 MigrationError::DeserializationError(format!("Failed to convert to domain: {}", e))
255 })
256 }
257
258 pub fn load<D: DeserializeOwned>(&self, entity: &str, json: &str) -> Result<D, MigrationError> {
286 let data: serde_json::Value = serde_json::from_str(json).map_err(|e| {
287 MigrationError::DeserializationError(format!("Failed to parse JSON: {}", e))
288 })?;
289 self.load_from(entity, data)
290 }
291
292 pub fn load_flat<D: DeserializeOwned>(
320 &self,
321 entity: &str,
322 json: &str,
323 ) -> Result<D, MigrationError> {
324 let data: serde_json::Value = serde_json::from_str(json).map_err(|e| {
325 MigrationError::DeserializationError(format!("Failed to parse JSON: {}", e))
326 })?;
327 self.load_flat_from(entity, data)
328 }
329
330 pub fn load_flat_from<D, T>(&self, entity: &str, value: T) -> Result<D, MigrationError>
359 where
360 D: DeserializeOwned,
361 T: Serialize,
362 {
363 let path = self
364 .paths
365 .get(entity)
366 .ok_or_else(|| MigrationError::EntityNotFound(entity.to_string()))?;
367
368 let version_key = &path.version_key;
369
370 let mut value = serde_json::to_value(value).map_err(|e| {
372 MigrationError::SerializationError(format!("Failed to convert input: {}", e))
373 })?;
374
375 let obj = value.as_object_mut().ok_or_else(|| {
377 MigrationError::DeserializationError(
378 "Expected object with version field at top level".to_string(),
379 )
380 })?;
381
382 let current_version = obj
383 .remove(version_key)
384 .ok_or_else(|| {
385 MigrationError::DeserializationError(format!(
386 "Missing '{}' field in flat format",
387 version_key
388 ))
389 })?
390 .as_str()
391 .ok_or_else(|| {
392 MigrationError::DeserializationError(format!(
393 "Invalid '{}' field type",
394 version_key
395 ))
396 })?
397 .to_string();
398
399 let mut current_data = serde_json::Value::Object(obj.clone());
401 let mut current_version = current_version;
402
403 while let Some(migrate_fn) = path.steps.get(¤t_version) {
405 current_data = migrate_fn(current_data.clone())?;
407
408 match path.versions.iter().position(|v| v == ¤t_version) {
410 Some(idx) if idx + 1 < path.versions.len() => {
411 current_version = path.versions[idx + 1].clone();
412 }
413 _ => break,
414 }
415 }
416
417 let domain_value = (path.finalize)(current_data)?;
419
420 serde_json::from_value(domain_value).map_err(|e| {
421 MigrationError::DeserializationError(format!("Failed to convert to domain: {}", e))
422 })
423 }
424
425 pub fn save<T: Versioned + Serialize>(&self, data: T) -> Result<String, MigrationError> {
456 let version_key = T::VERSION_KEY;
458 let data_key = T::DATA_KEY;
459
460 let data_value = serde_json::to_value(&data).map_err(|e| {
462 MigrationError::SerializationError(format!("Failed to serialize data: {}", e))
463 })?;
464
465 let mut map = serde_json::Map::new();
467 map.insert(
468 version_key.to_string(),
469 serde_json::Value::String(T::VERSION.to_string()),
470 );
471 map.insert(data_key.to_string(), data_value);
472
473 serde_json::to_string(&map).map_err(|e| {
474 MigrationError::SerializationError(format!("Failed to serialize wrapper: {}", e))
475 })
476 }
477
478 pub fn save_flat<T: Versioned + Serialize>(&self, data: T) -> Result<String, MigrationError> {
508 let version_key = T::VERSION_KEY;
509
510 let mut data_value = serde_json::to_value(&data).map_err(|e| {
512 MigrationError::SerializationError(format!("Failed to serialize data: {}", e))
513 })?;
514
515 let obj = data_value.as_object_mut().ok_or_else(|| {
517 MigrationError::SerializationError(
518 "Data must serialize to a JSON object for flat format".to_string(),
519 )
520 })?;
521
522 obj.insert(
524 version_key.to_string(),
525 serde_json::Value::String(T::VERSION.to_string()),
526 );
527
528 serde_json::to_string(&obj).map_err(|e| {
529 MigrationError::SerializationError(format!("Failed to serialize flat format: {}", e))
530 })
531 }
532
533 pub fn load_vec_from<D, T>(&self, entity: &str, data: Vec<T>) -> Result<Vec<D>, MigrationError>
566 where
567 D: DeserializeOwned,
568 T: Serialize,
569 {
570 data.into_iter()
571 .map(|item| self.load_from(entity, item))
572 .collect()
573 }
574
575 pub fn load_vec<D: DeserializeOwned>(
606 &self,
607 entity: &str,
608 json: &str,
609 ) -> Result<Vec<D>, MigrationError> {
610 let data: Vec<serde_json::Value> = serde_json::from_str(json).map_err(|e| {
611 MigrationError::DeserializationError(format!("Failed to parse JSON array: {}", e))
612 })?;
613 self.load_vec_from(entity, data)
614 }
615
616 pub fn load_vec_flat<D: DeserializeOwned>(
647 &self,
648 entity: &str,
649 json: &str,
650 ) -> Result<Vec<D>, MigrationError> {
651 let data: Vec<serde_json::Value> = serde_json::from_str(json).map_err(|e| {
652 MigrationError::DeserializationError(format!("Failed to parse JSON array: {}", e))
653 })?;
654 self.load_vec_flat_from(entity, data)
655 }
656
657 pub fn load_vec_flat_from<D, T>(
686 &self,
687 entity: &str,
688 data: Vec<T>,
689 ) -> Result<Vec<D>, MigrationError>
690 where
691 D: DeserializeOwned,
692 T: Serialize,
693 {
694 data.into_iter()
695 .map(|item| self.load_flat_from(entity, item))
696 .collect()
697 }
698
699 pub fn save_vec<T: Versioned + Serialize>(
736 &self,
737 data: Vec<T>,
738 ) -> Result<String, MigrationError> {
739 let version_key = T::VERSION_KEY;
740 let data_key = T::DATA_KEY;
741
742 let wrappers: Result<Vec<serde_json::Value>, MigrationError> = data
743 .into_iter()
744 .map(|item| {
745 let data_value = serde_json::to_value(&item).map_err(|e| {
746 MigrationError::SerializationError(format!("Failed to serialize item: {}", e))
747 })?;
748
749 let mut map = serde_json::Map::new();
750 map.insert(
751 version_key.to_string(),
752 serde_json::Value::String(T::VERSION.to_string()),
753 );
754 map.insert(data_key.to_string(), data_value);
755
756 Ok(serde_json::Value::Object(map))
757 })
758 .collect();
759
760 serde_json::to_string(&wrappers?).map_err(|e| {
761 MigrationError::SerializationError(format!("Failed to serialize data array: {}", e))
762 })
763 }
764
765 pub fn save_vec_flat<T: Versioned + Serialize>(
801 &self,
802 data: Vec<T>,
803 ) -> Result<String, MigrationError> {
804 let version_key = T::VERSION_KEY;
805
806 let flat_items: Result<Vec<serde_json::Value>, MigrationError> = data
807 .into_iter()
808 .map(|item| {
809 let mut data_value = serde_json::to_value(&item).map_err(|e| {
810 MigrationError::SerializationError(format!("Failed to serialize item: {}", e))
811 })?;
812
813 let obj = data_value.as_object_mut().ok_or_else(|| {
814 MigrationError::SerializationError(
815 "Data must serialize to a JSON object for flat format".to_string(),
816 )
817 })?;
818
819 obj.insert(
821 version_key.to_string(),
822 serde_json::Value::String(T::VERSION.to_string()),
823 );
824
825 Ok(serde_json::Value::Object(obj.clone()))
826 })
827 .collect();
828
829 serde_json::to_string(&flat_items?).map_err(|e| {
830 MigrationError::SerializationError(format!(
831 "Failed to serialize flat data array: {}",
832 e
833 ))
834 })
835 }
836}
837
838impl Default for Migrator {
839 fn default() -> Self {
840 Self::new()
841 }
842}
843
844pub struct MigratorBuilder {
846 default_version_key: Option<String>,
847 default_data_key: Option<String>,
848}
849
850impl MigratorBuilder {
851 pub(crate) fn new() -> Self {
852 Self {
853 default_version_key: None,
854 default_data_key: None,
855 }
856 }
857
858 pub fn default_version_key(mut self, key: impl Into<String>) -> Self {
864 self.default_version_key = Some(key.into());
865 self
866 }
867
868 pub fn default_data_key(mut self, key: impl Into<String>) -> Self {
874 self.default_data_key = Some(key.into());
875 self
876 }
877
878 pub fn build(self) -> Migrator {
880 Migrator {
881 paths: HashMap::new(),
882 default_version_key: self.default_version_key,
883 default_data_key: self.default_data_key,
884 }
885 }
886}
887
888pub struct Start;
890
891pub struct HasFrom<V>(PhantomData<V>);
893
894pub struct HasSteps<V>(PhantomData<V>);
896
897pub struct MigrationPathBuilder<State> {
899 entity: String,
900 steps: HashMap<String, MigrationFn>,
901 versions: Vec<String>,
902 version_key: String,
903 data_key: String,
904 custom_version_key: Option<String>,
905 custom_data_key: Option<String>,
906 _state: PhantomData<State>,
907}
908
909impl MigrationPathBuilder<Start> {
910 fn new(entity: String) -> Self {
911 Self {
912 entity,
913 steps: HashMap::new(),
914 versions: Vec::new(),
915 version_key: String::from("version"),
916 data_key: String::from("data"),
917 custom_version_key: None,
918 custom_data_key: None,
919 _state: PhantomData,
920 }
921 }
922
923 pub fn with_keys(
936 mut self,
937 version_key: impl Into<String>,
938 data_key: impl Into<String>,
939 ) -> Self {
940 self.custom_version_key = Some(version_key.into());
941 self.custom_data_key = Some(data_key.into());
942 self
943 }
944
945 pub fn from<V: Versioned + DeserializeOwned>(self) -> MigrationPathBuilder<HasFrom<V>> {
947 let mut versions = self.versions;
948 versions.push(V::VERSION.to_string());
949
950 MigrationPathBuilder {
951 entity: self.entity,
952 steps: self.steps,
953 versions,
954 version_key: V::VERSION_KEY.to_string(),
955 data_key: V::DATA_KEY.to_string(),
956 custom_version_key: self.custom_version_key,
957 custom_data_key: self.custom_data_key,
958 _state: PhantomData,
959 }
960 }
961}
962
963impl<V> MigrationPathBuilder<HasFrom<V>>
964where
965 V: Versioned + DeserializeOwned,
966{
967 pub fn step<Next>(mut self) -> MigrationPathBuilder<HasSteps<Next>>
969 where
970 V: MigratesTo<Next>,
971 Next: Versioned + DeserializeOwned + Serialize,
972 {
973 let from_version = V::VERSION.to_string();
974 let migration_fn: MigrationFn = Box::new(move |value| {
975 let from_value: V = serde_json::from_value(value).map_err(|e| {
976 MigrationError::DeserializationError(format!(
977 "Failed to deserialize version {}: {}",
978 V::VERSION,
979 e
980 ))
981 })?;
982
983 let to_value = from_value.migrate();
984
985 serde_json::to_value(&to_value).map_err(|e| MigrationError::MigrationStepFailed {
987 from: V::VERSION.to_string(),
988 to: Next::VERSION.to_string(),
989 error: e.to_string(),
990 })
991 });
992
993 self.steps.insert(from_version, migration_fn);
994 self.versions.push(Next::VERSION.to_string());
995
996 MigrationPathBuilder {
997 entity: self.entity,
998 steps: self.steps,
999 versions: self.versions,
1000 version_key: self.version_key,
1001 data_key: self.data_key,
1002 custom_version_key: self.custom_version_key,
1003 custom_data_key: self.custom_data_key,
1004 _state: PhantomData,
1005 }
1006 }
1007
1008 pub fn into<D: DeserializeOwned + Serialize>(self) -> MigrationPath<D>
1010 where
1011 V: IntoDomain<D>,
1012 {
1013 let finalize: Box<dyn Fn(serde_json::Value) -> Result<serde_json::Value, MigrationError>> =
1014 Box::new(move |value| {
1015 let versioned: V = serde_json::from_value(value).map_err(|e| {
1016 MigrationError::DeserializationError(format!(
1017 "Failed to deserialize final version: {}",
1018 e
1019 ))
1020 })?;
1021
1022 let domain = versioned.into_domain();
1023
1024 serde_json::to_value(domain).map_err(|e| MigrationError::MigrationStepFailed {
1025 from: V::VERSION.to_string(),
1026 to: "domain".to_string(),
1027 error: e.to_string(),
1028 })
1029 });
1030
1031 MigrationPath {
1032 entity: self.entity,
1033 inner: EntityMigrationPath {
1034 steps: self.steps,
1035 finalize,
1036 versions: self.versions.clone(),
1037 version_key: self.version_key,
1038 data_key: self.data_key,
1039 },
1040 versions: self.versions,
1041 custom_version_key: self.custom_version_key,
1042 custom_data_key: self.custom_data_key,
1043 _phantom: PhantomData,
1044 }
1045 }
1046}
1047
1048impl<V> MigrationPathBuilder<HasSteps<V>>
1049where
1050 V: Versioned + DeserializeOwned,
1051{
1052 pub fn step<Next>(mut self) -> MigrationPathBuilder<HasSteps<Next>>
1054 where
1055 V: MigratesTo<Next>,
1056 Next: Versioned + DeserializeOwned + Serialize,
1057 {
1058 let from_version = V::VERSION.to_string();
1059 let migration_fn: MigrationFn = Box::new(move |value| {
1060 let from_value: V = serde_json::from_value(value).map_err(|e| {
1061 MigrationError::DeserializationError(format!(
1062 "Failed to deserialize version {}: {}",
1063 V::VERSION,
1064 e
1065 ))
1066 })?;
1067
1068 let to_value = from_value.migrate();
1069
1070 serde_json::to_value(&to_value).map_err(|e| MigrationError::MigrationStepFailed {
1072 from: V::VERSION.to_string(),
1073 to: Next::VERSION.to_string(),
1074 error: e.to_string(),
1075 })
1076 });
1077
1078 self.steps.insert(from_version, migration_fn);
1079 self.versions.push(Next::VERSION.to_string());
1080
1081 MigrationPathBuilder {
1082 entity: self.entity,
1083 steps: self.steps,
1084 versions: self.versions,
1085 version_key: self.version_key,
1086 data_key: self.data_key,
1087 custom_version_key: self.custom_version_key,
1088 custom_data_key: self.custom_data_key,
1089 _state: PhantomData,
1090 }
1091 }
1092
1093 pub fn into<D: DeserializeOwned + Serialize>(self) -> MigrationPath<D>
1095 where
1096 V: IntoDomain<D>,
1097 {
1098 let finalize: Box<dyn Fn(serde_json::Value) -> Result<serde_json::Value, MigrationError>> =
1099 Box::new(move |value| {
1100 let versioned: V = serde_json::from_value(value).map_err(|e| {
1101 MigrationError::DeserializationError(format!(
1102 "Failed to deserialize final version: {}",
1103 e
1104 ))
1105 })?;
1106
1107 let domain = versioned.into_domain();
1108
1109 serde_json::to_value(domain).map_err(|e| MigrationError::MigrationStepFailed {
1110 from: V::VERSION.to_string(),
1111 to: "domain".to_string(),
1112 error: e.to_string(),
1113 })
1114 });
1115
1116 MigrationPath {
1117 entity: self.entity,
1118 inner: EntityMigrationPath {
1119 steps: self.steps,
1120 finalize,
1121 versions: self.versions.clone(),
1122 version_key: self.version_key,
1123 data_key: self.data_key,
1124 },
1125 versions: self.versions,
1126 custom_version_key: self.custom_version_key,
1127 custom_data_key: self.custom_data_key,
1128 _phantom: PhantomData,
1129 }
1130 }
1131}
1132
1133pub struct MigrationPath<D> {
1135 entity: String,
1136 inner: EntityMigrationPath,
1137 versions: Vec<String>,
1139 custom_version_key: Option<String>,
1141 custom_data_key: Option<String>,
1143 _phantom: PhantomData<D>,
1144}
1145
1146#[cfg(test)]
1147mod tests {
1148 use super::*;
1149 use crate::{IntoDomain, MigratesTo, Versioned, VersionedWrapper};
1150 use serde::{Deserialize, Serialize};
1151
1152 #[derive(Serialize, Deserialize, Debug, PartialEq)]
1154 struct V1 {
1155 value: String,
1156 }
1157
1158 impl Versioned for V1 {
1159 const VERSION: &'static str = "1.0.0";
1160 }
1161
1162 #[derive(Serialize, Deserialize, Debug, PartialEq)]
1163 struct V2 {
1164 value: String,
1165 count: u32,
1166 }
1167
1168 impl Versioned for V2 {
1169 const VERSION: &'static str = "2.0.0";
1170 }
1171
1172 #[derive(Serialize, Deserialize, Debug, PartialEq)]
1173 struct V3 {
1174 value: String,
1175 count: u32,
1176 enabled: bool,
1177 }
1178
1179 impl Versioned for V3 {
1180 const VERSION: &'static str = "3.0.0";
1181 }
1182
1183 #[derive(Serialize, Deserialize, Debug, PartialEq)]
1184 struct Domain {
1185 value: String,
1186 count: u32,
1187 enabled: bool,
1188 }
1189
1190 impl MigratesTo<V2> for V1 {
1191 fn migrate(self) -> V2 {
1192 V2 {
1193 value: self.value,
1194 count: 0,
1195 }
1196 }
1197 }
1198
1199 impl MigratesTo<V3> for V2 {
1200 fn migrate(self) -> V3 {
1201 V3 {
1202 value: self.value,
1203 count: self.count,
1204 enabled: true,
1205 }
1206 }
1207 }
1208
1209 impl IntoDomain<Domain> for V3 {
1210 fn into_domain(self) -> Domain {
1211 Domain {
1212 value: self.value,
1213 count: self.count,
1214 enabled: self.enabled,
1215 }
1216 }
1217 }
1218
1219 #[test]
1220 fn test_migrator_new() {
1221 let migrator = Migrator::new();
1222 assert_eq!(migrator.paths.len(), 0);
1223 }
1224
1225 #[test]
1226 fn test_migrator_default() {
1227 let migrator = Migrator::default();
1228 assert_eq!(migrator.paths.len(), 0);
1229 }
1230
1231 #[test]
1232 fn test_single_step_migration() {
1233 let path = Migrator::define("test")
1234 .from::<V2>()
1235 .step::<V3>()
1236 .into::<Domain>();
1237
1238 let mut migrator = Migrator::new();
1239 migrator.register(path).unwrap();
1240
1241 let v2 = V2 {
1242 value: "test".to_string(),
1243 count: 42,
1244 };
1245 let wrapper = VersionedWrapper::from_versioned(v2);
1246 let json = serde_json::to_string(&wrapper).unwrap();
1247
1248 let result: Domain = migrator.load("test", &json).unwrap();
1249 assert_eq!(result.value, "test");
1250 assert_eq!(result.count, 42);
1251 assert!(result.enabled);
1252 }
1253
1254 #[test]
1255 fn test_multi_step_migration() {
1256 let path = Migrator::define("test")
1257 .from::<V1>()
1258 .step::<V2>()
1259 .step::<V3>()
1260 .into::<Domain>();
1261
1262 let mut migrator = Migrator::new();
1263 migrator.register(path).unwrap();
1264
1265 let v1 = V1 {
1266 value: "multi_step".to_string(),
1267 };
1268 let wrapper = VersionedWrapper::from_versioned(v1);
1269 let json = serde_json::to_string(&wrapper).unwrap();
1270
1271 let result: Domain = migrator.load("test", &json).unwrap();
1272 assert_eq!(result.value, "multi_step");
1273 assert_eq!(result.count, 0);
1274 assert!(result.enabled);
1275 }
1276
1277 #[test]
1278 fn test_no_migration_needed() {
1279 let path = Migrator::define("test").from::<V3>().into::<Domain>();
1280
1281 let mut migrator = Migrator::new();
1282 migrator.register(path).unwrap();
1283
1284 let v3 = V3 {
1285 value: "latest".to_string(),
1286 count: 100,
1287 enabled: false,
1288 };
1289 let wrapper = VersionedWrapper::from_versioned(v3);
1290 let json = serde_json::to_string(&wrapper).unwrap();
1291
1292 let result: Domain = migrator.load("test", &json).unwrap();
1293 assert_eq!(result.value, "latest");
1294 assert_eq!(result.count, 100);
1295 assert!(!result.enabled);
1296 }
1297
1298 #[test]
1299 fn test_entity_not_found() {
1300 let migrator = Migrator::new();
1301
1302 let v1 = V1 {
1303 value: "test".to_string(),
1304 };
1305 let wrapper = VersionedWrapper::from_versioned(v1);
1306 let json = serde_json::to_string(&wrapper).unwrap();
1307
1308 let result: Result<Domain, MigrationError> = migrator.load("unknown", &json);
1309 assert!(matches!(result, Err(MigrationError::EntityNotFound(_))));
1310
1311 if let Err(MigrationError::EntityNotFound(entity)) = result {
1312 assert_eq!(entity, "unknown");
1313 }
1314 }
1315
1316 #[test]
1317 fn test_invalid_json() {
1318 let path = Migrator::define("test").from::<V3>().into::<Domain>();
1319
1320 let mut migrator = Migrator::new();
1321 migrator.register(path).unwrap();
1322
1323 let invalid_json = "{ invalid json }";
1324 let result: Result<Domain, MigrationError> = migrator.load("test", invalid_json);
1325
1326 assert!(matches!(
1327 result,
1328 Err(MigrationError::DeserializationError(_))
1329 ));
1330 }
1331
1332 #[test]
1333 fn test_multiple_entities() {
1334 #[derive(Serialize, Deserialize, Debug, PartialEq)]
1335 struct OtherDomain {
1336 value: String,
1337 }
1338
1339 impl IntoDomain<OtherDomain> for V1 {
1340 fn into_domain(self) -> OtherDomain {
1341 OtherDomain { value: self.value }
1342 }
1343 }
1344
1345 let path1 = Migrator::define("entity1")
1346 .from::<V1>()
1347 .step::<V2>()
1348 .step::<V3>()
1349 .into::<Domain>();
1350
1351 let path2 = Migrator::define("entity2")
1352 .from::<V1>()
1353 .into::<OtherDomain>();
1354
1355 let mut migrator = Migrator::new();
1356 migrator.register(path1).unwrap();
1357 migrator.register(path2).unwrap();
1358
1359 let v1 = V1 {
1361 value: "entity1".to_string(),
1362 };
1363 let wrapper = VersionedWrapper::from_versioned(v1);
1364 let json = serde_json::to_string(&wrapper).unwrap();
1365 let result: Domain = migrator.load("entity1", &json).unwrap();
1366 assert_eq!(result.value, "entity1");
1367
1368 let v1 = V1 {
1370 value: "entity2".to_string(),
1371 };
1372 let wrapper = VersionedWrapper::from_versioned(v1);
1373 let json = serde_json::to_string(&wrapper).unwrap();
1374 let result: OtherDomain = migrator.load("entity2", &json).unwrap();
1375 assert_eq!(result.value, "entity2");
1376 }
1377
1378 #[test]
1379 fn test_save() {
1380 let migrator = Migrator::new();
1381
1382 let v1 = V1 {
1383 value: "test_save".to_string(),
1384 };
1385
1386 let json = migrator.save(v1).unwrap();
1387
1388 assert!(json.contains("\"version\""));
1390 assert!(json.contains("\"1.0.0\""));
1391 assert!(json.contains("\"data\""));
1392 assert!(json.contains("\"test_save\""));
1393
1394 let parsed: VersionedWrapper<serde_json::Value> = serde_json::from_str(&json).unwrap();
1396 assert_eq!(parsed.version, "1.0.0");
1397 }
1398
1399 #[test]
1400 fn test_save_and_load_roundtrip() {
1401 let path = Migrator::define("test")
1402 .from::<V1>()
1403 .step::<V2>()
1404 .step::<V3>()
1405 .into::<Domain>();
1406
1407 let mut migrator = Migrator::new();
1408 migrator.register(path).unwrap();
1409
1410 let v1 = V1 {
1412 value: "roundtrip".to_string(),
1413 };
1414 let json = migrator.save(v1).unwrap();
1415
1416 let domain: Domain = migrator.load("test", &json).unwrap();
1418
1419 assert_eq!(domain.value, "roundtrip");
1420 assert_eq!(domain.count, 0); assert!(domain.enabled); }
1423
1424 #[test]
1425 fn test_save_latest_version() {
1426 let migrator = Migrator::new();
1427
1428 let v3 = V3 {
1429 value: "latest".to_string(),
1430 count: 42,
1431 enabled: false,
1432 };
1433
1434 let json = migrator.save(v3).unwrap();
1435
1436 assert!(json.contains("\"version\":\"3.0.0\""));
1438 assert!(json.contains("\"value\":\"latest\""));
1439 assert!(json.contains("\"count\":42"));
1440 assert!(json.contains("\"enabled\":false"));
1441 }
1442
1443 #[test]
1444 fn test_save_pretty() {
1445 let migrator = Migrator::new();
1446
1447 let v2 = V2 {
1448 value: "pretty".to_string(),
1449 count: 10,
1450 };
1451
1452 let json = migrator.save(v2).unwrap();
1453
1454 assert!(!json.contains('\n'));
1456 assert!(json.contains("\"version\":\"2.0.0\""));
1457 }
1458
1459 #[test]
1460 fn test_validation_invalid_version_order() {
1461 let entity = "test".to_string();
1463 let versions = vec!["2.0.0".to_string(), "1.0.0".to_string()]; let result = Migrator::validate_migration_path(&entity, &versions);
1466 assert!(matches!(
1467 result,
1468 Err(MigrationError::InvalidVersionOrder { .. })
1469 ));
1470
1471 if let Err(MigrationError::InvalidVersionOrder {
1472 entity: e,
1473 from,
1474 to,
1475 }) = result
1476 {
1477 assert_eq!(e, "test");
1478 assert_eq!(from, "2.0.0");
1479 assert_eq!(to, "1.0.0");
1480 }
1481 }
1482
1483 #[test]
1484 fn test_validation_circular_path() {
1485 let entity = "test".to_string();
1487 let versions = vec![
1488 "1.0.0".to_string(),
1489 "2.0.0".to_string(),
1490 "1.0.0".to_string(), ];
1492
1493 let result = Migrator::validate_migration_path(&entity, &versions);
1494 assert!(matches!(
1495 result,
1496 Err(MigrationError::CircularMigrationPath { .. })
1497 ));
1498
1499 if let Err(MigrationError::CircularMigrationPath { entity: e, path }) = result {
1500 assert_eq!(e, "test");
1501 assert!(path.contains("1.0.0"));
1502 assert!(path.contains("2.0.0"));
1503 }
1504 }
1505
1506 #[test]
1507 fn test_validation_valid_path() {
1508 let entity = "test".to_string();
1510 let versions = vec![
1511 "1.0.0".to_string(),
1512 "1.1.0".to_string(),
1513 "2.0.0".to_string(),
1514 ];
1515
1516 let result = Migrator::validate_migration_path(&entity, &versions);
1517 assert!(result.is_ok());
1518 }
1519
1520 #[test]
1521 fn test_validation_empty_path() {
1522 let entity = "test".to_string();
1524 let versions = vec![];
1525
1526 let result = Migrator::validate_migration_path(&entity, &versions);
1527 assert!(result.is_ok());
1528 }
1529
1530 #[test]
1531 fn test_validation_single_version() {
1532 let entity = "test".to_string();
1534 let versions = vec!["1.0.0".to_string()];
1535
1536 let result = Migrator::validate_migration_path(&entity, &versions);
1537 assert!(result.is_ok());
1538 }
1539
1540 #[test]
1542 fn test_save_vec_and_load_vec() {
1543 let migrator = Migrator::new();
1544
1545 let items = vec![
1547 V1 {
1548 value: "item1".to_string(),
1549 },
1550 V1 {
1551 value: "item2".to_string(),
1552 },
1553 V1 {
1554 value: "item3".to_string(),
1555 },
1556 ];
1557
1558 let json = migrator.save_vec(items).unwrap();
1559
1560 assert!(json.starts_with('['));
1562 assert!(json.ends_with(']'));
1563 assert!(json.contains("\"version\":\"1.0.0\""));
1564 assert!(json.contains("item1"));
1565 assert!(json.contains("item2"));
1566 assert!(json.contains("item3"));
1567
1568 let path = Migrator::define("test")
1570 .from::<V1>()
1571 .step::<V2>()
1572 .step::<V3>()
1573 .into::<Domain>();
1574
1575 let mut migrator = Migrator::new();
1576 migrator.register(path).unwrap();
1577
1578 let domains: Vec<Domain> = migrator.load_vec("test", &json).unwrap();
1580
1581 assert_eq!(domains.len(), 3);
1582 assert_eq!(domains[0].value, "item1");
1583 assert_eq!(domains[1].value, "item2");
1584 assert_eq!(domains[2].value, "item3");
1585
1586 for domain in &domains {
1588 assert_eq!(domain.count, 0);
1589 assert!(domain.enabled);
1590 }
1591 }
1592
1593 #[test]
1594 fn test_load_vec_empty_array() {
1595 let path = Migrator::define("test")
1596 .from::<V1>()
1597 .step::<V2>()
1598 .step::<V3>()
1599 .into::<Domain>();
1600
1601 let mut migrator = Migrator::new();
1602 migrator.register(path).unwrap();
1603
1604 let json = "[]";
1605 let domains: Vec<Domain> = migrator.load_vec("test", json).unwrap();
1606
1607 assert_eq!(domains.len(), 0);
1608 }
1609
1610 #[test]
1611 fn test_load_vec_mixed_versions() {
1612 let path = Migrator::define("test")
1614 .from::<V1>()
1615 .step::<V2>()
1616 .step::<V3>()
1617 .into::<Domain>();
1618
1619 let mut migrator = Migrator::new();
1620 migrator.register(path).unwrap();
1621
1622 let json = r#"[
1624 {"version":"1.0.0","data":{"value":"v1-item"}},
1625 {"version":"2.0.0","data":{"value":"v2-item","count":42}},
1626 {"version":"3.0.0","data":{"value":"v3-item","count":99,"enabled":false}}
1627 ]"#;
1628
1629 let domains: Vec<Domain> = migrator.load_vec("test", json).unwrap();
1630
1631 assert_eq!(domains.len(), 3);
1632
1633 assert_eq!(domains[0].value, "v1-item");
1635 assert_eq!(domains[0].count, 0);
1636 assert!(domains[0].enabled);
1637
1638 assert_eq!(domains[1].value, "v2-item");
1640 assert_eq!(domains[1].count, 42);
1641 assert!(domains[1].enabled);
1642
1643 assert_eq!(domains[2].value, "v3-item");
1645 assert_eq!(domains[2].count, 99);
1646 assert!(!domains[2].enabled);
1647 }
1648
1649 #[test]
1650 fn test_load_vec_from_json_values() {
1651 let path = Migrator::define("test")
1652 .from::<V1>()
1653 .step::<V2>()
1654 .step::<V3>()
1655 .into::<Domain>();
1656
1657 let mut migrator = Migrator::new();
1658 migrator.register(path).unwrap();
1659
1660 let values: Vec<serde_json::Value> = vec![
1662 serde_json::json!({"version":"1.0.0","data":{"value":"direct1"}}),
1663 serde_json::json!({"version":"1.0.0","data":{"value":"direct2"}}),
1664 ];
1665
1666 let domains: Vec<Domain> = migrator.load_vec_from("test", values).unwrap();
1667
1668 assert_eq!(domains.len(), 2);
1669 assert_eq!(domains[0].value, "direct1");
1670 assert_eq!(domains[1].value, "direct2");
1671 }
1672
1673 #[test]
1674 fn test_save_vec_empty() {
1675 let migrator = Migrator::new();
1676 let empty: Vec<V1> = vec![];
1677
1678 let json = migrator.save_vec(empty).unwrap();
1679
1680 assert_eq!(json, "[]");
1681 }
1682
1683 #[test]
1684 fn test_load_vec_invalid_json() {
1685 let path = Migrator::define("test")
1686 .from::<V1>()
1687 .step::<V2>()
1688 .step::<V3>()
1689 .into::<Domain>();
1690
1691 let mut migrator = Migrator::new();
1692 migrator.register(path).unwrap();
1693
1694 let invalid_json = "{ not an array }";
1695 let result: Result<Vec<Domain>, MigrationError> = migrator.load_vec("test", invalid_json);
1696
1697 assert!(matches!(
1698 result,
1699 Err(MigrationError::DeserializationError(_))
1700 ));
1701 }
1702
1703 #[test]
1704 fn test_load_vec_entity_not_found() {
1705 let migrator = Migrator::new();
1706
1707 let json = r#"[{"version":"1.0.0","data":{"value":"test"}}]"#;
1708 let result: Result<Vec<Domain>, MigrationError> = migrator.load_vec("unknown", json);
1709
1710 assert!(matches!(result, Err(MigrationError::EntityNotFound(_))));
1711 }
1712
1713 #[test]
1714 fn test_save_vec_latest_version() {
1715 let migrator = Migrator::new();
1716
1717 let items = vec![
1718 V3 {
1719 value: "latest1".to_string(),
1720 count: 10,
1721 enabled: true,
1722 },
1723 V3 {
1724 value: "latest2".to_string(),
1725 count: 20,
1726 enabled: false,
1727 },
1728 ];
1729
1730 let json = migrator.save_vec(items).unwrap();
1731
1732 assert!(json.contains("\"version\":\"3.0.0\""));
1734 assert!(json.contains("latest1"));
1735 assert!(json.contains("latest2"));
1736 assert!(json.contains("\"count\":10"));
1737 assert!(json.contains("\"count\":20"));
1738 }
1739}