# How the Button Action Catalog in OpenLogi Serializes and Deserializes TOML Data into Rust Enums

> Learn how OpenLogi's button action catalog serializes and deserializes TOML data into Rust enums using serde's derive macros and external tagging for seamless configuration management.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: internals
- Published: 2026-09-05

---

**OpenLogi relies on serde's derive macros and external tagging to translate the `Action` enum between human-readable TOML configuration files and strongly-typed Rust variants, with the mapping defined in [`crates/openlogi-core/src/binding/action.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/action.rs) and orchestrated through the `Config` struct in [`crates/openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs).**

The AprilNEA/OpenLogi repository implements a robust button remapping system that uses serde to serialize and deserialize TOML data into Rust enums for its button action catalog, enabling human-readable configuration files with compile-time type safety. This architecture ensures that user-defined gestures stored in [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) can be parsed into memory-safe enums while maintaining backward compatibility across application updates.

## The Core Data Structure: The `Action` Enum

### Location and Serde Integration

The canonical definition resides in **[`crates/openlogi-core/src/binding/action.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/action.rs)**. The enum declaration includes standard serde derives that enable automatic conversion:

```rust
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Action {
    LeftClick,
    RightClick,
    Copy,
    Paste,
    CustomShortcut(KeyCombo),
    // ... additional variants
}

```

This derive macro implementation eliminates manual parsing logic, allowing the `toml` crate to directly instantiate Rust variants from configuration strings.

### External Tagging Schema

OpenLogi utilizes serde's **default external tagging** scheme. According to the source comments at lines 11-14 of [`crates/openlogi-core/src/binding/action.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/action.rs), this design choice means:

- The variant name becomes the key in TOML tables
- Unit variants serialize as bare strings
- Complex variants wrap their payload in a single-key table structure

## TOML Representation Patterns

### Unit Variants as Bare Strings

Simple actions without payloads—such as `Copy` or `LeftClick`—serialize as string values in the TOML mapping found in [`docs/config.example.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/docs/config.example.toml):

```toml
[bindings]
LeftClick = "Copy"
RightClick = "Paste"

```

In [`crates/openlogi-core/src/binding/action.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/action.rs), these correspond to unit variants of the `Action` enum that carry no associated data.

### Payload Variants as Single-Key Tables

Variants containing data—like `CustomShortcut`—serialize as inline tables where the variant name acts as the key:

```toml
MiddleClick = { CustomShortcut = "⌘⌥←" }

```

The corresponding Rust definition references `KeyCombo`, which is defined in **[`crates/openlogi-core/src/binding/key_combo.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/key_combo.rs)**, allowing complex keyboard shortcuts to nest cleanly within the enum structure.

## Configuration Loading Pipeline

### Top-Level Config Structure

The orchestration occurs in **[`crates/openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs)**, which defines a `Config` struct containing the button-to-action mapping:

```rust
use std::collections::HashMap;
use crate::binding::{Action, ButtonId};

struct Config {
    bindings: HashMap<ButtonId, Action>,
    // ... other configuration fields
}

```

The `ButtonId` type—defined in **[`crates/openlogi-core/src/binding/button.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/button.rs)**—serves as the key for looking up actions in the TOML table structure.

### Deserialization Entry Point

When OpenLogi initializes, it reads [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) and invokes the deserialization pipeline:

```rust
let cfg: Config = toml::from_str(&text)?;

```

The `toml` crate uses the `Deserialize` implementation generated for `Action` to transform the TOML string representation into the appropriate Rust enum variant, handling both unit types and complex payloads automatically.

### Serialization for Persistence

To persist user modifications back to disk, OpenLogi calls:

```rust
let toml_string = toml::to_string_pretty(&cfg)?;

```

This utilizes the `Serialize` derive on `Action` to emit human-readable TOML that mirrors the structure documented in [`docs/config.example.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/docs/config.example.toml), ensuring that saved configurations remain editable by users.

