This repository has been archived on 2026-05-27. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
rustypipe/src/error.rs
2023-05-11 17:18:58 +02:00

198 lines
6.6 KiB
Rust

//! RustyPipe error types
use std::{borrow::Cow, fmt::Display};
/// Error type for the RustyPipe library
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum Error {
/// Error extracting content from YouTube
#[error("extraction error: {0}")]
Extraction(#[from] ExtractionError),
/// Error from the HTTP client
#[error("http error: {0}")]
Http(Cow<'static, str>),
/// Erroneous HTTP status code received
#[error("http status code: {0} message: {1}")]
HttpStatus(u16, Cow<'static, str>),
/// Unspecified error
#[error("error: {0}")]
Other(Cow<'static, str>),
}
/// Error extracting content from YouTube
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum ExtractionError {
/// Video cannot be extracted with RustyPipe
///
/// Reasons include:
/// - Deletion/Censorship
/// - Private video that requires a Google account
/// - DRM (Movies and TV shows)
#[error("video cant be played because it is {reason}. Reason (from YT): {msg}")]
VideoUnavailable {
/// Reason why the video could not be extracted
reason: UnavailabilityReason,
/// The error message as returned from YouTube
msg: String,
},
/// Content with the given ID does not exist
#[error("content `{id}` was not found ({msg})")]
NotFound {
/// ID of the requested content
id: String,
/// Error message
msg: Cow<'static, str>,
},
/// Bad request (Error 400 from YouTube), probably invalid input parameters
#[error("bad request ({0})")]
BadRequest(Cow<'static, str>),
/// YouTube returned data that could not be deserialized or parsed
#[error("invalid data from YT: {0}")]
InvalidData(Cow<'static, str>),
/// Error deobfuscating YouTube's URL signatures
#[error("deobfuscation error: {0}")]
Deobfuscation(Cow<'static, str>),
/// YouTube returned data that does not match the queried ID
///
/// Specifically YouTube may return this video <https://www.youtube.com/watch?v=aQvGIIdgFDM>,
/// which is a 5 minute error message, instead of the requested video when using an outdated
/// Android client.
#[error("wrong result from YT: {0}")]
WrongResult(String),
/// YouTube redirects you to another content ID
///
/// This is used internally for YouTube Music channels that link to a main channel.
#[error("redirecting to: {0}")]
Redirect(String),
/// Warnings occurred during deserialization/mapping
///
/// This error is only returned in strict mode.
#[error("warnings during deserialization/mapping")]
DeserializationWarnings,
}
/// Reason why a video cannot be extracted
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum UnavailabilityReason {
/// Video is age restricted.
///
/// Age restriction may be circumvented with the [`crate::client::ClientType::TvHtml5Embed`] client.
AgeRestricted,
/// Video was deleted or censored
Deleted,
/// Video is not available in your country
Geoblocked,
/// Video cannot be extracted with the specified client
UnsupportedClient,
/// Video is private
Private,
/// Video needs to be purchased and is protected by digital restrictions management
/// (e.g. movies and TV shows)
Paid,
/// Video is only available to YouTube Premium users
Premium,
/// Video is only available to channel members
MembersOnly,
/// Livestream has gone offline
OfflineLivestream,
/// Video cant be played for other reasons
#[default]
Unplayable,
}
impl Display for UnavailabilityReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
UnavailabilityReason::AgeRestricted => f.write_str("age restriction"),
UnavailabilityReason::Deleted => f.write_str("deleted"),
UnavailabilityReason::Geoblocked => f.write_str("geoblocking"),
UnavailabilityReason::UnsupportedClient => f.write_str("unsupported by client"),
UnavailabilityReason::Private => f.write_str("private"),
UnavailabilityReason::Paid => f.write_str("paid"),
UnavailabilityReason::Premium => f.write_str("premium-only"),
UnavailabilityReason::MembersOnly => f.write_str("members-only"),
UnavailabilityReason::OfflineLivestream => f.write_str("an offline stream"),
UnavailabilityReason::Unplayable => f.write_str("unplayable"),
}
}
}
pub(crate) mod internal {
use super::*;
/// Error that occurred during the initialization
/// or use of the YouTube URL signature deobfuscator.
#[derive(thiserror::Error, Debug)]
pub enum DeobfError {
/// Error during JavaScript execution
#[error("js execution error: {0}")]
JavaScript(#[from] quick_js::ExecutionError),
/// Error during JavaScript parsing
#[error("js parsing: {0}")]
JsParser(#[from] ress::error::Error),
/// Could not extract certain data
#[error("could not extract {0}")]
Extraction(&'static str),
/// Unspecified error
#[error("error: {0}")]
Other(&'static str),
}
impl From<DeobfError> for Error {
fn from(value: DeobfError) -> Self {
Self::Extraction(value.into())
}
}
impl From<DeobfError> for ExtractionError {
fn from(value: DeobfError) -> Self {
Self::Deobfuscation(value.to_string().into())
}
}
}
impl From<serde_json::Error> for ExtractionError {
fn from(value: serde_json::Error) -> Self {
Self::InvalidData(value.to_string().into())
}
}
impl From<reqwest::Error> for Error {
fn from(value: reqwest::Error) -> Self {
if value.is_status() {
if let Some(status) = value.status() {
return Self::HttpStatus(status.as_u16(), Default::default());
}
}
Self::Http(value.to_string().into())
}
}
impl From<serde_plain::Error> for Error {
fn from(value: serde_plain::Error) -> Self {
Self::Other(value.to_string().into())
}
}
impl ExtractionError {
pub(crate) fn should_report(&self) -> bool {
matches!(
self,
ExtractionError::InvalidData(_) | ExtractionError::WrongResult(_)
)
}
pub(crate) fn switch_client(&self) -> bool {
matches!(
self,
ExtractionError::VideoUnavailable {
reason: UnavailabilityReason::AgeRestricted
| UnavailabilityReason::UnsupportedClient,
..
} | ExtractionError::WrongResult(_)
)
}
}