use core::fmt;
use custom_error::custom_error;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::types::{CloseCode, MfaRequiredSchema, VoiceCloseCode, WebSocketEvent};
use chorus_macros::WebSocketEvent;
custom_error! {
#[derive(PartialEq, Eq, Clone, Hash)]
pub RegistrationError
Consent = "Consent must be 'true' to register.",
}
pub type ChorusResult<T> = std::result::Result<T, ChorusError>;
custom_error! {
#[derive(Clone, Hash, PartialEq, Eq)]
pub ChorusError
NoResponse = "Did not receive a response from the Server.",
RequestFailed{url:String, error: String} = "An error occurred while trying to GET from {url}: {error}",
ReceivedError{error: ApiError, response_text: String} = "Received the following error: {error}",
CantGetInformation{error:String} = "Something seems to be wrong with the instance. Cannot get information about the instance: {error}",
RateLimited{bucket:String} = "Ratelimited on Bucket {bucket}",
MultipartCreation{error: String} = "Got an error whilst creating the form: {error}",
FormCreation{error: String} = "Got an error whilst creating the form: {error}",
TokenExpired = "Token expired, invalid or not found.",
NoPermission = "You do not have the permissions needed to perform this action.",
NotFound{error: String} = "Received Not Found error ({error})",
PasswordRequired = "You need to provide your current password to authenticate for this action.",
InvalidResponse{error: String, http_status: reqwest::StatusCode} = "The response is malformed and cannot be processed. Error: {error} (status {http_status})",
InvalidArguments{error: String} = "Invalid arguments were provided. Error: {error}",
MfaRequired {error: MfaRequiredSchema} = "Mfa verification is required to perform this action",
SuspendUser { token: String } = "Your account has been suspended"
}
impl From<reqwest::Error> for ChorusError {
fn from(value: reqwest::Error) -> Self {
ChorusError::RequestFailed {
url: match value.url() {
Some(url) => url.to_string(),
None => "None".to_string(),
},
error: value.to_string(),
}
}
}
#[derive(Clone, Hash, Debug, PartialEq, Eq, Error)]
pub struct ApiError {
pub json_error: JsonError,
pub http_status: http::StatusCode,
}
impl fmt::Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(message) = &self.json_error.message {
return write!(
f,
"{} (code {}, status {})",
message, self.json_error.code, self.http_status
);
}
write!(
f,
"Unknown error (code {}, status {})",
self.json_error.code, self.http_status
)
}
}
#[derive(Clone, Serialize, Deserialize, Hash, Debug, PartialEq, Eq, Error)]
pub struct JsonError {
pub code: u32,
#[serde(default)]
pub message: Option<String>,
#[serde(default)]
pub errors: Option<serde_json::Value>,
}
impl fmt::Display for JsonError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(message) = &self.message {
return write!(f, "{} (code {})", message, self.code);
}
write!(f, "Unknown error (code {})", self.code)
}
}
#[derive(Clone, Serialize, Deserialize, Hash, Debug, PartialEq, Eq, Ord, PartialOrd)]
pub struct JsonErrorKey {
#[serde(rename = "_errors")]
pub errors: Vec<JsonErrorKeyError>,
}
#[derive(Clone, Serialize, Deserialize, Hash, Debug, PartialEq, Eq, Ord, PartialOrd)]
pub struct JsonErrorKeyError {
pub code: String,
pub message: String,
}
custom_error! {
#[derive(PartialEq, Eq)]
pub ObserverError
AlreadySubscribed = "Each event can only be subscribed to once."
}
custom_error! {
#[derive(PartialEq, Eq, Default, Clone, WebSocketEvent)]
pub GatewayError
#[default]
Unknown = "We're not sure what went wrong. Try reconnecting?",
UnknownOpcode = "You sent an invalid Gateway opcode or an invalid payload for an opcode",
Decode = "Gateway server couldn't decode payload",
NotAuthenticated = "You sent a payload prior to identifying",
AuthenticationFailed = "The account token sent with your identify payload is invalid",
AlreadyAuthenticated = "You've already identified, no need to reauthenticate",
InvalidSequenceNumber = "The sequence number sent when resuming the session was invalid. Reconnect and start a new session",
RateLimited = "You are being rate limited!",
SessionTimedOut = "Your session timed out. Reconnect and start a new one",
InvalidShard = "You sent us an invalid shard when identifying",
ShardingRequired = "The session would have handled too many guilds - you are required to shard your connection in order to connect",
InvalidAPIVersion = "You sent an invalid Gateway version",
InvalidIntents = "You sent an invalid intent",
DisallowedIntents = "You sent a disallowed intent. You may have tried to specify an intent that you have not enabled or are not approved for",
CannotConnect{error: String} = "Cannot connect due to a websocket error: {error}",
NonHelloOnInitiate{opcode: u8} = "Received non hello on initial gateway connection ({opcode}), something is definitely wrong",
NoResponse = "Server did not respond in time",
UnexpectedOpcodeReceived{opcode: u8} = "Received an opcode we weren't expecting to receive: {opcode}",
}
impl From<CloseCode> for GatewayError {
fn from(value: CloseCode) -> Self {
match value {
CloseCode::UnknownError => GatewayError::Unknown,
CloseCode::UnknownOpcode => GatewayError::UnknownOpcode,
CloseCode::DecodeError => GatewayError::Decode,
CloseCode::NotAuthenticated => GatewayError::NotAuthenticated,
CloseCode::AuthenticationFailed => GatewayError::AuthenticationFailed,
CloseCode::AlreadyAuthenticated => GatewayError::AlreadyAuthenticated,
CloseCode::InvalidSeq => GatewayError::InvalidSequenceNumber,
CloseCode::RateLimited => GatewayError::RateLimited,
CloseCode::SessionTimeout => GatewayError::SessionTimedOut,
CloseCode::SessionNoLongerValid => GatewayError::SessionTimedOut,
CloseCode::InvalidShard => GatewayError::InvalidShard,
CloseCode::ShardingRequired => GatewayError::ShardingRequired,
CloseCode::InvalidApiVersion => GatewayError::InvalidAPIVersion,
CloseCode::InvalidIntents => GatewayError::InvalidIntents,
CloseCode::DisallowedIntents => GatewayError::DisallowedIntents,
}
}
}
custom_error! {
#[derive(Clone, Default, PartialEq, Eq, WebSocketEvent)]
pub VoiceGatewayError
#[default]
UnknownOpcode = "You sent an invalid opcode",
FailedToDecodePayload = "You sent an invalid payload in your identifying to the (Voice) Gateway",
NotAuthenticated = "You sent a payload before identifying with the (Voice) Gateway",
AuthenticationFailed = "The token you sent in your identify payload is incorrect",
AlreadyAuthenticated = "You sent more than one identify payload",
SessionNoLongerValid = "Your session is no longer valid",
SessionTimedOut = "Your session has timed out",
ServerNotFound = "We can't find the server you're trying to connect to",
UnknownProtocol = "We didn't recognize the protocol you sent",
Disconnected = "Channel was deleted, you were kicked, voice server changed, or the main gateway session was dropped. Should not reconnect.",
VoiceServerCrashed = "The server crashed, try resuming",
UnknownEncryptionMode = "Server failed to decrypt data",
CannotConnect{error: String} = "Cannot connect due to a websocket error: {error}",
NonHelloOnInitiate{opcode: u8} = "Received non hello on initial gateway connection ({opcode}), something is definitely wrong",
UnexpectedOpcodeReceived{opcode: u8} = "Received an opcode we weren't expecting to receive: {opcode}",
}
impl From<VoiceCloseCode> for VoiceGatewayError {
fn from(value: VoiceCloseCode) -> Self {
match value {
VoiceCloseCode::UnknownOpcode => VoiceGatewayError::UnknownOpcode,
VoiceCloseCode::FailedToDecodePayload => VoiceGatewayError::FailedToDecodePayload,
VoiceCloseCode::NotAuthenticated => VoiceGatewayError::NotAuthenticated,
VoiceCloseCode::AuthenticationFailed => VoiceGatewayError::AuthenticationFailed,
VoiceCloseCode::AlreadyAuthenticated => VoiceGatewayError::AlreadyAuthenticated,
VoiceCloseCode::SessionTimeout => VoiceGatewayError::SessionTimedOut,
VoiceCloseCode::SessionNoLongerValid => VoiceGatewayError::SessionNoLongerValid,
VoiceCloseCode::ServerNotFound => VoiceGatewayError::ServerNotFound,
VoiceCloseCode::UnknownProtocol => VoiceGatewayError::UnknownProtocol,
VoiceCloseCode::DisconnectedChannelDeletedOrKicked => VoiceGatewayError::Disconnected,
VoiceCloseCode::VoiceServerCrashed => VoiceGatewayError::VoiceServerCrashed,
VoiceCloseCode::UnknownEncryptionMode => VoiceGatewayError::UnknownEncryptionMode,
}
}
}
custom_error! {
#[derive(Clone, PartialEq, Eq, WebSocketEvent)]
pub VoiceUdpError
BrokenSocket{error: String} = "Could not write / read from UDP socket: {error}",
NoData = "We have not set received the necessary data to perform this operation.",
EncryptionModeNotImplemented{encryption_mode: String} = "Voice encryption mode {encryption_mode} is not yet implemented.",
NoKey = "Tried to encrypt / decrypt rtp data, but no key has been received yet",
FailedEncryption = "Tried to encrypt rtp data, but failed. Most likely this is an issue chorus' nonce generation. Please open an issue on the chorus github: https://github.com/polyphony-chat/chorus/issues/new",
FailedDecryption = "Tried to decrypt rtp data, but failed. Most likely this is an issue chorus' nonce generation. Please open an issue on the chorus github: https://github.com/polyphony-chat/chorus/issues/new",
FailedNonceGeneration{error: String} = "Tried to generate nonce, but failed due to error: {error}.",
CannotBind{error: String} = "Cannot bind socket due to a UDP error: {error}",
CannotConnect{error: String} = "Cannot connect due to a UDP error: {error}",
}