# dispatch_map    [](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 ⭐️!**