# How OpenLogi Core Serializes Rust Variant Names for TOML Configuration

> Learn how OpenLogi Core serializes Rust variant names for TOML config using Serde. Discover how unit, tuple, and struct variants are converted for clear configuration files.

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

---

**OpenLogi Core uses Serde's default external tagging to serialize the `Action` enum, representing unit variants as bare strings and tuple or struct variants as single-key tables where the key matches the Rust variant name exactly.**

The action catalog in `openlogi-core` manages user-defined automation behaviors through a strongly-typed Rust enum that persists to human-readable TOML configuration files. Located in [`crates/openlogi-core/src/binding/action.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/action.rs) at lines 9–23, the `Action` enum leverages `#[derive(Serialize, Deserialize)]` at lines 11–14 and 22–23 to map Rust variant names directly to TOML keys, ensuring deterministic serialization without custom boilerplate.

## The Action Enum Schema in openlogi-core

The `Action` enum in [`crates/openlogi-core/src/binding/action.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/action.rs) defines the complete catalog of available automation actions. According to the source code at lines 11–14 and 22–23, the enum derives both `serde::Serialize` and `serde::Deserialize`, which instructs Serde to use **external tagging** by default. This means the variant name itself becomes the TOML key during serialization, creating a self-documenting configuration format where the Rust type system and the on-disk schema remain synchronized.

## External Tagging Rules for TOML Serialization

Serde applies consistent external tagging rules when converting the `Action` enum to TOML. The specific representation depends on the variant type—unit, tuple, or struct-like—determining whether the output appears as a simple string or a nested table.

### Unit Variants as Bare Strings

Unit variants containing no associated data serialize as **bare string values**. The TOML output consists solely of the variant name without braces or additional keys.

```toml

# Unit variant - simple string

action = "BrowserBack"

```

This matches Rust unit variants such as `Action::BrowserBack` directly, requiring no nested structure for empty payloads.

### Tuple Variants as Single-Key Tables

Tuple variants carrying a single associated value serialize as **single-key tables** where the variant name becomes the table key and the inner data becomes the value.

```toml

# Tuple variant - single-key table

action = { SetDpiPreset = 3 }

```

This representation corresponds to Rust instantiations like `Action::SetDpiPreset(3)`, wrapping the integer payload within a table keyed exactly to the variant name.

### Struct-Like Variants with Nested Data

Struct-like variants containing named fields follow the same single-key table rule, but the value becomes a nested map of field names to values.

```toml

# Struct-like variant - nested table

action = { CustomShortcut = { modifiers = ["Ctrl", "Shift"], key = "A" } }

```

This structure accommodates complex data structures such as the `KeyCombo` type 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), preserving field-level granularity within the TOML hierarchy.

### String Payload Variants

Variants wrapping generic data like `String` follow the single-key table convention, with the string payload placed directly as the value.

```toml

# Variant with a string payload

action = { RunShellCommand = "notify-send \"Hello\"" }

```

This handles payloads such as `Action::RunShellCommand("notify-send \"Hello\"".into())`, storing shell commands or other string data as scalar values within the variant-keyed table.

## Schema Stability and Migration Constraints

Because variant names constitute the on-disk schema, OpenLogi treats them as **frozen identifiers**. The source code comment at lines 15–17 of [`action.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/action.rs) explicitly warns that adding new actions is permitted, but removing or renaming existing variants requires a `schema_version` bump and a migration script. This constraint ensures that user-provided [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) files remain valid across software updates, as Serde relies on exact string matching to deserialize TOML keys back to Rust enum variants.

## Practical Configuration Examples

When OpenLogi reads a [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) file, Serde automatically maps these TOML representations back to the appropriate Rust enum variants. The mapping preserves exact spelling and case sensitivity, guaranteeing deterministic round-trip serialization.

### TOML Configuration Samples

```toml

# Unit variant – simple string

action = "BrowserBack"

# Tuple variant – single-key table

action = { SetDpiPreset = 2 }

# Struct-like variant – nested table

action = { CustomShortcut = { modifiers = ["Ctrl", "Shift"], key = "A" } }

# Variant with a string payload

action = { RunShellCommand = "notify-send \"Hello\"" }

```

### Equivalent Rust Constructions

These TOML structures correspond to the following Rust code using types from `openlogi_core::binding`:

```rust
use openlogi_core::binding::action::Action;
use openlogi_core::binding::key_combo::KeyCombo;

// Unit variant
let a = Action::BrowserBack;

// Tuple variant
let b = Action::SetDpiPreset(2);

// Struct-like variant with KeyCombo
let combo = KeyCombo::from_str("Ctrl+Shift+A").unwrap();
let c = Action::CustomShortcut(combo);

// String payload variant
let d = Action::RunShellCommand("notify-send \"Hello\"".into());

```

The `KeyCombo` parsing logic resides in [`crates/openlogi-core/src/binding/key_combo.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/key_combo.rs), while the integration of `Action` into the broader configuration system appears in [`crates/openlogi-core/src/config/settings.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config/settings.rs).

## Summary

- **External tagging**: Serde serializes the `Action` enum using the variant name as the TOML key, implemented via derived macros in [`crates/openlogi-core/src/binding/action.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/action.rs) at lines 11–14.
- **Unit variants** appear as bare strings (e.g., `"BrowserBack"`), while **tuple and struct variants** appear as single-key tables (e.g., `{ SetDpiPreset = 3 }`).
- **Schema stability**: Variant names are frozen to prevent configuration breakage; changes require explicit `schema_version` migrations per lines 15–17 of the source.
- **Related files**: Type definitions in [`key_combo.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/key_combo.rs) support complex variants like `CustomShortcut`, while [`settings.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/settings.rs) handles integration into the main TOML configuration.

## Frequently Asked Questions

### What file defines the Action enum in openlogi-core?

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) at lines 9–23, where it derives `Serialize` and `Deserialize` at lines 11–14 and 22–23 to enable TOML serialization.

### How does Serde serialize unit variants versus tuple variants in TOML?

Serde serializes **unit variants** as bare strings (e.g., `action = "BrowserBack"`) and **tuple variants** as single-key tables where the key matches the variant name (e.g., `action = { SetDpiPreset = 3 }`). This follows Serde's default external tagging behavior.

### Why are variant names frozen in OpenLogi's configuration schema?

Variant names are frozen because they form the structural keys in user [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) files. Renaming or removing a variant would invalidate existing configurations, requiring a `schema_version` bump and migration script as noted in lines 15–17 of [`action.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/action.rs).

### How can I construct Action variants directly in Rust code?

Import `openlogi_core::binding::action::Action` and instantiate variants using standard Rust enum syntax: `Action::BrowserBack` for units, `Action::SetDpiPreset(2)` for tuples, or `Action::RunShellCommand("cmd".into())` for string payloads. Complex variants like `Action::CustomShortcut` require constructing a `KeyCombo` from `openlogi_core::binding::key_combo::KeyCombo` first.