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.rsautomatically implementSerializeandDeserializefor theActionenum, enabling seamless TOML conversion. - External tagging creates human-readable TOML where unit variants become strings and payload variants become single-key tables.
- The
Configstruct incrates/openlogi-core/src/config.rsserves as the deserialization target, utilizingHashMap<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.tomlfiles and stable bincode IPC. - Round-trip safety is guaranteed through
toml::from_strandtoml::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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →