dispatch_map 0.1.3

Type-safe, declarative dispatch maps for Rust configuration with automatic glue and zero boilerplate.
Documentation
# dispatch_map  ![Crates.io]https://img.shields.io/crates/v/dispatch_map ![License]https://img.shields.io/crates/l/dispatch_map ![Build]https://img.shields.io/github/actions/workflow/status/your-org/dispatch_map/ci.yml?branch=main [![Docs.rs]https://docs.rs/dispatch_map/badge.svg]https://docs.rs/dispatch_map

> 💡 **Polymorphic Map dispatch, declarative deserialization, type safety, business structure and configuration perfectly
aligned!**

---

## 🚀 Background & Motivation

You are developing an aggregate payment platform, and each payment channel has its own unique Rust config type, for
example:

```rust
enum PaymentChannel {
    Stripe,
    AliPay,
    PayPal,
}

struct StripeConfig {
    api_key: String,
    region: String
}
struct AliPayConfig {
    app_id: String,
    private_key: String
}
struct PayPalConfig {
    client_id: String,
    client_secret: String
}

enum ChannelConfig {
    Stripe(StripeConfig),
    AliPay(AliPayConfig),
    PayPal(PayPalConfig),
}

struct AppConfig {
    channels: HashMap<PaymentChannel, ChannelConfig>
}
```

You want to configure these structures in `.toml`/`.yaml`/`.json` files, achieving "type-driven, clear structure".

---

## ⚠️ Pain Point: The Disaster of Default Serialization Structure

When using `#[derive(Serialize, Deserialize)]` directly, **you expect to get:**

```toml
[channels.Stripe]
api_key = "sk_test_123"
region = "us"
```

**But what you actually get is:**

```toml
[channels.Stripe.Stripe]
api_key = "sk_test_123"
region = "us"
```

**Each enum layer adds an extra nesting!**  
Maintaining this is extremely redundant, and deserialization becomes mechanical and repetitive.

---

## 🛠️ Traditional Solution: Handwritten Glue

You have to write such "deserialization glue":

```rust
fn deserialize_channels<'de, D>(deserializer: D) -> Result<HashMap<PaymentChannel, ChannelConfig>, D::Error>
where
    D: serde::Deserializer<'de>,
{
    let table = toml::Value::deserialize(deserializer)?;
    let map = table.as_table().ok_or_else(|| de::Error::custom("channels should be a table"))?;
    let mut result = HashMap::new();
    for (key, value) in map {
        let channel: PaymentChannel = key.parse().map_err(de::Error::custom)?;
        let config = match channel {
            PaymentChannel::Stripe => ChannelConfig::Stripe(StripeConfig::deserialize(value)?),
            PaymentChannel::AliPay => ChannelConfig::AliPay(AliPayConfig::deserialize(value)?),
            PaymentChannel::PayPal => ChannelConfig::PayPal(PayPalConfig::deserialize(value)?),
        };
        result.insert(channel, config);
    }
    Ok(result)
}
```

- Each channel needs a handwritten match, and maintenance cost increases rapidly as types grow.
- Serialization and deserialization are hard to make reversible, and it's easy to make type errors or miss branches.

---

## ✨ Advanced Solution: Use #[serde(untagged)] to Optimize Nesting

You can add one line to `ChannelConfig`:

```rust
#[serde(untagged)]
enum ChannelConfig {
    Stripe(StripeConfig),
    AliPay(AliPayConfig),
    PayPal(PayPalConfig),
}
```

This lets serde automatically unbox, leaving only one layer of nesting and a clean config structure.

---

## 🧩 dispatch_map: Declarative Dispatch, Ultimate Simplicity

**With dispatch_map, you can declare glue in one line, all type dispatch is automatic, no need to write tedious match by
hand.**

### 1️⃣ Type declarations are as elegant as business modeling

```rust
use dispatch_map::{DispatchMap, DispatchSeed};
use dispatch_map_derive::{DispatchKey, dispatch_pattern};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, DispatchKey)]
pub enum PaymentChannel {
    Stripe,
    AliPay,
    PayPal,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ChannelConfig {
    Stripe(StripeConfig),
    AliPay(AliPayConfig),
    PayPal(PayPalConfig),
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct StripeConfig {
    pub api_key: String,
    pub region: String,
}

// Other channels similar...
```

### 2️⃣ Glue dispatch only needs one macro line, type safe

```rust
dispatch_pattern! {
    PaymentChannel::Stripe  => ChannelConfig::Stripe(StripeConfig),
    PaymentChannel::AliPay  => ChannelConfig::AliPay(AliPayConfig),
    PaymentChannel::PayPal  => ChannelConfig::PayPal(PayPalConfig),
}
```

### 3️⃣ Business config uses DispatchMap, no need for custom deserializer

```rust
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AppConfig {
    pub channels: DispatchMap<PaymentChannel, ChannelConfig>,
}
```

---

## 🎯 Advantages at a Glance

- **Type safety**: Type-driven throughout, no more handwritten match.
- **Minimal glue**: Adding a new channel only needs one more line, no redundant code to maintain.
- **Reversible serialization/deserialization**: Clean config structure, format and business types perfectly aligned.
- **Generic friendly, IDE completion always available**.
- **Compatible with main serde formats**: toml, yaml, json all supported.
- **No performance loss**: All glue is generated at compile time, no runtime dispatch.

---

## 🧪 Complete Example (as unit test)

```rust
let src = r#"
[channels.Stripe]
api_key = "sk_test_123"
region = "us"

[channels.AliPay]
app_id = "2023"
private_key = "..."
"#;

let cfg: AppConfig = toml::from_str(src).unwrap();

assert!(matches!(cfg.channels.get(&PaymentChannel::Stripe), Some(ChannelConfig::Stripe(_))));
```

---

## 💡 FAQ

- **Q: Does it support other key/value types?**  
  As long as the key implements `Eq + Hash + Deserialize + Serialize`, and the value can be glued, it works.
- **Q: Does it support multi-level nesting/recursive structures?**  
  Any combination and nesting is possible, fully generic friendly.
- **Q: Compatible with serde features?**  
  Fully compatible with the serde ecosystem.

---

## 🏆 Summary

> **dispatch_map makes polymorphic config serialization/deserialization simple and type-safe, declaration is glue, focus
on business design, config experience as you wish!**

---

**Welcome star, PR, and questions! If you find it useful, don't forget to leave a like on crates.io ⭐️!**