1use std::{
21 cell::Cell,
22 fmt::{self, Display, Write},
23};
24
25use crate::types::{
26 AllowedMentions, Attachment, Component, File, InteractionResponse, MessageCallbackData,
27 MessageComponentData, MessageFlags, ModalCallbackData, PartialChannel, PartialEmoji, Role,
28 Snowflake, User,
29};
30
31use super::{
32 CommandOutput, ComponentCtx, Extras, HandlerError, IntoResponse, ModalChoice, ModalCtx, Update,
33 custom_id::CustomIdError, limits,
34};
35
36#[derive(Debug, Clone)]
45#[must_use]
46pub struct View {
47 pub(crate) components: Vec<Component>,
48 pub(crate) extras: Extras,
49}
50
51impl Default for View {
52 fn default() -> Self {
53 Self::new(Vec::new())
54 }
55}
56
57impl View {
58 pub fn new(components: Vec<Component>) -> Self {
60 Self {
61 components,
62 extras: Extras::default(),
63 }
64 }
65
66 pub fn components(&self) -> &[Component] {
68 &self.components
69 }
70
71 pub fn files(&self) -> &[File] {
74 self.extras.files()
75 }
76
77 pub fn flags(&self) -> MessageFlags {
81 self.extras.flags()
82 }
83
84 pub fn ephemeral(mut self) -> Self {
87 self.extras.inner().flags |= MessageFlags::Ephemeral;
88 self
89 }
90
91 pub fn suppress_notifications(mut self) -> Self {
93 self.extras.inner().flags |= MessageFlags::SuppressNotifications;
94 self
95 }
96
97 pub fn allowed_mentions(mut self, allowed_mentions: AllowedMentions) -> Self {
99 self.extras.inner().allowed_mentions = Some(allowed_mentions);
100 self
101 }
102
103 pub fn file(mut self, file: File) -> Self {
106 self.extras.inner().files.push(file);
107 self
108 }
109
110 #[cfg_attr(not(feature = "rest"), allow(dead_code))]
111 pub(crate) fn mentions(&self) -> Option<&AllowedMentions> {
112 self.extras.mentions()
113 }
114
115 #[cfg_attr(not(feature = "rest"), allow(dead_code))]
118 pub(crate) fn message_flags(&self, ephemeral: bool) -> MessageFlags {
119 let flags = MessageFlags::IsComponentsV2 | self.extras.flags();
120 if ephemeral {
121 flags
122 } else {
123 flags - MessageFlags::Ephemeral
124 }
125 }
126
127 pub fn check(&self) -> Result<(), ViewError> {
130 if let Some(error) = self.extras.custom_id_error() {
131 return Err(ViewError::CustomId(error.clone()));
132 }
133 limits::check_message(&self.components)
134 }
135
136 pub fn into_components(self) -> Result<Vec<Component>, CustomIdError> {
140 match self.extras.into_inner().error {
141 Some(error) => Err(error),
142 None => Ok(self.components),
143 }
144 }
145
146 pub fn into_message_data(self) -> Result<MessageCallbackData, ViewError> {
149 self.check()?;
150 let mut data = MessageCallbackData::components_v2(self.components);
151 let extras = self.extras.into_inner();
152 data.flags = Some(MessageFlags::IsComponentsV2 | extras.flags);
153 data.allowed_mentions = extras.allowed_mentions;
154 data.files = extras.files;
155 Ok(data)
156 }
157}
158
159impl From<Vec<Component>> for View {
160 fn from(components: Vec<Component>) -> Self {
161 Self::new(components)
162 }
163}
164
165impl IntoResponse for View {
167 fn into_response(self) -> InteractionResponse {
168 self.try_into_response()
169 .unwrap_or_else(|error| error.response())
170 }
171
172 fn try_into_response(self) -> Result<InteractionResponse, HandlerError> {
173 self.into_message_data()
174 .map(InteractionResponse::ChannelMessageWithSource)
175 .map_err(HandlerError::new)
176 }
177}
178
179#[derive(Debug, Clone, PartialEq, Eq)]
181#[non_exhaustive]
182pub enum ViewError {
183 CustomId(CustomIdError),
185 Limit {
187 what: &'static str,
189 min: usize,
191 max: usize,
193 found: usize,
195 },
196 Duplicate {
198 what: &'static str,
200 value: String,
202 },
203}
204
205impl Display for ViewError {
206 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207 match self {
208 ViewError::CustomId(error) => error.fmt(f),
209 ViewError::Limit {
210 what,
211 min: 0,
212 max,
213 found,
214 } => write!(f, "{found} {what}, Discord allows at most {max}"),
215 ViewError::Limit {
216 what,
217 min,
218 max,
219 found,
220 } => write!(f, "{found} {what}, Discord allows {min} to {max}"),
221 ViewError::Duplicate { what, value } => {
222 write!(
223 f,
224 "{what} {value:?} repeats, Discord requires each to be unique"
225 )
226 }
227 }
228 }
229}
230
231impl std::error::Error for ViewError {}
232
233impl From<CustomIdError> for ViewError {
234 fn from(error: CustomIdError) -> Self {
235 ViewError::CustomId(error)
236 }
237}
238
239#[derive(Debug, Clone)]
241#[must_use]
242pub struct ModalView {
243 pub(crate) data: ModalCallbackData,
244 pub(crate) error: Option<CustomIdError>,
245}
246
247impl ModalView {
248 pub fn data(&self) -> &ModalCallbackData {
250 &self.data
251 }
252
253 pub fn check(&self) -> Result<(), ViewError> {
256 if let Some(error) = &self.error {
257 return Err(ViewError::CustomId(error.clone()));
258 }
259 limits::check_modal(&self.data)
260 }
261
262 pub fn into_data(self) -> Result<ModalCallbackData, CustomIdError> {
264 match self.error {
265 Some(error) => Err(error),
266 None => Ok(self.data),
267 }
268 }
269}
270
271impl IntoResponse for ModalView {
273 fn into_response(self) -> InteractionResponse {
274 self.try_into_response()
275 .unwrap_or_else(|error| error.response())
276 }
277
278 fn try_into_response(self) -> Result<InteractionResponse, HandlerError> {
279 self.check().map_err(HandlerError::new)?;
280 Ok(InteractionResponse::Modal(self.data))
281 }
282}
283
284#[diagnostic::on_unimplemented(
287 message = "`{Self}` is not a View",
288 label = "not a View",
289 note = "a View is `rsx!`'s output or a #[view]'s Props"
290)]
291pub trait IntoView {
292 fn into_view(self) -> View;
294}
295
296impl IntoView for View {
297 fn into_view(self) -> View {
298 self
299 }
300}
301
302#[diagnostic::on_unimplemented(
306 message = "`{Self}` is not media",
307 note = "use a URL (&str or String), a File to upload with the message, or an &Attachment"
308)]
309pub trait IntoMedia {
310 fn into_media(self, extras: &mut Extras) -> String;
312}
313
314impl IntoMedia for &str {
315 fn into_media(self, _extras: &mut Extras) -> String {
316 self.to_string()
317 }
318}
319
320impl IntoMedia for String {
321 fn into_media(self, _extras: &mut Extras) -> String {
322 self
323 }
324}
325
326impl IntoMedia for &String {
327 fn into_media(self, _extras: &mut Extras) -> String {
328 self.clone()
329 }
330}
331
332impl IntoMedia for File {
334 fn into_media(self, extras: &mut Extras) -> String {
335 let url = format!("attachment://{}", self.name);
336 extras.inner().files.push(self);
337 url
338 }
339}
340
341impl IntoMedia for &Attachment {
343 fn into_media(self, _extras: &mut Extras) -> String {
344 self.url.clone()
345 }
346}
347
348#[diagnostic::on_unimplemented(
351 message = "`{Self}` cannot be placed in text",
352 note = "use &str, String, a number, or an Option of one; call `.to_string()` on other Display types"
353)]
354pub trait IntoText {
355 fn push_text(self, text: &mut String);
357}
358
359impl IntoText for &str {
360 fn push_text(self, text: &mut String) {
361 text.push_str(self);
362 }
363}
364
365impl IntoText for String {
366 fn push_text(self, text: &mut String) {
367 text.push_str(&self);
368 }
369}
370
371impl IntoText for &String {
372 fn push_text(self, text: &mut String) {
373 text.push_str(self);
374 }
375}
376
377impl<T: IntoText> IntoText for Option<T> {
378 fn push_text(self, text: &mut String) {
379 if let Some(value) = self {
380 value.push_text(text);
381 }
382 }
383}
384
385macro_rules! text {
386 ($($ty:ty),*) => {$(
387 impl IntoText for $ty {
388 fn push_text(self, text: &mut String) {
389 let _ = write!(text, "{self}");
390 }
391 }
392 )*};
393}
394
395text!(
396 char,
397 u8,
398 u16,
399 u32,
400 u64,
401 u128,
402 usize,
403 i8,
404 i16,
405 i32,
406 i64,
407 i128,
408 isize,
409 f32,
410 f64,
411 Snowflake,
412 std::fmt::Arguments<'_>
413);
414
415#[diagnostic::on_unimplemented(
418 message = "`{Self}` is not a valid value for this attribute",
419 note = "the attribute expects `{T}`"
420)]
421pub trait IntoAttr<T> {
422 fn into_attr(self) -> T;
424}
425
426impl<T> IntoAttr<T> for T {
427 fn into_attr(self) -> T {
428 self
429 }
430}
431
432impl<T> IntoAttr<Option<T>> for T {
433 fn into_attr(self) -> Option<T> {
434 Some(self)
435 }
436}
437
438impl IntoAttr<String> for &str {
439 fn into_attr(self) -> String {
440 self.to_string()
441 }
442}
443
444impl IntoAttr<Option<String>> for &str {
445 fn into_attr(self) -> Option<String> {
446 Some(self.to_string())
447 }
448}
449
450impl IntoAttr<String> for &String {
451 fn into_attr(self) -> String {
452 self.clone()
453 }
454}
455
456impl IntoAttr<Option<String>> for &String {
457 fn into_attr(self) -> Option<String> {
458 Some(self.clone())
459 }
460}
461
462impl IntoAttr<Option<PartialEmoji>> for &str {
464 fn into_attr(self) -> Option<PartialEmoji> {
465 Some(PartialEmoji {
466 id: None,
467 name: Some(self.to_string()),
468 animated: None,
469 })
470 }
471}
472
473#[diagnostic::on_unimplemented(message = "`{Self}` is not a message View")]
476pub trait Rerender {
477 fn rerender(self) -> Result<InteractionResponse, HandlerError>;
479}
480
481impl Rerender for View {
482 fn rerender(self) -> Result<InteractionResponse, HandlerError> {
483 Update(self).try_into_response()
484 }
485}
486
487impl<T: Rerender, E: Display + 'static> Rerender for Result<T, E> {
488 fn rerender(self) -> Result<InteractionResponse, HandlerError> {
489 self.map_err(super::response::into_handler_error)?
490 .rerender()
491 }
492}
493
494#[doc(hidden)]
498pub struct HandlerOutput<T>(Cell<Option<T>>);
499
500impl<T> HandlerOutput<T> {
501 pub fn new(output: T) -> Self {
502 Self(Cell::new(Some(output)))
503 }
504
505 fn take(&self) -> T {
506 self.0.take().expect("handler output answered once")
507 }
508}
509
510#[doc(hidden)]
511pub trait ViaRerender {
512 fn answer(&self) -> Result<InteractionResponse, HandlerError>;
513}
514
515impl<T: Rerender> ViaRerender for &HandlerOutput<T> {
516 fn answer(&self) -> Result<InteractionResponse, HandlerError> {
517 self.take().rerender()
518 }
519}
520
521#[doc(hidden)]
522pub trait ViaOutput {
523 fn answer(&self) -> Result<InteractionResponse, HandlerError>;
524}
525
526impl<T: CommandOutput> ViaOutput for HandlerOutput<T> {
527 fn answer(&self) -> Result<InteractionResponse, HandlerError> {
528 self.take().into_result()
529 }
530}
531
532#[derive(Debug, Clone, Copy)]
535pub struct ViewInfo {
536 pub prefix: &'static str,
538 pub handled: bool,
540 pub views: &'static [ViewInfo],
542}
543
544pub trait ViewDefinition {
546 const INFO: ViewInfo;
548}
549
550pub trait ViewHandler<S>: ViewDefinition {
553 fn component(
556 _ctx: ComponentCtx<'_, S>,
557 _fields: Option<&str>,
558 ) -> impl Future<Output = Option<Result<InteractionResponse, HandlerError>>> + Send {
559 async { None }
560 }
561
562 fn modal(
564 _ctx: ModalCtx<'_, S>,
565 _fields: Option<&str>,
566 ) -> impl Future<Output = Option<Result<InteractionResponse, HandlerError>>> + Send {
567 async { None }
568 }
569}
570
571#[diagnostic::on_unimplemented(
575 message = "`{Self}` cannot hold select values",
576 note = "use String, Snowflake, User, Role, PartialChannel, Attachment, or a #[derive(Choice)] enum, optionally in Option or Vec"
577)]
578pub trait SelectValue: Sized {
579 const MANY: bool = false;
581
582 fn extract(data: &MessageComponentData) -> Result<Self, String>;
584}
585
586fn selected(data: &MessageComponentData) -> &[String] {
587 data.values.as_deref().unwrap_or_default()
588}
589
590macro_rules! select_value {
591 ($ty:ty, |$value:ident, $data:ident| $parse:expr) => {
592 impl SelectValue for $ty {
593 fn extract(data: &MessageComponentData) -> Result<Self, String> {
594 let parse =
595 |$value: &str, $data: &MessageComponentData| -> Result<$ty, String> { $parse };
596 match selected(data).first() {
597 Some(value) => parse(value, data),
598 None => Err("nothing selected".to_string()),
599 }
600 }
601 }
602
603 impl SelectValue for Option<$ty> {
604 fn extract(data: &MessageComponentData) -> Result<Self, String> {
605 match selected(data).first() {
606 Some(_) => <$ty as SelectValue>::extract(data).map(Some),
607 None => Ok(None),
608 }
609 }
610 }
611
612 impl SelectValue for Vec<$ty> {
613 const MANY: bool = true;
614
615 fn extract(data: &MessageComponentData) -> Result<Self, String> {
616 let parse =
617 |$value: &str, $data: &MessageComponentData| -> Result<$ty, String> { $parse };
618 selected(data)
619 .iter()
620 .map(|value| parse(value, data))
621 .collect()
622 }
623 }
624 };
625}
626
627select_value!(String, |value, _data| Ok(value.to_string()));
628
629select_value!(Snowflake, |value, _data| value
630 .parse()
631 .map_err(|_| format!("`{value}` is not an id")));
632
633select_value!(User, |value, data| resolve(value, data, |r| r
634 .users
635 .as_ref()));
636
637select_value!(Role, |value, data| resolve(value, data, |r| r
638 .roles
639 .as_ref()));
640
641select_value!(PartialChannel, |value, data| resolve(value, data, |r| r
642 .channels
643 .as_ref()));
644
645select_value!(Attachment, |value, data| resolve(value, data, |r| r
646 .attachments
647 .as_ref()));
648
649fn resolve<T: Clone>(
650 value: &str,
651 data: &MessageComponentData,
652 get: impl Fn(&crate::types::ResolvedData) -> Option<&crate::types::SnowflakeMap<T>>,
653) -> Result<T, String> {
654 let id: Snowflake = value
655 .parse()
656 .map_err(|_| format!("`{value}` is not an id"))?;
657 data.resolved
658 .as_ref()
659 .and_then(get)
660 .and_then(|map| map.get(&id))
661 .cloned()
662 .ok_or_else(|| format!("{id} is not in the resolved data"))
663}
664
665fn choice<C: ModalChoice>(value: &str) -> Result<C, String> {
666 C::from_modal_value(value).ok_or_else(|| format!("`{value}` is not a choice"))
667}
668
669impl<C: ModalChoice> SelectValue for C {
670 fn extract(data: &MessageComponentData) -> Result<Self, String> {
671 match selected(data).first() {
672 Some(value) => choice(value),
673 None => Err("nothing selected".to_string()),
674 }
675 }
676}
677
678impl<C: ModalChoice> SelectValue for Option<C> {
679 fn extract(data: &MessageComponentData) -> Result<Self, String> {
680 selected(data)
681 .first()
682 .map(|value| choice(value))
683 .transpose()
684 }
685}
686
687impl<C: ModalChoice> SelectValue for Vec<C> {
688 const MANY: bool = true;
689
690 fn extract(data: &MessageComponentData) -> Result<Self, String> {
691 selected(data).iter().map(|value| choice(value)).collect()
692 }
693}
694
695#[doc(hidden)]
697pub fn select_value<T: SelectValue>(data: &MessageComponentData) -> Result<T, HandlerError> {
698 T::extract(data).map_err(|e| HandlerError::invalid_option(&data.custom_id, e))
699}
700
701#[doc(hidden)]
703pub fn modal_value<T: super::ModalValue>(
704 data: &crate::types::ModalSubmitData,
705 name: &str,
706) -> Result<T, HandlerError> {
707 T::extract(data.find(name), data).map_err(|e| HandlerError::invalid_option(name, e))
708}
709
710#[doc(hidden)]
712pub fn choice_options<C: ModalChoice>() -> &'static [(&'static str, &'static str)] {
713 C::OPTIONS
714}
715
716#[doc(hidden)]
718pub const fn required<T: super::ModalValue>() -> Option<bool> {
719 if T::REQUIRED { None } else { Some(false) }
720}