use std::sync::Arc;
use crate::outline::outlined;
use crate::types::{
ApplicationCommandInteraction, ApplicationCommandType, Event, InteractionResponse,
MessageComponentInteraction, ModalSubmitInteraction,
};
use super::{
Command, CommandDefinition, ComponentHandler, Ctx, ErrorRenderer, EventCtx, HandlerError,
Invocation, ModalHandler, ScopedDefinition, ViewHandler, ViewInfo, custom_id,
set::{scope_eq, str_eq},
};
#[derive(Debug, Clone, Copy)]
pub enum RouteInfo {
Command {
name: &'static str,
kind: ApplicationCommandType,
scope: Option<&'static str>,
},
Component {
prefix: &'static str,
},
Modal {
prefix: &'static str,
},
View(ViewInfo),
App {
routes: &'static [RouteInfo],
events: bool,
errors: bool,
},
}
impl RouteInfo {
pub const fn command<C: CommandDefinition>() -> Self {
Self::Command {
name: C::NAME,
kind: C::KIND,
scope: C::SCOPE,
}
}
pub const fn has_commands(&self) -> bool {
answers(self, Kind::Commands)
}
pub const fn has_components(&self) -> bool {
answers(self, Kind::Components)
}
pub const fn has_modals(&self) -> bool {
answers(self, Kind::Modals)
}
pub const fn has_events(&self) -> bool {
match self {
Self::App { routes, events, .. } => *events || count(routes, &Query::Events) > 0,
_ => false,
}
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` is not a Command, Group, Component or Modal Handler, View, or App",
label = "listed in `#[app(handlers(..))]`",
note = "list items made with `#[command]`, `#[group]`, `#[component]`, `#[modal]`, `#[view]` or `#[app]`"
)]
pub trait RouteDefinition {
const ROUTE: RouteInfo;
const ERRORS: Option<ErrorRenderer> = None;
fn definitions(_into: &mut Vec<ScopedDefinition>) {}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be routed with State `{S}`",
label = "listed in an `#[app]` served with State `{S}`"
)]
pub trait Route<S>: RouteDefinition {
fn dispatch(
_interaction: &Arc<ApplicationCommandInteraction>,
_state: &S,
_errors: ErrorRenderer,
) -> impl Future<Output = Option<Result<InteractionResponse, HandlerError>>> + Send {
async { None }
}
fn dispatch_autocomplete(
_interaction: &Arc<ApplicationCommandInteraction>,
_state: &S,
_errors: ErrorRenderer,
) -> impl Future<Output = Option<Result<InteractionResponse, HandlerError>>> + Send {
async { None }
}
fn dispatch_component(
_interaction: &Arc<MessageComponentInteraction>,
_state: &S,
_errors: ErrorRenderer,
) -> impl Future<Output = Option<Result<InteractionResponse, HandlerError>>> + Send {
async { None }
}
fn dispatch_modal(
_interaction: &Arc<ModalSubmitInteraction>,
_state: &S,
_errors: ErrorRenderer,
) -> impl Future<Output = Option<Result<InteractionResponse, HandlerError>>> + Send {
async { None }
}
fn dispatch_event(
_ctx: EventCtx<'_, S>,
_event: Event,
) -> impl Future<Output = Result<(), HandlerError>> + Send {
async { Ok(()) }
}
}
#[derive(Clone, Copy)]
enum Kind {
Commands,
Components,
Modals,
}
const fn answers(route: &RouteInfo, kind: Kind) -> bool {
match (route, kind) {
(RouteInfo::Command { .. }, Kind::Commands)
| (RouteInfo::Component { .. }, Kind::Components)
| (RouteInfo::Modal { .. }, Kind::Modals)
| (RouteInfo::View(_), Kind::Components | Kind::Modals) => true,
(RouteInfo::App { routes, .. }, _) => {
let mut i = 0;
while i < routes.len() {
if answers(&routes[i], kind) {
return true;
}
i += 1;
}
false
}
_ => false,
}
}
#[doc(hidden)]
pub type __Routed = Option<Result<InteractionResponse, HandlerError>>;
#[doc(hidden)]
pub async fn __dispatch_command<C: Command<S>, S: Sync>(
interaction: &Arc<ApplicationCommandInteraction>,
state: &S,
errors: ErrorRenderer,
) -> Option<Result<InteractionResponse, HandlerError>> {
let data = &interaction.data;
if data.name != C::NAME || data.t != C::KIND {
return None;
}
Some(
outlined(C::invoke(
Invocation::new(interaction, state).with_errors(errors),
))
.await,
)
}
#[doc(hidden)]
pub async fn __dispatch_autocomplete<C: Command<S>, S: Sync>(
interaction: &Arc<ApplicationCommandInteraction>,
state: &S,
errors: ErrorRenderer,
) -> Option<Result<InteractionResponse, HandlerError>> {
let data = &interaction.data;
if data.name != C::NAME || data.t != C::KIND {
return None;
}
outlined(C::autocomplete(
Invocation::new(interaction, state).with_errors(errors),
))
.await
}
#[doc(hidden)]
pub async fn __dispatch_component<H: ComponentHandler<S>, S: Sync>(
interaction: &Arc<MessageComponentInteraction>,
state: &S,
errors: ErrorRenderer,
) -> Option<Result<InteractionResponse, HandlerError>> {
let (prefix, fields) = custom_id::split(&interaction.data.custom_id);
if prefix != H::PREFIX {
return None;
}
Some(
outlined(H::invoke(
Ctx::new(interaction, state).with_errors(errors),
fields,
))
.await,
)
}
#[doc(hidden)]
pub async fn __dispatch_modal<H: ModalHandler<S>, S: Sync>(
interaction: &Arc<ModalSubmitInteraction>,
state: &S,
errors: ErrorRenderer,
) -> Option<Result<InteractionResponse, HandlerError>> {
let (prefix, fields) = custom_id::split(&interaction.data.custom_id);
if prefix != H::PREFIX {
return None;
}
Some(
outlined(H::invoke(
Ctx::new(interaction, state).with_errors(errors),
fields,
))
.await,
)
}
#[doc(hidden)]
pub async fn __dispatch_view_component<V: ViewHandler<S>, S: Sync>(
interaction: &Arc<MessageComponentInteraction>,
state: &S,
errors: ErrorRenderer,
) -> Option<Result<InteractionResponse, HandlerError>> {
let (prefix, fields) = custom_id::split(&interaction.data.custom_id);
if prefix != V::INFO.prefix {
return None;
}
outlined(V::component(
Ctx::new(interaction, state).with_errors(errors),
fields,
))
.await
}
#[doc(hidden)]
pub async fn __dispatch_view_modal<V: ViewHandler<S>, S: Sync>(
interaction: &Arc<ModalSubmitInteraction>,
state: &S,
errors: ErrorRenderer,
) -> Option<Result<InteractionResponse, HandlerError>> {
let (prefix, fields) = custom_id::split(&interaction.data.custom_id);
if prefix != V::INFO.prefix {
return None;
}
outlined(V::modal(
Ctx::new(interaction, state).with_errors(errors),
fields,
))
.await
}
#[doc(hidden)]
pub const fn __errors<R: RouteDefinition + ?Sized>() -> ErrorRenderer {
match R::ERRORS {
Some(render) => render,
None => HandlerError::response,
}
}
#[doc(hidden)]
pub const fn check_app(app: &RouteInfo) {
let root = std::slice::from_ref(app);
if count(root, &Query::Events) > 1 {
panic!("more than one App sets `events`: a bot has one events URL");
}
if count(root, &Query::Errors) > 1 {
panic!("more than one App sets `errors`: a bot renders its errors one way");
}
check_routes(root, root);
}
const fn check_routes(root: &[RouteInfo], routes: &[RouteInfo]) {
let mut i = 0;
while i < routes.len() {
match &routes[i] {
RouteInfo::App { routes, .. } => check_routes(root, routes),
RouteInfo::Command { name, kind, scope } => {
if count(root, &Query::Command(name, *kind)) > 1 {
panic!("two commands have the same name and type");
}
let limit = match kind {
ApplicationCommandType::ChatInput => 100,
ApplicationCommandType::User | ApplicationCommandType::Message => 15,
};
if count(root, &Query::Scope(*kind, *scope)) > limit {
panic!(
"too many commands of one type in one scope: Discord allows 100 chat-input, 15 user, and 15 message commands"
);
}
}
RouteInfo::Component { prefix } => {
if count(root, &Query::Prefix(prefix, Namespace::Component)) > 1 {
panic!("two handlers have the same custom_id prefix");
}
}
RouteInfo::Modal { prefix } => {
if count(root, &Query::Prefix(prefix, Namespace::Modal)) > 1 {
panic!("two handlers have the same custom_id prefix");
}
}
RouteInfo::View(view) => {
if count(root, &Query::Prefix(view.prefix, Namespace::Component)) > 1
|| count(root, &Query::Prefix(view.prefix, Namespace::Modal)) > 1
{
panic!("two handlers have the same custom_id prefix");
}
check_placed(root, view.views, 0);
}
}
i += 1;
}
}
const fn check_placed(root: &[RouteInfo], placed: &[ViewInfo], depth: usize) {
if depth > 16 {
return;
}
let mut i = 0;
while i < placed.len() {
let view = placed[i];
if view.handled && count(root, &Query::View(view.prefix)) == 0 {
panic!(
"a View with handlers placed by a listed View (a `<SubView .. />` tag in its markup) is missing from `#[app(handlers(..))]`"
);
}
check_placed(root, view.views, depth + 1);
i += 1;
}
}
#[derive(Clone, Copy)]
enum Namespace {
Component,
Modal,
}
enum Query<'a> {
Command(&'a str, ApplicationCommandType),
Scope(ApplicationCommandType, Option<&'a str>),
Prefix(&'a str, Namespace),
View(&'a str),
Events,
Errors,
}
const fn count(routes: &[RouteInfo], query: &Query) -> usize {
let mut n = 0;
let mut i = 0;
while i < routes.len() {
let hit = match (&routes[i], query) {
(
RouteInfo::App {
routes,
events,
errors,
},
_,
) => {
n += count(routes, query);
(*events && matches!(query, Query::Events))
|| (*errors && matches!(query, Query::Errors))
}
(RouteInfo::Command { name, kind, .. }, Query::Command(other, other_kind)) => {
*kind as u8 == *other_kind as u8 && str_eq(name, other)
}
(RouteInfo::Command { kind, scope, .. }, Query::Scope(other_kind, other_scope)) => {
*kind as u8 == *other_kind as u8 && scope_eq(*scope, *other_scope)
}
(RouteInfo::Component { prefix }, Query::Prefix(other, Namespace::Component))
| (RouteInfo::Modal { prefix }, Query::Prefix(other, Namespace::Modal)) => {
str_eq(prefix, other)
}
(RouteInfo::View(view), Query::Prefix(other, _) | Query::View(other)) => {
str_eq(view.prefix, other)
}
_ => false,
};
if hit {
n += 1;
}
i += 1;
}
n
}