refactor: uopdate NotFound error type

This commit is contained in:
ThetaDev 2023-05-08 15:21:06 +02:00
parent 289b1cdbf4
commit c021496a55
10 changed files with 119 additions and 137 deletions

View file

@ -164,7 +164,7 @@ impl MapResponse<Channel<Paginator<VideoItem>>> for response::Channel {
lang: Language, lang: Language,
_deobf: Option<&crate::deobfuscate::DeobfData>, _deobf: Option<&crate::deobfuscate::DeobfData>,
) -> Result<MapResult<Channel<Paginator<VideoItem>>>, ExtractionError> { ) -> Result<MapResult<Channel<Paginator<VideoItem>>>, ExtractionError> {
let content = map_channel_content(self.contents, self.alerts)?; let content = map_channel_content(id, self.contents, self.alerts)?;
let channel_data = map_channel( let channel_data = map_channel(
MapChannelData { MapChannelData {
@ -207,7 +207,7 @@ impl MapResponse<Channel<Paginator<PlaylistItem>>> for response::Channel {
lang: Language, lang: Language,
_deobf: Option<&crate::deobfuscate::DeobfData>, _deobf: Option<&crate::deobfuscate::DeobfData>,
) -> Result<MapResult<Channel<Paginator<PlaylistItem>>>, ExtractionError> { ) -> Result<MapResult<Channel<Paginator<PlaylistItem>>>, ExtractionError> {
let content = map_channel_content(self.contents, self.alerts)?; let content = map_channel_content(id, self.contents, self.alerts)?;
let channel_data = map_channel( let channel_data = map_channel(
MapChannelData { MapChannelData {
@ -244,7 +244,7 @@ impl MapResponse<Channel<ChannelInfo>> for response::Channel {
lang: Language, lang: Language,
_deobf: Option<&crate::deobfuscate::DeobfData>, _deobf: Option<&crate::deobfuscate::DeobfData>,
) -> Result<MapResult<Channel<ChannelInfo>>, ExtractionError> { ) -> Result<MapResult<Channel<ChannelInfo>>, ExtractionError> {
let content = map_channel_content(self.contents, self.alerts)?; let content = map_channel_content(id, self.contents, self.alerts)?;
let channel_data = map_channel( let channel_data = map_channel(
MapChannelData { MapChannelData {
header: self.header, header: self.header,
@ -304,22 +304,21 @@ fn map_channel(
id: &str, id: &str,
lang: Language, lang: Language,
) -> Result<MapResult<Channel<()>>, ExtractionError> { ) -> Result<MapResult<Channel<()>>, ExtractionError> {
let header = d let header = d.header.ok_or_else(|| ExtractionError::NotFound {
.header id: id.to_owned(),
.ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed( msg: "no header".into(),
"channel not found", })?;
)))?;
let metadata = d let metadata = d
.metadata .metadata
.ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed( .ok_or_else(|| ExtractionError::NotFound {
"channel not found", id: id.to_owned(),
)))? msg: "no metadata".into(),
})?
.channel_metadata_renderer; .channel_metadata_renderer;
let microformat = d let microformat = d.microformat.ok_or_else(|| ExtractionError::NotFound {
.microformat id: id.to_owned(),
.ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed( msg: "no microformat".into(),
"channel not found", })?;
)))?;
if metadata.external_id != id { if metadata.external_id != id {
return Err(ExtractionError::WrongResult(format!( return Err(ExtractionError::WrongResult(format!(
@ -405,6 +404,7 @@ struct MappedChannelContent {
} }
fn map_channel_content( fn map_channel_content(
id: &str,
contents: Option<response::channel::Contents>, contents: Option<response::channel::Contents>,
alerts: Option<Vec<response::Alert>>, alerts: Option<Vec<response::Alert>>,
) -> Result<MappedChannelContent, ExtractionError> { ) -> Result<MappedChannelContent, ExtractionError> {
@ -412,9 +412,10 @@ fn map_channel_content(
Some(contents) => { Some(contents) => {
let tabs = contents.two_column_browse_results_renderer.contents; let tabs = contents.two_column_browse_results_renderer.contents;
if tabs.is_empty() { if tabs.is_empty() {
return Err(ExtractionError::ContentUnavailable( return Err(ExtractionError::NotFound {
"channel not found".into(), id: id.to_owned(),
)); msg: "no tabs".into(),
});
} }
let cmp_url_suffix = |endpoint: &response::channel::ChannelTabEndpoint, let cmp_url_suffix = |endpoint: &response::channel::ChannelTabEndpoint,
@ -470,7 +471,7 @@ fn map_channel_content(
has_live, has_live,
}) })
} }
None => Err(response::alerts_to_err(alerts)), None => Err(response::alerts_to_err(id, alerts)),
} }
} }

View file

@ -16,18 +16,20 @@ impl RustyPipeQuery {
/// Fetching RSS feeds is a lot faster than querying the InnerTube API, so this method is great /// Fetching RSS feeds is a lot faster than querying the InnerTube API, so this method is great
/// for checking a lot of channels or implementing a subscription feed. /// for checking a lot of channels or implementing a subscription feed.
pub async fn channel_rss<S: AsRef<str>>(&self, channel_id: S) -> Result<ChannelRss, Error> { pub async fn channel_rss<S: AsRef<str>>(&self, channel_id: S) -> Result<ChannelRss, Error> {
let channel_id = channel_id.as_ref();
let url = format!( let url = format!(
"https://www.youtube.com/feeds/videos.xml?channel_id={}", "https://www.youtube.com/feeds/videos.xml?channel_id={}",
channel_id.as_ref() channel_id,
); );
let xml = self let xml = self
.client .client
.http_request_txt(self.client.inner.http.get(&url).build()?) .http_request_txt(self.client.inner.http.get(&url).build()?)
.await .await
.map_err(|e| match e { .map_err(|e| match e {
Error::HttpStatus(404, _) => Error::Extraction( Error::HttpStatus(404, _) => Error::Extraction(ExtractionError::NotFound {
ExtractionError::ContentUnavailable("Channel not found".into()), id: channel_id.to_owned(),
), msg: "404".into(),
}),
_ => e, _ => e,
})?; })?;

View file

@ -1106,17 +1106,20 @@ impl RustyPipeQuery {
if status.is_client_error() || status.is_server_error() { if status.is_client_error() || status.is_server_error() {
let error_msg = serde_json::from_str::<response::ErrorResponse>(&resp_str) let error_msg = serde_json::from_str::<response::ErrorResponse>(&resp_str)
.map(|r| r.error.message) .map(|r| Cow::from(r.error.message));
.unwrap_or_default();
return match status { return match status {
StatusCode::NOT_FOUND => Err(Error::Extraction( StatusCode::NOT_FOUND => Err(Error::Extraction(ExtractionError::NotFound {
ExtractionError::ContentUnavailable(error_msg.into()), id: id.to_owned(),
)), msg: error_msg.unwrap_or("404".into()),
})),
StatusCode::BAD_REQUEST => Err(Error::Extraction(ExtractionError::BadRequest( StatusCode::BAD_REQUEST => Err(Error::Extraction(ExtractionError::BadRequest(
error_msg.into(), error_msg.unwrap_or_default(),
))), ))),
_ => Err(Error::HttpStatus(status.as_u16(), error_msg.into())), _ => Err(Error::HttpStatus(
status.as_u16(),
error_msg.unwrap_or_default(),
)),
}; };
} }

View file

@ -193,9 +193,10 @@ impl MapResponse<TrackDetails> for response::MusicDetails {
} }
} }
let content = content.ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed( let content = content.ok_or_else(|| ExtractionError::NotFound {
"track not found", id: id.to_owned(),
)))?; msg: "no content".into(),
})?;
let track_item = content let track_item = content
.contents .contents
.c .c
@ -233,7 +234,7 @@ impl MapResponse<TrackDetails> for response::MusicDetails {
impl MapResponse<Paginator<TrackItem>> for response::MusicDetails { impl MapResponse<Paginator<TrackItem>> for response::MusicDetails {
fn map_response( fn map_response(
self, self,
_id: &str, id: &str,
lang: Language, lang: Language,
_deobf: Option<&crate::deobfuscate::DeobfData>, _deobf: Option<&crate::deobfuscate::DeobfData>,
) -> Result<MapResult<Paginator<TrackItem>>, ExtractionError> { ) -> Result<MapResult<Paginator<TrackItem>>, ExtractionError> {
@ -247,9 +248,10 @@ impl MapResponse<Paginator<TrackItem>> for response::MusicDetails {
let content = tabs let content = tabs
.into_iter() .into_iter()
.find_map(|t| t.tab_renderer.content) .find_map(|t| t.tab_renderer.content)
.ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed( .ok_or_else(|| ExtractionError::NotFound {
"radio unavailable", id: id.to_owned(),
)))? msg: "no content".into(),
})?
.music_queue_renderer .music_queue_renderer
.content .content
.playlist_panel_renderer; .playlist_panel_renderer;
@ -292,7 +294,7 @@ impl MapResponse<Paginator<TrackItem>> for response::MusicDetails {
impl MapResponse<Lyrics> for response::MusicLyrics { impl MapResponse<Lyrics> for response::MusicLyrics {
fn map_response( fn map_response(
self, self,
_id: &str, id: &str,
_lang: Language, _lang: Language,
_deobf: Option<&crate::deobfuscate::DeobfData>, _deobf: Option<&crate::deobfuscate::DeobfData>,
) -> Result<MapResult<Lyrics>, ExtractionError> { ) -> Result<MapResult<Lyrics>, ExtractionError> {
@ -305,7 +307,10 @@ impl MapResponse<Lyrics> for response::MusicLyrics {
.find_map(|item| item.music_description_shelf_renderer) .find_map(|item| item.music_description_shelf_renderer)
}) })
.ok_or(match self.contents.message_renderer { .ok_or(match self.contents.message_renderer {
Some(msg) => ExtractionError::ContentUnavailable(Cow::Owned(msg.text)), Some(msg) => ExtractionError::NotFound {
id: id.to_owned(),
msg: msg.text.into(),
},
None => ExtractionError::InvalidData(Cow::Borrowed("no content")), None => ExtractionError::InvalidData(Cow::Borrowed("no content")),
})?; })?;

View file

@ -62,7 +62,7 @@ impl MapResponse<Playlist> for response::Playlist {
) -> Result<MapResult<Playlist>, ExtractionError> { ) -> Result<MapResult<Playlist>, ExtractionError> {
let (contents, header) = match (self.contents, self.header) { let (contents, header) = match (self.contents, self.header) {
(Some(contents), Some(header)) => (contents, header), (Some(contents), Some(header)) => (contents, header),
_ => return Err(response::alerts_to_err(self.alerts)), _ => return Err(response::alerts_to_err(id, self.alerts)),
}; };
let video_items = contents let video_items = contents

View file

@ -353,16 +353,18 @@ impl From<Icon> for crate::model::Verification {
} }
} }
pub(crate) fn alerts_to_err(alerts: Option<Vec<Alert>>) -> ExtractionError { pub(crate) fn alerts_to_err(id: &str, alerts: Option<Vec<Alert>>) -> ExtractionError {
match alerts { ExtractionError::NotFound {
Some(alerts) => ExtractionError::ContentUnavailable( id: id.to_owned(),
alerts msg: alerts
.into_iter() .map(|alerts| {
.map(|a| a.alert_renderer.text) alerts
.collect::<Vec<_>>() .into_iter()
.join(" ") .map(|a| a.alert_renderer.text)
.into(), .collect::<Vec<_>>()
), .join(" ")
None => ExtractionError::ContentUnavailable("content not found".into()), .into()
})
.unwrap_or_default(),
} }
} }

View file

@ -163,18 +163,22 @@ impl RustyPipeQuery {
.await .await
{ {
Ok(target) => Ok(target), Ok(target) => Ok(target),
Err(Error::Extraction(ExtractionError::ContentUnavailable(e))) => { Err(e) => {
match util::VIDEO_ID_REGEX.is_match(id) { if matches!(
true => Ok(UrlTarget::Video { e,
id: id.to_owned(), Error::Extraction(ExtractionError::NotFound { .. })
start_time: get_start_time(), ) {
}), match util::VIDEO_ID_REGEX.is_match(id) {
false => Err(Error::Extraction( true => Ok(UrlTarget::Video {
ExtractionError::ContentUnavailable(e), id: id.to_owned(),
)), start_time: get_start_time(),
}),
false => Err(e),
}
} else {
Err(e)
} }
} }
Err(e) => Err(e),
} }
} else if util::VIDEO_ID_REGEX.is_match(id) { } else if util::VIDEO_ID_REGEX.is_match(id) {
Ok(UrlTarget::Video { Ok(UrlTarget::Video {

View file

@ -1,5 +1,3 @@
use std::borrow::Cow;
use serde::Serialize; use serde::Serialize;
use crate::{ use crate::{
@ -87,16 +85,16 @@ impl MapResponse<VideoDetails> for response::VideoDetails {
) -> Result<MapResult<VideoDetails>, ExtractionError> { ) -> Result<MapResult<VideoDetails>, ExtractionError> {
let mut warnings = Vec::new(); let mut warnings = Vec::new();
let contents = self let contents = self.contents.ok_or_else(|| ExtractionError::NotFound {
.contents id: id.to_owned(),
.ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed( msg: "no content".into(),
"Video not found", })?;
)))?;
let current_video_endpoint = let current_video_endpoint =
self.current_video_endpoint self.current_video_endpoint
.ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed( .ok_or_else(|| ExtractionError::NotFound {
"Video not found", id: id.to_owned(),
)))?; msg: "no current_video_endpoint".into(),
})?;
let video_id = current_video_endpoint.watch_endpoint.video_id; let video_id = current_video_endpoint.watch_endpoint.video_id;
if id != video_id { if id != video_id {
@ -110,9 +108,10 @@ impl MapResponse<VideoDetails> for response::VideoDetails {
.results .results
.results .results
.contents .contents
.ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed( .ok_or_else(|| ExtractionError::NotFound {
"Video not found", id: id.into(),
)))?; msg: "no primary_results".into(),
})?;
warnings.append(&mut primary_results.warnings); warnings.append(&mut primary_results.warnings);
let mut primary_info = None; let mut primary_info = None;
@ -585,7 +584,7 @@ mod tests {
let err = details.map_response("", Language::En, None).unwrap_err(); let err = details.map_response("", Language::En, None).unwrap_err();
assert!(matches!( assert!(matches!(
err, err,
crate::error::ExtractionError::ContentUnavailable(_) crate::error::ExtractionError::NotFound { .. }
)) ))
} }

View file

@ -30,21 +30,26 @@ pub enum ExtractionError {
/// - Deletion/Censorship /// - Deletion/Censorship
/// - Private video that requires a Google account /// - Private video that requires a Google account
/// - DRM (Movies and TV shows) /// - DRM (Movies and TV shows)
#[error("Video cant be played because it is {reason}. Reason (from YT): {msg}")] #[error("video cant be played because it is {reason}. Reason (from YT): {msg}")]
VideoUnavailable { VideoUnavailable {
/// Reason why the video could not be extracted /// Reason why the video could not be extracted
reason: UnavailabilityReason, reason: UnavailabilityReason,
/// The error message as returned from YouTube /// The error message as returned from YouTube
msg: String, msg: String,
}, },
/// Content is not available / does not exist /// Content with the given ID does not exist
#[error("Content is not available. Reason: {0}")] #[error("content `{id}` was not found ({msg})")]
ContentUnavailable(Cow<'static, str>), NotFound {
/// ID of the requested content
id: String,
/// Error message
msg: Cow<'static, str>,
},
/// Bad request (Error 400 from YouTube), probably invalid input parameters /// Bad request (Error 400 from YouTube), probably invalid input parameters
#[error("Bad request. Reason: {0}")] #[error("bad request ({0})")]
BadRequest(Cow<'static, str>), BadRequest(Cow<'static, str>),
/// YouTube returned data that could not be deserialized or parsed /// YouTube returned data that could not be deserialized or parsed
#[error("got invalid data from YT: {0}")] #[error("invalid data from YT: {0}")]
InvalidData(Cow<'static, str>), InvalidData(Cow<'static, str>),
/// Error deobfuscating YouTube's URL signatures /// Error deobfuscating YouTube's URL signatures
#[error("deobfuscation error: {0}")] #[error("deobfuscation error: {0}")]
@ -54,7 +59,7 @@ pub enum ExtractionError {
/// Specifically YouTube may return this video <https://www.youtube.com/watch?v=aQvGIIdgFDM>, /// 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 /// which is a 5 minute error message, instead of the requested video when using an outdated
/// Android client. /// Android client.
#[error("got wrong result from YT: {0}")] #[error("wrong result from YT: {0}")]
WrongResult(String), WrongResult(String),
/// YouTube redirects you to another content ID /// YouTube redirects you to another content ID
/// ///
@ -64,7 +69,7 @@ pub enum ExtractionError {
/// Warnings occurred during deserialization/mapping /// Warnings occurred during deserialization/mapping
/// ///
/// This error is only returned in strict mode. /// This error is only returned in strict mode.
#[error("Warnings during deserialization/mapping")] #[error("warnings during deserialization/mapping")]
DeserializationWarnings, DeserializationWarnings,
} }

View file

@ -383,10 +383,7 @@ fn playlist_not_found(rp: RustyPipe) {
.unwrap_err(); .unwrap_err();
assert!( assert!(
matches!( matches!(err, Error::Extraction(ExtractionError::NotFound { .. })),
err,
Error::Extraction(ExtractionError::ContentUnavailable(_))
),
"got: {err}" "got: {err}"
); );
} }
@ -729,10 +726,7 @@ fn get_video_details_not_found(rp: RustyPipe) {
let err = tokio_test::block_on(rp.query().video_details("abcdefgLi5X")).unwrap_err(); let err = tokio_test::block_on(rp.query().video_details("abcdefgLi5X")).unwrap_err();
assert!( assert!(
matches!( matches!(err, Error::Extraction(ExtractionError::NotFound { .. })),
err,
Error::Extraction(ExtractionError::ContentUnavailable(_))
),
"got: {err}" "got: {err}"
) )
} }
@ -973,10 +967,7 @@ fn channel_not_found(#[case] id: &str, rp: RustyPipe) {
let err = tokio_test::block_on(rp.query().channel_videos(&id)).unwrap_err(); let err = tokio_test::block_on(rp.query().channel_videos(&id)).unwrap_err();
assert!( assert!(
matches!( matches!(err, Error::Extraction(ExtractionError::NotFound { .. })),
err,
Error::Extraction(ExtractionError::ContentUnavailable(_))
),
"got: {err}" "got: {err}"
); );
} }
@ -1017,10 +1008,7 @@ mod channel_rss {
tokio_test::block_on(rp.query().channel_rss("UCHnyfMqiRRG1u-2MsSQLbXZ")).unwrap_err(); tokio_test::block_on(rp.query().channel_rss("UCHnyfMqiRRG1u-2MsSQLbXZ")).unwrap_err();
assert!( assert!(
matches!( matches!(err, Error::Extraction(ExtractionError::NotFound { .. })),
err,
Error::Extraction(ExtractionError::ContentUnavailable(_))
),
"got: {}", "got: {}",
err err
); );
@ -1164,7 +1152,7 @@ fn resolve_channel_not_found(rp: RustyPipe) {
assert!(matches!( assert!(matches!(
err, err,
Error::Extraction(ExtractionError::ContentUnavailable(_)) Error::Extraction(ExtractionError::NotFound { .. })
)); ));
} }
@ -1288,10 +1276,7 @@ fn music_playlist_not_found(rp: RustyPipe) {
.unwrap_err(); .unwrap_err();
assert!( assert!(
matches!( matches!(err, Error::Extraction(ExtractionError::NotFound { .. })),
err,
Error::Extraction(ExtractionError::ContentUnavailable(_))
),
"got: {err}" "got: {err}"
); );
} }
@ -1337,10 +1322,7 @@ fn music_album_not_found(rp: RustyPipe) {
let err = tokio_test::block_on(rp.query().music_album("MPREb_nlBWQROfvjoz")).unwrap_err(); let err = tokio_test::block_on(rp.query().music_album("MPREb_nlBWQROfvjoz")).unwrap_err();
assert!( assert!(
matches!( matches!(err, Error::Extraction(ExtractionError::NotFound { .. })),
err,
Error::Extraction(ExtractionError::ContentUnavailable(_))
),
"got: {err}" "got: {err}"
); );
} }
@ -1430,10 +1412,7 @@ fn music_artist_not_found(rp: RustyPipe) {
.unwrap_err(); .unwrap_err();
assert!( assert!(
matches!( matches!(err, Error::Extraction(ExtractionError::NotFound { .. })),
err,
Error::Extraction(ExtractionError::ContentUnavailable(_))
),
"got: {err}" "got: {err}"
); );
} }
@ -1857,10 +1836,7 @@ fn music_lyrics_not_found(rp: RustyPipe) {
let err = tokio_test::block_on(rp.query().music_lyrics(&track.lyrics_id.unwrap())).unwrap_err(); let err = tokio_test::block_on(rp.query().music_lyrics(&track.lyrics_id.unwrap())).unwrap_err();
assert!( assert!(
matches!( matches!(err, Error::Extraction(ExtractionError::NotFound { .. })),
err,
Error::Extraction(ExtractionError::ContentUnavailable(_))
),
"got: {err}" "got: {err}"
); );
} }
@ -1970,10 +1946,7 @@ fn music_details_not_found(rp: RustyPipe) {
let err = tokio_test::block_on(rp.query().music_details("7nigXQS1XbZ")).unwrap_err(); let err = tokio_test::block_on(rp.query().music_details("7nigXQS1XbZ")).unwrap_err();
assert!( assert!(
matches!( matches!(err, Error::Extraction(ExtractionError::NotFound { .. })),
err,
Error::Extraction(ExtractionError::ContentUnavailable(_))
),
"got: {err}" "got: {err}"
); );
} }
@ -1989,10 +1962,7 @@ fn music_radio_track_not_found(rp: RustyPipe) {
let err = tokio_test::block_on(rp.query().music_radio_track("7nigXQS1XbZ")).unwrap_err(); let err = tokio_test::block_on(rp.query().music_radio_track("7nigXQS1XbZ")).unwrap_err();
assert!( assert!(
matches!( matches!(err, Error::Extraction(ExtractionError::NotFound { .. })),
err,
Error::Extraction(ExtractionError::ContentUnavailable(_))
),
"got: {err}" "got: {err}"
); );
} }
@ -2016,10 +1986,7 @@ fn music_radio_playlist_not_found(rp: RustyPipe) {
if let Err(err) = res { if let Err(err) = res {
assert!( assert!(
matches!( matches!(err, Error::Extraction(ExtractionError::NotFound { .. })),
err,
Error::Extraction(ExtractionError::ContentUnavailable(_))
),
"got: {err}" "got: {err}"
); );
} }
@ -2038,10 +2005,7 @@ fn music_radio_not_found(rp: RustyPipe) {
tokio_test::block_on(rp.query().music_radio("RDEM_Ktu-TilkxtLvmc9wXZZZZ")).unwrap_err(); tokio_test::block_on(rp.query().music_radio("RDEM_Ktu-TilkxtLvmc9wXZZZZ")).unwrap_err();
assert!( assert!(
matches!( matches!(err, Error::Extraction(ExtractionError::NotFound { .. })),
err,
Error::Extraction(ExtractionError::ContentUnavailable(_))
),
"got: {err}" "got: {err}"
); );
} }
@ -2195,10 +2159,7 @@ fn music_genre_not_found(rp: RustyPipe) {
let err = tokio_test::block_on(rp.query().music_genre("ggMPOg1uX1JOQWZFeDByc2zz")).unwrap_err(); let err = tokio_test::block_on(rp.query().music_genre("ggMPOg1uX1JOQWZFeDByc2zz")).unwrap_err();
assert!( assert!(
matches!( matches!(err, Error::Extraction(ExtractionError::NotFound { .. })),
err,
Error::Extraction(ExtractionError::ContentUnavailable(_))
),
"got: {err}" "got: {err}"
); );
} }