1#[derive(thiserror::Error, Debug)]
2pub enum Error {
3 #[error("a connection could not be established with Discord")]
4 NoConnection,
5 #[error("a channel is full and can't receive more messages")]
6 ChannelFull,
7 #[error("a channel is disconnected and no more messages can be sent")]
8 ChannelDisconnected,
9 #[error("Discord closed the connection: {0}")]
10 Close(String),
11 #[error("received an invalid message Discord which indicates the connection is corrupted")]
12 CorruptConnection,
13 #[error("a message from Discord was missing expected field '{0}'")]
14 MissingField(&'static str),
15 #[error("a message from Discord contained invalid field '{0}'")]
16 InvalidField(&'static str),
17 #[error("an I/O error occured {action}: '{error}'")]
18 Io {
19 action: &'static str,
20 #[source]
21 error: std::io::Error,
22 },
23 #[error("more than 1 URL placeholder used in launch arguments")]
24 TooManyUrls,
25 #[error(transparent)]
26 Json(#[from] serde_json::Error),
27 #[error("encountered unknown variant '{value}' for '{kind}'")]
28 UnknownVariant { kind: &'static str, value: u32 },
29 #[error(transparent)]
30 AppRegistration(#[from] anyhow::Error),
31 #[error(transparent)]
32 Discord(#[from] DiscordErr),
33 #[error("a lobby activity join was not of the form '<lobby_id>:<lobby_secret>'")]
34 NonCanonicalLobbyActivitySecret,
35 #[error("an asynchronous operation did not complete in the allotted time")]
36 TimedOut,
37}
38
39impl<T> From<crossbeam_channel::TrySendError<T>> for Error {
40 #[inline]
41 fn from(se: crossbeam_channel::TrySendError<T>) -> Self {
42 match se {
43 crossbeam_channel::TrySendError::Full(_) => Self::ChannelFull,
44 crossbeam_channel::TrySendError::Disconnected(_) => Self::ChannelDisconnected,
45 }
46 }
47}
48
49impl<T> From<crossbeam_channel::SendError<T>> for Error {
50 #[inline]
51 fn from(_se: crossbeam_channel::SendError<T>) -> Self {
52 Self::ChannelDisconnected
53 }
54}
55
56impl<T> From<tokio::sync::mpsc::error::SendError<T>> for Error {
57 #[inline]
58 fn from(_se: tokio::sync::mpsc::error::SendError<T>) -> Self {
59 Self::ChannelDisconnected
60 }
61}
62
63impl From<tokio::sync::oneshot::error::RecvError> for Error {
64 #[inline]
65 fn from(_se: tokio::sync::oneshot::error::RecvError) -> Self {
66 Self::ChannelDisconnected
67 }
68}
69
70impl From<tokio::time::error::Elapsed> for Error {
71 #[inline]
72 fn from(_se: tokio::time::error::Elapsed) -> Self {
73 Self::TimedOut
74 }
75}
76
77impl Error {
78 #[inline]
79 pub(crate) fn io(action: &'static str, error: std::io::Error) -> Self {
80 Self::Io { action, error }
81 }
82}
83
84#[derive(thiserror::Error, Debug)]
86pub enum DiscordErr {
87 #[error("expected response of '{expected:?}' for request '{nonce}' but received '{actual:?}'")]
88 MismatchedResponse {
89 expected: crate::CommandKind,
90 actual: crate::CommandKind,
91 nonce: usize,
92 },
93 #[error(transparent)]
94 Api(#[from] DiscordApiErr),
95}
96
97#[derive(thiserror::Error, Debug)]
103pub enum DiscordApiErr {
104 #[error("already connected to lobby")]
105 AlreadyConnectedToLobby,
106 #[error("already connecting to lobby")]
107 AlreadyConnectingToLobby,
108 #[error("Discord encountered an unknown error processing the command")]
109 Unknown,
110 #[error("Discord sent an error response with no actual data")]
111 NoErrorData,
112 #[error("we sent a malformed RPC message to Discord")]
113 MalformedCommand,
114 #[error("{code:?}: error \"{message:?}\" not specifically known at this time")]
115 Generic {
116 code: Option<u32>,
117 message: Option<String>,
118 },
119 #[error("secret used to join a lobby was invalid")]
120 InvalidLobbySecret,
121 #[error("invalid command: {reason}")]
122 InvalidCommand { reason: String },
123}
124
125impl<'stack> From<Option<crate::types::ErrorPayloadStack<'stack>>> for DiscordApiErr {
126 fn from(payload: Option<crate::types::ErrorPayloadStack<'stack>>) -> Self {
127 match payload {
128 Some(payload) => {
129 let code = payload.code;
130 let message = payload.message;
131
132 let to_known = |expected: &'static str, err: Self| -> Self {
133 if message.as_deref() == Some(expected) {
134 err
135 } else {
136 Self::Generic {
137 code,
138 message: message.as_ref().map(|s| s.to_string()),
139 }
140 }
141 };
142
143 match payload.code {
144 Some(inner) => match inner {
145 1000 => to_known("Unknown Error", Self::Unknown),
146 1003 => to_known("protocol error", Self::MalformedCommand),
147 4000 => Self::InvalidCommand {
148 reason: message
149 .map_or_else(|| "unknown problem".to_owned(), |s| s.into_owned()),
150 },
151 4002 => match message.as_deref() {
152 Some(msg) if msg.starts_with("Invalid command: ") => {
153 Self::InvalidCommand {
154 reason: msg
155 .strip_prefix("Invalid command: ")
156 .unwrap_or("unknown")
157 .to_owned(),
158 }
159 }
160 _ => Self::Generic {
161 code,
162 message: message.map(|s| s.into_owned()),
163 },
164 },
165 _ => Self::Generic {
166 code,
167 message: message.map(|s| s.into_owned()),
168 },
169 },
170 None => Self::Generic {
171 code,
172 message: message.map(|s| s.into_owned()),
173 },
174 }
175 }
176 None => Self::NoErrorData,
177 }
178 }
179}