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,116 +2590,155 @@ 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()
"Telegram {}", .map(|choice| match choice {
if config.telegram.is_some() { ChannelMenuChoice::Telegram => format!(
"✅ connected" "Telegram {}",
} else { if config.telegram.is_some() {
"— connect your bot" "✅ connected"
} } else {
), "— connect your bot"
format!( }
"Discord {}", ),
if config.discord.is_some() { ChannelMenuChoice::Discord => format!(
"✅ connected" "Discord {}",
} else { if config.discord.is_some() {
"— connect your bot" "✅ connected"
} } else {
), "— connect your bot"
format!( }
"Slack {}", ),
if config.slack.is_some() { ChannelMenuChoice::Slack => format!(
"✅ connected" "Slack {}",
} else { if config.slack.is_some() {
"— connect your bot" "✅ connected"
} } else {
), "— connect your bot"
format!( }
"iMessage {}", ),
if config.imessage.is_some() { ChannelMenuChoice::IMessage => format!(
"✅ configured" "iMessage {}",
} else { if config.imessage.is_some() {
"— macOS only" "✅ configured"
} } else {
), "— macOS only"
format!( }
"Matrix {}", ),
if config.matrix.is_some() { ChannelMenuChoice::Matrix => format!(
"✅ connected" "Matrix {}",
} else { if config.matrix.is_some() {
"— self-hosted chat" "✅ connected"
} } else {
), "— self-hosted chat"
format!( }
"WhatsApp {}", ),
if config.whatsapp.is_some() { ChannelMenuChoice::WhatsApp => format!(
"✅ connected" "WhatsApp {}",
} else { if config.whatsapp.is_some() {
"— Business Cloud API" "✅ connected"
} } else {
), "— Business Cloud API"
format!( }
"Linq {}", ),
if config.linq.is_some() { ChannelMenuChoice::Linq => format!(
"✅ connected" "Linq {}",
} else { if config.linq.is_some() {
"— iMessage/RCS/SMS via Linq API" "✅ connected"
} } else {
), "— iMessage/RCS/SMS via Linq API"
format!( }
"IRC {}", ),
if config.irc.is_some() { ChannelMenuChoice::Irc => format!(
"✅ configured" "IRC {}",
} else { if config.irc.is_some() {
"— IRC over TLS" "✅ configured"
} } else {
), "— IRC over TLS"
format!( }
"Webhook {}", ),
if config.webhook.is_some() { ChannelMenuChoice::Webhook => format!(
"✅ configured" "Webhook {}",
} else { if config.webhook.is_some() {
"— HTTP endpoint" "✅ configured"
} } else {
), "— HTTP endpoint"
format!( }
"DingTalk {}", ),
if config.dingtalk.is_some() { ChannelMenuChoice::DingTalk => format!(
"✅ connected" "DingTalk {}",
} else { if config.dingtalk.is_some() {
"— DingTalk Stream Mode" "✅ connected"
} } else {
), "— DingTalk Stream Mode"
format!( }
"QQ Official {}", ),
if config.qq.is_some() { ChannelMenuChoice::QqOfficial => format!(
"✅ connected" "QQ Official {}",
} else { if config.qq.is_some() {
"— Tencent QQ Bot" "✅ connected"
} } else {
), "— Tencent QQ Bot"
format!( }
"Lark/Feishu {}", ),
if config.lark.is_some() { ChannelMenuChoice::LarkFeishu => format!(
"✅ connected" "Lark/Feishu {}",
} else { if config.lark.is_some() {
"— Lark/Feishu Bot" "✅ connected"
} } else {
), "— 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!();
} }