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

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 and orchestrated through the Config struct in 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 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. The enum declaration includes standard serde derives that enable automatic conversion:

#[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, 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:

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

In 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:

MiddleClick = { CustomShortcut = "⌘⌥←" }

The corresponding Rust definition references KeyCombo, which is defined in 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, which defines a Config struct containing the button-to-action mapping:

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—serves as the key for looking up actions in the TOML table structure.

Deserialization Entry Point

When OpenLogi initializes, it reads config.toml and invokes the deserialization pipeline:

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:

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, 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 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:

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:

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 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 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 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, 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. 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, 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—round-trips through toml::to_string_pretty and toml::from_str while preserving exact structure and data.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →