Skip to main content

twilight_model/application/interaction/application_command/
option.rs

1use crate::{
2    application::command::CommandOptionType,
3    id::{
4        Id,
5        marker::{AttachmentMarker, ChannelMarker, GenericMarker, RoleMarker, UserMarker},
6    },
7};
8use serde::{
9    Deserialize, Deserializer, Serialize, Serializer,
10    de::{Error as DeError, IgnoredAny, MapAccess, Unexpected, Visitor},
11    ser::SerializeStruct,
12};
13use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
14
15/// Data received when a user fills in a command option.
16///
17/// See [Discord Docs/Application Command Object].
18///
19/// [Discord Docs/Application Command Object]: https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-interaction-data-option-structure
20#[derive(Clone, Debug, PartialEq)]
21pub struct CommandDataOption {
22    /// Name of the option.
23    pub name: String,
24    /// Value of the option.
25    pub value: CommandOptionValue,
26}
27
28impl Serialize for CommandDataOption {
29    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
30        let subcommand_is_empty = matches!(
31            &self.value,
32            CommandOptionValue::SubCommand(o)
33            | CommandOptionValue::SubCommandGroup(o)
34                if o.is_empty()
35        );
36
37        let focused = matches!(&self.value, CommandOptionValue::Focused(_, _));
38
39        let len = 2 + usize::from(!subcommand_is_empty) + usize::from(focused);
40
41        let mut state = serializer.serialize_struct("CommandDataOption", len)?;
42
43        if focused {
44            state.serialize_field("focused", &focused)?;
45        }
46
47        state.serialize_field("name", &self.name)?;
48
49        state.serialize_field("type", &self.value.kind())?;
50
51        match &self.value {
52            CommandOptionValue::Attachment(a) => state.serialize_field("value", a)?,
53            CommandOptionValue::Boolean(b) => state.serialize_field("value", b)?,
54            CommandOptionValue::Channel(c) => state.serialize_field("value", c)?,
55            CommandOptionValue::Focused(f, _) => state.serialize_field("value", f)?,
56            CommandOptionValue::Integer(i) => state.serialize_field("value", i)?,
57            CommandOptionValue::Mentionable(m) => state.serialize_field("value", m)?,
58            CommandOptionValue::Number(n) => state.serialize_field("value", n)?,
59            CommandOptionValue::Role(r) => state.serialize_field("value", r)?,
60            CommandOptionValue::String(s) => state.serialize_field("value", s)?,
61            CommandOptionValue::User(u) => state.serialize_field("value", u)?,
62            CommandOptionValue::SubCommand(s) | CommandOptionValue::SubCommandGroup(s) => {
63                if !subcommand_is_empty {
64                    state.serialize_field("options", s)?
65                }
66            }
67        }
68
69        state.end()
70    }
71}
72
73impl<'de> Deserialize<'de> for CommandDataOption {
74    #[allow(clippy::too_many_lines)]
75    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
76        #[derive(Debug, Deserialize)]
77        #[serde(field_identifier, rename_all = "snake_case")]
78        enum Fields {
79            Focused,
80            Name,
81            Options,
82            Type,
83            Value,
84        }
85
86        // An `Id` variant is purposely not present here to prevent wrongly
87        // parsing string options as numbers, trimming leading zeroes.
88        #[derive(Debug, Deserialize)]
89        #[serde(untagged)]
90        enum ValueEnvelope {
91            Boolean(bool),
92            Integer(i64),
93            Number(f64),
94            String(String),
95        }
96
97        impl ValueEnvelope {
98            #[allow(clippy::missing_const_for_fn)]
99            fn as_unexpected(&self) -> Unexpected<'_> {
100                match self {
101                    Self::Boolean(b) => Unexpected::Bool(*b),
102                    Self::Integer(i) => Unexpected::Signed(*i),
103                    Self::Number(f) => Unexpected::Float(*f),
104                    Self::String(s) => Unexpected::Str(s),
105                }
106            }
107        }
108
109        impl Display for ValueEnvelope {
110            fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
111                match self {
112                    Self::Boolean(b) => Display::fmt(b, f),
113                    Self::Integer(i) => Display::fmt(i, f),
114                    Self::Number(n) => Display::fmt(n, f),
115                    Self::String(s) => Display::fmt(s, f),
116                }
117            }
118        }
119
120        struct CommandDataOptionVisitor;
121
122        impl<'de> Visitor<'de> for CommandDataOptionVisitor {
123            type Value = CommandDataOption;
124
125            fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
126                formatter.write_str("CommandDataOption")
127            }
128
129            #[allow(clippy::too_many_lines)]
130            fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
131                let mut name_opt = None;
132                let mut kind_opt = None;
133                let mut options = Vec::new();
134                let mut value_opt: Option<ValueEnvelope> = None;
135                let mut focused = None;
136
137                loop {
138                    let key = match map.next_key() {
139                        Ok(Some(key)) => key,
140                        Ok(None) => break,
141                        Err(_) => {
142                            map.next_value::<IgnoredAny>()?;
143
144                            continue;
145                        }
146                    };
147
148                    match key {
149                        Fields::Focused => {
150                            if focused.is_some() {
151                                return Err(DeError::duplicate_field("focused"));
152                            }
153
154                            focused = map.next_value()?;
155                        }
156                        Fields::Name => {
157                            if name_opt.is_some() {
158                                return Err(DeError::duplicate_field("name"));
159                            }
160
161                            name_opt = Some(map.next_value()?);
162                        }
163                        Fields::Options => {
164                            if !options.is_empty() {
165                                return Err(DeError::duplicate_field("options"));
166                            }
167
168                            options = map.next_value()?;
169                        }
170                        Fields::Type => {
171                            if kind_opt.is_some() {
172                                return Err(DeError::duplicate_field("type"));
173                            }
174
175                            kind_opt = Some(map.next_value()?);
176                        }
177                        Fields::Value => {
178                            if value_opt.is_some() {
179                                return Err(DeError::duplicate_field("value"));
180                            }
181
182                            value_opt = Some(map.next_value()?);
183                        }
184                    }
185                }
186
187                let focused = focused.unwrap_or_default();
188                let name = name_opt.ok_or_else(|| DeError::missing_field("name"))?;
189                let kind = kind_opt.ok_or_else(|| DeError::missing_field("type"))?;
190
191                let value = if focused {
192                    let val = value_opt.ok_or_else(|| DeError::missing_field("value"))?;
193
194                    CommandOptionValue::Focused(val.to_string(), kind)
195                } else {
196                    match kind {
197                        CommandOptionType::Attachment => {
198                            let val = value_opt.ok_or_else(|| DeError::missing_field("value"))?;
199
200                            if let ValueEnvelope::String(id) = &val {
201                                CommandOptionValue::Attachment(id.parse().map_err(|_| {
202                                    DeError::invalid_type(val.as_unexpected(), &"attachment id")
203                                })?)
204                            } else {
205                                return Err(DeError::invalid_type(
206                                    val.as_unexpected(),
207                                    &"attachment id",
208                                ));
209                            }
210                        }
211                        CommandOptionType::Boolean => {
212                            let val = value_opt.ok_or_else(|| DeError::missing_field("value"))?;
213
214                            if let ValueEnvelope::Boolean(b) = val {
215                                CommandOptionValue::Boolean(b)
216                            } else {
217                                return Err(DeError::invalid_type(val.as_unexpected(), &"boolean"));
218                            }
219                        }
220                        CommandOptionType::Channel => {
221                            let val = value_opt.ok_or_else(|| DeError::missing_field("value"))?;
222
223                            if let ValueEnvelope::String(id) = &val {
224                                CommandOptionValue::Channel(id.parse().map_err(|_| {
225                                    DeError::invalid_type(val.as_unexpected(), &"channel id")
226                                })?)
227                            } else {
228                                return Err(DeError::invalid_type(
229                                    val.as_unexpected(),
230                                    &"channel id",
231                                ));
232                            }
233                        }
234                        CommandOptionType::Integer => {
235                            let val = value_opt.ok_or_else(|| DeError::missing_field("value"))?;
236
237                            if let ValueEnvelope::Integer(i) = val {
238                                CommandOptionValue::Integer(i)
239                            } else {
240                                return Err(DeError::invalid_type(val.as_unexpected(), &"integer"));
241                            }
242                        }
243                        CommandOptionType::Mentionable => {
244                            let val = value_opt.ok_or_else(|| DeError::missing_field("value"))?;
245
246                            if let ValueEnvelope::String(id) = &val {
247                                CommandOptionValue::Mentionable(id.parse().map_err(|_| {
248                                    DeError::invalid_type(val.as_unexpected(), &"mentionable id")
249                                })?)
250                            } else {
251                                return Err(DeError::invalid_type(
252                                    val.as_unexpected(),
253                                    &"mentionable id",
254                                ));
255                            }
256                        }
257                        CommandOptionType::Number => {
258                            let val = value_opt.ok_or_else(|| DeError::missing_field("value"))?;
259
260                            match val {
261                                ValueEnvelope::Integer(i) => {
262                                    // As json allows sending floating
263                                    // points without the tailing decimals
264                                    // it may be interpreted as a integer
265                                    // but it is safe to cast as there can
266                                    // not occur any loss.
267                                    #[allow(clippy::cast_precision_loss)]
268                                    CommandOptionValue::Number(i as f64)
269                                }
270                                ValueEnvelope::Number(f) => CommandOptionValue::Number(f),
271                                other => {
272                                    return Err(DeError::invalid_type(
273                                        other.as_unexpected(),
274                                        &"number",
275                                    ));
276                                }
277                            }
278                        }
279                        CommandOptionType::Role => {
280                            let val = value_opt.ok_or_else(|| DeError::missing_field("value"))?;
281
282                            if let ValueEnvelope::String(id) = &val {
283                                CommandOptionValue::Role(id.parse().map_err(|_| {
284                                    DeError::invalid_type(val.as_unexpected(), &"role id")
285                                })?)
286                            } else {
287                                return Err(DeError::invalid_type(val.as_unexpected(), &"role id"));
288                            }
289                        }
290                        CommandOptionType::String => {
291                            let val = value_opt.ok_or_else(|| DeError::missing_field("value"))?;
292
293                            if let ValueEnvelope::String(s) = val {
294                                CommandOptionValue::String(s)
295                            } else {
296                                return Err(DeError::invalid_type(val.as_unexpected(), &"string"));
297                            }
298                        }
299                        CommandOptionType::SubCommand => CommandOptionValue::SubCommand(options),
300                        CommandOptionType::SubCommandGroup => {
301                            CommandOptionValue::SubCommandGroup(options)
302                        }
303                        CommandOptionType::User => {
304                            let val = value_opt.ok_or_else(|| DeError::missing_field("value"))?;
305
306                            if let ValueEnvelope::String(id) = &val {
307                                CommandOptionValue::User(id.parse().map_err(|_| {
308                                    DeError::invalid_type(val.as_unexpected(), &"user id")
309                                })?)
310                            } else {
311                                return Err(DeError::invalid_type(val.as_unexpected(), &"user id"));
312                            }
313                        }
314                    }
315                };
316
317                Ok(CommandDataOption { name, value })
318            }
319        }
320
321        deserializer.deserialize_map(CommandDataOptionVisitor)
322    }
323}
324
325/// Combined value and value type for a [`CommandDataOption`].
326#[derive(Clone, Debug, PartialEq)]
327pub enum CommandOptionValue {
328    /// Attachment option.
329    Attachment(Id<AttachmentMarker>),
330    /// Boolean option.
331    Boolean(bool),
332    /// Channel option.
333    Channel(Id<ChannelMarker>),
334    /// Focused option.
335    ///
336    /// Since Discord does not validate focused fields, they are sent as strings.
337    /// This means that you will not necessarily get a valid number from number options.
338    ///
339    /// See [Discord Docs/Autocomplete].
340    ///
341    /// The actual [`CommandOptionType`] is available through the second tuple value.
342    ///
343    /// [Discord Docs/Autocomplete]: https://discord.com/developers/docs/interactions/application-commands#autocomplete
344    /// [`CommandOptionType`]: crate::application::command::CommandOptionType
345    Focused(String, CommandOptionType),
346    /// Integer option.
347    Integer(i64),
348    /// Mentionable option.
349    Mentionable(Id<GenericMarker>),
350    /// Number option.
351    Number(f64),
352    /// Role option.
353    Role(Id<RoleMarker>),
354    /// String option.
355    String(String),
356    /// Subcommand option.
357    SubCommand(Vec<CommandDataOption>),
358    /// Subcommand group option.
359    SubCommandGroup(Vec<CommandDataOption>),
360    /// User option.
361    User(Id<UserMarker>),
362}
363
364impl CommandOptionValue {
365    pub const fn kind(&self) -> CommandOptionType {
366        match self {
367            CommandOptionValue::Attachment(_) => CommandOptionType::Attachment,
368            CommandOptionValue::Boolean(_) => CommandOptionType::Boolean,
369            CommandOptionValue::Channel(_) => CommandOptionType::Channel,
370            CommandOptionValue::Focused(_, t) => *t,
371            CommandOptionValue::Integer(_) => CommandOptionType::Integer,
372            CommandOptionValue::Mentionable(_) => CommandOptionType::Mentionable,
373            CommandOptionValue::Number(_) => CommandOptionType::Number,
374            CommandOptionValue::Role(_) => CommandOptionType::Role,
375            CommandOptionValue::String(_) => CommandOptionType::String,
376            CommandOptionValue::SubCommand(_) => CommandOptionType::SubCommand,
377            CommandOptionValue::SubCommandGroup(_) => CommandOptionType::SubCommandGroup,
378            CommandOptionValue::User(_) => CommandOptionType::User,
379        }
380    }
381}
382
383impl From<Id<AttachmentMarker>> for CommandOptionValue {
384    fn from(value: Id<AttachmentMarker>) -> Self {
385        CommandOptionValue::Attachment(value)
386    }
387}
388
389impl From<bool> for CommandOptionValue {
390    fn from(value: bool) -> Self {
391        CommandOptionValue::Boolean(value)
392    }
393}
394
395impl From<Id<ChannelMarker>> for CommandOptionValue {
396    fn from(value: Id<ChannelMarker>) -> Self {
397        CommandOptionValue::Channel(value)
398    }
399}
400
401impl From<(String, CommandOptionType)> for CommandOptionValue {
402    fn from((value, kind): (String, CommandOptionType)) -> Self {
403        CommandOptionValue::Focused(value, kind)
404    }
405}
406
407impl From<i64> for CommandOptionValue {
408    fn from(value: i64) -> Self {
409        CommandOptionValue::Integer(value)
410    }
411}
412
413impl From<Id<GenericMarker>> for CommandOptionValue {
414    fn from(value: Id<GenericMarker>) -> Self {
415        CommandOptionValue::Mentionable(value)
416    }
417}
418
419impl From<f64> for CommandOptionValue {
420    fn from(value: f64) -> Self {
421        CommandOptionValue::Number(value)
422    }
423}
424
425impl From<Id<RoleMarker>> for CommandOptionValue {
426    fn from(value: Id<RoleMarker>) -> Self {
427        CommandOptionValue::Role(value)
428    }
429}
430
431impl From<String> for CommandOptionValue {
432    fn from(value: String) -> Self {
433        CommandOptionValue::String(value)
434    }
435}
436
437impl From<Id<UserMarker>> for CommandOptionValue {
438    fn from(value: Id<UserMarker>) -> Self {
439        CommandOptionValue::User(value)
440    }
441}
442
443impl TryFrom<CommandOptionValue> for Id<AttachmentMarker> {
444    type Error = CommandOptionValue;
445
446    fn try_from(value: CommandOptionValue) -> Result<Self, Self::Error> {
447        match value {
448            CommandOptionValue::Attachment(inner) => Ok(inner),
449            _ => Err(value),
450        }
451    }
452}
453
454impl TryFrom<CommandOptionValue> for bool {
455    type Error = CommandOptionValue;
456
457    fn try_from(value: CommandOptionValue) -> Result<Self, Self::Error> {
458        match value {
459            CommandOptionValue::Boolean(inner) => Ok(inner),
460            _ => Err(value),
461        }
462    }
463}
464
465impl TryFrom<CommandOptionValue> for Id<ChannelMarker> {
466    type Error = CommandOptionValue;
467
468    fn try_from(value: CommandOptionValue) -> Result<Self, Self::Error> {
469        match value {
470            CommandOptionValue::Channel(inner) => Ok(inner),
471            _ => Err(value),
472        }
473    }
474}
475
476impl TryFrom<CommandOptionValue> for (String, CommandOptionType) {
477    type Error = CommandOptionValue;
478
479    fn try_from(value: CommandOptionValue) -> Result<Self, Self::Error> {
480        match value {
481            CommandOptionValue::Focused(value, kind) => Ok((value, kind)),
482            _ => Err(value),
483        }
484    }
485}
486
487impl TryFrom<CommandOptionValue> for i64 {
488    type Error = CommandOptionValue;
489
490    fn try_from(value: CommandOptionValue) -> Result<Self, Self::Error> {
491        match value {
492            CommandOptionValue::Integer(inner) => Ok(inner),
493            _ => Err(value),
494        }
495    }
496}
497
498impl TryFrom<CommandOptionValue> for Id<GenericMarker> {
499    type Error = CommandOptionValue;
500
501    fn try_from(value: CommandOptionValue) -> Result<Self, Self::Error> {
502        match value {
503            CommandOptionValue::Mentionable(inner) => Ok(inner),
504            _ => Err(value),
505        }
506    }
507}
508
509impl TryFrom<CommandOptionValue> for f64 {
510    type Error = CommandOptionValue;
511
512    fn try_from(value: CommandOptionValue) -> Result<Self, Self::Error> {
513        match value {
514            CommandOptionValue::Number(inner) => Ok(inner),
515            _ => Err(value),
516        }
517    }
518}
519
520impl TryFrom<CommandOptionValue> for Id<RoleMarker> {
521    type Error = CommandOptionValue;
522
523    fn try_from(value: CommandOptionValue) -> Result<Self, Self::Error> {
524        match value {
525            CommandOptionValue::Role(inner) => Ok(inner),
526            _ => Err(value),
527        }
528    }
529}
530
531impl TryFrom<CommandOptionValue> for String {
532    type Error = CommandOptionValue;
533
534    fn try_from(value: CommandOptionValue) -> Result<Self, Self::Error> {
535        match value {
536            CommandOptionValue::String(inner) => Ok(inner),
537            _ => Err(value),
538        }
539    }
540}
541
542impl TryFrom<CommandOptionValue> for Id<UserMarker> {
543    type Error = CommandOptionValue;
544
545    fn try_from(value: CommandOptionValue) -> Result<Self, Self::Error> {
546        match value {
547            CommandOptionValue::User(inner) => Ok(inner),
548            _ => Err(value),
549        }
550    }
551}
552
553#[cfg(test)]
554mod tests {
555    use crate::{
556        application::{
557            command::{CommandOptionType, CommandType},
558            interaction::application_command::{
559                CommandData, CommandDataOption, CommandOptionValue,
560            },
561        },
562        id::Id,
563    };
564    use serde_test::Token;
565
566    #[test]
567    fn no_options() {
568        let value = CommandData {
569            guild_id: Some(Id::new(2)),
570            id: Id::new(1),
571            kind: CommandType::ChatInput,
572            name: "permissions".to_owned(),
573            options: Vec::new(),
574            resolved: None,
575            target_id: None,
576        };
577        serde_test::assert_tokens(
578            &value,
579            &[
580                Token::Struct {
581                    name: "CommandData",
582                    len: 4,
583                },
584                Token::Str("guild_id"),
585                Token::Some,
586                Token::NewtypeStruct { name: "Id" },
587                Token::Str("2"),
588                Token::Str("id"),
589                Token::NewtypeStruct { name: "Id" },
590                Token::Str("1"),
591                Token::Str("type"),
592                Token::U8(CommandType::ChatInput.into()),
593                Token::Str("name"),
594                Token::Str("permissions"),
595                Token::StructEnd,
596            ],
597        )
598    }
599
600    #[test]
601    fn with_option() {
602        let value = CommandData {
603            guild_id: Some(Id::new(2)),
604            id: Id::new(1),
605            kind: CommandType::ChatInput,
606            name: "permissions".to_owned(),
607            options: Vec::from([CommandDataOption {
608                name: "cat".to_owned(),
609                value: CommandOptionValue::Integer(42),
610            }]),
611            resolved: None,
612            target_id: None,
613        };
614
615        serde_test::assert_tokens(
616            &value,
617            &[
618                Token::Struct {
619                    name: "CommandData",
620                    len: 5,
621                },
622                Token::Str("guild_id"),
623                Token::Some,
624                Token::NewtypeStruct { name: "Id" },
625                Token::Str("2"),
626                Token::Str("id"),
627                Token::NewtypeStruct { name: "Id" },
628                Token::Str("1"),
629                Token::Str("type"),
630                Token::U8(CommandType::ChatInput.into()),
631                Token::Str("name"),
632                Token::Str("permissions"),
633                Token::Str("options"),
634                Token::Seq { len: Some(1) },
635                Token::Struct {
636                    name: "CommandDataOption",
637                    len: 3,
638                },
639                Token::Str("name"),
640                Token::Str("cat"),
641                Token::Str("type"),
642                Token::U8(CommandOptionType::Integer as u8),
643                Token::Str("value"),
644                Token::I64(42),
645                Token::StructEnd,
646                Token::SeqEnd,
647                Token::StructEnd,
648            ],
649        )
650    }
651
652    #[test]
653    fn with_normal_option_and_autocomplete() {
654        let value = CommandData {
655            guild_id: Some(Id::new(2)),
656            id: Id::new(1),
657            kind: CommandType::ChatInput,
658            name: "permissions".to_owned(),
659            options: Vec::from([
660                CommandDataOption {
661                    name: "cat".to_owned(),
662                    value: CommandOptionValue::Integer(42),
663                },
664                CommandDataOption {
665                    name: "dog".to_owned(),
666                    value: CommandOptionValue::Focused(
667                        "Shiba".to_owned(),
668                        CommandOptionType::String,
669                    ),
670                },
671            ]),
672            resolved: None,
673            target_id: None,
674        };
675
676        serde_test::assert_de_tokens(
677            &value,
678            &[
679                Token::Struct {
680                    name: "CommandData",
681                    len: 5,
682                },
683                Token::Str("guild_id"),
684                Token::Some,
685                Token::NewtypeStruct { name: "Id" },
686                Token::Str("2"),
687                Token::Str("id"),
688                Token::NewtypeStruct { name: "Id" },
689                Token::Str("1"),
690                Token::Str("type"),
691                Token::U8(CommandType::ChatInput.into()),
692                Token::Str("name"),
693                Token::Str("permissions"),
694                Token::Str("options"),
695                Token::Seq { len: Some(2) },
696                Token::Struct {
697                    name: "CommandDataOption",
698                    len: 3,
699                },
700                Token::Str("name"),
701                Token::Str("cat"),
702                Token::Str("type"),
703                Token::U8(CommandOptionType::Integer as u8),
704                Token::Str("value"),
705                Token::I64(42),
706                Token::StructEnd,
707                Token::Struct {
708                    name: "CommandDataOption",
709                    len: 4,
710                },
711                Token::Str("focused"),
712                Token::Some,
713                Token::Bool(true),
714                Token::Str("name"),
715                Token::Str("dog"),
716                Token::Str("type"),
717                Token::U8(CommandOptionType::String as u8),
718                Token::Str("value"),
719                Token::String("Shiba"),
720                Token::StructEnd,
721                Token::SeqEnd,
722                Token::StructEnd,
723            ],
724        )
725    }
726
727    #[test]
728    fn subcommand_without_option() {
729        let value = CommandData {
730            guild_id: None,
731            id: Id::new(1),
732            kind: CommandType::ChatInput,
733            name: "photo".to_owned(),
734            options: Vec::from([CommandDataOption {
735                name: "cat".to_owned(),
736                value: CommandOptionValue::SubCommand(Vec::new()),
737            }]),
738            resolved: None,
739            target_id: None,
740        };
741
742        serde_test::assert_tokens(
743            &value,
744            &[
745                Token::Struct {
746                    name: "CommandData",
747                    len: 4,
748                },
749                Token::Str("id"),
750                Token::NewtypeStruct { name: "Id" },
751                Token::Str("1"),
752                Token::Str("type"),
753                Token::U8(CommandType::ChatInput.into()),
754                Token::Str("name"),
755                Token::Str("photo"),
756                Token::Str("options"),
757                Token::Seq { len: Some(1) },
758                Token::Struct {
759                    name: "CommandDataOption",
760                    len: 2,
761                },
762                Token::Str("name"),
763                Token::Str("cat"),
764                Token::Str("type"),
765                Token::U8(CommandOptionType::SubCommand as u8),
766                Token::StructEnd,
767                Token::SeqEnd,
768                Token::StructEnd,
769            ],
770        );
771    }
772
773    #[test]
774    fn numbers() {
775        let value = CommandDataOption {
776            name: "opt".to_string(),
777            value: CommandOptionValue::Number(5.0),
778        };
779
780        serde_test::assert_de_tokens(
781            &value,
782            &[
783                Token::Struct {
784                    name: "CommandDataOption",
785                    len: 3,
786                },
787                Token::Str("name"),
788                Token::Str("opt"),
789                Token::Str("type"),
790                Token::U8(CommandOptionType::Number as u8),
791                Token::Str("value"),
792                Token::I64(5),
793                Token::StructEnd,
794            ],
795        );
796    }
797
798    #[test]
799    fn autocomplete() {
800        let value = CommandDataOption {
801            name: "opt".to_string(),
802            value: CommandOptionValue::Focused(
803                "not a number".to_owned(),
804                CommandOptionType::Number,
805            ),
806        };
807
808        serde_test::assert_de_tokens(
809            &value,
810            &[
811                Token::Struct {
812                    name: "CommandDataOption",
813                    len: 4,
814                },
815                Token::Str("focused"),
816                Token::Some,
817                Token::Bool(true),
818                Token::Str("name"),
819                Token::Str("opt"),
820                Token::Str("type"),
821                Token::U8(CommandOptionType::Number as u8),
822                Token::Str("value"),
823                Token::String("not a number"),
824                Token::StructEnd,
825            ],
826        );
827    }
828
829    #[test]
830    fn autocomplete_number() {
831        let value = CommandDataOption {
832            name: "opt".to_string(),
833            value: CommandOptionValue::Focused("1".to_owned(), CommandOptionType::Number),
834        };
835
836        serde_test::assert_de_tokens(
837            &value,
838            &[
839                Token::Struct {
840                    name: "CommandDataOption",
841                    len: 4,
842                },
843                Token::Str("focused"),
844                Token::Some,
845                Token::Bool(true),
846                Token::Str("name"),
847                Token::Str("opt"),
848                Token::Str("type"),
849                Token::U8(CommandOptionType::Number as u8),
850                Token::Str("value"),
851                Token::String("1"),
852                Token::StructEnd,
853            ],
854        );
855    }
856
857    #[test]
858    fn leading_zeroes_string_option_value() {
859        let value = CommandDataOption {
860            name: "opt".to_string(),
861            value: CommandOptionValue::String("0001".to_owned()),
862        };
863
864        serde_test::assert_de_tokens(
865            &value,
866            &[
867                Token::Struct {
868                    name: "CommandDataOption",
869                    len: 3,
870                },
871                Token::Str("name"),
872                Token::Str("opt"),
873                Token::Str("type"),
874                Token::U8(CommandOptionType::String as u8),
875                Token::Str("value"),
876                Token::String("0001"),
877                Token::StructEnd,
878            ],
879        );
880    }
881}