diff --git a/src/client/music_genres.rs b/src/client/music_genres.rs index d6d31e9..f471f87 100644 --- a/src/client/music_genres.rs +++ b/src/client/music_genres.rs @@ -81,7 +81,7 @@ impl MapResponse> for response::MusicGenres { let genres = content_iter .enumerate() .flat_map(|(i, grid)| { - let mut grid = grid.grid_renderer.items; + let mut grid = grid.grid_renderer.contents; warnings.append(&mut grid.warnings); grid.c.into_iter().filter_map(move |section| match section { response::music_genres::NavigationButton::MusicNavigationButtonRenderer( diff --git a/src/client/playlist.rs b/src/client/playlist.rs index 2cfad2a..c204dca 100644 --- a/src/client/playlist.rs +++ b/src/client/playlist.rs @@ -94,7 +94,7 @@ impl MapResponse for response::Playlist { let (thumbnails, last_update_txt) = match self.sidebar { Some(sidebar) => { - let mut sidebar_items = sidebar.playlist_sidebar_renderer.items; + let mut sidebar_items = sidebar.playlist_sidebar_renderer.contents; let mut primary = sidebar_items .try_swap_remove(0) diff --git a/src/client/response/mod.rs b/src/client/response/mod.rs index 23cb797..551fd8c 100644 --- a/src/client/response/mod.rs +++ b/src/client/response/mod.rs @@ -57,7 +57,7 @@ use serde::{ use serde_with::{json::JsonString, serde_as, VecSkipError}; use crate::error::ExtractionError; -use crate::serializer::{text::Text, MapResult, VecLogError, VecSkipErrorWrap}; +use crate::serializer::{text::Text, MapResult, VecSkipErrorWrap}; use self::video_item::YouTubeListRenderer; @@ -72,6 +72,12 @@ pub(crate) struct ContentsRenderer { pub contents: Vec, } +#[derive(Debug, Deserialize)] +pub(crate) struct ContentsRendererLogged { + #[serde(alias = "items")] + pub contents: MapResult>, +} + #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct Tab { @@ -216,11 +222,9 @@ pub(crate) struct ContinuationActionWrap { pub append_continuation_items_action: ContinuationAction, } -#[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ContinuationAction { - #[serde_as(as = "VecLogError<_>")] pub continuation_items: MapResult>, } @@ -286,9 +290,10 @@ where let mut contents = None; while let Some(k) = map.next_key::>()? { - if k == "contents" || k == "tabs" { - let x = map.next_value::>()?; - contents = Some(ContentsRenderer { contents: x.0 }); + if k == "contents" || k == "tabs" || k == "items" { + contents = Some(ContentsRenderer { + contents: map.next_value::>()?.0, + }); } else { map.next_value::()?; } diff --git a/src/client/response/music_genres.rs b/src/client/response/music_genres.rs index 36dfd2e..8c145c8 100644 --- a/src/client/response/music_genres.rs +++ b/src/client/response/music_genres.rs @@ -1,12 +1,12 @@ use serde::Deserialize; use serde_with::{rust::deserialize_ignore_any, serde_as}; -use crate::serializer::{text::Text, MapResult, VecLogError}; +use crate::serializer::text::Text; use super::{ music_item::{ItemSection, SimpleHeader, SingleColumnBrowseResult}, url_endpoint::BrowseEndpointWrap, - SectionList, Tab, + ContentsRendererLogged, SectionList, Tab, }; #[derive(Debug, Deserialize)] @@ -18,15 +18,7 @@ pub(crate) struct MusicGenres { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct Grid { - pub grid_renderer: GridRenderer, -} - -#[serde_as] -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub(crate) struct GridRenderer { - #[serde_as(as = "VecLogError<_>")] - pub items: MapResult>, + pub grid_renderer: ContentsRendererLogged, } #[derive(Debug, Deserialize)] diff --git a/src/client/response/music_item.rs b/src/client/response/music_item.rs index fed6eb7..87d8399 100644 --- a/src/client/response/music_item.rs +++ b/src/client/response/music_item.rs @@ -9,7 +9,7 @@ use crate::{ param::Language, serializer::{ text::{Text, TextComponents}, - MapResult, VecLogError, + MapResult, }, util::{self, dictionary, TryRemove}, }; @@ -39,7 +39,6 @@ pub(crate) enum ItemSection { pub(crate) struct MusicShelf { /// Playlist ID (only for playlists) pub playlist_id: Option, - #[serde_as(as = "VecLogError<_>")] pub contents: MapResult>, /// Continuation token for fetching more (>100) playlist items #[serde(default)] @@ -53,12 +52,10 @@ pub(crate) struct MusicShelf { /// MusicCarouselShelf represents a horizontal list of music items displayed with /// large covers. -#[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct MusicCarouselShelf { pub header: Option, - #[serde_as(as = "VecLogError<_>")] pub contents: MapResult>, } @@ -76,7 +73,6 @@ pub(crate) struct MusicCardShelf { #[serde(default)] pub thumbnail: MusicThumbnailRenderer, #[serde(default)] - #[serde_as(as = "VecLogError<_>")] pub contents: MapResult>, } @@ -227,7 +223,6 @@ pub(crate) struct CoverMusicItem { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct PlaylistPanelRenderer { - #[serde_as(as = "VecLogError<_>")] pub contents: MapResult>, /// Continuation token for fetching more radio items #[serde(default)] @@ -362,15 +357,7 @@ pub(crate) struct ButtonRenderer { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct MusicItemMenu { - pub menu_renderer: MusicItemMenuRenderer, -} - -#[serde_as] -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub(crate) struct MusicItemMenuRenderer { - #[serde_as(as = "VecSkipError<_>")] - pub items: Vec, + pub menu_renderer: ContentsRenderer, } #[derive(Debug, Deserialize)] @@ -385,11 +372,9 @@ pub(crate) struct Grid { pub grid_renderer: GridRenderer, } -#[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct GridRenderer { - #[serde_as(as = "VecLogError<_>")] pub items: MapResult>, pub header: Option, } @@ -1147,7 +1132,7 @@ fn map_artist_id_fallback( menu: Option, fallback_artist: Option<&ArtistId>, ) -> Option { - menu.and_then(|m| map_artist_id(m.menu_renderer.items)) + menu.and_then(|m| map_artist_id(m.menu_renderer.contents)) .or_else(|| fallback_artist.and_then(|a| a.id.to_owned())) } diff --git a/src/client/response/player.rs b/src/client/response/player.rs index 86c8244..7f6b240 100644 --- a/src/client/response/player.rs +++ b/src/client/response/player.rs @@ -5,7 +5,7 @@ use serde_with::serde_as; use serde_with::{json::JsonString, DefaultOnError}; use super::{ResponseContext, Thumbnails}; -use crate::serializer::{text::Text, MapResult, VecLogError}; +use crate::serializer::{text::Text, MapResult}; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] @@ -75,10 +75,8 @@ pub(crate) struct StreamingData { #[serde_as(as = "JsonString")] pub expires_in_seconds: u32, #[serde(default)] - #[serde_as(as = "VecLogError<_>")] pub formats: MapResult>, #[serde(default)] - #[serde_as(as = "VecLogError<_>")] pub adaptive_formats: MapResult>, /// Only on livestreams pub dash_manifest_url: Option, diff --git a/src/client/response/playlist.rs b/src/client/response/playlist.rs index b0a0b16..9cbc503 100644 --- a/src/client/response/playlist.rs +++ b/src/client/response/playlist.rs @@ -3,8 +3,10 @@ use serde_with::{ json::JsonString, rust::deserialize_ignore_any, serde_as, DefaultOnError, VecSkipError, }; -use crate::serializer::text::{Text, TextComponent}; -use crate::serializer::{MapResult, VecLogError}; +use crate::serializer::{ + text::{Text, TextComponent}, + MapResult, +}; use crate::util::MappingError; use super::{ @@ -45,11 +47,9 @@ pub(crate) struct PlaylistVideoListRenderer { pub playlist_video_list_renderer: PlaylistVideoList, } -#[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct PlaylistVideoList { - #[serde_as(as = "VecLogError<_>")] pub contents: MapResult>, } @@ -102,15 +102,7 @@ pub(crate) struct BylineRenderer { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct Sidebar { - pub playlist_sidebar_renderer: SidebarRenderer, -} - -#[serde_as] -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub(crate) struct SidebarRenderer { - #[serde_as(as = "VecSkipError<_>")] - pub items: Vec, + pub playlist_sidebar_renderer: ContentsRenderer, } #[derive(Debug, Deserialize)] @@ -193,10 +185,8 @@ pub(crate) struct OnResponseReceivedAction { pub append_continuation_items_action: AppendAction, } -#[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct AppendAction { - #[serde_as(as = "VecLogError<_>")] pub continuation_items: MapResult>, } diff --git a/src/client/response/video_details.rs b/src/client/response/video_details.rs index fffa347..f43878a 100644 --- a/src/client/response/video_details.rs +++ b/src/client/response/video_details.rs @@ -6,21 +6,20 @@ use serde_with::{rust::deserialize_ignore_any, serde_as, DefaultOnError, VecSkip use crate::serializer::text::TextComponent; use crate::serializer::{ text::{AccessibilityText, AttributedText, Text, TextComponents}, - MapResult, VecLogError, + MapResult, }; use super::{ url_endpoint::BrowseEndpointWrap, ContinuationEndpoint, ContinuationItemRenderer, Icon, MusicContinuationData, Thumbnails, }; -use super::{ChannelBadge, ResponseContext, YouTubeListItem}; +use super::{ChannelBadge, ContentsRendererLogged, ResponseContext, YouTubeListItem}; /* #VIDEO DETAILS */ /// Video details response -#[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct VideoDetails { @@ -29,7 +28,6 @@ pub(crate) struct VideoDetails { /// Video ID pub current_video_endpoint: Option, /// Video chapters + comment section - #[serde_as(as = "VecLogError<_>")] pub engagement_panels: MapResult>, pub response_context: ResponseContext, } @@ -60,11 +58,9 @@ pub(crate) struct VideoResultsWrap { } /// Video metadata items -#[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct VideoResults { - #[serde_as(as = "Option>")] pub contents: Option>>, } @@ -303,7 +299,6 @@ pub(crate) struct RecommendationResultsWrap { #[serde(rename_all = "camelCase")] pub(crate) struct RecommendationResults { /// Can be `None` for age-restricted videos - #[serde_as(as = "Option>")] pub results: Option>>, #[serde_as(as = "Option>")] pub continuations: Option>, @@ -341,16 +336,7 @@ pub(crate) enum EngagementPanelRenderer { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ChapterMarkersContent { - pub macro_markers_list_renderer: MacroMarkersListRenderer, -} - -/// Chapter markers -#[serde_as] -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub(crate) struct MacroMarkersListRenderer { - #[serde_as(as = "VecLogError<_>")] - pub contents: MapResult>, + pub macro_markers_list_renderer: ContentsRendererLogged, } /// Chapter marker @@ -436,7 +422,6 @@ pub(crate) struct CommentItemSectionHeaderMenuItem { */ /// Video comments continuation response -#[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct VideoComments { @@ -450,7 +435,6 @@ pub(crate) struct VideoComments { /// - Comment replies: appendContinuationItemsAction /// - n*commentRenderer, continuationItemRenderer: /// replies + continuation - #[serde_as(as = "VecLogError<_>")] pub on_response_received_endpoints: MapResult>, } @@ -463,11 +447,9 @@ pub(crate) struct CommentsContItem { } /// Video comments continuation action -#[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct AppendComments { - #[serde_as(as = "VecLogError<_>")] pub continuation_items: MapResult>, } diff --git a/src/client/response/video_item.rs b/src/client/response/video_item.rs index d24fa1c..baf409f 100644 --- a/src/client/response/video_item.rs +++ b/src/client/response/video_item.rs @@ -15,7 +15,7 @@ use crate::{ param::Language, serializer::{ text::{AccessibilityText, Text, TextComponent}, - MapResult, VecLogError, + MapResult, }, timeago, util::{self, TryRemove}, @@ -69,7 +69,6 @@ pub(crate) enum YouTubeListItem { #[serde(alias = "expandedShelfContentsRenderer", alias = "gridRenderer")] ItemSectionRenderer { #[serde(alias = "items")] - #[serde_as(as = "VecLogError<_>")] contents: MapResult>, }, @@ -206,11 +205,9 @@ pub(crate) struct YouTubeListRendererWrap { pub section_list_renderer: YouTubeListRenderer, } -#[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct YouTubeListRenderer { - #[serde_as(as = "VecLogError<_>")] pub contents: MapResult>, } diff --git a/src/serializer/mod.rs b/src/serializer/mod.rs index fd888f4..b0a582e 100644 --- a/src/serializer/mod.rs +++ b/src/serializer/mod.rs @@ -6,7 +6,7 @@ mod vec_log_err; pub use date::DateYmd; pub use range::Range; -pub use vec_log_err::{VecLogError, VecSkipErrorWrap}; +pub use vec_log_err::VecSkipErrorWrap; use std::fmt::Debug; diff --git a/src/serializer/vec_log_err.rs b/src/serializer/vec_log_err.rs index d2251e5..9e3e32c 100644 --- a/src/serializer/vec_log_err.rs +++ b/src/serializer/vec_log_err.rs @@ -4,7 +4,6 @@ use serde::{ de::{IgnoredAny, SeqAccess, Visitor}, Deserialize, }; -use serde_with::{de::DeserializeAsWrap, DeserializeAs}; use super::MapResult; @@ -13,39 +12,26 @@ use super::MapResult; /// /// This is similar to `VecSkipError`, but it does not silently ignore /// faulty items. -pub struct VecLogError(PhantomData); - -impl<'de, T, U> DeserializeAs<'de, MapResult>> for VecLogError +impl<'de, T> Deserialize<'de> for MapResult> where - U: DeserializeAs<'de, T>, + T: Deserialize<'de>, { - fn deserialize_as(deserializer: D) -> Result>, D::Error> + fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { #[derive(serde::Deserialize)] - #[serde( - untagged, - bound(deserialize = "DeserializeAsWrap: Deserialize<'de>") - )] - enum GoodOrError<'a, T, TAs> - where - TAs: DeserializeAs<'a, T>, - { - Good(DeserializeAsWrap), - Error(serde_json::value::Value), - #[serde(skip)] - _JustAMarkerForTheLifetime(PhantomData<&'a u32>), + #[serde(untagged)] + enum GoodOrError { + Good(T), + Error(serde_json::Value), } - struct SeqVisitor { - marker: PhantomData, - marker2: PhantomData, - } + struct SeqVisitor(PhantomData); - impl<'de, T, U> Visitor<'de> for SeqVisitor + impl<'de, T> Visitor<'de> for SeqVisitor where - U: DeserializeAs<'de, T>, + T: Deserialize<'de>, { type Value = MapResult>; @@ -62,16 +48,15 @@ where while let Some(value) = seq.next_element()? { match value { - GoodOrError::::Good(value) => { - values.push(value.into_inner()); + GoodOrError::::Good(value) => { + values.push(value); } - GoodOrError::::Error(value) => { + GoodOrError::::Error(value) => { warnings.push(format!( "error deserializing item: {}", serde_json::to_string(&value).unwrap_or_default() )); } - _ => {} } } Ok(MapResult { @@ -81,15 +66,11 @@ where } } - let visitor = SeqVisitor:: { - marker: PhantomData, - marker2: PhantomData, - }; - deserializer.deserialize_seq(visitor) + deserializer.deserialize_seq(SeqVisitor(PhantomData::)) } } -/// Reimplementation of VecSkipError using a type wrapper +/// Reimplementation of VecSkipError using a wrapper type /// to allow use with generics pub struct VecSkipErrorWrap(pub Vec); @@ -145,32 +126,53 @@ where #[cfg(test)] mod tests { use serde::Deserialize; - use serde_with::serde_as; use crate::serializer::MapResult; - #[serde_as] + use super::VecSkipErrorWrap; + #[derive(Debug, Deserialize)] #[allow(dead_code)] - struct S { - #[serde_as(as = "crate::serializer::VecLogError<_>")] + struct SLog { items: MapResult>, } + #[derive(Deserialize)] + #[allow(dead_code)] + struct SSkip { + items: VecSkipErrorWrap, + } + #[derive(Debug, Deserialize)] #[allow(dead_code)] struct Item { name: String, } - #[test] - fn test() { - let json = r#"{"items": [{"name": "i1"}, {"xyz": "i2"}, {"name": "i3"}, {"namra": "i4"}]}"#; + const JSON: &str = + r#"{"items": [{"name": "i1"}, {"xyz": "i2"}, {"name": "i3"}, {"namra": "i4"}]}"#; - let res = serde_json::from_str::(json).unwrap(); + #[test] + fn skip_error() { + let res = serde_json::from_str::(JSON).unwrap(); + insta::assert_debug_snapshot!(res.items.0, @r###" + [ + Item { + name: "i1", + }, + Item { + name: "i3", + }, + ] + "###); + } + + #[test] + fn log_error() { + let res = serde_json::from_str::(JSON).unwrap(); insta::assert_debug_snapshot!(res, @r###" - S { + SLog { items: [ Item { name: "i1",