## Schema Stability and Backward Compatibility

The source code at lines 15-18 of [`crates/openlogi-core/src/binding/action.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/action.rs) establishes a strict **stability contract**: variant names constitute the on-disk schema and must remain immutable. New functionality can only be added by:

- **Appending** new variants to the enum (preserving numeric indices for bincode compatibility)
- Never renaming existing variants (ensuring old configs remain parseable)

This design protects both the TOML-based configuration format and any bincode-based IPC that relies on the enum's numeric representation, preventing breaking changes to user configurations across application updates.

## Practical Implementation Examples

### Parsing TOML into Rust Types

The following example demonstrates loading button mappings from a configuration string using the types defined in the OpenLogi core:

```rust
use openlogi_core::binding::{Action, ButtonId};
use std::collections::HashMap;
use serde::Deserialize;

#[derive(Deserialize)]
struct Bindings {
    #[serde(flatten)]
    map: HashMap<ButtonId, Action>,
}

let toml = r#"
LeftClick = "Copy"
RightClick = { CustomShortcut = "⌘⌥←" }
"#;

let bindings: Bindings = toml::from_str(toml).unwrap();
assert!(matches!(bindings.map[&ButtonId::LeftClick], Action::Copy));

```

### Serializing Actions Back to TOML

To generate configuration output programmatically:

```rust
use openlogi_core::binding::{Action, ButtonId, KeyCombo};
use std::collections::HashMap;

let mut map = HashMap::new();
map.insert(ButtonId::LeftClick, Action::Copy);
map.insert(
    ButtonId::MiddleClick,
    Action::CustomShortcut(KeyCombo::from_str("⌘⌥←").unwrap()),
);

let output = toml::to_string_pretty(&map).unwrap();
// Produces:
// LeftClick = "Copy"
// MiddleClick = { CustomShortcut = "⌘⌥←" }

```

## Summary

- **Serde derives** in [`crates/openlogi-core/src/binding/action.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/action.rs) automatically implement `Serialize` and `Deserialize` for the `Action` enum, enabling seamless TOML conversion.
- **External tagging** creates human-readable TOML where unit variants become strings and payload variants become single-key tables.
- The **`Config`** struct in [`crates/openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs) serves as the deserialization target, utilizing `HashMap<ButtonId, Action>` to map physical buttons to actions.
- **Schema stability** is enforced by freezing variant names and appending only, ensuring backward compatibility with existing [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) files and stable bincode IPC.
- **Round-trip safety** is guaranteed through `toml::from_str` and `toml::to_string_pretty`, maintaining data integrity across program restarts and configuration edits.

## Frequently Asked Questions

### What file defines the Action enum in OpenLogi?

The `Action` enum is defined in **[`crates/openlogi-core/src/binding/action.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/action.rs)**, where it implements both `Serialize` and `Deserialize` via derive macros to enable the TOML serialization and deserialization pipeline for the button action catalog.

### How does OpenLogi handle TOML parsing for configuration files?

OpenLogi uses the standard `toml` crate's `from_str` function to deserialize the top-level `Config` struct defined in [`crates/openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs). Because `Action` implements `Deserialize`, the parser automatically handles both simple string variants and complex table structures without custom parsing logic.

### Why must Action enum variants remain stable across versions?

According to comments at lines 15-18 of the source file, the variant names form the on-disk schema for [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml), and the numeric indices are used for bincode-based IPC. Changing names or reordering variants would break existing user configurations and inter-process communication protocols.

### Can custom button actions be serialized back to TOML without data loss?

Yes. The `Serialize` implementation generated by serde ensures that any `Action` variant—including complex types like `CustomShortcut` with `KeyCombo` payloads from [`crates/openlogi-core/src/binding/key_combo.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/key_combo.rs)—round-trips through `toml::to_string_pretty` and `toml::from_str` while preserving exact structure and data.