teamtalk 6.0.0

TeamTalk SDK for Rust
Documentation
use super::*;

impl Router {
    pub fn dispatch(
        &mut self,
        client: &Client,
        event: Event,
        message: &Message,
        state: &mut dyn super::super::StateStore,
    ) -> Result<HandlerResult> {
        let parsed_command = message
            .text()
            .and_then(|text| parse_command(&text.text, &self.command_prefixes));
        let parsed_command = parsed_command
            .as_ref()
            .map(|command| self.canonicalize_command(command));

        let mut ctx = Context {
            client,
            event,
            message,
            command: parsed_command.clone(),
            state,
        };

        for middleware in &mut self.middlewares {
            let result = catch_unwind(AssertUnwindSafe(|| middleware.before(&mut ctx))).map_err(
                |_| Error::IoError {
                    message: "middleware panic in before()".to_owned(),
                },
            )??;
            if matches!(result, HandlerResult::Stop) {
                return Ok(HandlerResult::Stop);
            }
        }

        let mut outcome = HandlerResult::Continue;
        let has_command = parsed_command.is_some();
        let mut matched_command = false;
        let mut matched_command_path = false;
        let mut usage_hint: Option<String> = None;
        let primary_prefix = self.primary_command_prefix();

        for route in &mut self.routes {
            ctx.command = parsed_command.clone();

            if has_command && matched_command && route.dialog_filter.is_some() {
                continue;
            }

            let mut should_run = match &route.matcher {
                RouteMatcher::Any => true,
                RouteMatcher::Event(expected) => expected == &ctx.event,
                RouteMatcher::Command(name) => {
                    if let Some(command) = parsed_command.as_ref() {
                        if let Some(adjusted) = match_command_route(command, name) {
                            matched_command_path = true;
                            if let Some(pattern) = route.command_pattern.as_ref() {
                                if !pattern.accepts(&adjusted.args) {
                                    if usage_hint.is_none() {
                                        usage_hint =
                                            Some(pattern.usage_with_prefix(primary_prefix));
                                    }
                                    false
                                } else {
                                    matched_command = true;
                                    ctx.command = Some(adjusted);
                                    true
                                }
                            } else {
                                matched_command = true;
                                ctx.command = Some(adjusted);
                                true
                            }
                        } else {
                            false
                        }
                    } else {
                        false
                    }
                }
            };

            if should_run && let Some(filter) = route.dialog_filter.as_ref() {
                should_run = if let Some(state) = ctx.dialog_current() {
                    if !state.dialog.eq_ignore_ascii_case(&filter.dialog) {
                        false
                    } else if let Some(expected_step) = filter.step.as_ref() {
                        state.step.eq_ignore_ascii_case(expected_step)
                    } else {
                        true
                    }
                } else {
                    false
                };
            }

            if !should_run {
                continue;
            }

            outcome =
                catch_unwind(AssertUnwindSafe(|| (route.handler)(&mut ctx))).map_err(|_| {
                    Error::IoError {
                        message: "handler panic".to_owned(),
                    }
                })??;
            if matches!(outcome, HandlerResult::Stop) {
                break;
            }
        }

        if has_command
            && !matched_command
            && matches!(outcome, HandlerResult::Continue)
            && let Some(command) = parsed_command.as_ref()
            && let Some(help) = self.render_auto_help(command)
        {
            let _ = ctx.reply_private(&help);
            matched_command = true;
            matched_command_path = true;
        }

        if has_command
            && !matched_command
            && matched_command_path
            && matches!(outcome, HandlerResult::Continue)
            && usage_hint.is_some()
            && let Some(usage) = usage_hint
        {
            let _ = ctx.reply_private(&format!("Usage: {usage}"));
            matched_command = true;
        }

        if has_command && !matched_command && matches!(outcome, HandlerResult::Continue) {
            if let Some(fallback) = self.on_unknown_command.as_mut() {
                outcome =
                    catch_unwind(AssertUnwindSafe(|| fallback(&mut ctx))).map_err(|_| {
                        Error::IoError {
                            message: "unknown-command handler panic".to_owned(),
                        }
                    })??;
            } else if let UnknownCommandPolicy::Reply(reply) = &self.unknown_command_policy {
                let mut reply = reply.clone();
                if let Some(command) = parsed_command.as_ref() {
                    let suggestions = self.suggest_commands(&command.name);
                    if !suggestions.is_empty() {
                        reply.push_str("\nDid you mean: ");
                        reply.push_str(&suggestions.join(", "));
                        reply.push('?');
                    }
                }
                let _ = ctx.reply_private(&reply);
            }
        }

        for middleware in self.middlewares.iter_mut().rev() {
            catch_unwind(AssertUnwindSafe(|| middleware.after(&mut ctx))).map_err(|_| {
                Error::IoError {
                    message: "middleware panic in after()".to_owned(),
                }
            })??;
        }

        Ok(outcome)
    }
}