use std::convert::Infallible; use std::error::Error as StdError; use std::panic::Location; #[derive(Debug)] pub struct Error { pub inner: ChorusError, location: &'static Location<'static>, } impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { Some(&self.inner) } } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}, {}", self.inner, self.location) } } /// Errors that can occur in the chorus crate #[derive(Debug)] pub enum ChorusError { // Nostr AUTH failure AuthFailure(String), // Auth required AuthRequired, // Bad request BadRequest(&'static str), // Bad X-Real-Ip header BadRealIpHeader(String), // Bad X-Real-Ip header characters BadRealIpHeaderCharacters, // Event is banned BannedEvent, // User is banned BannedUser, // Base64 Decode Error Base64Decode(base64::DecodeError), // 24b // Blocked IP BlockedIp, // Blossom Authorization failure BlossomAuthFailure(String), // Channel Recv ChannelRecv(tokio::sync::broadcast::error::RecvError), // 24b // Channel Send ChannelSend(tokio::sync::broadcast::error::SendError), // 16b // Config Config(Box), // 16b // Crypto Crypto(secp256k1::Error), // 16b // Closing on error(s) ErrorClose, // Event is Invalid EventIsInvalid(String), // From hex FromHex(hex::FromHexError), // 24b // From UTF8 FromUtf8(Box), // General General(String), // Http Http(Box), // Hyper Hyper(hyper::Error), // Infallible Infallible, // Invalid URI InvalidUri(hyper::http::uri::InvalidUri), // Invalid URI Parts InvalidUriParts(hyper::http::uri::InvalidUriParts), // I/O Io(std::io::Error), // Management Authorization failure ManagementAuthFailure(String), // Missing Table MissingTable(&'static str), // Negentropy error Negentropy(negentropy::Error), // Non-ASCII HTTP header value NonAsciiHttpHeaderValue(http::header::ToStrError), // No private key NoPrivateKey, // Not Implemented NotImplemented, // No such subscription NoSuchSubscription, // Protected Event ProtectedEvent, // Pocket Db Error PocketDb(pocket_db::Error), // Pocket Db Heed Error PocketDbHeed(Box), // Pocket Types Error PocketType(pocket_types::Error), // Rate limit exceeded RateLimitExceeded, // X-Real-Ip header is missing RealIpHeaderMissing, // Restricted Restricted, // Rustls Rustls(Box), // Filter is underspecified Scraper, // Serde JSON SerdeJson(serde_json::Error), // Shutting Down ShuttingDown, // Signal - Not Blossom Request SignalNotBlossom, // Speedy Speedy(speedy::Error), // Timed Out TimedOut, // Too many subscriptions TooManySubscriptions, // Tungstenite Tungstenite(Box), // URL Parse UrlParse(url::ParseError), // UTF-8 Utf8(std::str::Utf8Error), // UTF-8 Utf8Error, // Tungstenite Protocol WebsocketProtocol(hyper_tungstenite::tungstenite::error::ProtocolError), } impl std::fmt::Display for ChorusError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ChorusError::AuthFailure(s) => write!(f, "AUTH failure: {s}"), ChorusError::AuthRequired => write!(f, "AUTH required"), ChorusError::BadRequest(s) => write!(f, "Bad Request: {s}"), ChorusError::BadRealIpHeader(s) => write!(f, "Bad X-Real-Ip header: {s}"), ChorusError::BadRealIpHeaderCharacters => { write!(f, "Bad X-Real-Ip header (non utf-8 characters)") } ChorusError::BannedEvent => write!(f, "Event is banned"), ChorusError::BannedUser => write!(f, "User is banned"), ChorusError::Base64Decode(e) => write!(f, "{e}"), ChorusError::BlockedIp => write!(f, "IP is temporarily blocked"), ChorusError::BlossomAuthFailure(s) => write!(f, "Authorization failure: {s}"), ChorusError::ChannelRecv(e) => write!(f, "{e}"), ChorusError::ChannelSend(e) => write!(f, "{e}"), ChorusError::Config(e) => write!(f, "{e}"), ChorusError::Crypto(e) => write!(f, "{e}"), ChorusError::ErrorClose => write!(f, "Closing due to error(s)"), ChorusError::EventIsInvalid(s) => write!(f, "Event is invalid: {s}"), ChorusError::FromHex(e) => write!(f, "{e}"), ChorusError::FromUtf8(e) => write!(f, "{e}"), ChorusError::General(s) => write!(f, "{s}"), ChorusError::Http(e) => write!(f, "{e}"), ChorusError::Hyper(e) => write!(f, "{e}"), ChorusError::Infallible => panic!("INFALLIBLE"), ChorusError::InvalidUri(e) => write!(f, "{e}"), ChorusError::InvalidUriParts(e) => write!(f, "{e}"), ChorusError::Io(e) => write!(f, "{e}"), ChorusError::ManagementAuthFailure(s) => write!(f, "Authorization failure: {s}"), ChorusError::MissingTable(t) => write!(f, "Missing table: {t}"), ChorusError::Negentropy(e) => write!(f, "Negentropy: {e}"), ChorusError::NonAsciiHttpHeaderValue(e) => { write!(f, "Non ASCII HTTP header value: {e}") } ChorusError::NoPrivateKey => write!(f, "Private Key Not Found"), ChorusError::NotImplemented => write!(f, "Not implemented"), ChorusError::NoSuchSubscription => write!(f, "No such subscription"), ChorusError::PocketDb(e) => write!(f, "{e}"), ChorusError::PocketDbHeed(e) => write!(f, "{e}"), ChorusError::PocketType(e) => write!(f, "{e}"), ChorusError::RateLimitExceeded => write!(f, "Rate limit exceeded"), ChorusError::ProtectedEvent => write!(f, "Protected event"), ChorusError::RealIpHeaderMissing => write!(f, "X-Real-Ip header is missing"), ChorusError::Restricted => write!(f, "Restricted"), ChorusError::Rustls(e) => write!(f, "{e}"), ChorusError::Scraper => write!(f, "Filter is underspecified. Scrapers are not allowed"), ChorusError::SerdeJson(e) => write!(f, "{e}"), ChorusError::ShuttingDown => write!(f, "Shutting down"), ChorusError::SignalNotBlossom => write!(f, "internal-signal-not-blossom"), ChorusError::Speedy(e) => write!(f, "{e}"), ChorusError::TimedOut => write!(f, "Timed out"), ChorusError::TooManySubscriptions => write!(f, "Too many subscriptions"), ChorusError::Tungstenite(e) => write!(f, "{e}"), ChorusError::UrlParse(e) => write!(f, "{e}"), ChorusError::Utf8(e) => write!(f, "{e}"), ChorusError::Utf8Error => write!(f, "UTF-8 error"), ChorusError::WebsocketProtocol(e) => write!(f, "{e}"), } } } impl StdError for ChorusError { fn source(&self) -> Option<&(dyn StdError + 'static)> { match self { ChorusError::Base64Decode(e) => Some(e), ChorusError::ChannelRecv(e) => Some(e), ChorusError::ChannelSend(e) => Some(e), ChorusError::Config(e) => Some(e), ChorusError::Crypto(e) => Some(e), ChorusError::FromHex(e) => Some(e), ChorusError::FromUtf8(e) => Some(e), ChorusError::Http(e) => Some(e), ChorusError::Hyper(e) => Some(e), ChorusError::InvalidUri(e) => Some(e), ChorusError::InvalidUriParts(e) => Some(e), ChorusError::Io(e) => Some(e), ChorusError::NonAsciiHttpHeaderValue(e) => Some(e), ChorusError::PocketDb(e) => Some(e), ChorusError::PocketDbHeed(e) => Some(e), ChorusError::PocketType(e) => Some(e), ChorusError::Rustls(e) => Some(e), ChorusError::SerdeJson(e) => Some(e), ChorusError::Speedy(e) => Some(e), ChorusError::Tungstenite(e) => Some(e), ChorusError::UrlParse(e) => Some(e), ChorusError::Utf8(e) => Some(e), ChorusError::WebsocketProtocol(e) => Some(e), _ => None, } } } impl ChorusError { #[track_caller] pub fn into_err(self) -> Error { Error { inner: self, location: std::panic::Location::caller(), } } pub fn punishment(&self) -> f32 { match self { ChorusError::AuthFailure(_) => 0.25, ChorusError::AuthRequired => 0.0, ChorusError::BadRequest(_) => 0.1, ChorusError::BadRealIpHeader(_) => 0.0, ChorusError::BadRealIpHeaderCharacters => 0.0, ChorusError::BannedEvent => 0.1, ChorusError::BannedUser => 0.2, ChorusError::Base64Decode(_) => 0.0, ChorusError::BlockedIp => 0.0, ChorusError::BlossomAuthFailure(_) => 0.0, ChorusError::ChannelRecv(_) => 0.0, ChorusError::ChannelSend(_) => 0.0, ChorusError::Config(_) => 0.0, ChorusError::Crypto(_) => 0.1, ChorusError::ErrorClose => 1.0, ChorusError::EventIsInvalid(_) => 0.2, ChorusError::FromHex(_) => 0.2, ChorusError::FromUtf8(_) => 0.2, ChorusError::General(_) => 0.0, ChorusError::Http(_) => 0.0, ChorusError::Hyper(_) => 0.0, ChorusError::Infallible => panic!("INFALLIBLE"), ChorusError::InvalidUri(_) => 0.0, ChorusError::InvalidUriParts(_) => 0.0, ChorusError::Io(_) => 0.0, ChorusError::ManagementAuthFailure(_) => 0.0, ChorusError::MissingTable(_) => 0.0, ChorusError::Negentropy(_) => 0.1, ChorusError::NonAsciiHttpHeaderValue(_) => 0.2, ChorusError::NoPrivateKey => 0.0, ChorusError::NotImplemented => 0.0, ChorusError::NoSuchSubscription => 0.05, ChorusError::PocketDb(_) => 0.0, ChorusError::PocketDbHeed(_) => 0.0, ChorusError::PocketType(_) => 0.25, ChorusError::RateLimitExceeded => 1.0, ChorusError::ProtectedEvent => 0.35, ChorusError::RealIpHeaderMissing => 0.0, ChorusError::Restricted => 0.1, ChorusError::Rustls(_) => 0.0, ChorusError::Scraper => 0.4, ChorusError::SerdeJson(_) => 0.0, ChorusError::ShuttingDown => 0.0, ChorusError::SignalNotBlossom => 0.0, ChorusError::Speedy(_) => 0.0, ChorusError::TimedOut => 0.1, ChorusError::TooManySubscriptions => 0.1, ChorusError::Tungstenite(_) => 0.0, ChorusError::UrlParse(_) => 0.1, ChorusError::Utf8(_) => 0.1, ChorusError::Utf8Error => 0.1, ChorusError::WebsocketProtocol(_) => 0.1, } } } // Note: we impl Into because our typical pattern is ChorusError::Variant.into() // when we tried implementing From, the location was deep in rust code's // blanket into implementation, which wasn't the line number we wanted. // // As for converting other error types (below) the try! macro uses From so it // is correct. #[allow(clippy::from_over_into)] impl Into for ChorusError { #[track_caller] fn into(self) -> Error { Error { inner: self, location: std::panic::Location::caller(), } } } impl From for Error { #[track_caller] fn from(err: tokio::sync::broadcast::error::RecvError) -> Self { Error { inner: ChorusError::ChannelRecv(err), location: std::panic::Location::caller(), } } } impl From> for Error { #[track_caller] fn from(err: tokio::sync::broadcast::error::SendError) -> Self { Error { inner: ChorusError::ChannelSend(err), location: std::panic::Location::caller(), } } } impl From for Error { #[track_caller] fn from(err: toml::de::Error) -> Self { Error { inner: ChorusError::Config(Box::new(err)), location: std::panic::Location::caller(), } } } impl From for Error { #[track_caller] fn from(err: secp256k1::Error) -> Self { Error { inner: ChorusError::Crypto(err), location: std::panic::Location::caller(), } } } impl From for Error { #[track_caller] fn from(err: hyper::http::Error) -> Self { Error { inner: ChorusError::Http(Box::new(err)), location: std::panic::Location::caller(), } } } impl From for Error { #[track_caller] fn from(err: hyper::Error) -> Self { Error { inner: ChorusError::Hyper(err), location: std::panic::Location::caller(), } } } impl From for Error { #[track_caller] fn from(err: hyper::http::uri::InvalidUri) -> Self { Error { inner: ChorusError::InvalidUri(err), location: std::panic::Location::caller(), } } } impl From for Error { #[track_caller] fn from(err: hyper::http::uri::InvalidUriParts) -> Self { Error { inner: ChorusError::InvalidUriParts(err), location: std::panic::Location::caller(), } } } impl From for Error { #[track_caller] fn from(err: std::io::Error) -> Self { Error { inner: ChorusError::Io(err), location: std::panic::Location::caller(), } } } impl From for Error { #[track_caller] fn from(err: http::header::ToStrError) -> Self { Error { inner: ChorusError::NonAsciiHttpHeaderValue(err), location: std::panic::Location::caller(), } } } impl From for Error { #[track_caller] fn from(err: pocket_db::Error) -> Self { Error { inner: ChorusError::PocketDb(err), location: std::panic::Location::caller(), } } } impl From for Error { #[track_caller] fn from(err: pocket_db::heed::Error) -> Self { Error { inner: ChorusError::PocketDbHeed(Box::new(err)), location: std::panic::Location::caller(), } } } impl From for Error { #[track_caller] fn from(err: pocket_types::Error) -> Self { Error { inner: ChorusError::PocketType(err), location: std::panic::Location::caller(), } } } impl From for Error { #[track_caller] fn from(err: tokio_rustls::rustls::Error) -> Self { Error { inner: ChorusError::Rustls(Box::new(err)), location: std::panic::Location::caller(), } } } impl From for Error { #[track_caller] fn from(err: hyper_tungstenite::tungstenite::error::Error) -> Self { Error { inner: ChorusError::Tungstenite(Box::new(err)), location: std::panic::Location::caller(), } } } impl From for Error { #[track_caller] fn from(err: std::str::Utf8Error) -> Self { Error { inner: ChorusError::Utf8(err), location: std::panic::Location::caller(), } } } impl From for Error { #[track_caller] fn from(err: hyper_tungstenite::tungstenite::error::ProtocolError) -> Self { Error { inner: ChorusError::WebsocketProtocol(err), location: std::panic::Location::caller(), } } } impl From for Error { #[track_caller] fn from(err: url::ParseError) -> Self { Error { inner: ChorusError::UrlParse(err), location: std::panic::Location::caller(), } } } impl From for Error { #[track_caller] fn from(err: speedy::Error) -> Self { Error { inner: ChorusError::Speedy(err), location: std::panic::Location::caller(), } } } impl From for Error { #[track_caller] fn from(err: serde_json::Error) -> Self { Error { inner: ChorusError::SerdeJson(err), location: std::panic::Location::caller(), } } } impl From for Error { #[track_caller] fn from(err: hex::FromHexError) -> Self { Error { inner: ChorusError::FromHex(err), location: std::panic::Location::caller(), } } } impl From for Error { #[track_caller] fn from(err: std::string::FromUtf8Error) -> Self { Error { inner: ChorusError::FromUtf8(Box::new(err)), location: std::panic::Location::caller(), } } } impl From for Error { #[track_caller] fn from(err: base64::DecodeError) -> Self { Error { inner: ChorusError::Base64Decode(err), location: std::panic::Location::caller(), } } } impl From for Error { #[track_caller] fn from(e: negentropy::Error) -> Error { Error { inner: ChorusError::Negentropy(e), location: std::panic::Location::caller(), } } } impl From for Error { fn from(_: Infallible) -> Self { panic!("INFALLIBLE") } } impl From for std::io::Error { fn from(e: Error) -> std::io::Error { std::io::Error::other(e) } }