From c021496a558ea1e8e5b38810f3d06b56b5219de4 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Mon, 8 May 2023 15:21:06 +0200 Subject: [PATCH] refactor: uopdate NotFound error type --- src/client/channel.rs | 41 ++++++++++++----------- src/client/channel_rss.rs | 10 +++--- src/client/mod.rs | 17 ++++++---- src/client/music_details.rs | 23 ++++++++----- src/client/playlist.rs | 2 +- src/client/response/mod.rs | 24 +++++++------ src/client/url_resolver.rs | 24 +++++++------ src/client/video_details.rs | 27 +++++++-------- src/error.rs | 21 +++++++----- tests/youtube.rs | 67 ++++++++----------------------------- 10 files changed, 119 insertions(+), 137 deletions(-) diff --git a/src/client/channel.rs b/src/client/channel.rs index 5510037..0580f2f 100644 --- a/src/client/channel.rs +++ b/src/client/channel.rs @@ -164,7 +164,7 @@ impl MapResponse>> for response::Channel { lang: Language, _deobf: Option<&crate::deobfuscate::DeobfData>, ) -> Result>>, 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( MapChannelData { @@ -207,7 +207,7 @@ impl MapResponse>> for response::Channel { lang: Language, _deobf: Option<&crate::deobfuscate::DeobfData>, ) -> Result>>, 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( MapChannelData { @@ -244,7 +244,7 @@ impl MapResponse> for response::Channel { lang: Language, _deobf: Option<&crate::deobfuscate::DeobfData>, ) -> Result>, 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( MapChannelData { header: self.header, @@ -304,22 +304,21 @@ fn map_channel( id: &str, lang: Language, ) -> Result>, ExtractionError> { - let header = d - .header - .ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed( - "channel not found", - )))?; + let header = d.header.ok_or_else(|| ExtractionError::NotFound { + id: id.to_owned(), + msg: "no header".into(), + })?; let metadata = d .metadata - .ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed( - "channel not found", - )))? + .ok_or_else(|| ExtractionError::NotFound { + id: id.to_owned(), + msg: "no metadata".into(), + })? .channel_metadata_renderer; - let microformat = d - .microformat - .ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed( - "channel not found", - )))?; + let microformat = d.microformat.ok_or_else(|| ExtractionError::NotFound { + id: id.to_owned(), + msg: "no microformat".into(), + })?; if metadata.external_id != id { return Err(ExtractionError::WrongResult(format!( @@ -405,6 +404,7 @@ struct MappedChannelContent { } fn map_channel_content( + id: &str, contents: Option, alerts: Option>, ) -> Result { @@ -412,9 +412,10 @@ fn map_channel_content( Some(contents) => { let tabs = contents.two_column_browse_results_renderer.contents; if tabs.is_empty() { - return Err(ExtractionError::ContentUnavailable( - "channel not found".into(), - )); + return Err(ExtractionError::NotFound { + id: id.to_owned(), + msg: "no tabs".into(), + }); } let cmp_url_suffix = |endpoint: &response::channel::ChannelTabEndpoint, @@ -470,7 +471,7 @@ fn map_channel_content( has_live, }) } - None => Err(response::alerts_to_err(alerts)), + None => Err(response::alerts_to_err(id, alerts)), } } diff --git a/src/client/channel_rss.rs b/src/client/channel_rss.rs index d28f81e..0eb204d 100644 --- a/src/client/channel_rss.rs +++ b/src/client/channel_rss.rs @@ -16,18 +16,20 @@ impl RustyPipeQuery { /// 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. pub async fn channel_rss>(&self, channel_id: S) -> Result { + let channel_id = channel_id.as_ref(); let url = format!( "https://www.youtube.com/feeds/videos.xml?channel_id={}", - channel_id.as_ref() + channel_id, ); let xml = self .client .http_request_txt(self.client.inner.http.get(&url).build()?) .await .map_err(|e| match e { - Error::HttpStatus(404, _) => Error::Extraction( - ExtractionError::ContentUnavailable("Channel not found".into()), - ), + Error::HttpStatus(404, _) => Error::Extraction(ExtractionError::NotFound { + id: channel_id.to_owned(), + msg: "404".into(), + }), _ => e, })?; diff --git a/src/client/mod.rs b/src/client/mod.rs index 36d2d4a..ae339b6 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1106,17 +1106,20 @@ impl RustyPipeQuery { if status.is_client_error() || status.is_server_error() { let error_msg = serde_json::from_str::(&resp_str) - .map(|r| r.error.message) - .unwrap_or_default(); + .map(|r| Cow::from(r.error.message)); return match status { - StatusCode::NOT_FOUND => Err(Error::Extraction( - ExtractionError::ContentUnavailable(error_msg.into()), - )), + StatusCode::NOT_FOUND => Err(Error::Extraction(ExtractionError::NotFound { + id: id.to_owned(), + msg: error_msg.unwrap_or("404".into()), + })), 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(), + )), }; } diff --git a/src/client/music_details.rs b/src/client/music_details.rs index 66bfb53..0d35eae 100644 --- a/src/client/music_details.rs +++ b/src/client/music_details.rs @@ -193,9 +193,10 @@ impl MapResponse for response::MusicDetails { } } - let content = content.ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed( - "track not found", - )))?; + let content = content.ok_or_else(|| ExtractionError::NotFound { + id: id.to_owned(), + msg: "no content".into(), + })?; let track_item = content .contents .c @@ -233,7 +234,7 @@ impl MapResponse for response::MusicDetails { impl MapResponse> for response::MusicDetails { fn map_response( self, - _id: &str, + id: &str, lang: Language, _deobf: Option<&crate::deobfuscate::DeobfData>, ) -> Result>, ExtractionError> { @@ -247,9 +248,10 @@ impl MapResponse> for response::MusicDetails { let content = tabs .into_iter() .find_map(|t| t.tab_renderer.content) - .ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed( - "radio unavailable", - )))? + .ok_or_else(|| ExtractionError::NotFound { + id: id.to_owned(), + msg: "no content".into(), + })? .music_queue_renderer .content .playlist_panel_renderer; @@ -292,7 +294,7 @@ impl MapResponse> for response::MusicDetails { impl MapResponse for response::MusicLyrics { fn map_response( self, - _id: &str, + id: &str, _lang: Language, _deobf: Option<&crate::deobfuscate::DeobfData>, ) -> Result, ExtractionError> { @@ -305,7 +307,10 @@ impl MapResponse for response::MusicLyrics { .find_map(|item| item.music_description_shelf_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")), })?; diff --git a/src/client/playlist.rs b/src/client/playlist.rs index 5fed815..385f3a1 100644 --- a/src/client/playlist.rs +++ b/src/client/playlist.rs @@ -62,7 +62,7 @@ impl MapResponse for response::Playlist { ) -> Result, ExtractionError> { let (contents, header) = match (self.contents, self.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 diff --git a/src/client/response/mod.rs b/src/client/response/mod.rs index 551fd8c..b321dcc 100644 --- a/src/client/response/mod.rs +++ b/src/client/response/mod.rs @@ -353,16 +353,18 @@ impl From for crate::model::Verification { } } -pub(crate) fn alerts_to_err(alerts: Option>) -> ExtractionError { - match alerts { - Some(alerts) => ExtractionError::ContentUnavailable( - alerts - .into_iter() - .map(|a| a.alert_renderer.text) - .collect::>() - .join(" ") - .into(), - ), - None => ExtractionError::ContentUnavailable("content not found".into()), +pub(crate) fn alerts_to_err(id: &str, alerts: Option>) -> ExtractionError { + ExtractionError::NotFound { + id: id.to_owned(), + msg: alerts + .map(|alerts| { + alerts + .into_iter() + .map(|a| a.alert_renderer.text) + .collect::>() + .join(" ") + .into() + }) + .unwrap_or_default(), } } diff --git a/src/client/url_resolver.rs b/src/client/url_resolver.rs index f549d34..cecb849 100644 --- a/src/client/url_resolver.rs +++ b/src/client/url_resolver.rs @@ -163,18 +163,22 @@ impl RustyPipeQuery { .await { Ok(target) => Ok(target), - Err(Error::Extraction(ExtractionError::ContentUnavailable(e))) => { - match util::VIDEO_ID_REGEX.is_match(id) { - true => Ok(UrlTarget::Video { - id: id.to_owned(), - start_time: get_start_time(), - }), - false => Err(Error::Extraction( - ExtractionError::ContentUnavailable(e), - )), + Err(e) => { + if matches!( + e, + Error::Extraction(ExtractionError::NotFound { .. }) + ) { + match util::VIDEO_ID_REGEX.is_match(id) { + true => Ok(UrlTarget::Video { + 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) { Ok(UrlTarget::Video { diff --git a/src/client/video_details.rs b/src/client/video_details.rs index 68754f1..8bf791c 100644 --- a/src/client/video_details.rs +++ b/src/client/video_details.rs @@ -1,5 +1,3 @@ -use std::borrow::Cow; - use serde::Serialize; use crate::{ @@ -87,16 +85,16 @@ impl MapResponse for response::VideoDetails { ) -> Result, ExtractionError> { let mut warnings = Vec::new(); - let contents = self - .contents - .ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed( - "Video not found", - )))?; + let contents = self.contents.ok_or_else(|| ExtractionError::NotFound { + id: id.to_owned(), + msg: "no content".into(), + })?; let current_video_endpoint = self.current_video_endpoint - .ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed( - "Video not found", - )))?; + .ok_or_else(|| ExtractionError::NotFound { + id: id.to_owned(), + msg: "no current_video_endpoint".into(), + })?; let video_id = current_video_endpoint.watch_endpoint.video_id; if id != video_id { @@ -110,9 +108,10 @@ impl MapResponse for response::VideoDetails { .results .results .contents - .ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed( - "Video not found", - )))?; + .ok_or_else(|| ExtractionError::NotFound { + id: id.into(), + msg: "no primary_results".into(), + })?; warnings.append(&mut primary_results.warnings); let mut primary_info = None; @@ -585,7 +584,7 @@ mod tests { let err = details.map_response("", Language::En, None).unwrap_err(); assert!(matches!( err, - crate::error::ExtractionError::ContentUnavailable(_) + crate::error::ExtractionError::NotFound { .. } )) } diff --git a/src/error.rs b/src/error.rs index 32461cc..a5b4cc2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -30,21 +30,26 @@ pub enum ExtractionError { /// - 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}")] + #[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 is not available / does not exist - #[error("Content is not available. Reason: {0}")] - ContentUnavailable(Cow<'static, str>), + /// 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. Reason: {0}")] + #[error("bad request ({0})")] BadRequest(Cow<'static, str>), /// 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>), /// Error deobfuscating YouTube's URL signatures #[error("deobfuscation error: {0}")] @@ -54,7 +59,7 @@ pub enum ExtractionError { /// Specifically YouTube may return this video , /// which is a 5 minute error message, instead of the requested video when using an outdated /// Android client. - #[error("got wrong result from YT: {0}")] + #[error("wrong result from YT: {0}")] WrongResult(String), /// YouTube redirects you to another content ID /// @@ -64,7 +69,7 @@ pub enum ExtractionError { /// Warnings occurred during deserialization/mapping /// /// This error is only returned in strict mode. - #[error("Warnings during deserialization/mapping")] + #[error("warnings during deserialization/mapping")] DeserializationWarnings, } diff --git a/tests/youtube.rs b/tests/youtube.rs index 0fb95f7..928206d 100644 --- a/tests/youtube.rs +++ b/tests/youtube.rs @@ -383,10 +383,7 @@ fn playlist_not_found(rp: RustyPipe) { .unwrap_err(); assert!( - matches!( - err, - Error::Extraction(ExtractionError::ContentUnavailable(_)) - ), + matches!(err, Error::Extraction(ExtractionError::NotFound { .. })), "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(); assert!( - matches!( - err, - Error::Extraction(ExtractionError::ContentUnavailable(_)) - ), + matches!(err, Error::Extraction(ExtractionError::NotFound { .. })), "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(); assert!( - matches!( - err, - Error::Extraction(ExtractionError::ContentUnavailable(_)) - ), + matches!(err, Error::Extraction(ExtractionError::NotFound { .. })), "got: {err}" ); } @@ -1017,10 +1008,7 @@ mod channel_rss { tokio_test::block_on(rp.query().channel_rss("UCHnyfMqiRRG1u-2MsSQLbXZ")).unwrap_err(); assert!( - matches!( - err, - Error::Extraction(ExtractionError::ContentUnavailable(_)) - ), + matches!(err, Error::Extraction(ExtractionError::NotFound { .. })), "got: {}", err ); @@ -1164,7 +1152,7 @@ fn resolve_channel_not_found(rp: RustyPipe) { assert!(matches!( err, - Error::Extraction(ExtractionError::ContentUnavailable(_)) + Error::Extraction(ExtractionError::NotFound { .. }) )); } @@ -1288,10 +1276,7 @@ fn music_playlist_not_found(rp: RustyPipe) { .unwrap_err(); assert!( - matches!( - err, - Error::Extraction(ExtractionError::ContentUnavailable(_)) - ), + matches!(err, Error::Extraction(ExtractionError::NotFound { .. })), "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(); assert!( - matches!( - err, - Error::Extraction(ExtractionError::ContentUnavailable(_)) - ), + matches!(err, Error::Extraction(ExtractionError::NotFound { .. })), "got: {err}" ); } @@ -1430,10 +1412,7 @@ fn music_artist_not_found(rp: RustyPipe) { .unwrap_err(); assert!( - matches!( - err, - Error::Extraction(ExtractionError::ContentUnavailable(_)) - ), + matches!(err, Error::Extraction(ExtractionError::NotFound { .. })), "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(); assert!( - matches!( - err, - Error::Extraction(ExtractionError::ContentUnavailable(_)) - ), + matches!(err, Error::Extraction(ExtractionError::NotFound { .. })), "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(); assert!( - matches!( - err, - Error::Extraction(ExtractionError::ContentUnavailable(_)) - ), + matches!(err, Error::Extraction(ExtractionError::NotFound { .. })), "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(); assert!( - matches!( - err, - Error::Extraction(ExtractionError::ContentUnavailable(_)) - ), + matches!(err, Error::Extraction(ExtractionError::NotFound { .. })), "got: {err}" ); } @@ -2016,10 +1986,7 @@ fn music_radio_playlist_not_found(rp: RustyPipe) { if let Err(err) = res { assert!( - matches!( - err, - Error::Extraction(ExtractionError::ContentUnavailable(_)) - ), + matches!(err, Error::Extraction(ExtractionError::NotFound { .. })), "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(); assert!( - matches!( - err, - Error::Extraction(ExtractionError::ContentUnavailable(_)) - ), + matches!(err, Error::Extraction(ExtractionError::NotFound { .. })), "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(); assert!( - matches!( - err, - Error::Extraction(ExtractionError::ContentUnavailable(_)) - ), + matches!(err, Error::Extraction(ExtractionError::NotFound { .. })), "got: {err}" ); }