I am trying to deserialize youtube data from the endpoint http://www.youtube.com/feeds/videos.xml?channel_id=UCHnyfMqiRRG1u-2MsSQLbXA
into the Feed
struct in the below Rust
program. It is returning None for the media_group
field in the struct Entry
, even though the raw XML contains information.
NOTE :: PASTE THE http://www.youtube.com/feeds/videos.xml?channel_id=UCHnyfMqiRRG1u-2MsSQLbXA
INTO A BROWSER TO VISUALIZE THE XML BETTER
// youtube_data.rs
use hyper::{Body, Client, Uri};
use hyper_tls::HttpsConnector;
use serde::Deserialize;
use quick_xml::de::{from_str, DeError};
use crate::internals::singularity::WidgetError;
impl From<DeError> for WidgetError {
fn from(err: DeError) -> Self {
WidgetError::XmlParse(err.to_string())
}
}
#[derive(Debug, Deserialize)]
#[serde(rename = "feed")]
pub struct Feed {
id: String,
#[serde(rename = "channelId", default)]
yt_channel_id: Option<String>,
title: String,
published: String,
#[serde(default)]
#[serde(rename = "entry")]
entries: Vec<Entry>,
}
#[derive(Debug, Deserialize)]
struct Entry {
id: String,
title: String,
published: String,
#[serde(rename = "mediaGroup")]
media_group: Option<MediaGroup>,
}
#[derive(Debug, Deserialize)]
struct MediaGroup {
#[serde(rename = "mediaTitle")]
title: Option<String>,
#[serde(rename = "mediaContent")]
content: Option<MediaContent>,
#[serde(rename = "mediaThumbnail")]
thumbnail: Option<MediaThumbnail>,
#[serde(rename = "mediaCommunity")]
community: Option<MediaCommunity>,
}
#[derive(Debug, Deserialize)]
struct MediaContent {
#[serde(rename = "@url")]
url: String,
#[serde(rename = "@width")]
width: u32,
#[serde(rename = "@height")]
height: u32,
}
#[derive(Debug, Deserialize)]
struct MediaThumbnail {
#[serde(rename = "@url")]
url: String,
#[serde(rename = "@width")]
width: u32,
#[serde(rename = "@height")]
height: u32,
}
#[derive(Debug, Deserialize)]
struct MediaCommunity {
#[serde(rename = "mediaStarRating")]
star_rating: Option<StarRating>,
#[serde(rename = "mediaStatistics")]
statistics: Option<Statistics>,
}
#[derive(Debug, Deserialize)]
struct StarRating {
#[serde(rename = "@count")]
count: u64,
}
#[derive(Debug, Deserialize)]
struct Statistics {
#[serde(rename = "@views")]
views: u64,
}
pub async fn get_youtube_vids_for_a_channel(channel_id: String) -> Result<Feed, WidgetError> {
let url = format!(
"https://www.youtube.com/feeds/videos.xml?channel_id={}",
channel_id
);
let uri: Uri = url.parse().unwrap();
let https = HttpsConnector::new();
let client = Client::builder().build::<_, Body>(https);
let res = client.get(uri).await?;
let body = hyper::body::to_bytes(res.into_body()).await?;
let response_text = String::from_utf8(body.to_vec())
.map_err(|e| WidgetError::Utf8Error(e.to_string()))?;
// println!("{:#?}", response_text);
let feed: Feed = from_str(&response_text)?;
Ok(feed)
}
// singularity.rs
#[derive(Debug, Error)]
pub enum WidgetError {
#[error("Hyper error: {0}")]
Hyper(#[from] hyper::Error),
#[error("No geocoding data found")]
NoGeocodingData,
#[error("Error in reading HTML file")]
NoHtmlToString,
#[error("Serde JSON error: {0}")]
SerdeJson(#[from] serde_json::Error),
#[error("No timezone found")]
NoTimeZone,
#[error("XML parsing error: {0}")]
XmlParse(String),
#[error("UTF-8 conversion error: {0}")]
Utf8Error(String),
}
The dependencies used are
[dependencies]
hyper = { version = "0.14", features = ["full"] }
hyper-tls = "0.5"
regex = "1.10.5"
serde = { version = "1.0", features = ["derive"] }
thiserror = "1.0.63"
quick-xml = { version = "0.37", features = ["serialize"] }
Even though Raw XML contains the data why is it not serializing it properly ??
Namespaces like media:
aren't currently supported by serde: https://github.com/tafia/quick-xml/issues/218
As a workaround, since there aren't fields with the same name without the namespace, you can just drop the prefix, e.g., rename to group
:
#[derive(Debug, Deserialize)]
struct Entry {
id: String,
title: String,
published: String,
#[serde(rename = "group")]
media_group: Option<MediaGroup>,
}
#[derive(Debug, Deserialize)]
struct MediaGroup {
#[serde(rename = "title")]
title: Option<String>,
#[serde(rename = "content")]
content: Option<MediaContent>,
#[serde(rename = "thumbnail")]
thumbnail: Option<MediaThumbnail>,
#[serde(rename = "community")]
community: Option<MediaCommunity>,
}
#[derive(Debug, Deserialize)]
struct MediaCommunity {
#[serde(rename = "starRating")]
star_rating: Option<StarRating>,
#[serde(rename = "statistics")]
statistics: Option<Statistics>,
}