use std::fmt::Display;
use crate::types::{
ApplicationCommandOptionChoice, ApplicationCommandOptionChoiceValue, InteractionResponse,
};
use super::{Ctx, HandlerError, response::into_handler_error};
pub const MAX_CHOICES: usize = 25;
pub trait Autocomplete<S> {
fn complete(
ctx: Ctx<'_, S>,
input: &str,
) -> impl Future<Output = Result<InteractionResponse, HandlerError>> + Send;
}
pub trait AutocompleteOutput {
fn into_result(self) -> Result<InteractionResponse, HandlerError>;
}
impl<T: IntoChoice> AutocompleteOutput for Vec<T> {
fn into_result(self) -> Result<InteractionResponse, HandlerError> {
Ok(InteractionResponse::respond_with_autocomplete_choices(
self.into_iter()
.take(MAX_CHOICES)
.map(IntoChoice::into_choice)
.collect(),
))
}
}
impl<T: AutocompleteOutput, E: Display + 'static> AutocompleteOutput for Result<T, E> {
fn into_result(self) -> Result<InteractionResponse, HandlerError> {
self.map_err(into_handler_error)?.into_result()
}
}
pub trait IntoChoice {
fn into_choice(self) -> ApplicationCommandOptionChoice;
}
impl IntoChoice for ApplicationCommandOptionChoice {
fn into_choice(self) -> ApplicationCommandOptionChoice {
self
}
}
impl IntoChoice for String {
fn into_choice(self) -> ApplicationCommandOptionChoice {
(self.clone(), self).into_choice()
}
}
impl IntoChoice for &str {
fn into_choice(self) -> ApplicationCommandOptionChoice {
(self, self).into_choice()
}
}
macro_rules! number_choice {
($($ty:ty),*) => {$(
impl IntoChoice for $ty {
fn into_choice(self) -> ApplicationCommandOptionChoice {
(self.to_string(), self).into_choice()
}
}
)*};
}
number_choice!(i8, i16, i32, i64, u8, u16, u32, f64);
impl<N: Into<String>, V: ChoiceValue> IntoChoice for (N, V) {
fn into_choice(self) -> ApplicationCommandOptionChoice {
ApplicationCommandOptionChoice {
name: self.0.into(),
name_localizations: None,
value: self.1.into_value(),
}
}
}
pub trait ChoiceValue {
fn into_value(self) -> ApplicationCommandOptionChoiceValue;
}
impl ChoiceValue for String {
fn into_value(self) -> ApplicationCommandOptionChoiceValue {
ApplicationCommandOptionChoiceValue::String(self)
}
}
impl ChoiceValue for &str {
fn into_value(self) -> ApplicationCommandOptionChoiceValue {
ApplicationCommandOptionChoiceValue::String(self.to_string())
}
}
macro_rules! integer_value {
($($ty:ty),*) => {$(
impl ChoiceValue for $ty {
fn into_value(self) -> ApplicationCommandOptionChoiceValue {
ApplicationCommandOptionChoiceValue::Integer(self.into())
}
}
)*};
}
integer_value!(i8, i16, i32, i64, u8, u16, u32);
impl ChoiceValue for f64 {
fn into_value(self) -> ApplicationCommandOptionChoiceValue {
ApplicationCommandOptionChoiceValue::Double(self)
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
fn choices(output: impl AutocompleteOutput) -> serde_json::Value {
serde_json::to_value(output.into_result().unwrap()).unwrap()["data"]["choices"].clone()
}
#[test]
fn suggestions_become_choices() {
assert_eq!(
choices(vec!["apple", "pear"]),
json!([{ "name": "apple", "value": "apple" }, { "name": "pear", "value": "pear" }])
);
assert_eq!(
choices(vec![("One", 1i64), ("Two", 2)]),
json!([{ "name": "One", "value": 1 }, { "name": "Two", "value": 2 }])
);
assert_eq!(
choices(vec![2.5f64]),
json!([{ "name": "2.5", "value": 2.5 }])
);
assert_eq!(
choices((0..40u32).collect::<Vec<_>>())
.as_array()
.unwrap()
.len(),
25
);
}
#[test]
fn errors_are_handler_errors() {
let output: Result<Vec<String>, String> = Err("down".into());
assert_eq!(output.into_result().unwrap_err().to_string(), "down");
let output: Result<Vec<String>, HandlerError> = Err(HandlerError::user("nope"));
assert_eq!(
output.into_result().unwrap_err().kind(),
super::super::ErrorKind::User
);
}
}