# How OpenLogi Manages TOML Config Schema Versioning for Safe Migration

> Discover how OpenLogi ensures safe TOML config schema versioning and migration with a numeric constant and targeted functions, preventing data loss.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: deep-dive
- Published: 2026-08-30

---

**OpenLogi stores user preferences in a single TOML file and uses a numeric SCHEMA_VERSION constant combined with targeted migration functions to safely evolve the configuration format without data loss.**

OpenLogi, an open-source input device manager written in Rust, persists all user settings in a central [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) file. To ensure this file remains compatible across software updates, the project implements a robust **TOML config schema versioning** system that automatically migrates legacy formats while preventing data corruption from concurrent edits or obsolete fields.

## Version Tracking with SCHEMA_VERSION

In [`crates/openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs), OpenLogi defines a constant that represents the current schema revision:

```rust
const SCHEMA_VERSION: u32 = 7;

```

This constant (currently set to `7` at line 94) is incremented whenever the persisted data shape or enum vocabulary changes. When the application loads a configuration file, it compares the file's recorded version against this constant to determine if migration is required.

## Loading Pipeline and Version Detection

The entry point for configuration loading is `Config::load_from_path`. Before parsing the full configuration structure, the loader extracts a minimal `ConfigHeader` that contains the `schema_version` field stored in the TOML file.

If the header's version is older than `SCHEMA_VERSION`, the system invokes a series of targeted migration functions inside `parse_config` (lines 47-84 of [`crates/openlogi-core/src/config/file.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config/file.rs)). Each migration runs sequentially, transforming the in-memory representation step-by-step until it matches the latest schema.

## Targeted Migration Functions

OpenLogi uses discrete migration functions that trigger based on specific version thresholds. These functions live in the loading pipeline and rewrite only the affected portions of the configuration:

- **migrate_owner_locked_gestures**: Triggered when `schema_version` ≤ 3. Rewrites the obsolete "gesture-owner" model into the modern per-button gesture map.
- **migrate_transport_scoped_keys**: Triggered when `schema_version` ≤ 4. Renames transport-scoped `direct:` keys to stable identity fragments while preserving route links.
- **migrate_thumbwheel_native_direction**: Triggered when `schema_version` ≤ 6. Adjusts pre-v7 thumb-wheel defaults to the new normalized direction.
- **repair_duplicate_routes**: Runs for all versions. Normalizes duplicate route entries after the preceding migration passes.

After all applicable migrations complete, the in-memory `Config` struct has its `schema_version` field updated to match `SCHEMA_VERSION`, ensuring the next save operation writes the file in the latest format.

## Obsolete Field Protection

To prevent newer code from silently accepting outdated configuration keys, OpenLogi implements `reject_obsolete_fields` (lines 86-110 of [`crates/openlogi-core/src/config/file.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config/file.rs)). This function scans the raw TOML document for keys that were removed after specific schema versions, such as `button_bindings`, `gesture_bindings`, and `gesture_owner`. If any obsolete keys appear in a file claiming a newer schema version, the loader aborts with a descriptive error rather than ignoring the invalid data.

## Conflict-Aware Saving and Atomic Backup

Writing configuration changes safely requires preventing simultaneous modification conflicts. The `ConfigFile::save` method (implemented in [`crates/openlogi-core/src/config/file.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config/file.rs)) implements this protection through several mechanisms:

**Conflict Detection (lines 53-69):** Before writing, the implementation compares the current on-disk contents with the snapshot captured when the file was loaded (`self.source`). If they differ, a conflict error is returned, forcing the caller to reload and preventing accidental overwrites of concurrent edits.

**Atomic Writes with Backup (lines 71-90):** When a migration occurs, `migration_backup_path` creates a single backup of the original version before the new content is written atomically. This guarantees users can recover the pre-migration file if the process is interrupted by a crash or power loss.

## Practical Implementation Examples

The following Rust code demonstrates loading a configuration file with automatic migration:

```rust
use openlogi_core::config::{Config, ConfigFile};

// Load the config, automatically migrating older versions.
let cfg = Config::load_from_path(&path)?;   
// Returns a Config with schema_version == SCHEMA_VERSION (7)

```

To save changes while ensuring no external modifications occurred:

```rust
// Load the file wrapper which tracks the source state
let mut file = ConfigFile::load_from_path(&path)?;
// ... modify cfg ...
file.save(&cfg)?; // Returns error if file changed on disk

```

For testing specific migrations manually:

```rust
let (mut cfg, mut file) = ConfigFile::load_from_path(&path)?;
// Manually trigger specific migration for v6 -> v7
cfg.migrate_thumbwheel_native_direction();
file.save(&cfg)?;

```

## Summary

OpenLogi's approach to **TOML config schema versioning** provides a reliable foundation for long-term data integrity:

- **Explicit version constants** (`SCHEMA_VERSION`) make schema changes discoverable and auditable.
- **Header-based detection** enables early validation before full parsing.
- **Targeted migration functions** isolate transformation logic per schema revision.
- **Obsolete field rejection** prevents silent acceptance of invalid legacy data.
- **Conflict-aware saving** detects concurrent modifications to prevent data loss.
- **Atomic backup strategy** preserves original files during migration for disaster recovery.

## Frequently Asked Questions

### What happens if OpenLogi encounters a config file newer than the current SCHEMA_VERSION?

If the `schema_version` in the file header exceeds `SCHEMA_VERSION`, the loader treats this as an error condition. Since the application cannot safely parse formats it does not understand, it aborts the loading process and returns a descriptive error indicating that the configuration was created by a newer version of OpenLogi.

### How does OpenLogi prevent data loss when multiple processes edit the config simultaneously?

The `ConfigFile::save` method implements optimistic concurrency control by comparing the current disk contents against the snapshot stored in `self.source` when the file was first loaded. If any differences are detected, the save operation returns a conflict error rather than overwriting the changes, forcing the caller to reload the updated configuration before attempting to save again.

### Can users recover their original configuration if a migration corrupts the data?

Yes. When a migration is applied, OpenLogi creates a backup using `migration_backup_path` before writing the new format atomically. This backup preserves the pre-migration state exactly as it existed on disk, allowing users to manually restore the original file if the migrated version encounters issues or contains unexpected transformations.

### Where are the migration functions located in the OpenLogi source code?

The migration orchestration and loading logic reside in [`crates/openlogi-core/src/config/file.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config/file.rs), specifically within the `parse_config` function (lines 47-84). The individual migration methods such as `migrate_owner_locked_gestures` and `migrate_thumbwheel_native_direction` are defined on the `Config` struct in [`crates/openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs), while the version constant `SCHEMA_VERSION` is declared at line 94 of the same file.