discord_sdk/
error.rs

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/// An error related to the actual use of the Discord API.
85#[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/// An actual API error event sent from Discord. This list is currently incomplete
98/// and may change at any time as it is not a documented part of the public API
99/// of Discord, eg. the [Game SDK](https://discord.com/developers/docs/game-sdk/discord#data-models)
100/// uses a simplified version that collapses a wider range of errors into simpler
101/// categories
102#[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}