1use std::future::Future;
2
3use crate::action::ChatActionGuard;
4use crate::context::Context;
5
6#[cfg(not(target_arch = "wasm32"))]
7pub trait FromContextBounds: Send {}
8#[cfg(not(target_arch = "wasm32"))]
9impl<T: Send> FromContextBounds for T {}
10
11#[cfg(target_arch = "wasm32")]
12pub trait FromContextBounds {}
13#[cfg(target_arch = "wasm32")]
14impl<T> FromContextBounds for T {}
15
16#[cfg(not(target_arch = "wasm32"))]
17pub trait ContextFutureBounds: Future + Send {}
18#[cfg(not(target_arch = "wasm32"))]
19impl<T: Future + Send + ?Sized> ContextFutureBounds for T {}
20
21#[cfg(target_arch = "wasm32")]
22pub trait ContextFutureBounds: Future {}
23#[cfg(target_arch = "wasm32")]
24impl<T: Future + ?Sized> ContextFutureBounds for T {}
25
26pub trait FromContext: Sized + FromContextBounds {
43 fn from_context(ctx: &Context) -> impl ContextFutureBounds<Output = Self>;
45}
46
47impl FromContext for Context {
49 async fn from_context(ctx: &Context) -> Self {
50 ctx.clone()
51 }
52}
53
54#[derive(Debug, Clone)]
56pub struct User {
57 pub id: String,
58 pub name: String,
59}
60
61impl FromContext for User {
62 async fn from_context(ctx: &Context) -> Self {
63 Self {
64 id: ctx.user_id().to_string(),
65 name: ctx.user_name().to_string(),
66 }
67 }
68}
69
70#[derive(Debug, Clone)]
72pub struct Channel {
73 pub id: String,
74}
75
76impl FromContext for Channel {
77 async fn from_context(ctx: &Context) -> Self {
78 Self {
79 id: ctx.channel_id().to_string(),
80 }
81 }
82}
83
84#[derive(Debug, Clone)]
86pub struct CommandName(pub String);
87
88impl FromContext for CommandName {
89 async fn from_context(ctx: &Context) -> Self {
90 Self(ctx.command_name().unwrap_or_default().to_string())
91 }
92}
93
94#[derive(Debug, Clone)]
96pub struct CommandArgs(pub String);
97
98impl FromContext for CommandArgs {
99 async fn from_context(ctx: &Context) -> Self {
100 Self(ctx.command_args().unwrap_or_default().to_string())
101 }
102}
103
104#[derive(Debug, Clone)]
106pub struct ButtonId(pub String);
107
108impl FromContext for ButtonId {
109 async fn from_context(ctx: &Context) -> Self {
110 Self(ctx.button_id().unwrap_or_default().to_string())
111 }
112}
113
114#[derive(Debug, Clone)]
116pub struct MessageContent(pub String);
117
118impl FromContext for MessageContent {
119 async fn from_context(ctx: &Context) -> Self {
120 Self(ctx.message_content().unwrap_or_default().to_string())
121 }
122}
123
124macro_rules! impl_from_context_tuple {
126 () => {
127 impl FromContext for () {
128 async fn from_context(_ctx: &Context) -> Self {}
129 }
130 };
131 ($($ty:ident),+) => {
132 impl<$($ty: FromContext,)+> FromContext for ($($ty,)+) {
133 async fn from_context(ctx: &Context) -> Self {
134 ($($ty::from_context(ctx).await,)+)
135 }
136 }
137 };
138}
139
140impl_from_context_tuple!();
141impl_from_context_tuple!(T1);
142impl_from_context_tuple!(T1, T2);
143impl_from_context_tuple!(T1, T2, T3);
144impl_from_context_tuple!(T1, T2, T3, T4);
145
146pub struct Typing(pub Option<ChatActionGuard>);
159
160impl FromContext for Typing {
161 async fn from_context(ctx: &Context) -> Self {
162 Self(ctx.typing())
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 use crate::test_util::{EmptyData, StubData};
170 use futures_lite::future::block_on;
171
172 fn stub() -> Context {
173 Context::new(StubData)
174 }
175
176 fn empty() -> Context {
177 Context::new(EmptyData)
178 }
179
180 #[test]
181 fn extracts_user_and_channel() {
182 let user = block_on(User::from_context(&stub()));
183 assert_eq!(
184 (user.id.as_str(), user.name.as_str()),
185 ("stub-user", "stub-user")
186 );
187 assert_eq!(block_on(Channel::from_context(&stub())).id, "stub-channel");
188 }
189
190 #[test]
191 fn extracts_command_parts() {
192 assert_eq!(block_on(CommandName::from_context(&stub())).0, "cmd");
193 assert_eq!(block_on(CommandArgs::from_context(&stub())).0, "args");
194 assert_eq!(block_on(ButtonId::from_context(&stub())).0, "stub-button");
195 assert_eq!(
196 block_on(MessageContent::from_context(&stub())).0,
197 "stub message"
198 );
199 }
200
201 #[test]
202 fn absent_fields_extract_as_empty_strings() {
203 assert_eq!(block_on(CommandName::from_context(&empty())).0, "");
204 assert_eq!(block_on(CommandArgs::from_context(&empty())).0, "");
205 assert_eq!(block_on(ButtonId::from_context(&empty())).0, "");
206 assert_eq!(block_on(MessageContent::from_context(&empty())).0, "");
207 }
208
209 #[test]
210 fn tuples_extract_every_element() {
211 let (name, args, user) =
212 block_on(<(CommandName, CommandArgs, User)>::from_context(&stub()));
213 assert_eq!((name.0.as_str(), args.0.as_str()), ("cmd", "args"));
214 assert_eq!(user.id, "stub-user");
215 }
216
217 #[test]
218 fn context_extracts_itself() {
219 let ctx = block_on(Context::from_context(&stub()));
220 assert_eq!(ctx.user_id(), "stub-user");
221 }
222
223 #[test]
224 fn typing_is_absent_without_platform_support() {
225 assert!(block_on(Typing::from_context(&stub())).0.is_none());
227 }
228}