fix(onboard): remove fragile numeric channel dispatch

Use enum-backed channel menu dispatch to prevent duplicated match-arm indices and unreachable-pattern warnings (issue #913).

Also switch OpenAI native tool spec parsing to owned serde structs so tool-schema validation compiles.
This commit is contained in:
Chummy 2026-02-20 00:28:11 +08:00
parent ef82c7dbcd
commit 4531c342f5
2 changed files with 159 additions and 113 deletions

View file

@ -26,6 +26,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `enc:` prefix for encrypted secrets — Use `enc2:` (ChaCha20-Poly1305) instead. - `enc:` prefix for encrypted secrets — Use `enc2:` (ChaCha20-Poly1305) instead.
Legacy values are still decrypted for backward compatibility but should be migrated. Legacy values are still decrypted for backward compatibility but should be migrated.
### Fixed
- **Onboarding channel menu dispatch** now uses an enum-backed selector instead of hard-coded
numeric match arms, preventing duplicated pattern arms and related `unreachable pattern`
compiler warnings in `src/onboard/wizard.rs`.
- **OpenAI native tool spec parsing** now uses owned serializable/deserializable structs,
fixing a compile-time type mismatch when validating tool schemas before API calls.
## [0.1.0] - 2026-02-13 ## [0.1.0] - 2026-02-13
### Added ### Added

View file

@ -2590,10 +2590,43 @@ fn setup_channels() -> Result<ChannelsConfig> {
println!(); println!();
let mut config = ChannelsConfig::default(); let mut config = ChannelsConfig::default();
#[derive(Clone, Copy)]
enum ChannelMenuChoice {
Telegram,
Discord,
Slack,
IMessage,
Matrix,
WhatsApp,
Linq,
Irc,
Webhook,
DingTalk,
QqOfficial,
LarkFeishu,
Done,
}
let menu_choices = [
ChannelMenuChoice::Telegram,
ChannelMenuChoice::Discord,
ChannelMenuChoice::Slack,
ChannelMenuChoice::IMessage,
ChannelMenuChoice::Matrix,
ChannelMenuChoice::WhatsApp,
ChannelMenuChoice::Linq,
ChannelMenuChoice::Irc,
ChannelMenuChoice::Webhook,
ChannelMenuChoice::DingTalk,
ChannelMenuChoice::QqOfficial,
ChannelMenuChoice::LarkFeishu,
ChannelMenuChoice::Done,
];
loop { loop {
let options = vec![ let options: Vec<String> = menu_choices
format!( .iter()
.map(|choice| match choice {
ChannelMenuChoice::Telegram => format!(
"Telegram {}", "Telegram {}",
if config.telegram.is_some() { if config.telegram.is_some() {
"✅ connected" "✅ connected"
@ -2601,7 +2634,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
"— connect your bot" "— connect your bot"
} }
), ),
format!( ChannelMenuChoice::Discord => format!(
"Discord {}", "Discord {}",
if config.discord.is_some() { if config.discord.is_some() {
"✅ connected" "✅ connected"
@ -2609,7 +2642,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
"— connect your bot" "— connect your bot"
} }
), ),
format!( ChannelMenuChoice::Slack => format!(
"Slack {}", "Slack {}",
if config.slack.is_some() { if config.slack.is_some() {
"✅ connected" "✅ connected"
@ -2617,7 +2650,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
"— connect your bot" "— connect your bot"
} }
), ),
format!( ChannelMenuChoice::IMessage => format!(
"iMessage {}", "iMessage {}",
if config.imessage.is_some() { if config.imessage.is_some() {
"✅ configured" "✅ configured"
@ -2625,7 +2658,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
"— macOS only" "— macOS only"
} }
), ),
format!( ChannelMenuChoice::Matrix => format!(
"Matrix {}", "Matrix {}",
if config.matrix.is_some() { if config.matrix.is_some() {
"✅ connected" "✅ connected"
@ -2633,7 +2666,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
"— self-hosted chat" "— self-hosted chat"
} }
), ),
format!( ChannelMenuChoice::WhatsApp => format!(
"WhatsApp {}", "WhatsApp {}",
if config.whatsapp.is_some() { if config.whatsapp.is_some() {
"✅ connected" "✅ connected"
@ -2641,7 +2674,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
"— Business Cloud API" "— Business Cloud API"
} }
), ),
format!( ChannelMenuChoice::Linq => format!(
"Linq {}", "Linq {}",
if config.linq.is_some() { if config.linq.is_some() {
"✅ connected" "✅ connected"
@ -2649,7 +2682,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
"— iMessage/RCS/SMS via Linq API" "— iMessage/RCS/SMS via Linq API"
} }
), ),
format!( ChannelMenuChoice::Irc => format!(
"IRC {}", "IRC {}",
if config.irc.is_some() { if config.irc.is_some() {
"✅ configured" "✅ configured"
@ -2657,7 +2690,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
"— IRC over TLS" "— IRC over TLS"
} }
), ),
format!( ChannelMenuChoice::Webhook => format!(
"Webhook {}", "Webhook {}",
if config.webhook.is_some() { if config.webhook.is_some() {
"✅ configured" "✅ configured"
@ -2665,7 +2698,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
"— HTTP endpoint" "— HTTP endpoint"
} }
), ),
format!( ChannelMenuChoice::DingTalk => format!(
"DingTalk {}", "DingTalk {}",
if config.dingtalk.is_some() { if config.dingtalk.is_some() {
"✅ connected" "✅ connected"
@ -2673,7 +2706,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
"— DingTalk Stream Mode" "— DingTalk Stream Mode"
} }
), ),
format!( ChannelMenuChoice::QqOfficial => format!(
"QQ Official {}", "QQ Official {}",
if config.qq.is_some() { if config.qq.is_some() {
"✅ connected" "✅ connected"
@ -2681,7 +2714,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
"— Tencent QQ Bot" "— Tencent QQ Bot"
} }
), ),
format!( ChannelMenuChoice::LarkFeishu => format!(
"Lark/Feishu {}", "Lark/Feishu {}",
if config.lark.is_some() { if config.lark.is_some() {
"✅ connected" "✅ connected"
@ -2689,17 +2722,23 @@ fn setup_channels() -> Result<ChannelsConfig> {
"— Lark/Feishu Bot" "— Lark/Feishu Bot"
} }
), ),
"Done — finish setup".to_string(), ChannelMenuChoice::Done => "Done — finish setup".to_string(),
]; })
.collect();
let choice = Select::new() let selection = Select::new()
.with_prompt(" Connect a channel (or Done to continue)") .with_prompt(" Connect a channel (or Done to continue)")
.items(&options) .items(&options)
.default(options.len() - 1) .default(options.len() - 1)
.interact()?; .interact()?;
let choice = menu_choices
.get(selection)
.copied()
.unwrap_or(ChannelMenuChoice::Done);
match choice { match choice {
0 => { ChannelMenuChoice::Telegram => {
// ── Telegram ── // ── Telegram ──
println!(); println!();
println!( println!(
@ -2797,7 +2836,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
mention_only: false, mention_only: false,
}); });
} }
1 => { ChannelMenuChoice::Discord => {
// ── Discord ── // ── Discord ──
println!(); println!();
println!( println!(
@ -2896,7 +2935,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
mention_only: false, mention_only: false,
}); });
} }
2 => { ChannelMenuChoice::Slack => {
// ── Slack ── // ── Slack ──
println!(); println!();
println!( println!(
@ -3021,7 +3060,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
allowed_users, allowed_users,
}); });
} }
3 => { ChannelMenuChoice::IMessage => {
// ── iMessage ── // ── iMessage ──
println!(); println!();
println!( println!(
@ -3065,7 +3104,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
style(&contacts_str).cyan() style(&contacts_str).cyan()
); );
} }
4 => { ChannelMenuChoice::Matrix => {
// ── Matrix ── // ── Matrix ──
println!(); println!();
println!( println!(
@ -3177,7 +3216,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
allowed_users, allowed_users,
}); });
} }
5 => { ChannelMenuChoice::WhatsApp => {
// ── WhatsApp ── // ── WhatsApp ──
println!(); println!();
println!( println!(
@ -3274,7 +3313,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
allowed_numbers, allowed_numbers,
}); });
} }
6 => { ChannelMenuChoice::Linq => {
// ── Linq ── // ── Linq ──
println!(); println!();
println!( println!(
@ -3366,7 +3405,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
allowed_senders, allowed_senders,
}); });
} }
7 => { ChannelMenuChoice::Irc => {
// ── IRC ── // ── IRC ──
println!(); println!();
println!( println!(
@ -3505,7 +3544,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
verify_tls: Some(verify_tls), verify_tls: Some(verify_tls),
}); });
} }
8 => { ChannelMenuChoice::Webhook => {
// ── Webhook ── // ── Webhook ──
println!(); println!();
println!( println!(
@ -3538,7 +3577,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
style(&port).cyan() style(&port).cyan()
); );
} }
9 => { ChannelMenuChoice::DingTalk => {
// ── DingTalk ── // ── DingTalk ──
println!(); println!();
println!( println!(
@ -3608,7 +3647,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
allowed_users, allowed_users,
}); });
} }
10 => { ChannelMenuChoice::QqOfficial => {
// ── QQ Official ── // ── QQ Official ──
println!(); println!();
println!( println!(
@ -3684,7 +3723,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
allowed_users, allowed_users,
}); });
} }
11 => { ChannelMenuChoice::LarkFeishu => {
// ── Lark/Feishu ── // ── Lark/Feishu ──
println!(); println!();
println!( println!(
@ -3871,7 +3910,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
port, port,
}); });
} }
_ => break, // Done ChannelMenuChoice::Done => break,
} }
println!(); println!();
} }