pikadick_slash_framework/
convert.rs

1use crate::{
2    ArgumentParam,
3    BuilderError,
4};
5use serenity::model::application::{
6    CommandDataOptionValue,
7    CommandInteraction,
8};
9
10/// Error while converting from an interaction
11#[derive(Debug, thiserror::Error)]
12pub enum ConvertError {
13    /// The type is unknown
14    #[error("unexpected type for '{name}', expected '{expected}', got '{actual:?}'")]
15    UnexpectedType {
16        /// Name of the field that failed.
17        name: &'static str,
18        /// The expected datatype
19        expected: DataType,
20        /// The actual datatype.
21        ///
22        /// This is `None` if the actual datatype is unknown.
23        actual: Option<DataType>,
24    },
25
26    /// Missing a required field
27    #[error("missing required field for '{name}', expected '{expected}'")]
28    MissingRequiredField {
29        /// the name of the missing field
30        name: &'static str,
31        /// The expected datatype
32        expected: DataType,
33    },
34}
35
36/// A trait that allows converting from an application command interaction
37pub trait FromOptions: std::fmt::Debug + Send
38where
39    Self: Sized,
40{
41    /// Make arguments from a [`CommandInteraction`]
42    fn from_options(interaction: &CommandInteraction) -> Result<Self, ConvertError>;
43
44    /// Get the argument paramss of this object
45    fn get_argument_params() -> Result<Vec<ArgumentParam>, BuilderError> {
46        Ok(Vec::new())
47    }
48}
49
50// Allow the user to fill values while developing, or use a command with no arguments
51impl FromOptions for () {
52    fn from_options(_interaction: &CommandInteraction) -> Result<Self, ConvertError> {
53        Ok(())
54    }
55}
56
57/// A datatype
58#[derive(Debug, Copy, Clone)]
59pub enum DataType {
60    /// A string
61    String,
62
63    /// Integer
64    Integer,
65
66    /// Bool
67    Boolean,
68}
69
70impl DataType {
71    /// Get this as a str
72    pub fn as_str(self) -> &'static str {
73        match self {
74            Self::String => "String",
75            Self::Integer => "i64",
76            Self::Boolean => "bool",
77        }
78    }
79
80    /// Get the type of a [`CommandDataOptionValue`].
81    ///
82    /// This returns an option as [`DataType`] does not encode the concept of an unknown data type.
83    pub fn from_data_option_value(v: &CommandDataOptionValue) -> Option<Self> {
84        match v {
85            CommandDataOptionValue::String(_) => Some(DataType::String),
86            CommandDataOptionValue::Integer(_) => Some(DataType::Integer),
87            CommandDataOptionValue::Boolean(_) => Some(DataType::Boolean),
88            _ => None,
89        }
90    }
91}
92
93impl std::fmt::Display for DataType {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        self.as_str().fmt(f)
96    }
97}
98
99/// Convert from an option value
100pub trait FromOptionValue: Sized {
101    /// Parse from an option value
102    fn from_option_value(
103        name: &'static str,
104        option: &CommandDataOptionValue,
105    ) -> Result<Self, ConvertError>;
106
107    /// The expected data type
108    fn get_expected_data_type() -> DataType;
109
110    /// Kind of a hack to get the default "missing" value if the key was not present.
111    ///
112    /// # Returns
113    /// Returns None if this type is not optional.
114    fn get_missing_default() -> Option<Self> {
115        None
116    }
117}
118
119impl FromOptionValue for bool {
120    fn from_option_value(
121        name: &'static str,
122        option: &CommandDataOptionValue,
123    ) -> Result<Self, ConvertError> {
124        let expected = Self::get_expected_data_type();
125
126        match option {
127            CommandDataOptionValue::Boolean(b) => Ok(*b),
128            t => Err(ConvertError::UnexpectedType {
129                name,
130                expected,
131                actual: DataType::from_data_option_value(t),
132            }),
133        }
134    }
135
136    fn get_expected_data_type() -> DataType {
137        DataType::Boolean
138    }
139}
140
141impl FromOptionValue for String {
142    fn from_option_value(
143        name: &'static str,
144        option: &CommandDataOptionValue,
145    ) -> Result<Self, ConvertError> {
146        let expected = Self::get_expected_data_type();
147
148        match option {
149            CommandDataOptionValue::String(s) => Ok(s.clone()),
150            t => Err(ConvertError::UnexpectedType {
151                name,
152                expected,
153                actual: DataType::from_data_option_value(t),
154            }),
155        }
156    }
157
158    fn get_expected_data_type() -> DataType {
159        DataType::String
160    }
161}
162
163impl<T> FromOptionValue for Option<T>
164where
165    T: FromOptionValue,
166{
167    fn from_option_value(
168        name: &'static str,
169        option: &CommandDataOptionValue,
170    ) -> Result<Self, ConvertError> {
171        T::from_option_value(name, option).map(Some)
172    }
173
174    fn get_missing_default() -> Option<Self> {
175        Some(None)
176    }
177
178    fn get_expected_data_type() -> DataType {
179        T::get_expected_data_type()
180    }
181}