diff --git a/src/client/playlist.rs b/src/client/playlist.rs index 306f41b..152e041 100644 --- a/src/client/playlist.rs +++ b/src/client/playlist.rs @@ -6,7 +6,8 @@ use crate::{ deobfuscate::Deobfuscator, model::{ChannelId, Language, Paginator, Playlist, PlaylistVideo}, serializer::text::{PageType, TextLink}, - timeago, util, + timeago, + util::{self, TryRemove}, }; use super::{response, ClientType, MapResponse, MapResult, RustyPipeQuery, YTContext}; @@ -73,25 +74,21 @@ impl MapResponse for response::Playlist { // TODO: think about a deserializer that deserializes only first list item let mut tcbr_contents = self.contents.two_column_browse_results_renderer.contents; let video_items = some_or_bail!( - util::vec_try_swap_remove( - &mut some_or_bail!( - util::vec_try_swap_remove( - &mut some_or_bail!( - util::vec_try_swap_remove(&mut tcbr_contents, 0), - Err(anyhow!("twoColumnBrowseResultsRenderer empty")) - ) - .tab_renderer - .content - .section_list_renderer - .contents, - 0, - ), - Err(anyhow!("sectionListRenderer empty")) + some_or_bail!( + some_or_bail!( + tcbr_contents.try_swap_remove(0), + Err(anyhow!("twoColumnBrowseResultsRenderer empty")) ) - .item_section_renderer - .contents, - 0 - ), + .tab_renderer + .content + .section_list_renderer + .contents + .try_swap_remove(0), + Err(anyhow!("sectionListRenderer empty")) + ) + .item_section_renderer + .contents + .try_swap_remove(0), Err(anyhow!("itemSectionRenderer empty")) ) .playlist_video_list_renderer @@ -103,7 +100,7 @@ impl MapResponse for response::Playlist { Some(sidebar) => { let mut sidebar_items = sidebar.playlist_sidebar_renderer.items; let mut primary = some_or_bail!( - util::vec_try_swap_remove(&mut sidebar_items, 0), + sidebar_items.try_swap_remove(0), Err(anyhow!("no primary sidebar")) ); @@ -113,10 +110,10 @@ impl MapResponse for response::Playlist { .thumbnail_renderer .playlist_video_thumbnail_renderer .thumbnail, - util::vec_try_swap_remove( - &mut primary.playlist_sidebar_primary_info_renderer.stats, - 2, - ), + primary + .playlist_sidebar_primary_info_renderer + .stats + .try_swap_remove(2), ) } None => { @@ -126,7 +123,8 @@ impl MapResponse for response::Playlist { ); let mut byline = self.header.playlist_header_renderer.byline; - let last_update_txt = util::vec_try_swap_remove(&mut byline, 1) + let last_update_txt = byline + .try_swap_remove(1) .map(|b| b.playlist_byline_renderer.text); ( @@ -207,7 +205,7 @@ impl MapResponse> for response::PlaylistCont { ) -> Result>> { let mut actions = self.on_response_received_actions; let action = some_or_bail!( - util::vec_try_swap_remove(&mut actions, 0), + actions.try_swap_remove(0), Err(anyhow!("no continuation action")) ); @@ -422,11 +420,7 @@ mod tests { .await .unwrap(); - playlist - .videos - .extend_limit(rp.query(), 101) - .await - .unwrap(); + playlist.videos.extend_limit(rp.query(), 101).await.unwrap(); assert!(playlist.videos.items.len() > 100); } } diff --git a/src/util.rs b/src/util.rs index 581b8c8..672fc60 100644 --- a/src/util.rs +++ b/src/util.rs @@ -104,35 +104,43 @@ pub fn retry_delay( min_retry_interval.max(jittered_delay.min(max_retry_interval)) } -/// Removes and returns the element at position `index` within the vector, -/// shifting all elements after it to the left. -/// -/// Returns None if the index is out of bounds. -/// -/// Note: Because this shifts over the remaining elements, it has a -/// worst-case performance of *O*(*n*). If you don't need the order of elements -/// to be preserved, use [`vec_try_swap_remove`] instead. -pub fn vec_try_remove(vec: &mut Vec, index: usize) -> Option { - if index < vec.len() { - Some(vec.remove(index)) - } else { - None - } +pub trait TryRemove { + /// Removes and returns the element at position `index` within the vector, + /// shifting all elements after it to the left. + /// + /// Returns None if the index is out of bounds. + /// + /// Note: Because this shifts over the remaining elements, it has a + /// worst-case performance of *O*(*n*). If you don't need the order of elements + /// to be preserved, use [`vec_try_swap_remove`] instead. + fn try_remove(&mut self, index: usize) -> Option; + + /// Removes an element from the vector and returns it. + /// + /// The removed element is replaced by the last element of the vector. + /// + /// Returns None if the index is out of bounds. + /// + /// This does not preserve ordering, but is *O*(1). + /// If you need to preserve the element order, use [`vec_try_remove`] instead. + fn try_swap_remove(&mut self, index: usize) -> Option; } -/// Removes an element from the vector and returns it. -/// -/// The removed element is replaced by the last element of the vector. -/// -/// Returns None if the index is out of bounds. -/// -/// This does not preserve ordering, but is *O*(1). -/// If you need to preserve the element order, use [`vec_try_remove`] instead. -pub fn vec_try_swap_remove(vec: &mut Vec, index: usize) -> Option { - if index < vec.len() { - Some(vec.swap_remove(index)) - } else { - None +impl TryRemove for Vec { + fn try_remove(&mut self, index: usize) -> Option { + if index < self.len() { + Some(self.remove(index)) + } else { + None + } + } + + fn try_swap_remove(&mut self, index: usize) -> Option { + if index < self.len() { + Some(self.swap_remove(index)) + } else { + None + } } } @@ -174,4 +182,20 @@ mod tests { expect_max ); } + + #[test] + fn t_vec_try_remove() { + let mut v = vec![1, 2, 3]; + assert_eq!(v.try_remove(0).unwrap(), 1); + assert_eq!(v.try_remove(1).unwrap(), 3); + assert_eq!(v.try_remove(1), None); + } + + #[test] + fn t_vec_try_swap_remove() { + let mut v = vec![1, 2, 3]; + assert_eq!(v.try_swap_remove(0).unwrap(), 1); + assert_eq!(v.try_swap_remove(1).unwrap(), 2); + assert_eq!(v.try_swap_remove(1), None); + } }