Change summary
crates/migrator/src/migrations.rs | 36 +++++++++---
crates/migrator/src/migrator.rs | 92 +++++++++++++++++++++++++++++++++
2 files changed, 119 insertions(+), 9 deletions(-)
Detailed changes
@@ -29,9 +29,16 @@ pub(crate) fn migrate_settings(
if let Some(profiles) = root_object.get_mut("profiles") {
if let Some(profiles_object) = profiles.as_object_mut() {
- for (_profile_name, profile_settings) in profiles_object.iter_mut() {
- if let Some(profile_map) = profile_settings.as_object_mut() {
- migrate_one(profile_map)?;
+ for profile_value in profiles_object.values_mut() {
+ if let Some(profile_map) = profile_value.as_object_mut() {
+ if let Some(inner) = profile_map
+ .get_mut("settings")
+ .and_then(|v| v.as_object_mut())
+ {
+ migrate_one(inner)?;
+ } else {
+ migrate_one(profile_map)?;
+ }
}
}
}
@@ -93,12 +100,23 @@ pub(crate) fn migrate_language_setting(
if let Some(profiles_object) = profiles.as_object_mut() {
let profile_names: Vec<String> = profiles_object.keys().cloned().collect();
for profile_name in &profile_names {
- if let Some(profile_settings) = profiles_object.get_mut(profile_name.as_str()) {
- apply_to_value_and_languages(
- profile_settings,
- &["profiles", profile_name],
- migrate_fn,
- )?;
+ if let Some(profile_value) = profiles_object.get_mut(profile_name.as_str()) {
+ if let Some(settings_value) = profile_value
+ .as_object_mut()
+ .and_then(|m| m.get_mut("settings"))
+ {
+ apply_to_value_and_languages(
+ settings_value,
+ &["profiles", profile_name],
+ migrate_fn,
+ )?;
+ } else {
+ apply_to_value_and_languages(
+ profile_value,
+ &["profiles", profile_name],
+ migrate_fn,
+ )?;
+ }
}
}
}
@@ -4930,6 +4930,98 @@ mod tests {
);
}
+ #[test]
+ fn test_migration_helpers_handle_various_profile_forms() {
+ let setting = "a_setting";
+ let old_value = "old_value";
+ let new_value = "new_value";
+
+ fn language_setting_fn(value: &mut serde_json::Value, _: &[&str]) -> anyhow::Result<()> {
+ if let Some(obj) = value.as_object_mut() {
+ if let Some(v) = obj.get_mut("a_setting") {
+ *v = serde_json::json!("new_value");
+ }
+ }
+ Ok(())
+ }
+
+ let mut settings_fn = |map: &mut serde_json::Map<String, serde_json::Value>| {
+ if let Some(v) = map.get_mut(setting) {
+ *v = serde_json::json!(new_value);
+ }
+ Ok(())
+ };
+
+ // Legacy form
+ let input = serde_json::json!({
+ "profiles": {
+ "work": {
+ setting: old_value
+ }
+ }
+ });
+ let expected = serde_json::json!({
+ "profiles": {
+ "work": {
+ setting: new_value
+ }
+ }
+ });
+
+ let mut value = input.clone();
+ migrations::migrate_settings(&mut value, &mut settings_fn).unwrap();
+ assert_eq!(value, expected);
+
+ let mut value = input;
+ migrations::migrate_language_setting(&mut value, language_setting_fn).unwrap();
+ assert_eq!(value, expected);
+
+ // Form after migration: `m_2026_04_01`
+ let input = serde_json::json!({
+ "profiles": {
+ "work": {
+ "settings": {
+ setting: old_value
+ }
+ }
+ }
+ });
+ let expected = serde_json::json!({
+ "profiles": {
+ "work": {
+ "settings": {
+ setting: new_value
+ }
+ }
+ }
+ });
+
+ let mut value = input.clone();
+ migrations::migrate_settings(&mut value, &mut settings_fn).unwrap();
+ assert_eq!(value, expected);
+
+ let mut value = input;
+ migrations::migrate_language_setting(&mut value, language_setting_fn).unwrap();
+ assert_eq!(value, expected);
+
+ // Base-only form after migration: `m_2026_04_01` (no settings to migrate)
+ let input = serde_json::json!({
+ "profiles": {
+ "work": {
+ "base": "default"
+ }
+ }
+ });
+
+ let mut value = input.clone();
+ migrations::migrate_settings(&mut value, &mut settings_fn).unwrap();
+ assert_eq!(value, input);
+
+ let mut value = input.clone();
+ migrations::migrate_language_setting(&mut value, language_setting_fn).unwrap();
+ assert_eq!(value, input);
+ }
+
#[test]
fn test_rename_web_search_to_search_web_root_level_profile() {
assert_migrate_with_migrations(